class_name SnapshotEventRouter ## Routes snapshot fields to registered handlers (D-020, #559). ## ## Decouples main.gd from knowing which child node handles which snapshot field. ## Handlers are registered in main.gd._ready(); dispatch() is called each snapshot tick. ## ## Two handler types: ## - Keyed: called only when the snapshot contains a specific field. ## - Always: called every dispatch (every snapshot tick), regardless of fields present. ## ## All handlers are zero-argument callables — they read from GameState directly. ## This preserves GameState as the single source of truth post-apply_snapshot(). ## Keyed handlers: field_name → Array[Callable] ## Array per field allows multiple handlers on the same key (e.g., two consumers of same data). var _keyed: Dictionary = {} # String → Array[Callable] ## Always handlers: called every dispatch regardless of snapshot content. var _always: Array[Callable] = [] ## Register a handler for a specific snapshot field key. ## Handler is called (with no arguments) when snapshot.has(field) is true. ## Multiple handlers per field are supported — they run in registration order. func register(field: String, handler: Callable) -> void: if not _keyed.has(field): _keyed[field] = [] _keyed[field].append(handler) ## Register a handler that runs every dispatch tick (not keyed to a field). ## Use for child nodes that update from GameState on every snapshot, e.g. update_from_state(). func register_always(handler: Callable) -> void: _always.append(handler) ## Dispatch a snapshot: call always handlers first, then keyed handlers for present fields. ## Handlers read from GameState.* directly — apply_snapshot() must be called before dispatch(). func dispatch(snapshot: Dictionary) -> void: for handler in _always: handler.call() for field in _keyed: if snapshot.has(field): for handler in _keyed[field]: handler.call()