Grid Placement
Development ⚠️ GridPlacement 6.0 (GECS) is in active development. This is the GDScript ECS architecture.

Save, Load, and History (6.0)

Persisting game state using the 6.0 ECS serialization pipeline and GECSIO.

In GridBuilding 6.0, persistence is handled by the GECS (Godot ECS) serialization pipeline. Unlike 5.0, which serialized Node trees, 6.0 serializes Component Data. This makes save files smaller, faster to load, and easier to version.

The ECS Serialization Approach

Saving a level means saving the state of entities, not the entities themselves.

  • World State: Represented by a collection of entities.
  • Entity State: defined by its components (GridPositionComponent, PlaceableComponent, etc.).
  • Serialization: GECSIO extracts this data into a JSON-compatible format.

Saving the World

To save the game, you query for all entities that represent persistent objects (usually those with a GridPositionComponent and PlaceableComponent) and serialize them.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
func save_game(path: String):
    # 1. Define what to save
    # We want all entities that are placed on the grid
    var save_query = world.query.with_all([
        GridPositionComponent, 
        PlaceableComponent
    ])
    
    # 2. Serialize to Dictionary
    var world_data = GECSIO.serialize(save_query)
    
    # 3. Write to disk
    GECSIO.save(world_data, path)

What gets saved?

Only components with the @export hint or those registered for serialization are saved. Runtime-only components (like PlacementPreviewComponent or HoverHighlightComponent) are naturally excluded because they aren’t part of the query or don’t hold persistent data.

Loading the World

Loading is the reverse process: Read the file, create entities, and populate components.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func load_game(path: String):
    # 1. Clear current state
    clear_current_level()
    
    # 2. Deserialize (creates Entities + Components)
    var entities = GECSIO.deserialize(path)
    
    # 3. Add to world
    for entity in entities:
        world.add_entity(entity)

Save File Structure

The resulting save file is clean JSON (or binary).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  "entities": [
    {
      "id": 1,
      "components": {
        "GridPositionComponent": { "x": 10, "y": 5 },
        "PlaceableComponent": { "id": "tower_archer" },
        "HealthComponent": { "current": 85, "max": 100 }
      }
    }
  ]
}

This structure is version-agnostic (mostly). If you add a new component later, old saves just load without it. If you remove a component, the deserializer warns but continues.

History (Undo/Redo)

Because state is just data, implementing Undo/Redo is trivial. You don’t need Command patterns for every action; you just snapshot the relevant component state.

  • Snapshot: Store the serialized state of the modified entity before the change.
  • Undo: Deserialize that state back onto the entity.

Handling Custom Data

In 6.0, “Custom Data” is just Another Component. If your tower has health, you don’t add a key to a dictionary; you add a HealthComponent.

1
2
3
4
# Adding custom data is just adding a component
entity.add_component(HealthComponent.new(100))

# It gets saved automatically if included in the query!

Test Verification

The 6.0 serialization pipeline is verified by the ECS integration suite:

  • Core Serialization: res://addons/grid_placement/tests/integration/ecs/test_ecs_serialization.gd

    • Round Trip: Verifies that serialize() -> save() -> load() -> deserialize() results in an identical entity.
    • Complex Entities: Tests entities with multiple components (Position + Transform + Metadata) to ensure all are preserved.
    • Performance: Benchmarks serialization of 100+ entities to ensure it fits within frame budgets (target < 1ms).
  • API Contract: res://addons/grid_placement/tests/utils/ecs_serialization_test_utils.gd

    • Provides shared assertions for verifying component data integrity across tests.