fix(streaming): preserve portal destination ownership
Detach old-world spatial ownership atomically, prioritize destination retirement dependencies, and reveal the viewport at the retail transition edge. Give private paperdoll views independent mesh ownership and retain dormant ACE entities so portal revisits preserve server objects without extending active GPU lifetimes.
This commit is contained in:
parent
2c848d4167
commit
823936ec31
57 changed files with 2551 additions and 324 deletions
|
|
@ -1,154 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue