using Unity.Collections;
using Unity.Mathematics;
namespace ProjectM.Simulation
{
///
/// Pure, deterministic, ORDER-INDEPENDENT conveyor move resolver (the server ConveyorTransportSystem
/// applies the result). Determinism: sources are processed sorted by (NEVER hashmap
/// order); a destination belt cell accepts AT MOST ONE item and only if it was EMPTY in the pre-move snapshot
/// (double-buffering -> exactly one cell/tick); ties break to the lowest-CellKey source and losers STALL with no
/// loss; machine-input SINK cells always accept (a merge). World-free so it is exhaustively unit-tested.
///
public static class ConveyorMath
{
/// Cardinal grid step for a belt direction byte (0=+X,1=-X,2=+Z,3=-Z).
public static int2 DirOffset(byte dir)
{
switch (dir)
{
case 1: return new int2(-1, 0);
case 2: return new int2(0, 1);
case 3: return new int2(0, -1);
default: return new int2(1, 0); // 0 = +X
}
}
/// A stable, collision-free total order over grid cells (the deterministic tie-break key).
public static long CellKey(int2 cell) => ((long)cell.x << 32) | (uint)cell.y;
///
/// Resolve, for each belt holding an item, whether it advances one cell toward its direction this tick.
/// Inputs are read-only snapshots; outputs are the accepted moves ( ->
/// ), length . A move is accepted when the
/// destination is a SINK cell (always, a merge) or an EMPTY, unclaimed belt cell. Sources are iterated in
/// CellKey order so the result is identical regardless of input array order. Scratch is Temp + disposed.
///
public static void ResolveMoves(
NativeArray srcCells, NativeArray dirs,
NativeArray itemRes, NativeArray itemCnt,
NativeHashMap cellToIndex, NativeHashSet sinkCells,
NativeArray outMoveDst, NativeArray outMoveSrcIdx, out int moveCount)
{
int n = srcCells.Length;
moveCount = 0;
// Stable iteration order = sources sorted by CellKey (insertion sort; n is small).
var order = new NativeArray(n, Allocator.Temp);
for (int i = 0; i < n; i++) order[i] = i;
for (int i = 1; i < n; i++)
{
int cur = order[i];
long curKey = CellKey(srcCells[cur]);
int j = i - 1;
while (j >= 0 && CellKey(srcCells[order[j]]) > curKey)
{
order[j + 1] = order[j];
j--;
}
order[j + 1] = cur;
}
var claimed = new NativeHashSet(n, Allocator.Temp);
for (int oi = 0; oi < n; oi++)
{
int i = order[oi];
if (itemCnt[i] <= 0) continue; // nothing to move
int2 dst = srcCells[i] + DirOffset(dirs[i]);
bool accept = false;
bool isSink = sinkCells.Contains(dst);
if (isSink)
{
accept = true; // sinks merge -> unlimited acceptors, never claimed
}
else if (cellToIndex.TryGetValue(dst, out int dstIdx))
{
// dst is a belt cell: accept only if EMPTY in the snapshot AND not already claimed this tick.
if (itemCnt[dstIdx] == 0 && !claimed.Contains(dst))
accept = true;
}
// else: dst is neither a belt nor a sink -> dead end -> stall.
if (accept)
{
if (!isSink) claimed.Add(dst);
outMoveDst[moveCount] = dst;
outMoveSrcIdx[moveCount] = i;
moveCount++;
}
}
order.Dispose();
claimed.Dispose();
}
}
}