Move Region/environment, mandatory modern rendering, terrain, WB, texture, and sampler construction behind the typed Phase-4 composition boundary. Give every fallible GL constructor prefix retryable ownership so partial startup failure cannot leak or replay resource deletion while preserving the accepted render path and DAT inputs. Co-authored-by: Codex <codex@openai.com>
96 lines
2.9 KiB
C#
96 lines
2.9 KiB
C#
namespace AcDream.App.Rendering;
|
|
|
|
using System.Runtime.ExceptionServices;
|
|
|
|
/// <summary>
|
|
/// Reverse-order, all-attempted cleanup owner used while a composite resource
|
|
/// is still under construction and after it becomes the aggregate owner.
|
|
/// </summary>
|
|
internal sealed class ResourceCleanupGroup : IRetryableResourceCleanup
|
|
{
|
|
private sealed record Entry(string Name, Action Release)
|
|
{
|
|
public bool Complete { get; set; }
|
|
}
|
|
|
|
private readonly List<Entry> _entries = [];
|
|
private bool _running;
|
|
|
|
public bool IsCleanupComplete => _entries.All(static entry => entry.Complete);
|
|
|
|
public void Add(string name, Action release)
|
|
{
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(name);
|
|
ArgumentNullException.ThrowIfNull(release);
|
|
if (_running || IsCleanupComplete && _entries.Count != 0)
|
|
throw new InvalidOperationException("The resource cleanup group is no longer accepting ownership.");
|
|
_entries.Add(new Entry(name, release));
|
|
}
|
|
|
|
public void TransferAll()
|
|
{
|
|
if (_running)
|
|
throw new InvalidOperationException(
|
|
"The resource cleanup group is currently releasing resources.");
|
|
foreach (Entry entry in _entries)
|
|
entry.Complete = true;
|
|
}
|
|
|
|
public void RetryCleanup()
|
|
{
|
|
if (_running || IsCleanupComplete)
|
|
return;
|
|
|
|
_running = true;
|
|
List<Exception>? failures = null;
|
|
try
|
|
{
|
|
for (int i = _entries.Count - 1; i >= 0; i--)
|
|
{
|
|
Entry entry = _entries[i];
|
|
if (entry.Complete)
|
|
continue;
|
|
try
|
|
{
|
|
entry.Release();
|
|
entry.Complete = true;
|
|
}
|
|
catch (Exception failure)
|
|
{
|
|
(failures ??= []).Add(new InvalidOperationException(
|
|
$"Resource cleanup operation '{entry.Name}' failed.",
|
|
failure));
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_running = false;
|
|
}
|
|
|
|
if (failures is not null)
|
|
throw new AggregateException("Composite resource cleanup remains incomplete.", failures);
|
|
}
|
|
|
|
public void RollbackConstructionAndThrow(
|
|
string message,
|
|
Exception constructionFailure)
|
|
{
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(message);
|
|
ArgumentNullException.ThrowIfNull(constructionFailure);
|
|
try
|
|
{
|
|
RetryCleanup();
|
|
}
|
|
catch (Exception cleanupFailure)
|
|
{
|
|
throw new GlResourceConstructionException(
|
|
message,
|
|
this,
|
|
[constructionFailure, cleanupFailure]);
|
|
}
|
|
|
|
ExceptionDispatchInfo.Capture(constructionFailure).Throw();
|
|
throw new InvalidOperationException("Unreachable construction rollback path.");
|
|
}
|
|
}
|