Service Architecture & Adapters (6.0)

GridPlacement 6.0 uses service-based architecture at its core and adapters at the edges (Godot, UI, tools). This page explains how those pieces fit together for the GridPlacement 6.0 C# line.

Runtime chains

If you want the “follow the call” debugging view, use the chain pages:

1. Big Picture

At a high level, the architecture looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
┌────────────────────────────────────────────┐
│               Godot Layer                 │
│  (scenes, UI, input, visual feedback)     │
│  ┌──────────────────────────────────────┐ │
│  │  Adapter / System Nodes             │ │
│  │  (C# + optional GDScript wrappers)  │ │
│  └──────────────────────────────────────┘ │
└────────────────────────────────────────────┘
                    ↓  ↑  (signals, calls)
┌────────────────────────────────────────────┐
│               Core Services               │
│  (C# DLL, engine-agnostic)               │
│  • Domain services (e.g. IBuildingService)│
│  • Domain events (BuildingPlacedEvent…)   │
│  • States and value types                 │
└────────────────────────────────────────────┘
                    ↓  ↑
┌────────────────────────────────────────────┐
│     Composition & Configuration           │
│  • Service registry / DI container        │
│  • Config (C# 6.0), settings, templates   │
└────────────────────────────────────────────┘
  • Services live in the core and know nothing about Godot.
  • Domain events are raised by those services to describe what happened.
  • Adapters live in the Godot layer and:
    • Resolve services from the DI container / registry.
    • Subscribe to domain events.
    • Emit Godot signals or update Godot state.

2. “First-Class Wiring” (GameComposition + GameUserSessions)

GridPlacement 6.0 treats identity and service lifetime as first-class integration concerns. The goal is that the same Core DLL can be hosted by:

  • Godot (C#)
  • GDScript wrappers (thin)
  • tools/CLI (no engine)

Responsibilities and boundaries

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Godot scene tree
GPUserScopeRoot (engine adapter)
  - owns/receives a service registry (ServiceRegistry)
  - registers minimal Core services
  - routes Godot input -> CoreInputService
Core adapter: IPlacementWorkflowAdapter
  - orchestrates placement workflow using Core services
  - uses Core value types (CoreVector2/CoreVector2I)
  - supports per-user operations via IGridBuildingSession

Note:

  • The shipped GridPlacement Godot addon contract is:
    • GPUserScopeRoot + ServiceRegistry
  • Full GameComposition/GameUserSessions integration is intentionally treated as an integration-layer concern (may be added to the demo later, but is not required for most users).

Canonical cross-engine adapter pattern (Godot + Unity)

GridPlacement 6.0 uses the same core workflow ports regardless of host engine. This keeps your gameplay logic portable and makes Godot/Unity integration symmetrical.

In practice, integrations will typically include:

  • A Core port interface (example: IPlacementWorkflowAdapter).
  • A Core default implementation (example: PlacementWorkflowAdapter).
  • An engine bridge (Godot/Unity) that performs value conversion and forwards calls/events.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Engine caller (Node / MonoBehaviour)
Engine bridge (Godot: PlacementWorkflowBridge, Unity: PlacementWorkflowBridge)
  - converts engine vectors -> CoreVector2/CoreVector2I
  - forwards calls/events
Core port interface (IPlacementWorkflowAdapter)
Core concrete impl (PlacementWorkflowAdapter)
Workflow orchestrator (internal)
Core services + state

This pattern works equally well for:

  • Placement workflow
  • Manipulation workflow

Guardrail: keep ports State.* free

Core has an architecture guardrail: public workflow ports must not require engine hosts to reference GridPlacement.Core.State.* directly.

As part of the expected integration surface:

  • Workflow ports may expose small readiness interfaces (example: ITargetingStateReadiness, IBuildingStateReadiness) instead of concrete state types.
  • Core state objects (like TargetingState) can implement those readiness interfaces.
  • Engine bridges may manage engine-side state/lifecycle, while passing readiness interfaces into Core ports.

This keeps the boundary stable and makes it easier to add a Unity host without rewriting the Core workflow surface.

Identity integration seam

The shipped GridPlacement Godot addon is intentionally self-contained and does not depend on GameComposition or GameUserSessions.

If your host game uses GameComposition/GameUserSessions, implement identity wiring in an integration layer and validate it with the GridPlacement.Integrations.* test projects.

Session integration seam

Core supports per-user operations via:

  • IGridBuildingSession

The Godot adapter may provide a small session implementation (per game/session) that returns stable user scopes for a given GPUserId.

GPUserId vs Owner (1:many services vs 1:1 actor)

GridPlacement 6.0 intentionally keeps user/session identity separate from the actor/owner concept:

  • GPUserId / IUserScope / IGridBuildingSession model service lifetime and scoping.
  • IOwner (and IOwnerContext) model the actor performing an action (player, AI, system), without requiring engine types.

In other words:

  • A single GPUserId may correspond to multiple in-world actors over time (respawns, possessed units, etc.).
  • A single in-world actor may perform actions on behalf of a GPUserId.

Legacy note:

  • GPOwner (POCS) is deprecated and should not be used for new integrations.

3. Test alignment (reuse-first)

When introducing or changing wiring for identity/session integration:

  • Prefer extending Core integration tests first (no engine dependency).
  • Reuse the existing suite:
    • plugins/gameplay/GridPlacement/cs/Core/Tests/Core/GridBuilding.Core.Tests/PlacementWorkflowOrchestratorIntegrationTests.cs

Behaviors to assert:

  • HasUserSessionSupport correctness
  • Stable per-user scope resolution (same GBUserId -> same scope)
  • Workflow readiness does not depend on Godot

4. API docs generation domains (Core vs Godot)

GridPlacement v6.0 API pages under v6.0/api/** are auto-generated.

API generation is layer-based, and documentation is routed into separate domains:

1
2
3
v6.0/api/core/             -> Core (engine-agnostic)
v6.0/api/godot/            -> Godot adapter layer (engine-facing)
// Godot.Integration is intentionally not published on the website until it stabilizes.

Notes:

  • The api/** folders are treated as generated-only content.
  • The generator cleans stale output before regeneration so renamed/removed types do not leave orphan .md pages behind.
  • Any manual explanations belong in v6.0/guides/** (not in v6.0/api/**).

5. Service-Based Core

The core is a service architecture:

  • Services like IBuildingService expose operations:
    • PlaceBuilding(type, gridPosition, ownerId)
    • RemoveBuilding(instanceId)
    • ApplyDamage(instanceId, damageAmount)
  • Services operate on domain state (BuildingState, GridState, etc.).
  • Services raise events when something important happens, e.g.:
    • BuildingPlacedEvent
    • BuildingRemovedEvent
    • BuildingDamagedEvent

Key properties:

  • Engine-agnostic: No references to Godot types.
  • Testable: You can unit test services with pure C# tests.
  • Thread-aware: Core can be used in different hosts (tools, servers, etc.).

Dependency injection / composition container (described in the Project Architecture guide) wires these services together.

5. Adapter Pattern at the Edges

The adapter pattern is used where the core meets a specific host (Godot, CLI tool, etc.). For Godot, that typically means one or more C# nodes that:

  1. Resolve services via the composition root / service registry.
  2. Translate Godot-friendly calls into service calls.
  3. Subscribe to domain events and emit Godot signals.

Example: Building Events Adapter (Conceptual)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public partial class BuildingEventsAdapter : Node
{
    private IBuildingService _buildingService;

    [Signal]
    public delegate void BuildingPlacedEventHandler(string buildingType, Vector2I position, string instanceId);

    public override void _Ready()
    {
        var registry = ServiceCompositionRoot.GetGlobalRegistry();
        _buildingService = registry.GetService<IBuildingService>();

        _buildingService.BuildingPlaced += OnBuildingPlaced;
    }

    private void OnBuildingPlaced(object? sender, BuildingPlacedEvent e)
    {
        EmitSignal(SignalName.BuildingPlaced, e.BuildingType, e.Position, e.InstanceId);
    }

    public void PlaceBuilding(string type, Vector2I position)
    {
        _buildingService.PlaceBuilding(type, position);
    }
}

GDScript or other Godot nodes then talk to this adapter, not directly to the core DLL:

1
2
3
4
5
6
7
8
@onready var building_adapter := $BuildingEventsAdapter

func _ready():
    building_adapter.building_placed.connect(_on_building_placed)

func _on_building_placed(building_type: String, position: Vector2i, instance_id: String) -> void:
    # Update visuals, play sounds, etc.
    print("Placed ", building_type, " at ", position)

Why adapters?

  • Narrow responsibility: Each adapter focuses on a specific concern (buildings, input, logging, etc.).
  • Testable: You can test adapters in isolation (mock the service, assert signals).
  • Replaceable: If you change how UI works, you can swap adapters without touching the core services.

6. Where Services and Adapters Show Up in the Codebase

High-level mapping (names may evolve as 6.0 stabilizes):

  • Core services & events (C# DLL)
    • GridPlacement.Core.Services.*
    • Domain events like BuildingPlacedEvent, BuildingRemovedEvent.
  • Godot integration & adapters (C#)
    • Logging: GodotLogger, LoggerBridge, GodotLog.
    • Godot service access: GodotServiceBridge, GodotServiceLocator.
    • Placement/collision adapters: CollisionProcessorBridge, GridOccupancyAdapter, others.
    • Input, targeting, and manipulation nodes (see the Project Architecture guide).

7. Expected integration shape

At a high level, engine hosts (Godot, Unity) will typically interact with:

  • A small set of Core services (and their domain events).
  • One or more workflow ports (example: IPlacementWorkflowAdapter) when the host needs a stable, workflow-shaped API.
  • Engine-side adapter/bridge nodes to convert input, coordinate space, and presentation concerns.

8. When to Think in Terms of “Service Architecture” vs “Adapter Pattern”

  • Use “service architecture” when you are:

    • Designing or changing core domain logic.
    • Thinking about API surfaces, validation rules, or domain events.
    • Writing tests that run without Godot.
  • Use “adapter pattern” when you are:

    • Wiring services into Godot scenes or UI.
    • Translating between C# types and Godot types.
    • Emitting signals or updating visual state based on domain events.

They are complementary:

  • Core: services + events (engine-agnostic, long-lived, testable).
  • Edge: adapters (engine-specific, scene-aware, narrow responsibilities).

See also: