A union says the following: "This block of memory can be interpreted to mean
one of the following things." A state system like this one operates on the assumption that the program (or system, or whatever we're dealing with) can only be in
one state at a time.
Firstly, because in the union I only store pointers to state-specific data, there's no memory being wasted (pointers are all equal in size, independent of what they're pointing to); the union serves as effectively a cast of raw memory.
Using the TitleScreenData instantiation "casts" the memory to be used as if it's an instantiation of TitleScreenData. Of course, if it isn't arranged like your program expects a TitleScreenData instantiation to be arranged, you'll run into some issues (and your program isn't operating safely). That's what the "type" integer is for; it is designed to determine which data is meant to be used. The programmer's job is just to ensure that they're only accessing, say, the TitleScreenData struct when type == STATE_TITLE.
The definition of the update_gamestate function might look something like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | Gamestate *update_gamestate(Gamestate *g) {
switch(g->type) {
case STATE_TITLE: {
//update the title screen, using the TitleScreenData struct
break;
}
case STATE_GAME: {
//update the game, using the GameData struct
break;
}
//note: my strategy is to branch further into specific update_title and update_game
// functions; this isn't necessarily fastest as it causes more indirection but it helps
// with organization (game functions can be thrown into a separate file, and same with
// title functions)
default: break;
}
return g;
}
|
It's, of course, expected that before update_gamestate is called, the state-specific memory has been allocated according to the state's type integer.
Your question seemed to also involve state changing; my strategy for that is the following:
| Gamestate *current = NULL, *next = NULL;
current = new_title();
//...
//at the end of a game loop, maybe somewhere you set next to be the result of new_game()
if(next) {
free_gamestate(current);
current = next;
next = NULL;
}
|
So, for example, in the title's update function, you might have the following code:
| //in title update function
if(play_button_clicked) {
next = new_game();
}
//at the end of this loop, the current state will be freed and reassigned to next
//this will begin updating and rendering using the /GameData/ instead of the
//TitleScreenData. This, of course, assumes that new_game() returns a valid Gamestate *
//that properly allocates and initializes a GameData struct.
|
Hopefully that cleared things up a bit.