Lost Lily/Grid ToolkitDocs 1.0
Table of Contents

Run an action on a code-owned board

When to use

After creating and owning a board in code, run the action component below against that retained state. Use Place your first structure when GridBoardAuthoring owns the state instead.

Before you start

Exit Play Mode. Keep ProgrammaticBoardExample on the Programmatic Board GameObject.

Procedure

  1. Create Assets/ProgrammaticBoard/ProgrammaticFirstActionExample.cs.
  2. Copy the complete component below into the file.
Complete tested example
using LostLily.GridToolkit.Actions;
using LostLily.GridToolkit.Board;
using LostLily.GridToolkit.Occupancy;
using LostLily.GridToolkit.Topology.Square;
using UnityEngine;

namespace GridToolkit.Tutorials
{
    [DisallowMultipleComponent]
    public sealed class ProgrammaticFirstActionExample : MonoBehaviour
    {
        // Moving this structure later uses the same instance ID.
        private const string DefinitionId = "programmatic-board.crate";
        private const string InstanceId = "programmatic-board.crate-1";

        [SerializeField] private MonoBehaviour stateSourceComponent;
        [SerializeField] private Vector2Int placementCell = new(1, 1);

        private void Start()
        {
            Place();
        }

        public GridActionResult Place()
        {
            IGridBoardStateSource stateSource = FindStateSource();
            GridBoardState state = stateSource?.State;

            if (state == null || state.IsDisposed)
            {
                Debug.LogError("No ready IGridBoardStateSource was found on this object.", this);
                return null;
            }

            GridStructureDefinition definition = new GridStructureDefinitionBuilder(
                DefinitionId,
                GridStructureFootprint.CreateSingleCell(SquareGridTopology.Instance),
                topology: SquareGridTopology.Instance).Build();
            SquareGridCoordinate coordinate = new(placementCell.x, placementCell.y);
            IGridAction action = GridActions.PlaceStructure(definition, coordinate, InstanceId);

            // Use the owner's state; preview leaves that state unchanged.
            GridActionResult preview = GridActionRunner.Preview(state, action);
            if (!preview.Success)
            {
                Debug.LogError(preview.FormatDiagnostics(), this);
                return preview;
            }

            GridActionResult applied = GridActionRunner.Apply(state, action);
            if (!applied.Success)
            {
                Debug.LogError(applied.FormatDiagnostics(), this);
                return applied;
            }

            Debug.Log($"Placed {InstanceId}. Board revision {state.Revision}.", this);
            return applied;
        }

        private IGridBoardStateSource FindStateSource()
        {
            if (stateSourceComponent is IGridBoardStateSource assignedSource)
                return assignedSource;

            foreach (MonoBehaviour component in GetComponents<MonoBehaviour>())
            {
                if (component is IGridBoardStateSource source)
                    return source;
            }

            return null;
        }
    }
}
  1. Add Programmatic First Action Example beside Programmatic Board Example on the same GameObject.
  2. Enter Play Mode.

Place() resolves the owner's current state, creates a definition and placement request, previews it, and applies an accepted request. Rejected results log diagnostics and stop. Start calls Place() once; remove Start when your own input should trigger the request.

Resolve the retained state

Excerpt — resolve the retained state. Tested using public APIs.

IGridBoardStateSource stateSource = FindStateSource();
GridBoardState state = stateSource?.State;

Create the placement

Excerpt — create the placement. Tested using public APIs.

GridStructureDefinition definition = new GridStructureDefinitionBuilder(
    DefinitionId,
    GridStructureFootprint.CreateSingleCell(SquareGridTopology.Instance),
    topology: SquareGridTopology.Instance).Build();
SquareGridCoordinate coordinate = new(placementCell.x, placementCell.y);
IGridAction action = GridActions.PlaceStructure(definition, coordinate, InstanceId);

Preview without changing the board

Excerpt — preview and stop on rejection. Tested using public APIs.

// Use the owner's state; preview leaves that state unchanged.
GridActionResult preview = GridActionRunner.Preview(state, action);
if (!preview.Success)
{
    Debug.LogError(preview.FormatDiagnostics(), this);
    return preview;
}

Apply the accepted request

Excerpt — apply the accepted request. Tested using public APIs.

GridActionResult applied = GridActionRunner.Apply(state, action);
if (!applied.Success)
{
    Debug.LogError(applied.FormatDiagnostics(), this);
    return applied;
}

Result

The Console first reports the 12-cell board and then reports:

Placed programmatic-board.crate-1. Board revision 1.

When you stop Play Mode or destroy the GameObject, the Console also reports:

Programmatic board disposed.

IGridBoardStateSource provides the exact GridBoardState retained by the neighboring owner. The action component neither creates nor disposes that state.

GridActionRunner plans the placement against the owner's current state. Preview leaves revision 0 with no committed batch. Apply builds a fresh plan, advances revision to 1, publishes one batch, and creates the expected instance. The owner later disposes the same state from OnDestroy.

Troubleshooting

Symptom Check
“No ready IGridBoardStateSource was found” Put both workflow components on the same enabled GameObject.
Preview reports an invalid coordinate Keep the 4 × 3 square board and coordinate (1, 1).
Placement reports a duplicate instance ID Stop and re-enter Play Mode, or remove the existing instance.
A renderer stays empty Connect a board view to state and configure a renderer.