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
|
|
@ -1,6 +1,7 @@
|
|||
using AcDream.App.Update;
|
||||
using AcDream.App.World;
|
||||
using AcDream.Core.World;
|
||||
using AcDream.Runtime.Entities;
|
||||
|
||||
namespace AcDream.App.Rendering.Scene;
|
||||
|
||||
|
|
@ -9,7 +10,7 @@ internal interface ILiveRenderProjectionSink : IRenderProjectionSyncPhase
|
|||
bool OnEntityReady(LiveEntityReadyCandidate candidate);
|
||||
void OnProjectionVisibilityChanged(LiveEntityRecord record, bool visible);
|
||||
void OnProjectionPoseReady(uint serverGuid);
|
||||
void OnProjectionRemoved(uint localEntityId);
|
||||
void OnProjectionRemoved(LiveEntityRecord record);
|
||||
void OnResourceUnregister(WorldEntity entity);
|
||||
}
|
||||
|
||||
|
|
@ -25,7 +26,7 @@ internal sealed class LiveRenderProjectionJournal : ILiveRenderProjectionSink
|
|||
private readonly LiveEntityRuntime _runtime;
|
||||
private readonly RenderProjectionJournal _journal;
|
||||
private readonly IRenderTraversalOrderSource _traversalOrder;
|
||||
private readonly Dictionary<uint, TrackedProjection> _byLocalId = [];
|
||||
private readonly Dictionary<RuntimeEntityKey, TrackedProjection> _byKey = [];
|
||||
private readonly List<LiveEntityRecord> _activeRootScratch = [];
|
||||
private readonly List<TrackedProjection> _activeScratch = [];
|
||||
|
||||
|
|
@ -40,7 +41,7 @@ internal sealed class LiveRenderProjectionJournal : ILiveRenderProjectionSink
|
|||
?? throw new ArgumentNullException(nameof(traversalOrder));
|
||||
}
|
||||
|
||||
public int ProjectionCount => _byLocalId.Count;
|
||||
public int ProjectionCount => _byKey.Count;
|
||||
|
||||
public bool OnEntityReady(LiveEntityReadyCandidate candidate)
|
||||
{
|
||||
|
|
@ -62,7 +63,8 @@ internal sealed class LiveRenderProjectionJournal : ILiveRenderProjectionSink
|
|||
ArgumentNullException.ThrowIfNull(record);
|
||||
if (!_runtime.IsCurrentRecord(record)
|
||||
|| record.WorldEntity is not { } entity
|
||||
|| !_byLocalId.ContainsKey(entity.Id))
|
||||
|| record.ProjectionKey is not { } key
|
||||
|| !_byKey.ContainsKey(key))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -87,10 +89,12 @@ internal sealed class LiveRenderProjectionJournal : ILiveRenderProjectionSink
|
|||
Upsert(record, entity, record.IsSpatiallyVisible);
|
||||
}
|
||||
|
||||
public void OnProjectionRemoved(uint localEntityId)
|
||||
public void OnProjectionRemoved(LiveEntityRecord record)
|
||||
{
|
||||
if (_byLocalId.TryGetValue(
|
||||
localEntityId,
|
||||
ArgumentNullException.ThrowIfNull(record);
|
||||
if (record.ProjectionKey is { } key
|
||||
&& _byKey.TryGetValue(
|
||||
key,
|
||||
out TrackedProjection? tracked)
|
||||
&& tracked.Projection.ProjectionClass is
|
||||
RenderProjectionClass.EquippedChild)
|
||||
|
|
@ -116,11 +120,17 @@ internal sealed class LiveRenderProjectionJournal : ILiveRenderProjectionSink
|
|||
public void OnResourceUnregister(WorldEntity entity)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entity);
|
||||
if (_byLocalId.TryGetValue(entity.Id, out TrackedProjection? tracked)
|
||||
&& ReferenceEquals(tracked.Entity, entity))
|
||||
TrackedProjection? tracked = null;
|
||||
foreach (TrackedProjection candidate in _byKey.Values)
|
||||
{
|
||||
Remove(tracked);
|
||||
if (ReferenceEquals(candidate.Entity, entity))
|
||||
{
|
||||
tracked = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (tracked is not null)
|
||||
Remove(tracked);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -153,7 +163,7 @@ internal sealed class LiveRenderProjectionJournal : ILiveRenderProjectionSink
|
|||
// synchronization only refreshes children whose attachment projection
|
||||
// is still retained by this derived scene.
|
||||
_activeScratch.Clear();
|
||||
foreach (TrackedProjection tracked in _byLocalId.Values)
|
||||
foreach (TrackedProjection tracked in _byKey.Values)
|
||||
{
|
||||
if (tracked.Record.IsSpatiallyProjected
|
||||
&& tracked.Record.ProjectionKind is
|
||||
|
|
@ -166,8 +176,9 @@ internal sealed class LiveRenderProjectionJournal : ILiveRenderProjectionSink
|
|||
TrackedProjection tracked = _activeScratch[i];
|
||||
if (!_runtime.IsCurrentRecord(tracked.Record)
|
||||
|| !ReferenceEquals(tracked.Record.WorldEntity, tracked.Entity)
|
||||
|| !_byLocalId.TryGetValue(
|
||||
tracked.Entity.Id,
|
||||
|| tracked.Record.ProjectionKey is not { } key
|
||||
|| !_byKey.TryGetValue(
|
||||
key,
|
||||
out TrackedProjection? current)
|
||||
|| !ReferenceEquals(current, tracked))
|
||||
{
|
||||
|
|
@ -183,7 +194,7 @@ internal sealed class LiveRenderProjectionJournal : ILiveRenderProjectionSink
|
|||
|
||||
public void ResetTracking()
|
||||
{
|
||||
_byLocalId.Clear();
|
||||
_byKey.Clear();
|
||||
_activeRootScratch.Clear();
|
||||
_activeScratch.Clear();
|
||||
}
|
||||
|
|
@ -192,9 +203,11 @@ internal sealed class LiveRenderProjectionJournal : ILiveRenderProjectionSink
|
|||
uint localEntityId,
|
||||
out RenderProjectionRecord record)
|
||||
{
|
||||
if (_byLocalId.TryGetValue(
|
||||
if (_runtime.TryGetRecordByLocalEntityId(
|
||||
localEntityId,
|
||||
out TrackedProjection? tracked))
|
||||
out LiveEntityRecord owner)
|
||||
&& owner.ProjectionKey is { } key
|
||||
&& _byKey.TryGetValue(key, out TrackedProjection? tracked))
|
||||
{
|
||||
record = tracked.Projection;
|
||||
return true;
|
||||
|
|
@ -217,11 +230,12 @@ internal sealed class LiveRenderProjectionJournal : ILiveRenderProjectionSink
|
|||
record,
|
||||
entity,
|
||||
spatiallyVisible);
|
||||
if (!_byLocalId.TryGetValue(entity.Id, out TrackedProjection? tracked))
|
||||
RuntimeEntityKey key = RequireProjectionKey(record);
|
||||
if (!_byKey.TryGetValue(key, out TrackedProjection? tracked))
|
||||
{
|
||||
_journal.Register(in projected);
|
||||
_byLocalId.Add(
|
||||
entity.Id,
|
||||
_byKey.Add(
|
||||
key,
|
||||
new TrackedProjection(record, entity, projected));
|
||||
return;
|
||||
}
|
||||
|
|
@ -234,7 +248,7 @@ internal sealed class LiveRenderProjectionJournal : ILiveRenderProjectionSink
|
|||
!= projected.ProjectionClass)
|
||||
{
|
||||
_journal.Register(in projected);
|
||||
_byLocalId[entity.Id] =
|
||||
_byKey[key] =
|
||||
new TrackedProjection(record, entity, projected);
|
||||
return;
|
||||
}
|
||||
|
|
@ -283,8 +297,9 @@ internal sealed class LiveRenderProjectionJournal : ILiveRenderProjectionSink
|
|||
// Spatial withdrawal is not logical destruction. Retain the last
|
||||
// resident order while the projection is inactive so a portal/rebucket
|
||||
// edge does not manufacture an unrelated ordering mutation.
|
||||
return _byLocalId.TryGetValue(
|
||||
entity.Id,
|
||||
RuntimeEntityKey key = RequireProjectionKey(record);
|
||||
return _byKey.TryGetValue(
|
||||
key,
|
||||
out TrackedProjection? retained)
|
||||
? projected with { SortKey = retained.Projection.SortKey }
|
||||
: projected;
|
||||
|
|
@ -292,7 +307,8 @@ internal sealed class LiveRenderProjectionJournal : ILiveRenderProjectionSink
|
|||
|
||||
private void Remove(TrackedProjection tracked)
|
||||
{
|
||||
if (!_byLocalId.Remove(tracked.Entity.Id))
|
||||
RuntimeEntityKey key = RequireProjectionKey(tracked.Record);
|
||||
if (!_byKey.Remove(key))
|
||||
return;
|
||||
_journal.Unregister(
|
||||
tracked.Projection.Id,
|
||||
|
|
@ -302,6 +318,13 @@ internal sealed class LiveRenderProjectionJournal : ILiveRenderProjectionSink
|
|||
private static uint Canonicalize(uint cellOrLandblockId) =>
|
||||
(cellOrLandblockId & 0xFFFF0000u) | 0xFFFFu;
|
||||
|
||||
private static RuntimeEntityKey RequireProjectionKey(
|
||||
LiveEntityRecord record) =>
|
||||
record.ProjectionKey
|
||||
?? throw new InvalidOperationException(
|
||||
$"Live entity 0x{record.ServerGuid:X8}/{record.Generation} " +
|
||||
"has no exact projection key.");
|
||||
|
||||
private sealed class TrackedProjection(
|
||||
LiveEntityRecord record,
|
||||
WorldEntity entity,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue