using AcDream.Core.Terrain; namespace AcDream.App.Rendering; /// /// Adds GPU-frame retirement to the terrain renderer's CPU slot allocator. /// Removing a landblock stops future draws immediately, but its VBO/EBO slot /// is not returned for overwrite until older submitted draws have completed. /// internal sealed class GpuRetiredTerrainSlotAllocator { private readonly TerrainSlotAllocator _allocator; private readonly GpuRetirementLedger _retirementLedger; private readonly Dictionary _pendingReleases = []; public GpuRetiredTerrainSlotAllocator( int initialCapacity, IGpuResourceRetirementQueue retirement) { _allocator = new TerrainSlotAllocator(initialCapacity); _retirementLedger = new GpuRetirementLedger( retirement ?? throw new ArgumentNullException(nameof(retirement))); } public int Capacity => _allocator.Capacity; public int LoadedCount => _allocator.LoadedCount; internal int PendingReleaseCount => _pendingReleases.Count; public int Allocate(out bool needsGrow) => _allocator.Allocate(out needsGrow); public void GrowTo(int newCapacity) => _allocator.GrowTo(newCapacity); /// /// Returns a slot whose contents were never published to a GPU draw. No /// retirement fence is required because no submitted command can refer to /// it. /// public void ReleaseUnsubmitted(int slot) { if (_pendingReleases.ContainsKey(slot)) { throw new InvalidOperationException( "A GPU-submitted terrain slot cannot be released as unsubmitted."); } _allocator.Free(slot); } public void FreeAfterGpuUse(int slot) { if (_pendingReleases.TryGetValue(slot, out RetryableGpuResourceRelease? pending)) { try { _retirementLedger.RetryPendingPublication(pending); } catch when (pending.IsComplete) { // Completion is stronger than publication acknowledgement. } return; } var release = new RetryableGpuResourceRelease( () => _allocator.Free(slot), () => { if (!_pendingReleases.Remove(slot)) { throw new InvalidOperationException( "Terrain-slot retirement lost its ownership record."); } }); _pendingReleases.Add(slot, release); try { _retirementLedger.Retire(release); } catch when (release.IsComplete) { // An immediate retirement queue can complete before surfacing a // wrapper failure. The slot is already safely reusable. } } public void RetryPendingPublications() => _retirementLedger.RetryPendingPublications(); }