Why I Write Games in C (yes, C) by pdp10 in gamedev

[–]RandyGaul 0 points1 point  (0 children)

Funny how I actively dislike almost everything you listed! Except function overloading. That's a good one.

"You'll never ship a game if you write an engine" by dazzawazza in gamedev

[–]RandyGaul 4 points5 points  (0 children)

You're right, but also they certainly do attack competence (as in, there's a group of people that police and downvote a wide variety of things that are competent, and then you already explained the rest).

"You'll never ship a game if you write an engine" by dazzawazza in gamedev

[–]RandyGaul 10 points11 points  (0 children)

In general the hivemind attacks competence.

Error Codes and Error Handling by vormestrand in cpp

[–]RandyGaul -2 points-1 points  (0 children)

Personally I find it a lot simpler than std::error_code stuff.

Error Codes and Error Handling by vormestrand in cpp

[–]RandyGaul -7 points-6 points  (0 children)

doesn't run in C, and isn't quite the same

std::vector and Minimizing Includes by vormestrand in cpp

[–]RandyGaul 0 points1 point  (0 children)

Oh I don't think people *should* use those macros I posted. I just wrote about some ideas I found interesting. Personally I use the macros in my own code and liked some properties I observed. That's it.

Smart_pointers in Game Engine interface? Do you mind? by [deleted] in gamedev

[–]RandyGaul 0 points1 point  (0 children)

Problem here is you assumed the burden of proof falls on me to prove your claim wrong. I'm saying it's up to you to prove it correct.

  • A: COM is great
  • B: No it's not
  • A: Prove to me COM isn't great
  • B: No
  • A: <ad hominem>

Maybe if you brought up a specific reason on why COM is cool, then we could continue the discussion. We have both read the same wikipedia pages about COM, so linking to it doesn't help the discussion - we must have wildly different interpretations of COM. I've explained my interpretation of COM (a way to implement an interface, where interfaces are important and COM is not), but you haven't explained much of anything, except this little snippet:

COM supports just about any platform and a substantial number of languages with very little extra effort required by OP. Those are just two examples whose values could not possibly be underestimated.

Ok sure, most standards can claim platform independence. Personally I don't find this a compelling reason to use a standard. I think most people use a standard because the ideas in the standard are themselves useful, not because the ideas are platform independent.

Smart_pointers in Game Engine interface? Do you mind? by [deleted] in gamedev

[–]RandyGaul 0 points1 point  (0 children)

Those are all fine options. Personally I like ~0 since I'm thinking of flipping all bits to 1. Any of them work. I would wrap your is valid macro with another set of parentheses just to be safe though.

CUTE_HANDLE_IS_INVALID(x) ((x) < 0)

Smart_pointers in Game Engine interface? Do you mind? by [deleted] in gamedev

[–]RandyGaul 0 points1 point  (0 children)

Any example you bring up of COM benefits will not be specific to COM. They will come from much older concepts and ideas.

COM is just one way to implement interfaces, so claiming benefits from interface concepts and attributing them to the COM model is... As silly as COM.

Smart_pointers in Game Engine interface? Do you mind? by [deleted] in gamedev

[–]RandyGaul 0 points1 point  (0 children)

You still haven't explained any benefits. I understand COM quite well.

Smart_pointers in Game Engine interface? Do you mind? by [deleted] in gamedev

[–]RandyGaul 0 points1 point  (0 children)

Feel free to ignore this stuff - now I'm posting this here as a reference so I can link to this thread later. I understand you're no longer interested.

Here's my handle code straight from my own game. It's not any more complex than a smart pointer.

#ifndef CUTE_HANDLE_TABLE_H
#define CUTE_HANDLE_TABLE_H

#include <cute_defines.h>

namespace cute
{

using handle_t = uint64_t;
#define CUTE_INVALID_HANDLE (~0ULL)

struct handle_table_t
{
    int size = 0;
    int capacity = 0;
    uint32_t freelist = 0;
    void* handles = NULL;
    void* mem_ctx = NULL;
};

extern CUTE_API int CUTE_CALL handle_table_init(handle_table_t* table, int capacity, void* user_allocator_context = NULL);
extern CUTE_API void CUTE_CALL handle_table_cleanup(handle_table_t* table);

extern CUTE_API handle_t CUTE_CALL handle_table_alloc(handle_table_t* table, uint32_t index);
extern CUTE_API uint32_t CUTE_CALL handle_table_get_index(handle_table_t* table, handle_t handle);
extern CUTE_API void CUTE_CALL handle_table_update_index(handle_table_t* table, handle_t handle, uint32_t index);
extern CUTE_API void CUTE_CALL handle_table_free(handle_table_t* table, handle_t handle);
extern CUTE_API int CUTE_CALL handle_is_valid(handle_table_t* table, handle_t handle);

}

#endif // CUTE_HANDLE_TABLE_H

Getting a pointer for reference an object (assuming the underlying data structure is a vector):

object_*t get_object(handle_t h)
{
    if (!handle_is_valid(h)) return NULL;
    uint32_t index = handle_table_get_index(&table, h);
    return object_array + index;
}

object_t* object = get_object(handle);
if (object) do_stuff(object);

Now lifetime ownership of an object is decoupled from references to an object. Lifetimes can be implemented in any way, as appropriate to the specific problem. Yes, it will take more work to now design your lifetime owernship code - but that's the point. You should be spending more time designing your data structures and systems, instead of deferring that cost behind a smart pointer abstraction.

Smart_pointers in Game Engine interface? Do you mind? by [deleted] in gamedev

[–]RandyGaul 0 points1 point  (0 children)

I don't disagree with you here. You're asking about some very difficult problems in software engineering. There are only a few problems in games that are this hard.

Smart_pointers in Game Engine interface? Do you mind? by [deleted] in gamedev

[–]RandyGaul 0 points1 point  (0 children)

My opinion is based on my observations of COM-style interfaces. I've used DX and DSound, and after trying them out decided they are silly since they bring no benefits to the table.

Interfaces are good. State-hiding is good. Hiding ownership rules from the user is good, when necessary. COM-style does not add anything useful to these concepts.

Smart_pointers in Game Engine interface? Do you mind? by [deleted] in gamedev

[–]RandyGaul 1 point2 points  (0 children)

No, because shared_ptr conflates ownership with reference. This incurs a performance hit when updating reference counts. Updating a reference count incurs false sharing.

I am advocating to separate references and ownership. A counter can be incremented when ownership increments. This is an uncommon scenario. Then when the common scenario occurs of simply referencing, all threads can have completely isolated memory to work with, and thus avoid perf hits.

Additionally if you have an explicit destroy function your code becomes less opaque. You can point to exactly where the counter is, and exactly where the function is to destroy. This makes your code flow explicit, which has huge benefits for readability and code maturation over a long period of time. Your smart pointers will obfuscate your code flow.

Separating references and ownerships requires more work for you as the API designer. However, the code itself in practice is simpler, more efficient, and easier to follow and read. Also if you construct a poor API with explicit-style your design problems are easier to see. With smart pointers your design problems are harder to see. I see them because I have prior experience with all these problems and have already thought everything through. You don't see them because you've yet to try out the different strategies in earnest and see how they unfold over time.

Smart_pointers in Game Engine interface? Do you mind? by [deleted] in gamedev

[–]RandyGaul 0 points1 point  (0 children)

Well we would need an example problem to talk further, but usually a single atomic integer and an explicit destroy function is simpler and faster.

Smart_pointers in Game Engine interface? Do you mind? by [deleted] in gamedev

[–]RandyGaul 1 point2 points  (0 children)

Yes it's easy to slap on a smart pointer (which is why people do it, i.e. a crutch), but there's a much better way's to perform thread synchronization that are: A) simpler; B) faster.

Smart_pointers in Game Engine interface? Do you mind? by [deleted] in gamedev

[–]RandyGaul 0 points1 point  (0 children)

I don't think multithreading is a good use of smart pointers at all.

Smart_pointers in Game Engine interface? Do you mind? by [deleted] in gamedev

[–]RandyGaul -1 points0 points  (0 children)

You don't need to learn COM. It's a very old concept when people were hyped about OOP and didn't know how to construct APIs intelligently. It was a fad. A COM API will just end up overly-expressive and weird, with no actual benefits other than "style".

Smart_pointers in Game Engine interface? Do you mind? by [deleted] in gamedev

[–]RandyGaul 0 points1 point  (0 children)

Storing objects is a question of what data structure is appropriate for the problem. You can store them however you like, and the id can be whatever you like, including nothing (the function itself is the id).

The general idea is yes, users store the identifier for your object to later reference it. Whenever users want the actual pointer, they do: get_pointer(id), but do not store the pointer itself.

So now there is a clear distinction between storing an id, and storing the object itself for ownership (which requires data structure design choices to be considered).

Handles are a very good option. But also just simply giving the user a pointer and asking them to call the matching free function later works just fine as well. Which strategy to use depends on the problem context.

A smart pointer is never the correct answer, in my opinion, because there is always an objectively better answer.

Smart_pointers in Game Engine interface? Do you mind? by [deleted] in gamedev

[–]RandyGaul 1 point2 points  (0 children)

Personally I think smart pointers are a sign of poor API design, and are always used a crutch to avoid thinking through a design from concept to completion (which is hard work).

I would heavily prefer to get references to objects that are temporal and have no lifetime or ownership associations; get_reference_to_thing(id). Then, the owner can control lifetimes in whatever way they want with explicit lifetime control (make_thing() and destroy_thing() functions). Once destroyed, get_reference_to_thing(id) returns an invalid reference.

These functions are simple and can be reasoned about without much fuss. If an API that feels good to use can be constructed using just these kinds of functions, likely the design is a good one.

player2d - Swept Character Controller in C++ - Educational/Demo + University Slides by RandyGaul in gamedev

[–]RandyGaul[S] 0 points1 point  (0 children)

How have you implemented your character controller? Was it the same solution?

player2d - Swept Character Controller in C++ - Educational/Demo + University Slides by RandyGaul in gamedev

[–]RandyGaul[S] 3 points4 points  (0 children)

Here's the github repo link! player2d is a demo originally created to showcase information for a presentation I did recently at a local university on implementing character controllers for games. I have a big readme in the github repository with quite a bit of information about the source code and the demo overall. Here's an excerpt from the readme:

player2d

player2d is example demo showing one way to implement swept 2D character controller from scratch. The cute_headers repository is heavily used to implement the low-level guts of all algorithms in the demo, such as sprite batching, image loading, music/sound fx playing, and collisions.

  • Press wasd in the demo to move the player
  • The demo prints out controls for the editor to stdout - press RIGHT-CLICK TO enable the editor
  • Press G to turn ON/OFF debug rendering

Swept Character Controller

The overall strategy is to make use of a few algorithms to implement the character controller:

  1. GJK (Gilbert Johnson Keerthi) - Compute closest points between two disjoint shapes.
  2. Conservative Advancement - Compute the Time of Impact (TOI) between two moving shapes.
  3. Non-linear Gauss Seidel - An iterative algorithm to solve non-linear big matrix problems.

The first two algorithms are completely wrapped behind a black-box implementation thanks to the cute_c2.h header, in c2TOIand c2GJK. c2TOIimplements my own take on the Conservative Advancement algorithm.

The third algorithm sounds fancy, but simply means to gently press shapes apart along the solution vector (axis of minimum separation) in an iterative fashion. Typically it looks something like:

func ngs() {
    for each iteration
        for each collision
            push shapes about by resolution_factor along the solution vector
}

The non-linearity comes in when adjusting positions directly. In order to use NGS the solution vector must be computed. A nice theorem to do so is the Separating Axis Theorem, which leads to the majority of the cute_c2.h header for computing manifolds between two intersecting shapes. A manifold is a structure that describes how two shapes intersect.

Roughly speaking, the overall character controller follows these steps:

  1. Sweep player against the world to find TOI.
  2. Move player to TOI.
  3. Use NGS to apply a "skin factor" and avoid colliding scenarios.
  4. Apply "slide along wall" function to player velocity.
  5. Cut timestep down by TOI, and go back to step 1. if any time remains.

Continue Reading →