sandpypi/Sand.ChunkPrototype/PrototypeChunkResidencyWorld.cs

255 lines
8.2 KiB
C#

using AdvChkSys.Chunk;
using AdvChkSys.Manager;
namespace Sand.ChunkPrototype;
public sealed class PrototypeChunkResidencyWorld
{
private readonly ChunkManager2D<byte> _manager;
private readonly Dictionary<(int ChunkX, int ChunkY), int> _occupancyCounts = new();
private readonly HashSet<(int ChunkX, int ChunkY)> _dirtyChunks = new();
private readonly ChunkResidencyConfig _config;
public PrototypeChunkResidencyWorld(ChunkResidencyConfig? config = null)
{
_config = config ?? ChunkResidencyConfig.Default;
_manager = new ChunkManager2D<byte>(
capacity: _config.Capacity,
chunkWidth: _config.ChunkWidth,
chunkHeight: _config.ChunkHeight);
}
public int LoadedChunkCount => _manager.GetAllChunks().Count();
public int ChunkWidth => _config.ChunkWidth;
public int ChunkHeight => _config.ChunkHeight;
public int ActiveChunkCount => _occupancyCounts.Count;
public int DirtyChunkCount => _dirtyChunks.Count;
public long EstimatedLoadedBytes => (long)LoadedChunkCount * _config.ChunkWidth * _config.ChunkHeight * sizeof(byte);
public IReadOnlyCollection<(int ChunkX, int ChunkY)> ActiveChunks => _occupancyCounts.Keys.ToArray();
public IReadOnlyCollection<(int ChunkX, int ChunkY)> DirtyChunks => _dirtyChunks.ToArray();
public void SetOccupied(int cellX, int cellY, byte value = 1)
{
var chunk = GetChunk(cellX, cellY, out var localX, out var localY, out var key);
if (chunk[localX, localY] == 0)
{
_occupancyCounts[key] = _occupancyCounts.TryGetValue(key, out var count) ? count + 1 : 1;
}
chunk[localX, localY] = value;
_dirtyChunks.Add(key);
}
public void ClearOccupied(int cellX, int cellY)
{
var chunk = GetChunk(cellX, cellY, out var localX, out var localY, out var key);
if (chunk[localX, localY] == 0)
{
return;
}
chunk[localX, localY] = 0;
if (!_occupancyCounts.TryGetValue(key, out var count))
{
return;
}
if (count <= 1)
{
_occupancyCounts.Remove(key);
_dirtyChunks.Add(key);
return;
}
_occupancyCounts[key] = count - 1;
_dirtyChunks.Add(key);
}
public bool IsOccupied(int cellX, int cellY)
{
var chunk = GetChunk(cellX, cellY, out var localX, out var localY, out _);
return chunk[localX, localY] != 0;
}
public bool IsChunkLoaded(int chunkX, int chunkY) => _manager.GetChunk(chunkX, chunkY) is not null;
public int GetChunkOccupancyCount(int chunkX, int chunkY) =>
_occupancyCounts.TryGetValue((chunkX, chunkY), out var count) ? count : 0;
public void ClearDirtyChunks() => _dirtyChunks.Clear();
public bool MoveOccupied(int fromCellX, int fromCellY, int toCellX, int toCellY, byte value = 1)
{
var sourceChunk = GetChunk(fromCellX, fromCellY, out var sourceLocalX, out var sourceLocalY, out var sourceKey);
if (sourceChunk[sourceLocalX, sourceLocalY] == 0)
{
return false;
}
if (fromCellX == toCellX && fromCellY == toCellY)
{
sourceChunk[sourceLocalX, sourceLocalY] = value;
_dirtyChunks.Add(sourceKey);
return true;
}
var destinationChunk = GetChunk(toCellX, toCellY, out var destinationLocalX, out var destinationLocalY, out var destinationKey);
if (destinationChunk[destinationLocalX, destinationLocalY] != 0)
{
return false;
}
sourceChunk[sourceLocalX, sourceLocalY] = 0;
destinationChunk[destinationLocalX, destinationLocalY] = value;
DecrementChunkOccupancy(sourceKey);
_occupancyCounts[destinationKey] = _occupancyCounts.TryGetValue(destinationKey, out var destinationCount) ? destinationCount + 1 : 1;
_dirtyChunks.Add(sourceKey);
_dirtyChunks.Add(destinationKey);
return true;
}
public bool MoveOccupiedWithinChunk(int chunkX, int chunkY, int fromLocalX, int fromLocalY, int toLocalX, int toLocalY, byte value = 1)
{
var chunk = _manager.LoadOrCreateChunk(chunkX, chunkY, _config.ChunkWidth, _config.ChunkHeight);
if (chunk[fromLocalX, fromLocalY] == 0 || chunk[toLocalX, toLocalY] != 0)
{
return false;
}
chunk[fromLocalX, fromLocalY] = 0;
chunk[toLocalX, toLocalY] = value;
_dirtyChunks.Add((chunkX, chunkY));
return true;
}
public bool SwapOccupied(int firstCellX, int firstCellY, int secondCellX, int secondCellY, byte firstValue, byte secondValue)
{
var firstChunk = GetChunk(firstCellX, firstCellY, out var firstLocalX, out var firstLocalY, out var firstKey);
var secondChunk = GetChunk(secondCellX, secondCellY, out var secondLocalX, out var secondLocalY, out var secondKey);
if (firstChunk[firstLocalX, firstLocalY] == 0 || secondChunk[secondLocalX, secondLocalY] == 0)
{
return false;
}
firstChunk[firstLocalX, firstLocalY] = secondValue;
secondChunk[secondLocalX, secondLocalY] = firstValue;
_dirtyChunks.Add(firstKey);
_dirtyChunks.Add(secondKey);
return true;
}
public bool SwapOccupiedWithinChunk(int chunkX, int chunkY, int firstLocalX, int firstLocalY, int secondLocalX, int secondLocalY, byte firstValue, byte secondValue)
{
var chunk = _manager.LoadOrCreateChunk(chunkX, chunkY, _config.ChunkWidth, _config.ChunkHeight);
if (chunk[firstLocalX, firstLocalY] == 0 || chunk[secondLocalX, secondLocalY] == 0)
{
return false;
}
chunk[firstLocalX, firstLocalY] = secondValue;
chunk[secondLocalX, secondLocalY] = firstValue;
_dirtyChunks.Add((chunkX, chunkY));
return true;
}
public int UnloadEmptyChunks()
{
var unloaded = 0;
foreach (var chunk in _manager.GetAllChunks().ToArray())
{
var key = (chunk.X, chunk.Y);
if (_occupancyCounts.ContainsKey(key))
{
continue;
}
if (_manager.UnloadChunk(chunk.X, chunk.Y))
{
unloaded++;
}
}
return unloaded;
}
public int UnloadInactiveChunks(int marginChunks = 0)
{
if (_occupancyCounts.Count == 0)
{
return UnloadEmptyChunks();
}
var keep = new HashSet<(int ChunkX, int ChunkY)>();
foreach (var (chunkX, chunkY) in _occupancyCounts.Keys)
{
for (var offsetX = -marginChunks; offsetX <= marginChunks; offsetX++)
{
for (var offsetY = -marginChunks; offsetY <= marginChunks; offsetY++)
{
keep.Add((chunkX + offsetX, chunkY + offsetY));
}
}
}
var unloaded = 0;
foreach (var chunk in _manager.GetAllChunks().ToArray())
{
var key = (chunk.X, chunk.Y);
if (keep.Contains(key))
{
continue;
}
if (_manager.UnloadChunk(chunk.X, chunk.Y))
{
unloaded++;
}
}
return unloaded;
}
private void DecrementChunkOccupancy((int ChunkX, int ChunkY) key)
{
if (!_occupancyCounts.TryGetValue(key, out var count))
{
return;
}
if (count <= 1)
{
_occupancyCounts.Remove(key);
return;
}
_occupancyCounts[key] = count - 1;
}
private Chunk2D<byte> GetChunk(int cellX, int cellY, out int localX, out int localY, out (int ChunkX, int ChunkY) key)
{
var chunkX = Math.DivRem(cellX, _config.ChunkWidth, out localX);
var chunkY = Math.DivRem(cellY, _config.ChunkHeight, out localY);
if (localX < 0)
{
localX += _config.ChunkWidth;
chunkX--;
}
if (localY < 0)
{
localY += _config.ChunkHeight;
chunkY--;
}
key = (chunkX, chunkY);
return _manager.LoadOrCreateChunk(chunkX, chunkY, _config.ChunkWidth, _config.ChunkHeight);
}
}