Dependency Injection System
The plugin includes a lightweight, opt‑in dependency injection (DI) mechanism that removes most manual wiring between gameplay nodes and the Grid Building systems. It centers around two core pieces:
- GBCompositionContainer – A per-context (e.g., per player / world / session) resource that owns configured settings, templates, actions, states, contexts, and logging.
- GBInjectorSystem – A scene system that discovers nodes needing dependencies and injects the shared GBCompositionContainer into them at runtime.
- Centralize access to configuration, states, and contexts.
- Avoid deep node path lookups or duplicate singletons.
- Allow multiple independent grid building contexts to coexist (e.g., splitscreen players) by scoping injection roots.
- Support late / dynamic node creation without extra boilerplate.
- Provide consistent logging and validation gateways.
Opt‑In Contract
Section titled “Opt‑In Contract”Any node that implements:
func resolve_gb_dependencies(p_config: GBCompositionContainer) -> void: # Pull what you need from p_config var logger := p_config.get_logger() var targeting_state := p_config.get_targeting_state()
…will automatically receive the active composition container when it enters an injection scope.
No interface registration is required—presence of the method name resolve_gb_dependencies
is the marker.
GBCompositionContainer is the authoritative gateway to all major systems:
- Settings (
get_settings()
,get_visual_settings()
,get_manipulation_settings()
,get_actions()
,get_messages()
). - Templates (
get_templates()
). - Contexts (
get_contexts()
) and sub-context accessors likeget_systems_context()
orget_indicator_context()
. - Mutable runtime state holders (
get_states()
returning building / targeting / manipulation / mode states). - Logging (
get_logger()
) – with graceful fallback when configuration is incomplete. - Validation (
validate_configuration()
,validate_runtime_configuration()
,validate_and_log_issues()
).
It lazy‑constructs internal components so that missing or partially configured resources can still emit structured warnings via the logger.
Being a Resource
allows cloning or per‑player instancing while keeping memory light and avoiding tight coupling to any scene tree node. It also simplifies tooling and export workflows.
Central Configuration: GBConfig
Section titled “Central Configuration: GBConfig”GBConfig is the single authoritative configuration resource consumed by GBCompositionContainer. It aggregates previously scattered exported fields (grid sizing, placement & validation toggles, indicator visuals, manipulation tuning, action/message resources, debug flags) into one asset.
Key aspects:
- Acts as the root object passed into composition; other sub-resources (settings, templates, messages) are referenced from it.
- Enables cloning per player/session by duplicating a single resource tree.
- Reduces risk of version skew (all tuning lives together, easier diff/merge in VCS).
- Validation runs against GBConfig via GBConfigurationValidator before/after injection.
- Eliminates brittle node export wiring; systems retrieve needed values through container accessors which delegate to GBConfig.
Minimal setup workflow:
- Create a GBConfig resource (e.g.,
res://config/grid_building_config.tres
). - Populate its sub-sections (grid settings, visual settings, manipulation, actions/messages, templates references).
- Assign the resource to a GBCompositionContainer (inspector or factory method).
- Provide that container to GBInjectorSystem.
Benefits vs scattered exports:
Previous Pattern (4.x) | Centralized in 5.0.0 (GBConfig) | Outcome |
---|---|---|
Dozens of per-system exported tuning fields | Single resource with structured sections | Faster onboarding & consistent defaults |
Hard to validate completeness | Validator can traverse one tree | Early warning & logging |
Duplicate tweaks across multiple scenes | Reuse one shared asset | Lower maintenance cost |
See the API: GBConfig and GBConfigurationValidator for field-level detail.
- Initialization (
_ready
→_initialize
):- Verifies a
composition_container
is assigned; otherwise disables itself (push_error). - Captures debug settings from the container (
_debug
). - Performs an initial recursive injection over each configured injection root (or the scene tree root if none).
- Emits
initial_injection_completed
after the first pass.
- Verifies a
- Runtime Monitoring:
- Connects to
SceneTree.node_added
to intercept dynamically added nodes. - Connects
child_entered_tree
per injection root to scope additions. - Connects
tree_exiting
per injected node for cleanup / meta removal and optional verbose exit logging.
- Connects to
- Injection Execution (
inject_node
):- Skips nodes not yet inside the tree or already injected by this injector instance.
- If the node exposes
resolve_gb_dependencies
, calls it with the sharedcomposition_container
. - Attaches metadata (
gb_injection_meta
) with injector id, weakref, timestamp, and stored signal callbacks. - Emits
node_injected
and logs a verbose message (if logging level allows).
- Validation:
validate_and_log_issues()
defers to the container’s validation methods, reporting warnings through the logger.
Injection Scoping
Section titled “Injection Scoping”Specify one or more injection_roots
on the GBInjectorSystem to restrict which subtrees participate. If left empty, the entire scene tree root is used.
Scope check logic:
- A newly added node is injected only if it is (or is a descendant of) one of the root nodes.
- Multiple roots can coexist; each is monitored independently.
This enables patterns like separate player worlds sharing the same code paths but isolated state.
Metadata & Idempotency
Section titled “Metadata & Idempotency”A node stores gb_injection_meta
after successful injection. Subsequent passes (e.g., from other signal hooks) short‑circuit if the stored injector id matches, preventing duplicate dependency assignments or log spam.
Metadata fields (representative):
injector_ref
: Weak reference to the injector.injector_id
: Numeric instance id snapshot.timestamp_ms
: Time of injection for debugging.- Internal keys for connected callbacks (
_child_entered_cb
,_tree_exiting_cb
).
On node exit, callbacks are disconnected and metadata removed (or trimmed) to avoid memory / reference leaks.
Logging & Debug Levels
Section titled “Logging & Debug Levels”The injector defers most formatting to GBLogger retrieved from the container. Verbose per-node lines (e.g., [GBInjectorSystem] Injected: NodeName at /root/...
) appear only when the debug level threshold permits. Exit events optionally log a one‑time debug line via log_debug_once
to reduce noise.
If logger acquisition fails early, the injector falls back to standard printing until the container can provide a logger.
Validation Flow
Section titled “Validation Flow”Typical startup order:
- Create or assign a GBCompositionContainer with at least a GBConfig resource.
- Add a GBInjectorSystem node, set its
composition_container
, optionally defineinjection_roots
. - Let
_ready()
trigger initial injection; nodes withresolve_gb_dependencies
now have access to configuration. - Call
validate_and_log_issues()
on the injector or directly on the container to surface configuration warnings (misconfigured templates, missing settings, etc.).
Runtime validations (e.g., after loading a level scene) can re-run to surface dynamic setup issues.
Example Pattern
Section titled “Example Pattern”# In a scene script needing grid building systemsextends Node
var _logger : GBLoggervar _targeting_state : GridTargetingState
func resolve_gb_dependencies(p_config: GBCompositionContainer) -> void: _logger = p_config.get_logger() _targeting_state = p_config.get_targeting_state() _logger.log_debug(self, "Dependencies resolved for %s" % name)
Testing Utilities
Section titled “Testing Utilities”Unit tests often construct an injector with:
var injector := GBInjectorSystem.create_with_injection(container)
Or use factory helpers (e.g., UnifiedTestFactory.create_test_injector
) to streamline container + system creation, guaranteeing a fresh context for each test case.
When Not To Use Injection
Section titled “When Not To Use Injection”- Extremely simple helper nodes that only need a single exported reference.
- One-off editor tools where direct resource assignment is clearer.
- Performance‑critical inner loops (avoid extra method indirection during hot path creation if profiling reveals overhead).
Best Practices
Section titled “Best Practices”- Keep
resolve_gb_dependencies
fast; perform heavy initialization lazily afterwards if needed. - Avoid storing the entire GBCompositionContainer on every node—pull only what you need to reduce coupling.
- Prefer using accessor methods (e.g.,
get_targeting_state()
) instead of reaching deeply into nested resources. - Use validation early in development scenes to catch configuration gaps.
- Scope injection roots to smallest reasonable subtree in large worlds to reduce signal churn.
Related API Pages
Section titled “Related API Pages”Future Enhancements (Planned / Considerations)
Section titled “Future Enhancements (Planned / Considerations)”- Automatic parameter type discovery for richer docs (parsing argument annotations in
resolve_gb_dependencies
). - Optional interface tagging (annotation or signal-based) to reduce reliance on method name convention.
- Hot-reload re-injection hooks for editor tooling.
If you have additional needs, open an issue or propose an extension in the contributor docs.