namespace AcDream.App.Rendering; internal interface IRetryableResourceCleanup { bool IsCleanupComplete { get; } void RetryCleanup(); } internal sealed class GlResourceConstructionException : AggregateException, IRetryableResourceCleanup { private readonly IRetryableResourceCleanup _cleanup; public GlResourceConstructionException( string message, IRetryableResourceCleanup cleanup, IEnumerable failures) : base(message, failures) { _cleanup = cleanup ?? throw new ArgumentNullException(nameof(cleanup)); } public bool IsCleanupComplete => _cleanup.IsCleanupComplete; public void RetryCleanup() => _cleanup.RetryCleanup(); } /// /// Lifetime root for cleanup work that could not finish before a throwing GL /// factory returned control. The original exception remains the retry owner; /// this ledger prevents it and its exact pending names from becoming local-only. /// internal sealed class GlConstructionCleanupLedger : IDisposable { private readonly List _pending = []; private bool _disposing; public bool IsComplete => _pending.Count == 0; public bool RetainFrom(Exception failure) { ArgumentNullException.ThrowIfNull(failure); bool retained = false; Visit(failure); return retained; void Visit(Exception current) { if (current is IRetryableResourceCleanup cleanup) { if (!cleanup.IsCleanupComplete && !_pending.Contains(cleanup)) _pending.Add(cleanup); retained = true; } if (current is AggregateException aggregate) { foreach (Exception inner in aggregate.InnerExceptions) Visit(inner); } else if (current.InnerException is { } inner) { Visit(inner); } } } public void Dispose() { if (_disposing || _pending.Count == 0) return; _disposing = true; List? failures = null; try { for (int i = _pending.Count - 1; i >= 0; i--) { IRetryableResourceCleanup cleanup = _pending[i]; try { cleanup.RetryCleanup(); if (cleanup.IsCleanupComplete) _pending.RemoveAt(i); } catch (Exception failure) { (failures ??= []).Add(failure); } } } finally { _disposing = false; } if (failures is not null) throw new AggregateException( "One or more failed GL construction transactions remain pending.", failures); } }