36. Global Game State
At the current version of this code base, there are 18 separate gs := cast(^Game_State)context.user_ptr assignments - this indicates the "escape hatch" (rare use-case, just-in-case) is not how we are actually using it, and it'd be better off as an explicit global.
Passing explicit parameters through the program is ideal for debugging as you get a useful stack trace.
However, since we store Game_State in a contiguous block of memory to facilitate hot reloading, it makes sense that any part of our program that touches state is required to touch something in Game_State
For our code base, create a gs global of type ^Game_State and assign at these points:
gs: ^Game_State
@(export)
game_post_window_create :: proc(game_state: ^Game_State) {
gs = game_state
// ...
}
@(export)
game_post_reload :: proc(game_state: ^Game_State, rl_api: ^raylib_api.Raylib_API) {
gs = game_state
rl = rl_api
}
Below are the slides for this lesson:
Use When:
- Many systems must touch the same data
- Data is a single instance (Game_State)
- Passing through every function adds more friction than clarity
- Global is already global (
context.user_ptr)
Don't Use When:
- Multiple instance might exist
- You want to run tests or simulations...
Tests and Simulations
Why:
- Balancing gameplay (running 10,000 simulated battles to test builds)
- Regression testing (replaying a sequence after an update)
Caveat:
- Still possible with globals, but becomes a discipline problem rather than structural
Foot-guns:
- Shadowing (-vet helps)
- Hidden dependencies
- Data initialisation order
- Data update order
How to Decide
- Unless very obvious: start with explicit parameterisation
- If touched everywhere, long lived, singular - promote
- If using hot reloading, don't forget to re-assign at entry point