Table of Contents

Class GameStateSnapshot

Namespace
Core
Assembly
Core.dll

ENGINE - Double-buffer pattern for zero-blocking UI reads

Problem: UI needs province data without blocking simulation Victoria 3 uses locks → "waiting" bars in profiler

Solution: Double-buffer (two NativeArrays)

  • Simulation writes to buffer A
  • UI reads from buffer B
  • After tick: swap pointers (O(1), not memcpy!)

Memory: 2x hot data (~160KB for 10k provinces at 8 bytes) Performance: O(1) pointer swap (no copying!) Staleness: Zero (UI reads completed tick)

Pattern from Paradox analysis:

  • Victoria 3 locks = bad (waiting bars)
  • Snapshots = good (zero blocking)
  • Double-buffer = optimal (no memcpy overhead)
public class GameStateSnapshot
Inheritance
object
GameStateSnapshot

Properties

Capacity

Get buffer capacity

public int Capacity { get; }

Property Value

int

CurrentWriteBufferIndex

Get current write buffer index (0 or 1) - for debugging

public int CurrentWriteBufferIndex { get; }

Property Value

int

IsInitialized

Check if initialized

public bool IsInitialized { get; }

Property Value

bool

Methods

Dispose()

Dispose both buffers

public void Dispose()

GetProvinceReadBuffer()

Get read buffer (UI reads here) Thread-safe: UI can read while simulation writes to other buffer

public NativeArray<ProvinceState> GetProvinceReadBuffer()

Returns

NativeArray<ProvinceState>

GetProvinceWriteBuffer()

Get write buffer (simulation writes here)

public NativeArray<ProvinceState> GetProvinceWriteBuffer()

Returns

NativeArray<ProvinceState>

Initialize(int)

Initialize double buffers

public void Initialize(int provinceCapacity)

Parameters

provinceCapacity int

SwapBuffers()

Swap buffers after simulation tick (O(1) pointer flip)

Call from TimeManager after tick completes:

  • Simulation just finished writing to write buffer
  • Flip pointers so UI sees new data
  • Simulation starts writing to old read buffer next tick

Performance: O(1) pointer flip only

NOTE: This just flips the buffer pointers. The caller (ProvinceSystem.SwapBuffers) is responsible for copying dirty entries to preserve persistent state. This separation allows ProvinceSystem to use dirty tracking for O(dirty) copies instead of O(all) full buffer copies.

public void SwapBuffers()

SyncBuffersAfterLoad()

Synchronize read buffer with write buffer (copy write → read)

Used after scenario loading to ensure both buffers have the same initial data. This prevents the first tick from reading from an empty buffer after the first swap.

Performance: O(n) memcpy - only call during initialization, not during gameplay

public void SyncBuffersAfterLoad()