UI User Id Service Architecture
Overview
This document tracks the migration of GridBuilding UI components to use a service-layer user id (player user) instead of relying on Godot @export fields (exported NodePaths/resources) to bind UI to a specific player or character.
Goals for GridBuilding 6.0:
- Associate all UI behavior with a player user id managed by the plugin service layer.
- Avoid brittle
@exportfields for identity (NodePaths, Resources, hard-coded scene references). - Make it straightforward to support multiple players and re-bindable characters without rewriting UI logic.
Motivation
Relying on @export fields to point at a specific player node or resource couples UI directly to the scene graph. This becomes fragile when:
- Players join/leave or respawn.
- Characters are switched or possessed.
- Multiplayer or split-screen scenarios are introduced.
By moving to a user-id-centric architecture:
- UI targets a logical player user, not a specific NodePath.
- The service layer is responsible for mapping user ids → active characters / sessions.
- The same UI code works in single-player and multiplayer setups.
Architecture Principles
User identity lives in the service layer
- The plugin exposes a user / player context through services (e.g., session or player-management services).
- Godot UI nodes resolve the current user id through the service layer, not via exported references.
UI depends on services, not NodePaths
- UI nodes resolve required services (e.g., targeting, placement, mode, user context) from the ServiceRegistry.
- UI queries the current user id and asks services for data/actions on behalf of that user.
No new identity
@exportfields- Avoid new exported fields whose only purpose is to locate “the player” or “the current character”.
- Scene references are still fine for pure visual wiring, but identity and user association must come from services.
Testability and multiplayer readiness
- Tests can construct service-layer user contexts without needing a specific scene layout.
- Adding another player is primarily a matter of creating another user id + character binding, not adding new UI scripts.
Migration Targets (Examples)
This table lists key UI components that should migrate to user-id-based patterns. Status values are placeholders; update them as work progresses.
| Area | Node / Component | Status | Notes |
|---|---|---|---|
| Targeting feedback UI | TargetInformer (Godot/Systems/UI/TargetInformer.cs) | Planned | Resolve user id via services and drive UI based on the active user, not a single exported target node. |
| Action log UI | ActionLog (Godot/Systems/UI/Actions/ActionLog.cs) | Planned | Log actions per user session; avoid hard-coded player references via exports. |
| Other HUD / overlays | (to be identified) | Planned | Any HUD elements that implicitly assume a single local player should be reviewed. |
Migration Checklist
When migrating a UI component:
Identify current identity coupling
- Locate
@exportfields or hard-coded NodePaths/resources used to find “the player” or character.
- Locate
Introduce user-id awareness
- Resolve the relevant user / player context from the plugin service layer.
- Store and pass around user ids, not direct Node references, as the unit of identity.
Route operations through services
- For operations that depend on the player (targeting, placement, manipulation, modes), call into services using the user id.
- Let services decide which character / entities that user currently controls.
Remove identity-related exports
- After services are wired, remove unnecessary
@exportfields that previously located players or characters.
- After services are wired, remove unnecessary
Update tests and docs
- Add or update tests that cover user-id-based flows.
- Update relevant v6 docs (e.g., UI or dependency-resolution guides) to describe the new pattern.
Session / UserId Creation Patterns
There are two practical ways sessions and UserId values can be created when wiring UI:
Game-owned direct commands (canonical for v6)
- The host game/session model owns
UserId. - Game code maps controller slots, accounts, or peers →
UserIdand passes it into:- Core commands (e.g.,
IPlacementCommands,IManipulationCommands). - UI initializers (e.g.,
TargetInformer.InitializeForUser(UserId, ...),ActionLog.InitializeForUser(UserId, ...)).
- Core commands (e.g.,
- GridBuilding stays engine/network agnostic and only sees
UserId.
- The host game/session model owns
CompositionContainer-based scaffolding (legacy/migration)
CompositionContaineris a temporary migration and test helper used to keep older GBCompositionContainer/.tres driven projects and tests running while v6.0 is rolled out.- It can be used to stand up services and contexts in test environments, but it is not the final game architecture and should not own user/session identity in new projects.
- The class is marked
[Obsolete]and its XML docs point developers toServiceCompositionRoot + ServiceRegistryand game-ownedUserIdpatterns.
For v6.0 and beyond, approach (1) is the recommended pattern. Use approach (2) only when maintaining or testing legacy setups that have not yet migrated to the new DI and user-id model.
How Plugin Consumers Set This Up In Their Game
This section is written for plugin users integrating GridBuilding into their own games.
There are two practical integration phases:
1. Simple setup (single user, quick start)
Use this when you are prototyping or have only one local player.
- Scene setup
- Add
ServiceCompositionRootto your main scene so the ServiceRegistry is created. - Place the core GridBuilding nodes you need (targeting, placement, manipulation, HUD).
- Add
- User identity
- In your game code, create a single
UserIdfor the local player. - When you call GridBuilding commands, always pass that
UserId:IPlacementCommands.TryPlace(userId, ...)IManipulationCommands.TryMove(userId, ...), etc.
- When you wire HUD, use the same
UserId:TargetInformer.InitializeForUser(userId, ...)ActionLog.InitializeForUser(userId, ...).
- In your game code, create a single
- What the plugin does
ServiceCompositionRootowns the service registry and Core services.- Your game owns who the user is and hands that
UserIdinto the plugin.
This keeps the setup easy while still matching the v6 user-id architecture.
2. Advanced setup (game-owned bootstrap and multi-user)
When your game grows (multiple players, networked sessions, AI, tools), move to a game-owned bootstrap that coordinates all services and users.
- Game/session model owns identity
- Introduce game-side classes such as
PlayerSession/GameUserSessionthat track:- Engine/network ids (controller index, peer id, account id, etc.).
- The corresponding GridBuilding
UserId.
- Add a small registry/manager (e.g.
PlayerSessionManager) to map engine ids →UserId.
- Introduce game-side classes such as
- Bootstrap and services
- Keep using
ServiceCompositionRoot(or an equivalent bootstrap node) to register GridBuilding services. - Your game-level bootstrap is responsible for:
- Creating the required
UserIds. - Resolving GridBuilding services from the registry.
- Passing
UserIdinto commands and UI initializers.
- Creating the required
- Keep using
- Per-user controllers and UI
- Input/controller nodes (per player or per character) query your game’s session model for
the correct
UserIdand call GridBuilding commands with it. - HUD elements such as
TargetInformerandActionLogare initialized withInitializeForUser(userId, ...)for the user they represent.
- Input/controller nodes (per player or per character) query your game’s session model for
the correct
In this phase, GridBuilding remains engine/network agnostic; it only sees UserId and
command calls, while your game architecture fully owns sessions, players, and networking.
3. Where CompositionContainer and legacy scope nodes fit
During the v6 migration there are still legacy pieces such as CompositionContainer and older
“scope” nodes:
- They exist to keep older projects and tests running while you migrate.
- They are helpful in test environments or small demos where you want a quick way to construct services and contexts.
- They are not the final pattern for production games and should not be the primary owner of user/session identity.
For new or actively maintained games:
- Prefer game-owned
UserId+ ServiceCompositionRoot as described above. - Treat
CompositionContainerand similar nodes as migration/test scaffolding only.
Anti-Patterns (What Not to Do)
- Do not add new
@exportfields just to find the player character. - Do not assume there is only ever one player or one character.
- Do not bake user identity into Node names or scene paths.
Instead:
- Treat the user id as the primary key for identity.
- Let the service layer manage mappings from user ids to players, characters, and sessions.
- Keep UI focused on presentation and interaction, with all user/identity logic in services.