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