using AdvChkSys.Chunk; using AdvChkSys.Manager; namespace Sand.ChunkPrototype; public sealed class PrototypeChunkResidencyWorld { private readonly ChunkManager2D _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( 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 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); } }