Wave

Typeclasses in C

Sara Gerretsen

(This is a modified version of the pattern shown in this article by Phantz but botched modified into working for my own usecase which I thought I'd document for both others and myself)

Motivation

I really like making games in C. I'm not sure why, the language just meshes with my brain in a way that I can't get rid of.
Using C as a language for games is nice, it basically forces simple, straight to the point solutions. None of the endless abstractions and hierarchies that I've caught myself writing in Rust, C# or C++ (this may or may not be a me problem).
General purpose game engines are another problem though. You can make a game with reusable parts, but that's generally not what I'd call a “game engine”. The definition of engine I quite like is that for a framework to be a Game Engine it needs to handle at the very least:

  • Asset Management,
  • Actor/Entity/GameObject lifecycles,
  • Input polling/passing
  • Media (audio, rendering, etc.),
  • And the application loop.

While not prescribing the player's actions or the world's reactions.

Now I use SDL (_render, _mixer, etc.) for my games, so media and input is pretty easy to handle. Asset management takes a bit of writing, but isn't too hard to do in any language. The application loop is similarly easy as well.

But object management kept throwing me for a loop in C specifically. I was never taught the C language, I just have this book that taunts me in my sleep. So I had no clue as to how to do polymorphism in C in a “clean” way. I just wanted some way to pass the engine some object that refers to a game object's data “anonymously” while still exposing the functions the engine needed (Start, Tick, Destroy, etc). So enter stage left:

The Typeclass pattern

In Haskell this is a language feature, Rust has something similar in the form of traits, in C++ we have abstract classes and C# this would be an interface. C obviously doesn't have any of that as part of the language so we have to write it ourselves. At it's most basic, pattern requires two objects and a macro. The two objects are an interface struct containing function pointers, and the typeclass object which contains the interface and a void pointer to what I'll call the data.
Defining this looks something like:

drop.h

typedef struct {
    void (*const drop)(void*);
} IDrop;

typedef struct {
    void *data;
    IDrop const *tc;
} Drop;

And then we need a function which produces a typeclass Drop for an object of some type, to do this we define a macro.

#define impl_Drop_for(T, drop_f)\
Drop T##_as_Drop(T* x) {\
    TC_FN_TYPECHECK(void, drop_f, T*);\
    static IDrop const tc = {\
        .drop = (void(*const)(void*)) drop_f,\
    };\
    return (Drop){.tc = &tc, .data = x};\
}

(TC_FN_TYPECLASS is a helper macro defined as #define TC_FN_TYPECHECK(__Return, __Name, ...) __Return (*const __Name##_)(__VA_ARGS__) = __Name; (void)__Name##_ I use to trigger compile time type checking on the functions)

impl_Drop_for can then be used in an implementation file like

Player.c:

impl_Drop_for(Player,
    PlayerDestroy
)

Which can then be used to create an object of type drop like:

Player *player = MakePlayer(at_location); // allocate player
Drop player_as_drop = Player_as_Drop(player); // wrap in a Drop typeclass
player_as_drop.tc->drop(player_as_drop.data); // destroy player using drop typeclass

This is slightly different to Phantz' previously mentioned version. As it forces you into using the <Struct>_as_<Typeclass> naming scheme. This means that I can somewhat streamline Phantz' version of combining typeclasses by letting you require other typeclasses implicitly.

For example my “game engine” (it's not that far along yet) has a BehaviourEntity typeclass. Which serves as the primary way for the engine to interact with game-specific objects. Because I want the engine to take ownership of any entities in the game, it also requires the Drop typeclass.

Now, instead of passing Player_as_Drop to impl_BehaviourEntity_for as an argument, the macro can assume that the naming scheme was followed.

#define impl_BehaviourEntity_for(T, start_f, update_f, draw_f, get_depth_f)\
BehaviourEntity T##_as_BehaviourEntity(T* x) {\
    TC_FN_TYPECHECK(void, start_f, T*);\
    TC_FN_TYPECHECK(void, update_f, T*, float);\
    TC_FN_TYPECHECK(void, draw_f, T*);\
    TC_FN_TYPECHECK(long, get_depth_f, T*);\
    static IEntityBehaviour const tc = {\
        .update =  (void(*const)(void*, float))  update_f,\
        .start =   (void(*const)(void*))         start_f,\
        .draw =    (void(*const)(void*))         draw_f,\
        .get_depth=(long(*const)(void*))         get_depth_f,\
    };\
    TC_FN_TYPECHECK(Drop, T##_as_Drop, T*);\
    TC_FN_TYPECHECK(Mirror, T##_as_Mirror, T*);\
    IDrop const* drop = T##_as_Drop(x).tc;\
    IMirror const* mirror = T##_as_Mirror(x).tc;\
    return (BehaviourEntity){.data = x, .tc = &tc, .drop = drop, .mirror = mirror};\
}

(Don't mind the Mirror bits, or if you do check the Bonus section :3)

This version will still complain when you don't have the typeclass function declared, but will grab it if it's available, while still checking if the Player_as_Drop function signature is valid. So now we are able to pass ownership over our behaviour entities to the engine knowing that they'll be destroyed when we ask it to load a different level.

Now we can put

impl_BehaviourEntity_for(Player,
    PlayerStart,
    PlayerUpdate,
    PlayerDraw,
    PlayerGetDepth
)

Below the Drop implementation in Player.c. Then, using a much simpler macro, add

Player.h:

decl_typeclass_impl(Drop, Player);
decl_typeclass_impl(BehaviourEntity, Player);

/* decl_typeclass_impl is defined as
 * #define decl_typeclass_impl(__Typeclass, __Type) extern __Typeclass __Type##_as_##__Typeclass(__Type*)
 * The semicolon is being eaten intentionally here.
 */

to Player.h and you have everything my engine would require to be a “valid scene entity”.

Wrapping up

I personally quite like this pattern. Mainly because (at least in the way I use it) it's easy to just use it only where it's needed. In the engine I'm writing it's entirely possible to use typeclasses only as a language to speak to the engine while writing your own code using some other form of polymorphism. Or none if that's your style.
This is a pattern mainly for places where there's a need for basic polymorphism with no inheritance or OOP features.

Which is one of the reasons I'm fond of it.

Bonus: The Mirror typeclass

Here's a typeclass that enables casting between typeclasses.

Skip the code if you just want the explanation.
Or at least something that looks like an explanation.

mirror.h:

#include "typeclass_helpers.h"
#include "stdint.h"
#include "string.h"
#include "dictionary.h"
#include "strutil.h" // included because the impl macros require strhash

typedef uintptr_t typeid;

typedef struct {
    const char *(*const get_typestring)(void* self);
    typeid (*const get_typeid)(void* self);
    Dictionary *(*const get_typeclasses)(void* self);
} IMirror;

typedef struct {
    void* data;
    union {
        IMirror const *tc;
        // this is cursed, but it allows the TC_CAST macro to work
        IMirror const *mirror;
    };
} Mirror;

typedef struct {
    const void *typeclass;
    void *function;
} MirroredTypeclass;

static inline int mirror_is_typeid(const Mirror *mirror, typeid id) {
    return mirror->tc->get_typeid(mirror->data) == id;
}
static inline int mirror_is_typestring(const Mirror *mirror, const char *id) {
    return strcmp(id, mirror->tc->get_typestring(mirror->data)) == 0;
}
static inline int mirror_eq(const Mirror *lhs, const Mirror *rhs) {
    return lhs->tc->get_typeid(lhs->data) == rhs->tc->get_typeid(rhs->data);
}

extern const void* mirror_get_typeclass(void* data, IMirror const* tc, const char* typeclass);
// get the wrapper function for a given typeclass name
// example:
// mirror_get_function(physics_entity.data, physics_entity.mirror, "BehaviourEntity")
extern void* mirror_get_function(void* data, IMirror const* tc, const char* typeclass_name);

// macro reexport of mirror_get_function which will cast the function so it can be called immediately
// example:
// MIRROR_GET_WRAP_FUNC(physics_entity.data, physics_entity.mirror, BehaviourEntity)(physics_entity.data)
#define MIRROR_GET_WRAP_FUNC(Data_, MirrorTc_, Typeclass_)\
((Typeclass_(*)(void*))mirror_get_function(Data_, MirrorTc_, #Typeclass_))

// casting only works if the typeclass in question exposes IMirror as .mirror
// will segfault if the Mirror does not expose To_
// example:
// TC_CAST(physics_entity, BehaviourEntity)
#define TC_CAST(From_, To_)\
MIRROR_GET_WRAP_FUNC(From_.data, From_.mirror, To_)(From_.data)

#define TC_MIRRORS(From_, To_)\
MIRROR_GET_WRAP_FUNC(From_.data, From_.mirror, To_) != NULL

#define MIRROR_TRY_WRAP(Into_, Mirror_, Typeclass_){\
    MirroredTypeclassWrapFunc fn_ = mirror_get_typeclass(Mirror_, #Typeclass_);\
    if(fn_ != NULL) {\
        Into_ = (TypeClass_)fn(Mirror_->data);\
    }\
}

#define impl_Mirror_for(T, get_typestring_f, get_typeid_f, get_typeclasses_f)\
Mirror T##_as_Mirror(T *x) {\
    TC_FN_TYPECHECK(const char*, get_typestring_f, T*);\
    TC_FN_TYPECHECK(typeid, get_typeid_f, T*);\
    TC_FN_TYPECHECK(Dictionary*, get_typeclasses_f, T*);\
    static IMirror const tc = {\
        .get_typestring = (const char*(*const)(void*)) get_typestring_f,\
        .get_typeid = (typeid (*const)(void*)) get_typeid_f,\
        .get_typeclasses = (Dictionary* (*const)(void*)) get_typeclasses_f,\
    };\
    return (Mirror){.tc = &tc, .data = x};\
}

#define DECL_REFLECT(T)\
extern const char *T##_get_typestring(T *self);\
extern typeid T##_get_typeid(T *self);\
extern Dictionary *T##_get_typeclasses(T* self);\
decl_typeclass_impl(Mirror, T)

#define START_REFLECT(T)\
const char* T##_get_typestring(T *self) {\
    static char const *const typestring = #T;\
    return typestring;\
}\
typeid T##_get_typeid(T *self) {\
    static char init_flag = 0;\
    static typeid id = 0;\
    if(!init_flag) {\
        init_flag = 1;\
        id = strhash(#T);\
    }\
    return id;\
}\
Dictionary *T##_get_typeclasses(T* self) {\
    static char init_flag = 0;\
    static Dictionary typeclasses;\
    if(!init_flag) {\
        init_flag = 1;\
        typeclasses = dictionary_new(sizeof(MirroredTypeclass));\
        MirroredTypeclass tc;\
    REFLECT_TYPECLASS(T, Mirror)

#define REFLECT_TYPECLASS(T, TypeClass_)\
    tc = (MirroredTypeclass){\
        .typeclass = (void*)T##_as_##TypeClass_(NULL).tc,\
        .function = (void*)T##_as_##TypeClass_\
    };\
    dictionary_set_raw(&typeclasses, #TypeClass_, &tc);

#define END_REFLECT(T)\
    }\
    return &typeclasses;\
}\
impl_Mirror_for(T, T##_get_typestring, T##_get_typeid, T##_get_typeclasses)
mirror.c

#include "mirror.h"

MirroredTypeclass* internal_mirror_get_typeclass(void* self, IMirror const* tc, const char* typeclass) {
    return dictionary_get_raw(tc->get_typeclasses(self), typeclass);
}

const void* mirror_get_typeclass(void* data, IMirror const* tc, const char* typeclass) {
    MirroredTypeclass* class = internal_mirror_get_typeclass(data, tc, typeclass);
    if(class != NULL)
        return class->typeclass;
    else
        return NULL;
}

void* mirror_get_function(void* data, IMirror const* tc, const char* typeclass_name) {
    MirroredTypeclass* class = internal_mirror_get_typeclass(data, tc, typeclass_name);
    if(class != NULL)
        return class->function;
    else
        return NULL;
}

Huh, what, why?

So, my game engine has a rudimentary physics engine. I wanted some way to query that physics engine with simple functions like

PhysicsEntity *result = physics_world_query_box(position, extents, layers);

But this comes with a limitation. Say I want to have some objects that can be damaged. I would want a DealDamage function that could be called on the target object. But with what I had at the time, I'd have had to add a deal_damage function to the interface of PhysicsEntity. Which I really didn't want to do because of the

While not prescribing the player's actions or the world's reactions.

part of my “definition” of a generic game engine.
So instead I needed to add another layer of abstraction. I needed some way of exposing any function through the PhysicsEntity typeclass.

There's two ways I considered implementing the Mirror typeclass. It would either allow you to call functions using a string, or allow you to get other typeclasses using a string. I decided on the latter as it would allow me to be slightly better about respecting function signatures. The alternative would require casting function pointers even more than I'm already doing, which I dislike.

How

Implementing the Mirror typeclass for any given struct looks something like this.

Player.h:
...
DECL_REFLECT(Player)

This serves the same purpose as the generic decl_typeclass_impl but rather than just declaring Player_as_Mirror it also declares Player_get_typestring, Player_get_typeid, and Player_get_typeclasses. This is because these functions are implemented by some more macros which we'll place in Player.c:

Player.c
START_REFLECT(Player)
    REFLECT_TYPECLASS(Player, Drop)
    REFLECT_TYPECLASS(Player, PhysicsEntity)
    REFLECT_TYPECLASS(Player, BehaviourEntity)
    REFLECT_TYPECLASS(Player, Transformable)
END_REFLECT(Player)

...

This is where most of the magic happens, whether it's curses or miracles we can deliberate later.
START_REFLECT will implement the aforementioned functions Player_get_typestring and Player_get_typeid. When END_REFLECT expands, it'll include impl_Mirror_for_Player and all of it's arguments. START_REFLECT ends before the closing brace of Player_get_typeclasses, instead the closing braces are expanded from END_REFLECT.

REFLECT_TYPECLASS is then put inbetween the two. Every instance of REFLECT_TYPECLASS adds some code that will be run the first time Player_get_typeclasses is called. This bit of code inserts a value to a static dictionary containing a pointer to the function that converts a Player instance to a typeclass, with the name of the typeclass as it's key.

So now, if another typeclass has Mirror as a requirement, an end programmer can use Mirror to

  1. find out if this object implements a given typeclass.

and

  1. get the object as a different typeclass.

Meaning that I don't have to add a deal_damage function to my PhysicsEntity interface. Instead I can require Mirror in PhysicsObject like any other typeclass, then check if the hit entity implements Damagable, get an instance of Damagable (or IDamagable directly) and call the object's deal_damage function that way.

About the Author

Sara Gerretsen

Sara is a game developer studying game development who makes games in their free time. She likes to throw ideas at the wall and make whatever hits the ground first.

Mia Rose WinterReviewer

This might also interest you

Generation 3 of Pokémon is held together by duct tape and I can prove it

Forbidden Tempura 4/16/2024

The problem space Pokémon is a franchise that needs no introduction. Millions upon millions of people have had some exposure thereto. I would like to focus on one tiny implementation detail of the games that formed the third generation of Pokémon: Ruby, Sapphire, FireRed, LeafGreen, and Emerald. In this generation of the games, the developers of the series first started trying to discourage cheating. To understand what they did, I first need to introduce a few concepts. A Pokémon is an individual member of a Pokémon species. Think of it like a specific individual, just like how there is a sheep that you encounter and there is the species of sheep, of which the aforementioned individual of a member. Every Pokémon has a 32-bit personality value (also commonly called personality ID or PID for short). A (Pokémon) trainer is a concept in the games: Trainers go around and make their Pokémon fight against other Pokémon. They hold contests to find out who is the strongest trainer. Supposedly

Game DevelopmentGamesInfodump

My First Game In Godot

Mia Rose Winter 3/7/2024

Last Sunday I decided to finally take a shot at using Godot. I only looked a bit at the getting started section of the documentation but haven't looked at anything past that or even tried using it past myself, past downloading it. As I used many engines before, and tried to make my own, a lot of it was very familiar, but many things were also easier to do than in other engines I know. As I do, I noted in a group chat that I still don't know how to make the 404 page of Wave more interesting, when Nyx suggested &ldquo;dino minigame but with shork&rdquo;. As I was a bit kaputt that morning I thought &ldquo;yea let's go&rdquo; and opened up Godot. Well, first I needed my main character, so I crudely copied a blahaj design from a sticker Rain gave me a while ago. With that set, I opened up the editor and looked around YouTube and the Godot documentation for all the little pieces of Information I needed. This &ldquo;Godot 4 Crash Course for Beginners&rdquo; gave me a good start. In it the c

Game DevelopmentShowcase

Depth Ordering in GameMaker 2

Mia Rose Winter 2/29/2024

At some point during your usage of GameMaker 2 you will very likely encounter a very classical issue in game development, how do I ensure my sprites are drawn in the right order? Anything 2D with movement will still have a sense of hierarchy, some things are in front of others, but how do you do that with GameMaker? When you will look into your trusty search engine, you will most likely find either this blog about using a technique called z-tilting, and if the following doesn't satisfy you, definitely check it out, or you may find the YouTube channel FriendlyCosmonaut with this tutorial video, which is a bit older but still teaches some valid techniques. As someone with more background in different engines and other areas of software techniques, I want to elaborate on some of my strategies to solve this issue, including the one I used in my student project game fairy-strategy, a turn based 2D strategy game. The classic depth = -y The oldest and easiest way to get this working. Things l

Game DevelopmentTutorial
Powered by Wave