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:
Erik 2026-07-25 08:35:12 +02:00
parent 2c848d4167
commit 823936ec31
57 changed files with 2551 additions and 324 deletions

View file

@ -20,6 +20,7 @@ internal sealed class LiveEntityDeletionController : ILiveEntityPruneSink
private readonly ClientObjectTable _objects;
private readonly ILiveEntityTeardownCoordinator _teardown;
private readonly ILocalPlayerIdentitySource _identity;
private readonly DormantLiveEntityStore _dormant;
private readonly Action<string>? _diagnostic;
public LiveEntityDeletionController(
@ -27,12 +28,14 @@ internal sealed class LiveEntityDeletionController : ILiveEntityPruneSink
ClientObjectTable objects,
ILiveEntityTeardownCoordinator teardown,
ILocalPlayerIdentitySource identity,
DormantLiveEntityStore? dormant = null,
Action<string>? diagnostic = null)
{
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
_objects = objects ?? throw new ArgumentNullException(nameof(objects));
_teardown = teardown ?? throw new ArgumentNullException(nameof(teardown));
_identity = identity ?? throw new ArgumentNullException(nameof(identity));
_dormant = dormant ?? new DormantLiveEntityStore();
_diagnostic = diagnostic;
}
@ -46,7 +49,8 @@ internal sealed class LiveEntityDeletionController : ILiveEntityPruneSink
if (delete.Guid == _identity.ServerGuid)
return false;
if (!_runtime.TryGetRecord(delete.Guid, out _))
bool hasActiveRecord = _runtime.TryGetRecord(delete.Guid, out _);
if (!hasActiveRecord)
_teardown.ForgetUnknownOwner(delete.Guid);
bool removed = _runtime.UnregisterLiveEntity(
@ -54,16 +58,45 @@ internal sealed class LiveEntityDeletionController : ILiveEntityPruneSink
isLocalPlayer: false,
beforeTeardown: () =>
ObjectTableWiring.ApplyEntityDelete(_objects, delete));
bool removedDormant = _dormant.RemoveExact(delete);
if (!removed && removedDormant)
ObjectTableWiring.ApplyEntityDelete(_objects, delete);
if (removed)
{
_dormant.RemoveExact(delete);
_diagnostic?.Invoke(
$"live: delete guid=0x{delete.Guid:X8} instSeq={delete.InstanceSequence}");
}
return removed;
else if (removedDormant)
{
_diagnostic?.Invoke(
$"live: delete dormant guid=0x{delete.Guid:X8} instSeq={delete.InstanceSequence}");
}
return removed || removedDormant;
}
public bool Prune(LiveEntityPruneCandidate candidate) =>
Delete(new DeleteObject.Parsed(
candidate.ServerGuid,
candidate.Generation));
public bool Prune(LiveEntityPruneCandidate candidate)
{
if (!_runtime.TryGetRecord(
candidate.ServerGuid,
out LiveEntityRecord record)
|| record.Generation != candidate.Generation)
{
return false;
}
WorldSession.EntitySpawn snapshot = record.Snapshot;
bool removed = _runtime.UnregisterLiveEntity(
new DeleteObject.Parsed(
candidate.ServerGuid,
candidate.Generation),
isLocalPlayer: false);
if (!removed)
return false;
_dormant.Retain(snapshot);
_diagnostic?.Invoke(
$"live: dormant guid=0x{candidate.ServerGuid:X8} instSeq={candidate.Generation}");
return true;
}
}