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.
overview
Section titled “overview”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.
ownership and lifecycle rules
Section titled “ownership and lifecycle rules”- 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
orcontainer.owner_name
).
injector placement and injection roots
Section titled “injector placement and injection roots”- 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.
construction factory (recommended)
Section titled “construction factory (recommended)”Provide a single factory that assembles the per-player composition container, injector and core systems. examples:
# pseudo-code factoryfunc 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).
initialization ordering and signals
Section titled “initialization ordering and signals”- 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()
.
api helpers (small additions)
Section titled “api helpers (small additions)”Add lightweight register_service(name, obj)
and get_service(name)
helpers on GBCompositionContainer to simplify extension and avoid mutating internals directly.
# suggested API surfacefunc 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.
testing guidance
Section titled “testing guidance”- 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.
checklist (quick)
Section titled “checklist (quick)”- 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.