Table of Contents

Checkerboard Dispatch

Red/Black two-phase parallel dispatch so neighbor-touching systems never race across a cell boundary.

Status: โœ… Implemented ยท Visibility: Public ยท Level: ๐ŸŸฃ Advanced ยท Category: Spatial

๐ŸŽฏ What it solves

Some systems read or write data in cells adjacent to the one they're processing โ€” pheromone diffusion blending values with its neighbors is the canonical example. Dispatching all clusters to worker threads in one pass, as Tiered Simulation Dispatch normally does, lets two adjacent cells run on different workers at the same instant and race on the shared boundary data. Without a way to serialize neighbor access, game code is forced to make such systems single-threaded, losing parallelism for exactly the systems most likely to be expensive (full-grid diffusion/propagation passes).

โš™๏ธ How it works (in brief)

Each cell is colored Red or Black from the parity of its grid coordinates: (cellX + cellY) % 2. No two orthogonally-adjacent (face-sharing) cells share a color, so all Red clusters can run in parallel safely, then all Black clusters can run in parallel safely, and a system that only touches face-adjacent neighbors never sees a half-written neighbor. checkerboard: true turns this into two sequential dispatch phases inside the same DAG node โ€” your callback runs twice per tick (once per phase), with ctx.Entities scoped to that phase's clusters only. Downstream systems see one node and wait for both phases; nothing else in the DAG needs to know dispatch happened twice.

๐Ÿ’ป Usage

schedule.QuerySystem("Pheromone_Diffuse", ctx =>
{
    // Safe to read/write neighboring cells' data โ€” no other worker is touching an adjacent cell
    // during this phase.
    foreach (var id in ctx.Entities)
    {
        ref var pher = ref ctx.Accessor.OpenMut(id).Write(Pheromone.Data);
        pher.Value = Diffuse(pher.Value, NeighborSamples(ctx, id));
    }
},  input: () => pheromoneView,
    tier: SimTier.Near,
    parallel: true,
    checkerboard: true);
Option Default Effect
checkerboard: (QuerySystem) / b.Checkerboard() (class-based) false Split the tier-filtered cluster list into Red/Black and dispatch as two sequential parallel phases

โš ๏ธ Guarantees & limits

  • Requires parallel: true โ€” registering checkerboard: true without it throws InvalidOperationException at schedule build time, not at first tick.
  • Incompatible with ChunkedParallel โ€” both throw if combined.
  • Composes with tier:/CellAmortize โ€” coloring is computed over whichever cluster set the tier filter (and dormancy) already produced; sleeping clusters are excluded before the split, same as any other tier-filtered system.
  • Red โˆช Black is always the full filtered cluster set with no overlap โ€” every cluster runs exactly once per tick, never zero or twice.
  • Protects face adjacency only (2-color scheme). Diagonal neighbors share the same color โ€” a system that reads/writes diagonal cells, not just orthogonal ones, is not protected by this 2-phase split.
  • Non-spatial archetypes (no spatial grid, or the archetype has no cell mapping) degenerate to all clusters in the Red phase and an empty Black phase โ€” the callback still runs twice, but the second call sees no entities.
  • Overhead is one extra dispatch-prepare + one extra worker barrier per tick (~10-15ฮผs) โ€” negligible unless the per-phase entity processing itself is already very cheap.

๐Ÿงช Tests

  • CheckerboardTests โ€” Checkerboard_TwoPhases_BothExecute, Checkerboard_RedBlack_NoOverlap, Checkerboard_ZeroRedClusters_BlackStillRuns, Checkerboard_RequiresParallel_Throws, Checkerboard_WithDormancy_SleepingSkipped, Checkerboard_NoTierFilter_StillSplits