refactor(world): extract live appearance and parenting
Move ObjDesc, Parent, Pickup, child-ready, and retained projection recovery into LiveEntityHydrationController while preserving retail timestamp and leave-world semantics. Pin ready publication to exact projection authority and restore attached descendant chains synchronously.
This commit is contained in:
parent
40352d5a7a
commit
fe5514967c
9 changed files with 1098 additions and 199 deletions
|
|
@ -98,9 +98,12 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
lock (_datLock)
|
||||
{
|
||||
if (spawn.ParentGuid is { } parentGuid and not 0
|
||||
&& spawn.ParentLocation is { } parentLocation
|
||||
&& spawn.PlacementId is { } placementId)
|
||||
&& spawn.ParentLocation is { } parentLocation)
|
||||
{
|
||||
// PhysicsDesc::PhysicsDesc / UnPack (0x0051D4D0 /
|
||||
// 0x0051DDD0) default an absent AnimationFrame to zero.
|
||||
// Parent remains a complete relation in that case.
|
||||
uint placementId = spawn.PlacementId ?? 0u;
|
||||
Relations.AcceptCreateObjectRelation(new ParentAttachmentRelation(
|
||||
parentGuid,
|
||||
spawn.Guid,
|
||||
|
|
@ -118,6 +121,51 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retail <c>SmartBox::UpdateVisualDesc @ 0x00451F40</c> replaces an
|
||||
/// attached child's visual description without requiring a world
|
||||
/// Position. Re-realize the last accepted relation from the newest
|
||||
/// canonical snapshot while retaining the logical entity.
|
||||
/// </summary>
|
||||
internal bool TryApplyAttachedAppearance(
|
||||
LiveEntityRecord expectedRecord,
|
||||
ulong expectedObjDescAuthorityVersion)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(expectedRecord);
|
||||
lock (_datLock)
|
||||
{
|
||||
if (!_liveEntities.IsCurrentObjDescAuthority(
|
||||
expectedRecord,
|
||||
expectedObjDescAuthorityVersion)
|
||||
|| expectedRecord.ProjectionKind is not
|
||||
LiveEntityProjectionKind.Attached
|
||||
|| !expectedRecord.IsSpatiallyProjected
|
||||
|| expectedRecord.WorldEntity is not { } expectedEntity
|
||||
|| !Relations.RestoreLastAccepted(expectedRecord.ServerGuid))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool projected = ResolveAndTryRealize(expectedRecord.ServerGuid);
|
||||
if (projected)
|
||||
{
|
||||
// WithdrawPriorProjection removes the complete attached
|
||||
// subtree and retains each descendant relation for recovery.
|
||||
// The ObjDesc transaction is not published until that same
|
||||
// subtree is synchronously whole again.
|
||||
RetryWaitingDescendants(expectedRecord.ServerGuid);
|
||||
}
|
||||
return projected
|
||||
&& _liveEntities.IsCurrentObjDescAuthority(
|
||||
expectedRecord,
|
||||
expectedObjDescAuthorityVersion)
|
||||
&& expectedRecord.ProjectionKind is
|
||||
LiveEntityProjectionKind.Attached
|
||||
&& expectedRecord.IsSpatiallyProjected
|
||||
&& ReferenceEquals(expectedRecord.WorldEntity, expectedEntity);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Completes attachment projection after a root world entity has been
|
||||
/// registered. Relation acceptance intentionally happens before root
|
||||
|
|
@ -448,46 +496,31 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
// CPhysicsObj::set_hidden (0x00514C60).
|
||||
_liveEntities.SetAttachedChildNoDraw(childGuid, noDraw: true);
|
||||
}
|
||||
ulong readyCreateIntegrationVersion = childRecord.CreateIntegrationVersion;
|
||||
PublishChildPose(entity, parentWorld, parentEntity.ParentCellId, pose);
|
||||
Console.WriteLine(
|
||||
$"equipment: attached child=0x{childGuid:X8} parent=0x{pending.ParentGuid:X8} " +
|
||||
$"location={parentLocation} placement={placement}");
|
||||
Relations.MarkProjected(pending, candidateKind);
|
||||
LiveEntityReadyCandidate readyCandidate =
|
||||
LiveEntityReadyCandidate.Capture(childRecord);
|
||||
ProjectionPoseReady?.Invoke(childGuid);
|
||||
return PublishEntityReadyExact(
|
||||
_liveEntities,
|
||||
childRecord,
|
||||
readyCreateIntegrationVersion,
|
||||
entity,
|
||||
readyCandidate,
|
||||
EntityReady);
|
||||
}
|
||||
|
||||
internal static bool PublishEntityReadyExact(
|
||||
LiveEntityRuntime runtime,
|
||||
LiveEntityRecord expectedRecord,
|
||||
ulong expectedCreateIntegrationVersion,
|
||||
WorldEntity expectedEntity,
|
||||
LiveEntityReadyCandidate candidate,
|
||||
Action<LiveEntityReadyCandidate>? publish)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(runtime);
|
||||
ArgumentNullException.ThrowIfNull(expectedRecord);
|
||||
ArgumentNullException.ThrowIfNull(expectedEntity);
|
||||
if (!runtime.IsCurrentCreateIntegration(
|
||||
expectedRecord,
|
||||
expectedCreateIntegrationVersion)
|
||||
|| !ReferenceEquals(expectedRecord.WorldEntity, expectedEntity))
|
||||
{
|
||||
if (!candidate.IsCurrent(runtime))
|
||||
return false;
|
||||
}
|
||||
|
||||
publish?.Invoke(new LiveEntityReadyCandidate(
|
||||
expectedRecord,
|
||||
expectedCreateIntegrationVersion));
|
||||
return runtime.IsCurrentCreateIntegration(
|
||||
expectedRecord,
|
||||
expectedCreateIntegrationVersion)
|
||||
&& ReferenceEquals(expectedRecord.WorldEntity, expectedEntity);
|
||||
publish?.Invoke(candidate);
|
||||
return candidate.IsCurrent(runtime);
|
||||
}
|
||||
|
||||
private void PublishChildPose(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue