namespace Sand.ChunkPrototype; internal sealed class ChunkCellPage { private readonly PrototypeParticle[] _particles; private readonly int[] _processedStep; private readonly int[] _occupiedRowCounts; private readonly int[] _gasRowCounts; private readonly int[] _rowRevision; private readonly int[] _blockedSolidSignature; private readonly int[] _blockedGasRiseSignature; private readonly int[] _gasRetrySignature; private readonly int[] _gasRetryUntilStep; private readonly int[] _gasRowCooldownSignature; private readonly int[] _gasRowCooldownUntilStep; private readonly sbyte[] _driftState; private readonly float[] _pressureDuration; private readonly float[] _temperature; private readonly float[] _burnTime; private readonly byte[] _burning; private readonly short[] _sparkTime; private readonly float[] _lifetime; private readonly float[] _cellAge; private readonly float[] _integrity; public ChunkCellPage(int width, int height) { Width = width; Height = height; _particles = new PrototypeParticle[width * height]; _processedStep = new int[width * height]; _occupiedRowCounts = new int[height]; _gasRowCounts = new int[height]; _rowRevision = new int[height]; _blockedSolidSignature = new int[width * height]; _blockedGasRiseSignature = new int[width * height]; _gasRetrySignature = new int[width * height]; _gasRetryUntilStep = new int[width * height]; _gasRowCooldownSignature = new int[height]; _gasRowCooldownUntilStep = new int[height]; _driftState = new sbyte[width * height]; _pressureDuration = new float[width * height]; _temperature = new float[width * height]; _burnTime = new float[width * height]; _burning = new byte[width * height]; _sparkTime = new short[width * height]; _lifetime = new float[width * height]; _cellAge = new float[width * height]; _integrity = new float[width * height]; ClearDirtyBands(); ClearVisualDirty(); } public int Width { get; } public int Height { get; } public int OccupancyCount { get; private set; } public int GasCellCount { get; private set; } public int DynamicCellCount { get; private set; } public int RuntimeCellCount { get; private set; } public int PendingActiveSteps { get; private set; } public bool IsActive { get; set; } = true; public bool HasFieldActivity { get; set; } public int LastTouchedFrame { get; set; } public int DirtyMinRow { get; private set; } public int DirtyMaxRow { get; private set; } public int DirtyMinCol { get; private set; } public int DirtyMaxCol { get; private set; } public bool HasDirtyBands { get; private set; } public int VisualDirtyMinRow { get; private set; } public int VisualDirtyMaxRow { get; private set; } public int VisualDirtyMinCol { get; private set; } public int VisualDirtyMaxCol { get; private set; } public bool HasVisualDirty { get; private set; } public PrototypeParticle this[int localX, int localY] { get => _particles[GetIndex(localX, localY)]; set => _particles[GetIndex(localX, localY)] = value; } public bool IsProcessed(int localX, int localY, int stepToken) => _processedStep[GetIndex(localX, localY)] == stepToken; public void MarkProcessed(int localX, int localY, int stepToken) => _processedStep[GetIndex(localX, localY)] = stepToken; public bool IsOccupied(int localX, int localY) => _particles[GetIndex(localX, localY)].TypeId != 0; public int GetOccupiedRowCount(int localY) => _occupiedRowCounts[localY]; public int GetGasRowCount(int localY) => _gasRowCounts[localY]; public int GetRowRevision(int localY) => _rowRevision[localY]; public int GetBlockedSolidSignature(int localX, int localY) => _blockedSolidSignature[GetIndex(localX, localY)]; public void SetBlockedSolidSignature(int localX, int localY, int signature) => _blockedSolidSignature[GetIndex(localX, localY)] = signature; public int GetBlockedGasRiseSignature(int localX, int localY) => _blockedGasRiseSignature[GetIndex(localX, localY)]; public void SetBlockedGasRiseSignature(int localX, int localY, int signature) => _blockedGasRiseSignature[GetIndex(localX, localY)] = signature; public int GetGasRetrySignature(int localX, int localY) => _gasRetrySignature[GetIndex(localX, localY)]; public int GetGasRetryUntilStep(int localX, int localY) => _gasRetryUntilStep[GetIndex(localX, localY)]; public void SetGasRetryState(int localX, int localY, int signature, int untilStep) { var index = GetIndex(localX, localY); _gasRetrySignature[index] = signature; _gasRetryUntilStep[index] = untilStep; } public void ClearGasRetryState(int localX, int localY) { var index = GetIndex(localX, localY); _gasRetrySignature[index] = 0; _gasRetryUntilStep[index] = 0; } public int GetGasRowCooldownSignature(int localY) => _gasRowCooldownSignature[localY]; public int GetGasRowCooldownUntilStep(int localY) => _gasRowCooldownUntilStep[localY]; public void SetGasRowCooldown(int localY, int signature, int untilStep) { _gasRowCooldownSignature[localY] = signature; _gasRowCooldownUntilStep[localY] = untilStep; } public void ClearGasRowCooldown(int localY) { _gasRowCooldownSignature[localY] = 0; _gasRowCooldownUntilStep[localY] = 0; } public sbyte GetDriftState(int localX, int localY) => _driftState[GetIndex(localX, localY)]; public void SetDriftState(int localX, int localY, sbyte driftState) => _driftState[GetIndex(localX, localY)] = driftState; public float GetPressureDuration(int localX, int localY) => _pressureDuration[GetIndex(localX, localY)]; public void SetPressureDuration(int localX, int localY, float value) => _pressureDuration[GetIndex(localX, localY)] = value; public float GetTemperature(int localX, int localY) => _temperature[GetIndex(localX, localY)]; public void SetTemperature(int localX, int localY, float value) => _temperature[GetIndex(localX, localY)] = value; public float GetBurnTime(int localX, int localY) => _burnTime[GetIndex(localX, localY)]; public void SetBurnTime(int localX, int localY, float value) => _burnTime[GetIndex(localX, localY)] = value; public byte GetBurning(int localX, int localY) => _burning[GetIndex(localX, localY)]; public void SetBurning(int localX, int localY, byte value) => _burning[GetIndex(localX, localY)] = value; public short GetSparkTime(int localX, int localY) => _sparkTime[GetIndex(localX, localY)]; public void SetSparkTime(int localX, int localY, short value) => _sparkTime[GetIndex(localX, localY)] = value; public float GetLifetime(int localX, int localY) => _lifetime[GetIndex(localX, localY)]; public void SetLifetime(int localX, int localY, float value) => _lifetime[GetIndex(localX, localY)] = value; public float GetCellAge(int localX, int localY) => _cellAge[GetIndex(localX, localY)]; public void SetCellAge(int localX, int localY, float value) => _cellAge[GetIndex(localX, localY)] = value; public float GetIntegrity(int localX, int localY) => _integrity[GetIndex(localX, localY)]; public void SetIntegrity(int localX, int localY, float value) => _integrity[GetIndex(localX, localY)] = value; public void SetCell(int localX, int localY, PrototypeParticle particle) { var index = GetIndex(localX, localY); var previous = _particles[index]; if (previous.TypeId == 0 && particle.TypeId != 0) { OccupancyCount++; _occupiedRowCounts[localY]++; _rowRevision[localY]++; } else if (previous.TypeId != 0 && particle.TypeId == 0) { OccupancyCount--; _occupiedRowCounts[localY]--; _rowRevision[localY]++; } else if (previous.TypeId != particle.TypeId) { _rowRevision[localY]++; } if (previous.MotionType == PrototypeParticleType.Steam) { GasCellCount--; _gasRowCounts[localY]--; } if (previous.TypeId != 0 && !previous.IsStatic) { DynamicCellCount--; } if (previous.TypeId != 0 && previous.RequiresFullRuntimeStep) { RuntimeCellCount--; } _particles[index] = particle; if (particle.MotionType == PrototypeParticleType.Steam) { GasCellCount++; _gasRowCounts[localY]++; } if (particle.TypeId != 0 && !particle.IsStatic) { DynamicCellCount++; } if (particle.TypeId != 0 && particle.RequiresFullRuntimeStep) { RuntimeCellCount++; } if (particle.TypeId == 0) { _driftState[index] = 0; _pressureDuration[index] = 0f; _temperature[index] = 0f; _burnTime[index] = 0f; _burning[index] = 0; _sparkTime[index] = 0; _lifetime[index] = 0f; _cellAge[index] = 0f; _integrity[index] = 0f; } _blockedSolidSignature[index] = 0; _blockedGasRiseSignature[index] = 0; _gasRetrySignature[index] = 0; _gasRetryUntilStep[index] = 0; _gasRowCooldownSignature[localY] = 0; _gasRowCooldownUntilStep[localY] = 0; MarkTouched(localX, localY); } public void MarkTouched(int localX, int localY) { MarkDirtyBand(localX, localY); MarkVisualDirty(localX, localY); IsActive = true; PendingActiveSteps = Math.Max(PendingActiveSteps, 1); } public void RequestFutureSteps(int steps) { if (steps > PendingActiveSteps) { PendingActiveSteps = steps; } } public void DecayPendingSteps() { if (PendingActiveSteps > 0) { PendingActiveSteps--; } } public void MarkDirtyBand(int localX, int localY) { if (!HasDirtyBands) { DirtyMinRow = DirtyMaxRow = localY; DirtyMinCol = DirtyMaxCol = localX; HasDirtyBands = true; return; } DirtyMinRow = Math.Min(DirtyMinRow, localY); DirtyMaxRow = Math.Max(DirtyMaxRow, localY); DirtyMinCol = Math.Min(DirtyMinCol, localX); DirtyMaxCol = Math.Max(DirtyMaxCol, localX); } public void MarkVisualDirty(int localX, int localY) { if (!HasVisualDirty) { VisualDirtyMinRow = VisualDirtyMaxRow = localY; VisualDirtyMinCol = VisualDirtyMaxCol = localX; HasVisualDirty = true; return; } VisualDirtyMinRow = Math.Min(VisualDirtyMinRow, localY); VisualDirtyMaxRow = Math.Max(VisualDirtyMaxRow, localY); VisualDirtyMinCol = Math.Min(VisualDirtyMinCol, localX); VisualDirtyMaxCol = Math.Max(VisualDirtyMaxCol, localX); } public void MarkFullVisualDirty() { HasVisualDirty = true; VisualDirtyMinRow = 0; VisualDirtyMaxRow = Height - 1; VisualDirtyMinCol = 0; VisualDirtyMaxCol = Width - 1; } public void ClearDirtyBands() { HasDirtyBands = false; DirtyMinRow = Height; DirtyMaxRow = -1; DirtyMinCol = Width; DirtyMaxCol = -1; } public void ClearVisualDirty() { HasVisualDirty = false; VisualDirtyMinRow = Height; VisualDirtyMaxRow = -1; VisualDirtyMinCol = Width; VisualDirtyMaxCol = -1; } public IEnumerable<(int LocalX, int LocalY, PrototypeParticle Particle)> EnumerateOccupied() { for (var y = 0; y < Height; y++) { for (var x = 0; x < Width; x++) { var particle = _particles[GetIndex(x, y)]; if (particle.TypeId != 0) { yield return (x, y, particle); } } } } private int GetIndex(int localX, int localY) => (localY * Width) + localX; }