Separate logical ownership, render publication, and GPU retirement across live entities, landblocks, particles, textures, mesh arenas, portal/UI teardown, and per-frame scratch storage. Add bounded DAT/texture caches, upload budgets, three-frame fence retirement, exact-incarnation appearance reconciliation, frame pacing, and extensive lifetime conformance coverage.\n\nThe seven-destination connected route now cuts peak working/private memory roughly in half, returns Caul to 125-153 FPS locally, and produces no WER or AMD reset.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
154 lines
4.8 KiB
C#
154 lines
4.8 KiB
C#
using AcDream.App.Rendering.Wb;
|
|
|
|
namespace AcDream.App.Rendering;
|
|
|
|
/// <summary>
|
|
/// Owns the synthetic portal creature's mesh references. Acquisition happens
|
|
/// only after the presentation has been published to its runtime owner, so a
|
|
/// partial backend failure can never strand an unreachable constructor-local
|
|
/// reference. Per-reference physical state makes both acquisition and teardown
|
|
/// resumable without replaying successful mutations.
|
|
/// </summary>
|
|
internal sealed class PortalMeshReferenceOwner : IDisposable
|
|
{
|
|
private sealed class ReferenceState(ulong gfxObjId)
|
|
{
|
|
public ulong GfxObjId { get; } = gfxObjId;
|
|
public bool Held { get; set; }
|
|
}
|
|
|
|
private readonly IWbMeshAdapter _meshAdapter;
|
|
private readonly ReferenceState[] _references;
|
|
private bool _desired;
|
|
private bool _disposeRequested;
|
|
private bool _disposed;
|
|
private bool _reconciling;
|
|
private bool _reconcileAgain;
|
|
|
|
public PortalMeshReferenceOwner(
|
|
IWbMeshAdapter meshAdapter,
|
|
IEnumerable<ulong> gfxObjIds)
|
|
{
|
|
_meshAdapter = meshAdapter ?? throw new ArgumentNullException(nameof(meshAdapter));
|
|
ArgumentNullException.ThrowIfNull(gfxObjIds);
|
|
_references = gfxObjIds
|
|
.Where(static id => id != 0u)
|
|
.Distinct()
|
|
.Select(static id => new ReferenceState(id))
|
|
.ToArray();
|
|
}
|
|
|
|
public bool IsDisposed => _disposed;
|
|
|
|
public void Acquire()
|
|
{
|
|
ObjectDisposedException.ThrowIf(_disposeRequested, this);
|
|
_desired = true;
|
|
try
|
|
{
|
|
Reconcile();
|
|
ObjectDisposedException.ThrowIf(_disposeRequested, this);
|
|
}
|
|
catch (Exception acquisitionFailure)
|
|
{
|
|
// Acquisition is transactional even though the owner is already
|
|
// reachable. Roll back every committed sibling; a failed rollback
|
|
// remains represented by Held=true so Dispose can resume it.
|
|
_desired = false;
|
|
try
|
|
{
|
|
Reconcile();
|
|
}
|
|
catch (Exception rollbackFailure)
|
|
{
|
|
throw new AggregateException(
|
|
"Portal mesh acquisition failed and its rollback did not fully converge.",
|
|
acquisitionFailure,
|
|
rollbackFailure);
|
|
}
|
|
|
|
System.Runtime.ExceptionServices.ExceptionDispatchInfo
|
|
.Capture(acquisitionFailure)
|
|
.Throw();
|
|
throw new InvalidOperationException("Unreachable exception dispatch path.");
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_disposed)
|
|
return;
|
|
|
|
_disposeRequested = true;
|
|
_desired = false;
|
|
if (_reconciling)
|
|
{
|
|
_reconcileAgain = true;
|
|
return;
|
|
}
|
|
|
|
Reconcile();
|
|
}
|
|
|
|
private void Reconcile()
|
|
{
|
|
if (_reconciling)
|
|
{
|
|
_reconcileAgain = true;
|
|
return;
|
|
}
|
|
|
|
_reconciling = true;
|
|
List<Exception>? failures = null;
|
|
try
|
|
{
|
|
do
|
|
{
|
|
_reconcileAgain = false;
|
|
for (int i = 0; i < _references.Length; i++)
|
|
{
|
|
ReferenceState reference = _references[i];
|
|
bool targetHeld = _desired;
|
|
if (reference.Held == targetHeld)
|
|
continue;
|
|
|
|
try
|
|
{
|
|
if (targetHeld)
|
|
_meshAdapter.IncrementRefCount(reference.GfxObjId);
|
|
else
|
|
_meshAdapter.DecrementRefCount(reference.GfxObjId);
|
|
reference.Held = targetHeld;
|
|
}
|
|
catch (Exception error)
|
|
{
|
|
if (error is MeshReferenceMutationException
|
|
{
|
|
MutationCommitted: true,
|
|
})
|
|
{
|
|
reference.Held = targetHeld;
|
|
}
|
|
|
|
string operation = targetHeld ? "acquisition" : "release";
|
|
(failures ??= []).Add(new InvalidOperationException(
|
|
$"Portal mesh 0x{reference.GfxObjId:X10} reference {operation} failed.",
|
|
error));
|
|
}
|
|
}
|
|
}
|
|
while (_reconcileAgain);
|
|
}
|
|
finally
|
|
{
|
|
_reconciling = false;
|
|
if (_disposeRequested && _references.All(static reference => !reference.Held))
|
|
_disposed = true;
|
|
}
|
|
|
|
if (failures is not null)
|
|
throw new AggregateException(
|
|
"One or more portal mesh references failed to reconcile.",
|
|
failures);
|
|
}
|
|
}
|