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();
}