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
- Create
Assets/ProgrammaticBoard/ProgrammaticFirstActionExample.cs. - Copy the complete component below into the file.
Complete tested example
using System;
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
{
private const string StructureId = "programmatic-board.crate-1";
[SerializeField] private MonoBehaviour stateSourceComponent;
public (
bool PreviewSucceeded,
bool ApplySucceeded,
long RevisionBeforePreview,
long RevisionAfterPreview,
long RevisionAfterApply,
int BatchesAfterPreview,
int CommittedBatchCount,
bool StructureExists) LastReport { get; private set; }
private void Start()
{
LastReport = Run();
}
public (
bool PreviewSucceeded,
bool ApplySucceeded,
long RevisionBeforePreview,
long RevisionAfterPreview,
long RevisionAfterApply,
int BatchesAfterPreview,
int CommittedBatchCount,
bool StructureExists) Run()
{
IGridBoardStateSource stateSource = FindStateSource();
GridBoardState retainedState = stateSource?.State;
if (retainedState == null)
{
Debug.LogError(
"No ready IGridBoardStateSource was found on this object.",
this);
return default;
}
LastReport = PreviewAndApply(retainedState);
if (!LastReport.PreviewSucceeded || !LastReport.ApplySucceeded)
return LastReport;
Debug.Log(
$"Preview: revision {LastReport.RevisionAfterPreview}, "
+ $"committed batches {LastReport.BatchesAfterPreview}. "
+ $"Apply: revision {LastReport.RevisionAfterApply}, "
+ $"committed batches {LastReport.CommittedBatchCount}, "
+ $"structure {StructureId} exists: {LastReport.StructureExists}, "
+ $"same retained state: {ReferenceEquals(retainedState, stateSource.State)}.",
this);
return LastReport;
}
public (
bool PreviewSucceeded,
bool ApplySucceeded,
long RevisionBeforePreview,
long RevisionAfterPreview,
long RevisionAfterApply,
int BatchesAfterPreview,
int CommittedBatchCount,
bool StructureExists) PreviewAndApply(GridBoardState state)
{
return PreviewAndApply(
state,
new SquareGridCoordinate(1, 1),
StructureId);
}
public (
bool PreviewSucceeded,
bool ApplySucceeded,
long RevisionBeforePreview,
long RevisionAfterPreview,
long RevisionAfterApply,
int BatchesAfterPreview,
int CommittedBatchCount,
bool StructureExists) PreviewAndApply(
GridBoardState state,
SquareGridCoordinate coordinate,
string instanceId)
{
if (state == null)
throw new ArgumentNullException(nameof(state));
if (string.IsNullOrWhiteSpace(instanceId))
throw new ArgumentException("An instance ID is required.", nameof(instanceId));
GridStructureDefinition crate = new GridStructureDefinitionBuilder(
"programmatic-board.crate",
GridStructureFootprint.CreateSingleCell(SquareGridTopology.Instance),
topology: SquareGridTopology.Instance).Build();
IGridAction action = GridActions.PlaceStructure(
crate,
coordinate,
instanceId);
long revisionBeforePreview = state.Revision;
int committedBatchCount = 0;
void CountCommittedBatch(GridBoardEventBatch _) => committedBatchCount++;
state.EventsCommitted += CountCommittedBatch;
try
{
GridActionResult preview = GridActionRunner.Preview(state, action);
long revisionAfterPreview = state.Revision;
int batchesAfterPreview = committedBatchCount;
if (!preview.Success)
{
Debug.LogError(preview.FormatDiagnostics(), this);
return (
false,
false,
revisionBeforePreview,
revisionAfterPreview,
state.Revision,
batchesAfterPreview,
committedBatchCount,
false);
}
GridActionResult applied = GridActionRunner.Apply(state, action);
if (!applied.Success)
Debug.LogError(applied.FormatDiagnostics(), this);
return (
true,
applied.Success,
revisionBeforePreview,
revisionAfterPreview,
state.Revision,
batchesAfterPreview,
committedBatchCount,
state.TryGetStructure(instanceId, out _));
}
finally
{
state.EventsCommitted -= CountCommittedBatch;
}
}
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;
}
}
}
- Add Programmatic First Action Example beside Programmatic Board Example on the same GameObject.
- Enter Play Mode.
The component performs the following focused operations against the retained state.
Resolve the retained state
Excerpt — resolve the retained state. Tested using public APIs.
IGridBoardStateSource stateSource = FindStateSource();
GridBoardState retainedState = stateSource?.State;
Create the placement
Excerpt — create the placement. Tested using public APIs.
GridStructureDefinition crate = new GridStructureDefinitionBuilder(
"programmatic-board.crate",
GridStructureFootprint.CreateSingleCell(SquareGridTopology.Instance),
topology: SquareGridTopology.Instance).Build();
IGridAction action = GridActions.PlaceStructure(
crate,
coordinate,
instanceId);
Preview without changing the board
Excerpt — preview and stop on rejection. Tested using public APIs.
GridActionResult preview = GridActionRunner.Preview(state, action);
Apply the accepted request
Excerpt — apply the accepted request. Tested using public APIs.
GridActionResult applied = GridActionRunner.Apply(state, action);
Result
The Console first reports the 12-cell board and then reports:
Preview: revision 0, committed batches 0. Apply: revision 1, committed batches 1, structure programmatic-board.crate-1 exists: True, same retained state: True.
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). |
| Apply 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. |