Stan44 01d2cbf4b3 fixed code formatter.
minor bug fixes to a few systems as well.
2025-05-11 22:47:43 -05:00

174 lines
5.5 KiB
C#

#nullable enable
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using AdvChkSys.Interfaces;
namespace AdvChkSys.Chunk
{
/// <summary>
/// Represents a 2D chunk of data in the world.
/// Supports all-air singleton for memory efficiency and chunk array pooling.
/// </summary>
public class Chunk2D<T> : IChunk, IDisposable
{
// Flyweight: one all-air instance per size
private static readonly Dictionary<(int, int), Chunk2D<T>> _allAirChunks = new();
// Array pool for chunk data arrays
private static readonly ConcurrentBag<T[,]> _arrayPool = new();
/// <summary>
/// Returns a singleton all-air chunk for the given size and position.
/// </summary>
public static Chunk2D<T> AllAir(int x, int y, int width, int height)
{
var key = (width, height);
if (!_allAirChunks.TryGetValue(key, out var chunk))
{
chunk = new Chunk2D<T>(x, y, width, height, isAllAir: true);
_allAirChunks[key] = chunk;
}
chunk.SetPosition(x, y); // Use explicit method for position update
return chunk;
}
private readonly bool _isAllAir;
private T[,]? _data;
// Properties required by IChunk
/// <summary>
/// The chunk's X position in chunk coordinates.
/// </summary>
public int X { get; private set; }
/// <summary>
/// The chunk's Y position in chunk coordinates.
/// </summary>
public int Y { get; private set; }
/// <summary>
/// The width of the chunk in cells.
/// </summary>
public int Width { get; }
/// <summary>
/// The height of the chunk in cells.
/// </summary>
public int Height { get; }
/// <summary>
/// Metadata dictionary for arbitrary chunk information.
/// </summary>
public Dictionary<string, object> Metadata { get; }
private bool _disposed;
/// <summary>
/// Normal constructor (private for singleton/factory).
/// </summary>
public Chunk2D(int x, int y, int width, int height, bool isAllAir = false)
{
X = x;
Y = y;
Width = width;
Height = height;
_isAllAir = isAllAir;
_data = isAllAir ? null : RentArray(width, height);
Metadata = new Dictionary<string, object>();
}
/// <summary>
/// Explicitly sets the chunk's position. Used internally for all-air singleton.
/// </summary>
internal void SetPosition(int x, int y)
{
X = x;
Y = y;
}
/// <summary>
/// Gets or sets the value at the given local chunk coordinates.
/// </summary>
public T this[int localX, int localY]
{
get
{
if (localX < 0 || localX >= Width || localY < 0 || localY >= Height)
throw new ArgumentOutOfRangeException($"Coordinates ({localX}, {localY}) out of bounds [0-{Width - 1}, 0-{Height - 1}]");
return _isAllAir ? default! : _data![localX, localY];
}
set
{
if (localX < 0 || localX >= Width || localY < 0 || localY >= Height)
throw new ArgumentOutOfRangeException($"Coordinates ({localX}, {localY}) out of bounds [0-{Width - 1}, 0-{Height - 1}]");
if (_isAllAir)
throw new InvalidOperationException("Cannot set cell in all-air chunk.");
_data![localX, localY] = value;
}
}
/// <summary>
/// Fills the chunk with a specified value (no-op for all-air).
/// </summary>
public void Fill(T value)
{
if (_isAllAir) return;
var data = _data!;
for (int x = 0; x < Width; x++)
for (int y = 0; y < Height; y++)
data[x, y] = value;
}
/// <summary>
/// Returns true if this chunk is the all-air singleton.
/// </summary>
public bool IsAllAir => _isAllAir;
/// <summary>
/// Returns the underlying data array (for pooling).
/// </summary>
internal T[,]? GetDataArray() => _data;
/// <summary>
/// Releases the data array back to the pool (for chunk manager).
/// </summary>
internal void ReleaseDataArray()
{
if (_data != null)
{
if (!_isAllAir)
{
ReturnArray(_data);
}
_data = null;
}
}
/// <summary>
/// Rents an array from the pool or creates a new one.
/// </summary>
private static T[,] RentArray(int width, int height)
{
if (_arrayPool.TryTake(out var arr) && arr.GetLength(0) == width && arr.GetLength(1) == height)
return arr;
return new T[width, height];
}
/// <summary>
/// Returns an array to the pool.
/// </summary>
internal static void ReturnArray(T[,] arr)
{
_arrayPool.Add(arr);
}
/// <summary>
/// Disposes the chunk and returns its resources to the pool.
/// </summary>
public void Dispose()
{
if (!_disposed)
{
ReleaseDataArray();
_disposed = true;
}
}
}
}