Skip to content

DI container lifecycle

This page documents the recommended lifecycle, ownership, and integration patterns for the GBCompositionContainer when used as a per-player (per-character) container in the Grid Building Plugin.

The intended model: each player (or character) that can build independently gets its own GBCompositionContainer plus an associated GBInjectorSystem and local systems (IndicatorManager, etc.). This keeps state isolated, simplifies testing, and supports multiplayer/multi‑tenant scenarios.

  • the GBOwner (or player root) owns the container and the injector. Create the container when the player joins or the level spawns the player, and free it when the player leaves.
  • container lifetime should match the player’s game lifetime. avoid putting a player container under a global root.
  • container should have a stable id or owner reference to make logs and validation messages clear (for example: container.owner_id or container.owner_name).
  • put the GBInjectorSystem as a child of the player subtree (for example player_node/Systems/GBInjectorSystem).
  • configure injection_roots or rely on the injector being under the player subtree so injection scans are scoped to that player only. do not use a single global injector unless you intentionally want shared wiring.
  • for dynamically-created nodes, either:
    • create them under the player subtree so the injector will catch them, or
    • call an explicit helper: injector.inject_node(node) after instantiation.

Provide a single factory that assembles the per-player composition container, injector and core systems. examples:

# pseudo-code factory
func create_player_container(player_node: Node) -> GBInjectorSystem:
var container := GBCompositionContainer.new()
container.owner_id = player_node.get_name()
var system_root := Node.new()
system_root.name = "GridBuildingSystem"
player_node.add_child(system_root)
var comp := preload("res://addons/grid_building/resources/gb_composition_container.gd").new()
system_root.add_child(comp)
comp.set_config(prepared_config)
var injector := preload("res://addons/grid_building/systems/injection/gb_injector_system.gd").new()
injector.composition_container = comp
system_root.add_child(injector)
injector.initialize(comp) # or call in _ready()/factory
return injector

Use this factory both in gameplay code and tests (UnifiedTestFactory pattern already exists in the repo).

  • standardize on a single initialization point. call injector.initialize(container) once the container is assigned and systems are parented.
  • after validation, emit a container_ready or similar signal. systems should listen for that signal rather than assuming dependencies immediately in _ready().

Add lightweight register_service(name, obj) and get_service(name) helpers on GBCompositionContainer to simplify extension and avoid mutating internals directly.

# suggested API surface
func register_service(name: String, obj: Object) -> void:
_services[name] = obj
func get_service(name: String) -> Object:
return _services.get(name, null)

Use these for 3rd-party integrations or optional cross-service bridges.

  • use factory helpers (see UnifiedTestFactory) to create per-player containers and injectors for isolated tests.
  • add a test that creates two containers and asserts lookups return different instances to guard against accidental singletons.

Note: IndicatorManager uses GridTargetingState.get_runtime_issues() (non-mutating) to guard indicator setup; prefer calling get_runtime_issues() when only diagnostics are required and avoid validate_runtime() unless you want to flip readiness state.

  • container created per-player and freed on player leave
  • injector placed under the player subtree (scoped scans)
  • use factory to assemble container + injector + systems
  • expose register_service / get_service for extensions
  • emit container_ready after initialize/validation
  • tests covering multi-container isolation
  • prefer passing small services (logger, states) rather than persisting the full container on every node. document this rule in PRs and code comments.
  • shared resources (templates, .tres assets) may remain global; runtime systems and states should be per‑container.

If you want I can add the register_service helper to gb_composition_container.gd and a small unit test that verifies two containers are isolated.