namespace AcDream.App.Rendering;
///
/// Owns the two independent residency edges used by TerrainAtlas. Acquisition
/// and release publish each successful edge immediately, so a partial failure
/// retries only work that is still pending.
///
internal sealed class BindlessTexturePair(
uint firstTexture,
uint secondTexture,
Func acquire,
Action release)
{
private ulong _firstHandle;
private ulong _secondHandle;
private bool _firstResident;
private bool _secondResident;
public bool IsFullyResident => _firstResident && _secondResident;
public bool HasAnyResident => _firstResident || _secondResident;
public (ulong First, ulong Second) Acquire()
{
if (!_firstResident)
{
_firstHandle = acquire(firstTexture);
_firstResident = true;
}
if (!_secondResident)
{
try
{
_secondHandle = acquire(secondTexture);
_secondResident = true;
}
catch (Exception acquisitionFailure)
{
try
{
Release();
}
catch (Exception cleanupFailure)
{
throw new AggregateException(
"Second bindless texture acquisition failed and the resident prefix did not cleanly roll back.",
acquisitionFailure,
cleanupFailure);
}
throw;
}
}
return (_firstHandle, _secondHandle);
}
public void Release()
{
List? failures = null;
if (_firstResident)
{
try
{
release(_firstHandle);
_firstResident = false;
_firstHandle = 0;
}
catch (Exception failure)
{
(failures ??= []).Add(failure);
}
}
if (_secondResident)
{
try
{
release(_secondHandle);
_secondResident = false;
_secondHandle = 0;
}
catch (Exception failure)
{
(failures ??= []).Add(failure);
}
}
if (failures is not null)
throw new AggregateException(
"One or more bindless texture handles could not be made non-resident.",
failures);
}
}