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:
Erik 2026-07-25 21:50:58 +02:00
parent e18df84437
commit 420e5eea70
73 changed files with 2939 additions and 1715 deletions

View file

@ -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.");
}