174 lines
5.5 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
} |