Development ⚠️ GridPlacement 6.0 documentation is in active development. APIs and content may change, and the site may be temporarily unstable.

Service Architecture & Adapters (6.0)

GridBuilding 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 and how it replaces the legacy StateBridge pattern. In the 6.0 C# plugin, this architecture is shipped to Godot users as GridPlacement 6.0, even though the underlying C# namespaces remain GridBuilding.*.

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. 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 project_architecture.md) wires these services together.

3. 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.

4. How This Differs from the Legacy StateBridge

Pre-6.0, the typical integration story was a single StateBridge node:

  • StateBridge:
    • Lived at a fixed path (e.g. /root/StateBridge).
    • Held references to many backend services and states.
    • Exposed lots of GDScript-friendly methods (get_application_mode(), get_edit_mode(), get_available_buildings(), …).
    • Emitted a wide set of signals for different state changes.

This made StateBridge a kind of mega-adapter / god object for integration.

In 6.0:

  • There is no required StateBridge node.
  • Instead you have multiple smaller adapters, each:
    • Handles a small slice of functionality.
    • Wires one area of the core (a service or group of events) to the Godot layer.

Conceptually:

1
2
3
4
5
Legacy:
  Godot → /root/StateBridge → many services + states

6.0:
  Godot UI / scenes → specific adapters → specific services + events

The new approach keeps the advantages of a bridge (clear Godot-facing API) while avoiding the drawbacks of a single global node with too many responsibilities.

5. Where Services and Adapters Show Up in the Codebase

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

  • Core services & events (C# DLL)
    • GridBuilding.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 project_architecture.md).
  • Docs
    • Legacy StateBridge (pre-6.0): gridbuilding/CSHARP_STATE_INTEGRATION.md.
    • 6.0 Core/Godot integration guide: v6.0/guides/core-godot-integration.md.
    • 6.0 breaking changes (StateBridge removal): v6.0/guides/breaking-changes.md.

6. 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).

7. Migration Notes (Legacy StateBridge → 6.0 Adapters)

If you have existing code that used StateBridge:

  1. Treat StateBridge as legacy for 6.0+.
  2. Identify what you used it for:
    • Reading modes / states.
    • Listening to state change signals.
    • Triggering certain operations.
  3. For each concern, introduce a small adapter node instead of a single global bridge, and wire it to the relevant services and events.
  4. Update GDScript to:
    • get_node() the specific adapter you need (often local to a scene), and
    • connect to its signals or call its methods.

See also: