Grid Placement

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 @export fields 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

  1. 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.
  2. 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.
  3. No new identity @export fields

    • 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.
  4. 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.

AreaNode / ComponentStatusNotes
Targeting feedback UITargetInformer (Godot/Systems/UI/TargetInformer.cs)PlannedResolve user id via services and drive UI based on the active user, not a single exported target node.
Action log UIActionLog (Godot/Systems/UI/Actions/ActionLog.cs)PlannedLog actions per user session; avoid hard-coded player references via exports.
Other HUD / overlays(to be identified)PlannedAny HUD elements that implicitly assume a single local player should be reviewed.

Migration Checklist

When migrating a UI component:

  1. Identify current identity coupling

    • Locate @export fields or hard-coded NodePaths/resources used to find “the player” or character.
  2. 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.
  3. 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.
  4. Remove identity-related exports

    • After services are wired, remove unnecessary @export fields that previously located players or characters.
  5. 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:

  1. Game-owned direct commands (canonical for v6)

    • The host game/session model owns UserId.
    • Game code maps controller slots, accounts, or peers → UserId and passes it into:
      • Core commands (e.g., IPlacementCommands, IManipulationCommands).
      • UI initializers (e.g., TargetInformer.InitializeForUser(UserId, ...), ActionLog.InitializeForUser(UserId, ...)).
    • GridBuilding stays engine/network agnostic and only sees UserId.
  2. CompositionContainer-based scaffolding (legacy/migration)

    • CompositionContainer is 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 to ServiceCompositionRoot + ServiceRegistry and game-owned UserId patterns.

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 ServiceCompositionRoot to your main scene so the ServiceRegistry is created.
    • Place the core GridBuilding nodes you need (targeting, placement, manipulation, HUD).
  • User identity
    • In your game code, create a single UserId for 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, ...).
  • What the plugin does
    • ServiceCompositionRoot owns the service registry and Core services.
    • Your game owns who the user is and hands that UserId into 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 / GameUserSession that 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.
  • 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 UserId into commands and UI initializers.
  • Per-user controllers and UI
    • Input/controller nodes (per player or per character) query your game’s session model for the correct UserId and call GridBuilding commands with it.
    • HUD elements such as TargetInformer and ActionLog are initialized with InitializeForUser(userId, ...) for the user they represent.

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 CompositionContainer and similar nodes as migration/test scaffolding only.

Anti-Patterns (What Not to Do)

  • Do not add new @export fields 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.