PVG
36. Global Game State — Program Video Games

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