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
|
|
@ -3,6 +3,7 @@ using AcDream.App.Rendering;
|
|||
using AcDream.App.World;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.World;
|
||||
using AcDream.Runtime.Entities;
|
||||
|
||||
namespace AcDream.App.Physics;
|
||||
|
||||
|
|
@ -32,7 +33,7 @@ internal sealed class RemoteTeleportController : IDisposable
|
|||
private readonly Action<uint, ushort, bool> _completeAuthoritativePlacement;
|
||||
private readonly Action<uint, ushort> _beginAuthoritativePlacement;
|
||||
private readonly PlacementResolver _resolvePlacement;
|
||||
private readonly Dictionary<uint, PendingPlacement> _pending = new();
|
||||
private readonly Dictionary<RuntimeEntityKey, PendingPlacement> _pending = new();
|
||||
|
||||
internal RemoteTeleportController(
|
||||
PhysicsEngine physics,
|
||||
|
|
@ -178,9 +179,9 @@ internal sealed class RemoteTeleportController : IDisposable
|
|||
bool wasInContact = liveRecord.PhysicsBody.InContact;
|
||||
bool wasOnWalkable = liveRecord.PhysicsBody.OnWalkable;
|
||||
RollbackPlacement rollback = CaptureRollback(remote, liveRecord.PhysicsBody);
|
||||
if (_pending.TryGetValue(entity.ServerGuid, out PendingPlacement prior)
|
||||
&& ReferenceEquals(prior.Record, liveRecord)
|
||||
&& prior.Generation == generation)
|
||||
RuntimeEntityKey key = RequireProjectionKey(liveRecord);
|
||||
if (_pending.TryGetValue(key, out PendingPlacement prior)
|
||||
&& ReferenceEquals(prior.Record, liveRecord))
|
||||
{
|
||||
wasInContact = prior.WasInContact;
|
||||
wasOnWalkable = prior.WasOnWalkable;
|
||||
|
|
@ -219,7 +220,7 @@ internal sealed class RemoteTeleportController : IDisposable
|
|||
return Superseded(request);
|
||||
if (!placement.Ok)
|
||||
{
|
||||
_pending.Remove(entity.ServerGuid);
|
||||
_pending.Remove(key);
|
||||
if (!RollBackDeferredPlacement(request))
|
||||
return Superseded(request);
|
||||
return new Result(
|
||||
|
|
@ -230,24 +231,18 @@ internal sealed class RemoteTeleportController : IDisposable
|
|||
request.Body.Orientation);
|
||||
}
|
||||
|
||||
_pending.Remove(entity.ServerGuid);
|
||||
_pending.Remove(key);
|
||||
return CommitResolved(request, placement);
|
||||
}
|
||||
|
||||
internal void Forget(uint serverGuid)
|
||||
{
|
||||
_pending.Remove(serverGuid);
|
||||
}
|
||||
|
||||
internal void Forget(LiveEntityRecord record)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(record);
|
||||
if (_pending.TryGetValue(record.ServerGuid, out PendingPlacement pending)
|
||||
&& pending.Generation == record.Generation
|
||||
&& (record.WorldEntity is null
|
||||
|| ReferenceEquals(pending.Entity, record.WorldEntity)))
|
||||
if (record.ProjectionKey is { } key
|
||||
&& _pending.TryGetValue(key, out PendingPlacement pending)
|
||||
&& ReferenceEquals(pending.Record, record))
|
||||
{
|
||||
_pending.Remove(record.ServerGuid);
|
||||
_pending.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -256,7 +251,10 @@ internal sealed class RemoteTeleportController : IDisposable
|
|||
_pending.Clear();
|
||||
}
|
||||
|
||||
internal bool HasPending(uint serverGuid) => _pending.ContainsKey(serverGuid);
|
||||
internal bool HasPending(uint serverGuid) =>
|
||||
_liveEntities.TryGetRecord(serverGuid, out LiveEntityRecord record)
|
||||
&& record.ProjectionKey is { } key
|
||||
&& _pending.ContainsKey(key);
|
||||
internal int PendingPlacementCount => _pending.Count;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -404,7 +402,7 @@ internal sealed class RemoteTeleportController : IDisposable
|
|||
request.Entity.Rotation = request.RequestedOrientation;
|
||||
if (!IsCurrent(request))
|
||||
return false;
|
||||
_pending[request.Entity.ServerGuid] = request;
|
||||
_pending[RequireProjectionKey(request.Record)] = request;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -413,14 +411,15 @@ internal sealed class RemoteTeleportController : IDisposable
|
|||
if (!visible)
|
||||
return;
|
||||
|
||||
if (_pending.TryGetValue(record.ServerGuid, out PendingPlacement pending))
|
||||
RuntimeEntityKey key = RequireProjectionKey(record);
|
||||
if (_pending.TryGetValue(key, out PendingPlacement pending))
|
||||
{
|
||||
if (record.WorldEntity is null
|
||||
|| !ReferenceEquals(record, pending.Record)
|
||||
|| !ReferenceEquals(record.WorldEntity, pending.Entity)
|
||||
|| record.Generation != pending.Generation)
|
||||
{
|
||||
_pending.Remove(record.ServerGuid);
|
||||
_pending.Remove(key);
|
||||
return;
|
||||
}
|
||||
if (record.RemoteMotionRuntime is not ILiveEntityRemotePlacementRuntime currentRemote
|
||||
|
|
@ -428,7 +427,7 @@ internal sealed class RemoteTeleportController : IDisposable
|
|||
|| !ReferenceEquals(record.PhysicsBody, pending.Body)
|
||||
|| !ReferenceEquals(currentRemote.Body, record.PhysicsBody))
|
||||
{
|
||||
_pending.Remove(record.ServerGuid);
|
||||
_pending.Remove(key);
|
||||
if (record.RemoteMotionRuntime is not null
|
||||
&& (record.PhysicsBody is null
|
||||
|| !ReferenceEquals(record.RemoteMotionRuntime.Body, record.PhysicsBody)))
|
||||
|
|
@ -441,7 +440,7 @@ internal sealed class RemoteTeleportController : IDisposable
|
|||
if (!ReferenceEquals(currentRemote, pending.Remote))
|
||||
{
|
||||
pending = pending with { Remote = currentRemote };
|
||||
_pending[record.ServerGuid] = pending;
|
||||
_pending[key] = pending;
|
||||
}
|
||||
if (!_liveEntities.IsCurrentPositionAuthority(
|
||||
pending.Record,
|
||||
|
|
@ -454,7 +453,7 @@ internal sealed class RemoteTeleportController : IDisposable
|
|||
return;
|
||||
}
|
||||
|
||||
_pending.Remove(record.ServerGuid);
|
||||
_pending.Remove(key);
|
||||
ResolveResult placement = Resolve(pending);
|
||||
if (!IsCurrent(pending))
|
||||
return;
|
||||
|
|
@ -588,4 +587,11 @@ internal sealed class RemoteTeleportController : IDisposable
|
|||
|
||||
private static bool IsPlayerGuid(uint guid) =>
|
||||
(guid & 0xFF000000u) == 0x50000000u;
|
||||
|
||||
private static RuntimeEntityKey RequireProjectionKey(
|
||||
LiveEntityRecord record) =>
|
||||
record.ProjectionKey
|
||||
?? throw new InvalidOperationException(
|
||||
$"Live entity 0x{record.ServerGuid:X8}/{record.Generation} " +
|
||||
"has no exact projection key.");
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue