namespace AcDream.App.Rendering;
internal interface IGlTextureNameApi
{
uint GenTexture();
void DeleteTexture(uint texture);
}
internal sealed class GlTextureNameApi(Silk.NET.OpenGL.GL gl) : IGlTextureNameApi
{
public uint GenTexture() =>
GlResourceCommand.CreateTexture(gl, "terrain construction texture");
public void DeleteTexture(uint texture) =>
GlResourceCommand.DeleteTexture(
gl,
texture,
$"delete terrain construction texture {texture}");
}
///
/// Retains every texture name immediately after allocation until the complete
/// aggregate owner has been constructed. A failed factory rolls names back in
/// reverse acquisition order and attempts every deletion.
///
internal sealed class GlTextureConstructionTransaction(IGlTextureNameApi api)
: IRetryableResourceCleanup
{
private readonly List _ownedNames = [];
private bool _finished;
public bool IsCleanupComplete => _finished;
public uint Allocate()
{
if (_finished)
throw new InvalidOperationException("The texture construction transaction is finished.");
uint texture = api.GenTexture();
if (texture == 0)
throw new InvalidOperationException("OpenGL returned no texture name.");
_ownedNames.Add(texture);
return texture;
}
public void Commit()
{
if (_finished)
throw new InvalidOperationException("The texture construction transaction is finished.");
_ownedNames.Clear();
_finished = true;
}
public void Rollback()
{
if (_finished)
return;
List? failures = null;
for (int i = _ownedNames.Count - 1; i >= 0; i--)
{
uint texture = _ownedNames[i];
try
{
api.DeleteTexture(texture);
_ownedNames.RemoveAt(i);
}
catch (Exception failure)
{
(failures ??= []).Add(new InvalidOperationException(
$"Texture construction rollback could not delete OpenGL name {texture}.",
failure));
}
}
if (failures is not null)
throw new AggregateException(
"Texture construction rollback did not release every allocated OpenGL name.",
failures);
_finished = true;
}
public void RetryCleanup() => Rollback();
}