2025-184
15:40:29, atom feed.
When I went to implement basic player movement, I needed to capture keyboard input. This is done in SDL by polling for events and then processing keyboard-related ones.
SDL_Event e;
while (SDL_PollEvent(&e)) {
switch (e.type) {
case SDL_EVENT_KEY_UP:
// Fall through.
case SDL_EVENT_KEY_DOWN: {
if (event.key.key == SDLK_DOWN) {
// ...
}
}
break;
default:
break;
}
}
What I want to focus on in this post is the implementation of SDL_Event
. If
you look at its documentation, you'll
see that it's a union
. Its first member is type
which encodes the event
type--it's what we're switching on in the example above. If you look at the
implementation of any of the SDL_*
union members, you will see the first
field of each of those structs is an instance of SDL_EventType
.
SDL_EventType
is implemented as a C enum, which is represented as an int
behind the scenes, which is the same number of bits (although different
semantics) as the Uint32
(uint32_t
-equivalent) type
member in the
SDL_Event
union on nearly all modern platforms.
For reference, see the implementations of, for example, SDL_ClipboardEvent, SDL_TouchFingerEvent, SDL_MouseButtonEvent, or SDL_QuitEvent.
In other words, no matter which member of the union you work with, the first field will always be something encoding the event type.
This construct is called a discriminated union (or tagged union). Here is an all-in-one example:
enum TypeTag { kFoo, kBar, kNone };
struct Foo {
TypeTag tag;
// ...
};
struct Bar {
TypeTag tag;
// ...
};
union DiscriminatedUnion {
TypeTag tag;
Foo foo;
Bar bar;
};
With discriminated unions, you can avoid inheritance and still compose one type
that behaves as an instance of this or that type depending on some context, and
you just check the value of tag
to know how to access fields of the union. You
lose anything you might have otherwise gained through polymorphism, and the
total size of DiscriminatedUnion
is the size of its largest member (plus
padding, potentially; see the interesting note at the bottom of the SDL_Event
implementation). However, the
discriminated union construct supports
data-oriented design by
concentrating data of interest nearby in memory instead of potentially scattered
about through a network of pointers (whether these pointers be
pointers-to-base-class in some data structure, or members of instances pointing
to this and that data, or the CPU figuring out which methods to call via
dynamic dispatch).