refactor(app): key live projections by runtime identity
Move materialized live-object sidecars and presentation worksets to exact RuntimeEntityKey ownership. Runtime remains the only GUID/incarnation/local-ID authority while hydration, animation, effects, lights, equipped children, renderer resources, visibility, liveness, and teardown resolve exact projection identities. Preserve synchronous callbacks, local-ID allocation order, and current rendering behavior.
This commit is contained in:
parent
e18df84437
commit
420e5eea70
73 changed files with 2939 additions and 1715 deletions
|
|
@ -41,25 +41,34 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
/// <summary>Raised after an attached projection has its composed pose.</summary>
|
||||
public event Action<uint>? ProjectionPoseReady;
|
||||
/// <summary>Raised when an attached projection leaves its cell presentation.</summary>
|
||||
public event Action<uint>? ProjectionRemoved;
|
||||
private readonly Dictionary<uint, AttachedChild> _attachedByChild = new();
|
||||
private readonly Dictionary<uint, PendingUnparentTransition> _pendingUnparentByChild = new();
|
||||
private readonly Dictionary<uint, PendingOrdinaryRemoval> _pendingOrdinaryRemovalByRoot = new();
|
||||
private readonly Dictionary<uint, PendingProjectionSubtree> _pendingDetachedRemovalByChild = new();
|
||||
private readonly Dictionary<uint, PendingProjectionSubtree> _pendingReparentRemovalByChild = new();
|
||||
private readonly Dictionary<uint, PendingProjectionSubtree> _pendingPoseLossRemovalByChild = new();
|
||||
private readonly Dictionary<uint, PendingProjectionSubtree> _pendingOrphanRemovalByChild = new();
|
||||
public event Action<LiveEntityRecord>? ProjectionRemoved;
|
||||
private readonly Dictionary<RuntimeEntityKey, AttachedChild> _attachedByChild = [];
|
||||
private readonly Dictionary<RuntimeEntityKey, PendingUnparentTransition>
|
||||
_pendingUnparentByChild = [];
|
||||
private readonly Dictionary<RuntimeEntityKey, PendingOrdinaryRemoval>
|
||||
_pendingOrdinaryRemovalByRoot = [];
|
||||
private readonly Dictionary<RuntimeEntityKey, PendingProjectionSubtree>
|
||||
_pendingDetachedRemovalByChild = [];
|
||||
private readonly Dictionary<RuntimeEntityKey, PendingProjectionSubtree>
|
||||
_pendingReparentRemovalByChild = [];
|
||||
private readonly Dictionary<RuntimeEntityKey, PendingProjectionSubtree>
|
||||
_pendingPoseLossRemovalByChild = [];
|
||||
private readonly Dictionary<RuntimeEntityKey, PendingProjectionSubtree>
|
||||
_pendingOrphanRemovalByChild = [];
|
||||
private readonly List<uint> _pendingProjectionChildrenScratch = new();
|
||||
private readonly List<KeyValuePair<uint, PendingOrdinaryRemoval>>
|
||||
private readonly List<KeyValuePair<RuntimeEntityKey, PendingOrdinaryRemoval>>
|
||||
_pendingOrdinaryRemovalScratch = new();
|
||||
private readonly List<KeyValuePair<uint, PendingProjectionSubtree>>
|
||||
private readonly List<KeyValuePair<RuntimeEntityKey, PendingProjectionSubtree>>
|
||||
_pendingProjectionSubtreeScratch = new();
|
||||
private readonly List<KeyValuePair<uint, PendingUnparentTransition>>
|
||||
private readonly List<KeyValuePair<RuntimeEntityKey, PendingUnparentTransition>>
|
||||
_pendingUnparentScratch = new();
|
||||
private readonly AttachmentUpdateOrder<AttachedChild> _updateOrder = new();
|
||||
private readonly Func<AttachedChild, uint?> _parentOfAttached;
|
||||
private readonly Func<uint, bool> _tickAttached;
|
||||
private readonly Func<uint, bool> _reconcileAttached;
|
||||
private readonly AttachmentUpdateOrder<RuntimeEntityKey, AttachedChild>
|
||||
_updateOrder = new();
|
||||
private readonly AttachmentUpdateOrder<uint, uint> _relationRecoveryOrder =
|
||||
new();
|
||||
private readonly Func<AttachedChild, RuntimeEntityKey?> _parentOfAttached;
|
||||
private readonly Func<RuntimeEntityKey, bool> _tickAttached;
|
||||
private readonly Func<RuntimeEntityKey, bool> _reconcileAttached;
|
||||
private int _activePoseCompositionVisits;
|
||||
|
||||
internal int LastFullPoseCompositionVisits { get; private set; }
|
||||
|
|
@ -92,7 +101,7 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
_acceptParent = acceptParent ?? throw new ArgumentNullException(nameof(acceptParent));
|
||||
_withdrawProjection = withdrawProjection
|
||||
?? throw new ArgumentNullException(nameof(withdrawProjection));
|
||||
_parentOfAttached = static child => child.ParentGuid;
|
||||
_parentOfAttached = static child => child.ParentRecord.ProjectionKey;
|
||||
_tickAttached = TickChild;
|
||||
_reconcileAttached = ReconcileChild;
|
||||
|
||||
|
|
@ -258,11 +267,11 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
{
|
||||
if (!_liveEntities.TryGetRecord(childGuid, out LiveEntityRecord record))
|
||||
{
|
||||
_pendingUnparentByChild.Remove(childGuid);
|
||||
Relations.EndChildProjection(childGuid);
|
||||
return ChildUnparentDisposition.NotAttached;
|
||||
}
|
||||
|
||||
RuntimeEntityKey key = RequireProjectionKey(record);
|
||||
var pending = new PendingUnparentTransition(
|
||||
record,
|
||||
record.PositionAuthorityVersion,
|
||||
|
|
@ -271,7 +280,7 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
restoreRootRelation: false,
|
||||
restoreDescendantRelations: true),
|
||||
continuation);
|
||||
return AdvanceUnparentTransition(childGuid, pending);
|
||||
return AdvanceUnparentTransition(key, pending);
|
||||
}
|
||||
|
||||
/// <summary>Recompose every child after the parent's animation tick.</summary>
|
||||
|
|
@ -279,7 +288,7 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
{
|
||||
RetryPendingProjectionTransitions();
|
||||
_activePoseCompositionVisits = 0;
|
||||
IReadOnlyList<uint> failed = _updateOrder.ForEachParentFirst(
|
||||
IReadOnlyList<RuntimeEntityKey> failed = _updateOrder.ForEachParentFirst(
|
||||
_attachedByChild,
|
||||
_parentOfAttached,
|
||||
_tickAttached);
|
||||
|
|
@ -297,7 +306,7 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
{
|
||||
RetryPendingProjectionTransitions();
|
||||
_activePoseCompositionVisits = 0;
|
||||
IReadOnlyList<uint> failed = _updateOrder.ForEachParentFirst(
|
||||
IReadOnlyList<RuntimeEntityKey> failed = _updateOrder.ForEachParentFirst(
|
||||
_attachedByChild,
|
||||
_parentOfAttached,
|
||||
_reconcileAttached);
|
||||
|
|
@ -313,9 +322,9 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
_pendingOrdinaryRemovalScratch);
|
||||
for (int i = 0; i < _pendingOrdinaryRemovalScratch.Count; i++)
|
||||
{
|
||||
(uint rootGuid, PendingOrdinaryRemoval pending) =
|
||||
(RuntimeEntityKey rootKey, PendingOrdinaryRemoval pending) =
|
||||
_pendingOrdinaryRemovalScratch[i];
|
||||
AdvanceOrdinaryRemoval(rootGuid, pending);
|
||||
AdvanceOrdinaryRemoval(rootKey, pending);
|
||||
}
|
||||
|
||||
CopyEntries(
|
||||
|
|
@ -323,9 +332,9 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
_pendingProjectionSubtreeScratch);
|
||||
for (int i = 0; i < _pendingProjectionSubtreeScratch.Count; i++)
|
||||
{
|
||||
(uint childGuid, PendingProjectionSubtree pending) =
|
||||
(RuntimeEntityKey childKey, PendingProjectionSubtree pending) =
|
||||
_pendingProjectionSubtreeScratch[i];
|
||||
AdvanceDetachedRemoval(childGuid, pending);
|
||||
AdvanceDetachedRemoval(childKey, pending);
|
||||
}
|
||||
|
||||
RetryProjectionSubtrees(_pendingReparentRemovalByChild);
|
||||
|
|
@ -335,16 +344,16 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
CopyEntries(_pendingUnparentByChild, _pendingUnparentScratch);
|
||||
for (int i = 0; i < _pendingUnparentScratch.Count; i++)
|
||||
{
|
||||
(uint childGuid, PendingUnparentTransition pending) =
|
||||
(RuntimeEntityKey childKey, PendingUnparentTransition pending) =
|
||||
_pendingUnparentScratch[i];
|
||||
if (!_liveEntities.IsCurrentRecord(pending.Record)
|
||||
|| pending.Record.PositionAuthorityVersion
|
||||
!= pending.PositionAuthorityVersion)
|
||||
{
|
||||
_pendingUnparentByChild.Remove(childGuid);
|
||||
_pendingUnparentByChild.Remove(childKey);
|
||||
continue;
|
||||
}
|
||||
AdvanceUnparentTransition(childGuid, pending);
|
||||
AdvanceUnparentTransition(childKey, pending);
|
||||
}
|
||||
|
||||
Relations.CopyPendingProjectionChildrenTo(_pendingProjectionChildrenScratch);
|
||||
|
|
@ -353,17 +362,17 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
}
|
||||
|
||||
private static void CopyEntries<T>(
|
||||
Dictionary<uint, T> source,
|
||||
List<KeyValuePair<uint, T>> destination)
|
||||
Dictionary<RuntimeEntityKey, T> source,
|
||||
List<KeyValuePair<RuntimeEntityKey, T>> destination)
|
||||
{
|
||||
destination.Clear();
|
||||
foreach (KeyValuePair<uint, T> entry in source)
|
||||
foreach (KeyValuePair<RuntimeEntityKey, T> entry in source)
|
||||
destination.Add(entry);
|
||||
}
|
||||
|
||||
private bool TickChild(uint childGuid)
|
||||
private bool TickChild(RuntimeEntityKey childKey)
|
||||
{
|
||||
if (!_attachedByChild.TryGetValue(childGuid, out AttachedChild? child))
|
||||
if (!_attachedByChild.TryGetValue(childKey, out AttachedChild? child))
|
||||
return false;
|
||||
|
||||
_activePoseCompositionVisits++;
|
||||
|
|
@ -403,15 +412,15 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
return false;
|
||||
}
|
||||
|
||||
private bool ReconcileChild(uint childGuid)
|
||||
private bool ReconcileChild(RuntimeEntityKey childKey)
|
||||
{
|
||||
if (!_attachedByChild.TryGetValue(childGuid, out AttachedChild? child)
|
||||
if (!_attachedByChild.TryGetValue(childKey, out AttachedChild? child)
|
||||
|| !TryResolveExactAttachment(child, out WorldEntity parent))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return ParentPresentationMatches(child, parent) || TickChild(childGuid);
|
||||
return ParentPresentationMatches(child, parent) || TickChild(childKey);
|
||||
}
|
||||
|
||||
private bool ParentPresentationMatches(
|
||||
|
|
@ -445,7 +454,9 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
if (!_liveEntities.TryGetRecord(pending.ParentGuid, out LiveEntityRecord parentRecord)
|
||||
|| parentRecord.WorldEntity is not { } parentEntity
|
||||
|| !parentRecord.IsSpatiallyProjected
|
||||
|| !_liveEntities.TryGetRecord(childGuid, out LiveEntityRecord childRecord)
|
||||
|| !_liveEntities.TryGetCanonical(
|
||||
childGuid,
|
||||
out RuntimeEntityRecord childCanonical)
|
||||
|| !_liveEntities.TryGetSnapshot(pending.ParentGuid, out WorldSession.EntitySpawn parentSpawn)
|
||||
|| !_liveEntities.TryGetSnapshot(childGuid, out WorldSession.EntitySpawn childSpawn))
|
||||
return false;
|
||||
|
|
@ -496,22 +507,25 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
// hydration path sees it. Install its logical effect profile before
|
||||
// MaterializeLiveEntity registers runtime resources, exactly as for a
|
||||
// top-level projection; rebucketing/reattachment must never recreate it.
|
||||
if (!_liveEntities.TryGetEffectProfile(childGuid, out _))
|
||||
var effectProfile = childSpawn.Physics is { } physics
|
||||
? Vfx.EntityEffectProfile.CreateLive(childSetup, physics)
|
||||
: Vfx.EntityEffectProfile.CreateDatStatic(childSetup);
|
||||
if (_liveEntities.TryGetProjection(
|
||||
childCanonical,
|
||||
out LiveEntityRecord? retainedChild)
|
||||
&& !_liveEntities.TryGetEffectProfile(childGuid, out _))
|
||||
{
|
||||
var effectProfile = childSpawn.Physics is { } physics
|
||||
? Vfx.EntityEffectProfile.CreateLive(childSetup, physics)
|
||||
: Vfx.EntityEffectProfile.CreateDatStatic(childSetup);
|
||||
_liveEntities.SetEffectProfile(childGuid, effectProfile);
|
||||
}
|
||||
|
||||
if (!_liveEntities.IsCurrentRecord(parentRecord)
|
||||
|| !_liveEntities.IsCurrentRecord(childRecord)
|
||||
|| !_liveEntities.IsCurrentCanonical(childCanonical)
|
||||
|| !Relations.IsPending(pending, candidateKind))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
WorldEntity? entity = _liveEntities.MaterializeLiveEntity(
|
||||
childGuid,
|
||||
childCanonical,
|
||||
parentCellId,
|
||||
localId =>
|
||||
{
|
||||
|
|
@ -529,8 +543,10 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
created.SetIndexedPartPoses(pose.PartLocal, childPartAvailability);
|
||||
return created;
|
||||
},
|
||||
LiveEntityProjectionKind.Attached);
|
||||
if (entity is null)
|
||||
LiveEntityProjectionKind.Attached,
|
||||
initializeProjection: record => record.EffectProfile = effectProfile,
|
||||
out LiveEntityRecord? childRecord);
|
||||
if (entity is null || childRecord is null)
|
||||
return false;
|
||||
if (!_liveEntities.IsCurrentRecord(parentRecord)
|
||||
|| !_liveEntities.IsCurrentRecord(childRecord)
|
||||
|
|
@ -576,8 +592,9 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
scale,
|
||||
entity);
|
||||
CaptureParentPresentation(attached, parentEntity);
|
||||
_attachedByChild[childGuid] = attached;
|
||||
_pendingUnparentByChild.Remove(childGuid);
|
||||
RuntimeEntityKey childKey = RequireProjectionKey(childRecord);
|
||||
_attachedByChild[childKey] = attached;
|
||||
_pendingUnparentByChild.Remove(childKey);
|
||||
if ((parentRecord.FinalPhysicsState & PhysicsStateFlags.Hidden) != 0)
|
||||
{
|
||||
// A child attached while its parent is already Hidden inherits
|
||||
|
|
@ -809,14 +826,15 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
ParentAttachmentRelation relation,
|
||||
ParentProjectionCandidateKind candidateKind)
|
||||
{
|
||||
if (!_liveEntities.TryGetRecord(
|
||||
if (!_liveEntities.TryGetCanonical(
|
||||
relation.ChildGuid,
|
||||
out LiveEntityRecord childRecord))
|
||||
out RuntimeEntityRecord childCanonical))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
ulong positionAuthorityVersion = childRecord.PositionAuthorityVersion;
|
||||
ulong positionAuthorityVersion =
|
||||
childCanonical.PositionAuthorityVersion;
|
||||
if (candidateKind is ParentProjectionCandidateKind.Staged)
|
||||
{
|
||||
if (!_liveEntities.CommitStagedParent(relation, out _)
|
||||
|
|
@ -833,9 +851,15 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
|
||||
if (!Relations.IsPending(relation, candidateKind)
|
||||
|| !_liveEntities.CommitAcceptedParentCellless(
|
||||
childRecord,
|
||||
positionAuthorityVersion)
|
||||
|| !WithdrawPriorProjection(childRecord))
|
||||
childCanonical,
|
||||
positionAuthorityVersion))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
if (_liveEntities.TryGetProjection(
|
||||
childCanonical,
|
||||
out LiveEntityRecord? childRecord)
|
||||
&& !WithdrawPriorProjection(childRecord))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
|
@ -850,7 +874,7 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
if (relation.ParentGuid == relation.ChildGuid)
|
||||
return ParentProjectionValidationDisposition.Rejected;
|
||||
if (!_liveEntities.TryGetRecord(relation.ParentGuid, out LiveEntityRecord parent)
|
||||
|| !_liveEntities.TryGetRecord(relation.ChildGuid, out _)
|
||||
|| !_liveEntities.TryGetCanonical(relation.ChildGuid, out _)
|
||||
|| parent.WorldEntity is null
|
||||
|| !parent.HasPartArray)
|
||||
{
|
||||
|
|
@ -877,7 +901,7 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
|
||||
private void RetryWaitingDescendants(uint parentGuid)
|
||||
{
|
||||
_updateOrder.RealizeDescendants(
|
||||
_relationRecoveryOrder.RealizeDescendants(
|
||||
parentGuid,
|
||||
Relations.ChildrenWaitingForParent,
|
||||
ResolveAndTryRealize);
|
||||
|
|
@ -985,12 +1009,12 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
}
|
||||
|
||||
private void AdvanceDetachedRemoval(
|
||||
uint childGuid,
|
||||
RuntimeEntityKey childKey,
|
||||
PendingProjectionSubtree pending)
|
||||
{
|
||||
AdvanceProjectionSubtree(
|
||||
_pendingDetachedRemovalByChild,
|
||||
childGuid,
|
||||
childKey,
|
||||
pending);
|
||||
}
|
||||
|
||||
|
|
@ -998,22 +1022,29 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
{
|
||||
if (item.CurrentlyEquippedLocation == EquipMask.None)
|
||||
return;
|
||||
if (_attachedByChild.TryGetValue(item.ObjectId, out AttachedChild? attached)
|
||||
if (!_liveEntities.TryGetRecord(
|
||||
item.ObjectId,
|
||||
out LiveEntityRecord record)
|
||||
|| record.ProjectionKey is not { } key)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (_attachedByChild.TryGetValue(key, out AttachedChild? attached)
|
||||
&& _liveEntities.IsCurrentRecord(attached.ChildRecord)
|
||||
&& attached.ChildRecord.IsSpatiallyProjected)
|
||||
{
|
||||
// A component-stage failure left the exact equipped projection
|
||||
// untouched. The authoritative rollback therefore has nothing to
|
||||
// reconstruct and merely cancels its retained leave-world retry.
|
||||
_pendingDetachedRemovalByChild.Remove(item.ObjectId);
|
||||
_pendingDetachedRemovalByChild.Remove(key);
|
||||
return;
|
||||
}
|
||||
if (_pendingDetachedRemovalByChild.TryGetValue(
|
||||
item.ObjectId,
|
||||
key,
|
||||
out PendingProjectionSubtree pending))
|
||||
{
|
||||
AdvanceDetachedRemoval(item.ObjectId, pending);
|
||||
if (_pendingDetachedRemovalByChild.ContainsKey(item.ObjectId))
|
||||
AdvanceDetachedRemoval(key, pending);
|
||||
if (_pendingDetachedRemovalByChild.ContainsKey(key))
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1032,7 +1063,11 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
|
||||
private bool Remove(uint childGuid)
|
||||
{
|
||||
if (!_attachedByChild.TryGetValue(childGuid, out AttachedChild? child))
|
||||
if (!_liveEntities.TryGetRecord(
|
||||
childGuid,
|
||||
out LiveEntityRecord record)
|
||||
|| record.ProjectionKey is not { } key
|
||||
|| !_attachedByChild.TryGetValue(key, out AttachedChild? child))
|
||||
return false;
|
||||
if (!_liveEntities.IsCurrentRecord(child.ChildRecord))
|
||||
return CommitProjectionRemoval(child);
|
||||
|
|
@ -1062,13 +1097,14 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
|
||||
private bool WithdrawPriorProjection(LiveEntityRecord childRecord)
|
||||
{
|
||||
RuntimeEntityKey key = RequireProjectionKey(childRecord);
|
||||
if (_pendingReparentRemovalByChild.TryGetValue(
|
||||
childRecord.ServerGuid,
|
||||
key,
|
||||
out PendingProjectionSubtree pending))
|
||||
{
|
||||
return AdvanceProjectionSubtree(
|
||||
_pendingReparentRemovalByChild,
|
||||
childRecord.ServerGuid,
|
||||
key,
|
||||
pending);
|
||||
}
|
||||
return BeginProjectionSubtreeWithdrawal(
|
||||
|
|
@ -1079,7 +1115,7 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
}
|
||||
|
||||
private ChildUnparentDisposition AdvanceUnparentTransition(
|
||||
uint childGuid,
|
||||
RuntimeEntityKey childKey,
|
||||
PendingUnparentTransition pending)
|
||||
{
|
||||
ExactProjectionWithdrawalOutcome outcome = AdvanceProjectionSubtree(
|
||||
|
|
@ -1088,7 +1124,7 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
|
||||
if (outcome.Disposition is ExactProjectionWithdrawalDisposition.Pending)
|
||||
{
|
||||
_pendingUnparentByChild[childGuid] = pending with { Subtree = next };
|
||||
_pendingUnparentByChild[childKey] = pending with { Subtree = next };
|
||||
if (outcome.Failure is not null)
|
||||
throw outcome.Failure;
|
||||
return ChildUnparentDisposition.Pending;
|
||||
|
|
@ -1096,22 +1132,22 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
|
||||
if (outcome.Disposition is ExactProjectionWithdrawalDisposition.Superseded)
|
||||
{
|
||||
_pendingUnparentByChild.Remove(childGuid);
|
||||
_pendingUnparentByChild.Remove(childKey);
|
||||
if (outcome.Failure is not null)
|
||||
throw outcome.Failure;
|
||||
return ChildUnparentDisposition.Superseded;
|
||||
}
|
||||
|
||||
PendingUnparentTransition awaitingContinuation = pending with { Subtree = next };
|
||||
_pendingUnparentByChild[childGuid] = awaitingContinuation;
|
||||
_pendingUnparentByChild[childKey] = awaitingContinuation;
|
||||
if (outcome.Failure is not null)
|
||||
throw outcome.Failure;
|
||||
|
||||
Relations.EndChildProjection(childGuid);
|
||||
Relations.EndChildProjection(pending.Record.ServerGuid);
|
||||
try
|
||||
{
|
||||
awaitingContinuation.Continuation?.Invoke();
|
||||
_pendingUnparentByChild.Remove(childGuid);
|
||||
_pendingUnparentByChild.Remove(childKey);
|
||||
return ChildUnparentDisposition.Completed;
|
||||
}
|
||||
catch
|
||||
|
|
@ -1124,9 +1160,9 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
&& awaitingContinuation.Record.ProjectionKind
|
||||
is LiveEntityProjectionKind.World;
|
||||
if (continuationCommitted)
|
||||
_pendingUnparentByChild.Remove(childGuid);
|
||||
_pendingUnparentByChild.Remove(childKey);
|
||||
else
|
||||
_pendingUnparentByChild[childGuid] = awaitingContinuation;
|
||||
_pendingUnparentByChild[childKey] = awaitingContinuation;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
|
@ -1165,20 +1201,23 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
|
||||
private bool CommitProjectionRemoval(AttachedChild child)
|
||||
{
|
||||
if (!_attachedByChild.TryGetValue(child.ChildGuid, out AttachedChild? current)
|
||||
RuntimeEntityKey key = RequireProjectionKey(child.ChildRecord);
|
||||
if (!_attachedByChild.TryGetValue(key, out AttachedChild? current)
|
||||
|| !ReferenceEquals(current, child))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_attachedByChild.Remove(child.ChildGuid);
|
||||
ProjectionRemoved?.Invoke(child.Entity.Id);
|
||||
_attachedByChild.Remove(key);
|
||||
ProjectionRemoved?.Invoke(child.ChildRecord);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void WithdrawForPoseLoss(uint childGuid)
|
||||
private void WithdrawForPoseLoss(RuntimeEntityKey childKey)
|
||||
{
|
||||
if (!_liveEntities.TryGetRecord(childGuid, out LiveEntityRecord record))
|
||||
if (!_liveEntities.TryGetRecord(
|
||||
childKey,
|
||||
out LiveEntityRecord record))
|
||||
return;
|
||||
BeginProjectionSubtreeWithdrawal(
|
||||
_pendingPoseLossRemovalByChild,
|
||||
|
|
@ -1191,25 +1230,28 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
{
|
||||
if (_liveEntities.TryGetRecord(guid, out LiveEntityRecord record))
|
||||
{
|
||||
RuntimeEntityKey key = RequireProjectionKey(record);
|
||||
List<AttachedRemovalCapture> captures = CaptureRecordSubtreeParentFirst(record);
|
||||
AdvanceOrdinaryRemoval(
|
||||
guid,
|
||||
key,
|
||||
new PendingOrdinaryRemoval(record, captures, NextIndex: 0));
|
||||
return;
|
||||
}
|
||||
|
||||
if (_attachedByChild.TryGetValue(guid, out AttachedChild? stale))
|
||||
AttachedChild? stale = _attachedByChild.Values.FirstOrDefault(
|
||||
candidate => candidate.ChildGuid == guid);
|
||||
if (stale is not null)
|
||||
CommitProjectionRemoval(stale);
|
||||
Relations.EndChildProjection(guid);
|
||||
}
|
||||
|
||||
private void AdvanceOrdinaryRemoval(
|
||||
uint rootGuid,
|
||||
RuntimeEntityKey rootKey,
|
||||
PendingOrdinaryRemoval pending)
|
||||
{
|
||||
if (!_liveEntities.IsCurrentRecord(pending.RootRecord))
|
||||
{
|
||||
_pendingOrdinaryRemovalByRoot.Remove(rootGuid);
|
||||
_pendingOrdinaryRemovalByRoot.Remove(rootKey);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1219,7 +1261,7 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
ExactProjectionWithdrawalOutcome outcome = WithdrawCaptured(captured);
|
||||
if (outcome.Disposition is ExactProjectionWithdrawalDisposition.Pending)
|
||||
{
|
||||
_pendingOrdinaryRemovalByRoot[rootGuid] = pending with { NextIndex = i };
|
||||
_pendingOrdinaryRemovalByRoot[rootKey] = pending with { NextIndex = i };
|
||||
if (outcome.Failure is not null)
|
||||
throw outcome.Failure;
|
||||
return;
|
||||
|
|
@ -1233,7 +1275,7 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
|
||||
if (outcome.Failure is not null)
|
||||
{
|
||||
_pendingOrdinaryRemovalByRoot[rootGuid] = pending with
|
||||
_pendingOrdinaryRemovalByRoot[rootKey] = pending with
|
||||
{
|
||||
NextIndex = i + 1,
|
||||
};
|
||||
|
|
@ -1241,23 +1283,24 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
_pendingOrdinaryRemovalByRoot.Remove(rootGuid);
|
||||
Relations.EndChildProjection(rootGuid);
|
||||
_pendingOrdinaryRemovalByRoot.Remove(rootKey);
|
||||
Relations.EndChildProjection(pending.RootRecord.ServerGuid);
|
||||
}
|
||||
|
||||
private void TearDownRecordProjections(LiveEntityRecord record)
|
||||
{
|
||||
if (_pendingUnparentByChild.TryGetValue(record.ServerGuid, out var pending)
|
||||
RuntimeEntityKey key = RequireProjectionKey(record);
|
||||
if (_pendingUnparentByChild.TryGetValue(key, out var pending)
|
||||
&& ReferenceEquals(pending.Record, record))
|
||||
{
|
||||
_pendingUnparentByChild.Remove(record.ServerGuid);
|
||||
_pendingUnparentByChild.Remove(key);
|
||||
}
|
||||
if (_pendingOrdinaryRemovalByRoot.TryGetValue(
|
||||
record.ServerGuid,
|
||||
key,
|
||||
out PendingOrdinaryRemoval ordinary)
|
||||
&& ReferenceEquals(ordinary.RootRecord, record))
|
||||
{
|
||||
_pendingOrdinaryRemovalByRoot.Remove(record.ServerGuid);
|
||||
_pendingOrdinaryRemovalByRoot.Remove(key);
|
||||
}
|
||||
RemovePendingProjectionSubtree(_pendingDetachedRemovalByChild, record);
|
||||
RemovePendingProjectionSubtree(_pendingReparentRemovalByChild, record);
|
||||
|
|
@ -1293,7 +1336,8 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
{
|
||||
var result = new List<AttachedRemovalCapture>();
|
||||
var visited = new HashSet<LiveEntityRecord>(ReferenceEqualityComparer.Instance);
|
||||
if (_attachedByChild.TryGetValue(record.ServerGuid, out AttachedChild? root)
|
||||
if (record.ProjectionKey is { } key
|
||||
&& _attachedByChild.TryGetValue(key, out AttachedChild? root)
|
||||
&& ReferenceEquals(root.ChildRecord, record))
|
||||
{
|
||||
result.Add(Capture(root));
|
||||
|
|
@ -1327,8 +1371,10 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
private ExactProjectionWithdrawalOutcome WithdrawCaptured(
|
||||
AttachedRemovalCapture captured)
|
||||
{
|
||||
RuntimeEntityKey key = RequireProjectionKey(
|
||||
captured.Attached.ChildRecord);
|
||||
if (!_attachedByChild.TryGetValue(
|
||||
captured.Attached.ChildGuid,
|
||||
key,
|
||||
out AttachedChild? current)
|
||||
|| !ReferenceEquals(current, captured.Attached))
|
||||
{
|
||||
|
|
@ -1369,7 +1415,9 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
{
|
||||
if (!visited.Add(record))
|
||||
return;
|
||||
_attachedByChild.TryGetValue(record.ServerGuid, out AttachedChild? attached);
|
||||
AttachedChild? attached = null;
|
||||
if (record.ProjectionKey is { } key)
|
||||
_attachedByChild.TryGetValue(key, out attached);
|
||||
if (attached is not null && !ReferenceEquals(attached.ChildRecord, record))
|
||||
attached = null;
|
||||
if (record.IsSpatiallyProjected || attached is not null)
|
||||
|
|
@ -1396,7 +1444,7 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
}
|
||||
|
||||
private bool BeginProjectionSubtreeWithdrawal(
|
||||
Dictionary<uint, PendingProjectionSubtree> pendingByRoot,
|
||||
Dictionary<RuntimeEntityKey, PendingProjectionSubtree> pendingByRoot,
|
||||
LiveEntityRecord root,
|
||||
bool restoreRootRelation,
|
||||
bool restoreDescendantRelations)
|
||||
|
|
@ -1405,12 +1453,15 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
root,
|
||||
restoreRootRelation,
|
||||
restoreDescendantRelations);
|
||||
return AdvanceProjectionSubtree(pendingByRoot, root.ServerGuid, pending);
|
||||
return AdvanceProjectionSubtree(
|
||||
pendingByRoot,
|
||||
RequireProjectionKey(root),
|
||||
pending);
|
||||
}
|
||||
|
||||
private bool AdvanceProjectionSubtree(
|
||||
Dictionary<uint, PendingProjectionSubtree> pendingByRoot,
|
||||
uint rootGuid,
|
||||
Dictionary<RuntimeEntityKey, PendingProjectionSubtree> pendingByRoot,
|
||||
RuntimeEntityKey rootKey,
|
||||
PendingProjectionSubtree pending)
|
||||
{
|
||||
ExactProjectionWithdrawalOutcome outcome = AdvanceProjectionSubtree(
|
||||
|
|
@ -1419,9 +1470,9 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
bool retry = outcome.Disposition is ExactProjectionWithdrawalDisposition.Pending
|
||||
|| (outcome.Failure is not null && next.NextIndex < next.Captures.Count);
|
||||
if (retry)
|
||||
pendingByRoot[rootGuid] = next;
|
||||
pendingByRoot[rootKey] = next;
|
||||
else
|
||||
pendingByRoot.Remove(rootGuid);
|
||||
pendingByRoot.Remove(rootKey);
|
||||
if (outcome.Failure is not null)
|
||||
throw outcome.Failure;
|
||||
return outcome.Disposition is ExactProjectionWithdrawalDisposition.Completed
|
||||
|
|
@ -1508,27 +1559,29 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
}
|
||||
|
||||
private void RetryProjectionSubtrees(
|
||||
Dictionary<uint, PendingProjectionSubtree> pendingByRoot)
|
||||
Dictionary<RuntimeEntityKey, PendingProjectionSubtree> pendingByRoot)
|
||||
{
|
||||
CopyEntries(pendingByRoot, _pendingProjectionSubtreeScratch);
|
||||
for (int i = 0; i < _pendingProjectionSubtreeScratch.Count; i++)
|
||||
{
|
||||
(uint rootGuid, PendingProjectionSubtree pending) =
|
||||
(RuntimeEntityKey rootKey, PendingProjectionSubtree pending) =
|
||||
_pendingProjectionSubtreeScratch[i];
|
||||
AdvanceProjectionSubtree(pendingByRoot, rootGuid, pending);
|
||||
AdvanceProjectionSubtree(pendingByRoot, rootKey, pending);
|
||||
}
|
||||
}
|
||||
|
||||
private static void RemovePendingProjectionSubtree(
|
||||
Dictionary<uint, PendingProjectionSubtree> pendingByRoot,
|
||||
Dictionary<RuntimeEntityKey, PendingProjectionSubtree> pendingByRoot,
|
||||
LiveEntityRecord record)
|
||||
{
|
||||
uint[] keys = pendingByRoot
|
||||
.Where(pair => ReferenceEquals(pair.Value.RootRecord, record))
|
||||
.Select(pair => pair.Key)
|
||||
.ToArray();
|
||||
for (int i = 0; i < keys.Length; i++)
|
||||
pendingByRoot.Remove(keys[i]);
|
||||
if (record.ProjectionKey is { } key
|
||||
&& pendingByRoot.TryGetValue(
|
||||
key,
|
||||
out PendingProjectionSubtree pending)
|
||||
&& ReferenceEquals(pending.RootRecord, record))
|
||||
{
|
||||
pendingByRoot.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
|
|
@ -1606,6 +1659,13 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
LiveEntityRecord RootRecord,
|
||||
IReadOnlyList<AttachedRemovalCapture> Captures,
|
||||
int NextIndex);
|
||||
|
||||
private static RuntimeEntityKey RequireProjectionKey(
|
||||
LiveEntityRecord record) =>
|
||||
record.ProjectionKey
|
||||
?? throw new InvalidOperationException(
|
||||
$"Live entity 0x{record.ServerGuid:X8}/{record.Generation} " +
|
||||
"has no exact projection key.");
|
||||
}
|
||||
|
||||
public enum ChildUnparentDisposition
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue