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

@ -1,6 +1,7 @@
using AcDream.App.World;
using AcDream.Core.Lighting;
using AcDream.Core.Physics;
using AcDream.Runtime.Entities;
using DatReaderWriter.DBObjs;
namespace AcDream.App.Rendering.Vfx;
@ -22,8 +23,8 @@ public sealed class LiveEntityLightController : IDisposable
private readonly EntityEffectPoseRegistry _poses;
private readonly LightingHookSink _lighting;
private readonly Func<uint, Setup?> _loadSetup;
private readonly Dictionary<uint, uint> _serverGuidByOwner = new();
private readonly HashSet<uint> _presentOwners = new();
private readonly HashSet<RuntimeEntityKey> _trackedOwners = [];
private readonly HashSet<RuntimeEntityKey> _presentOwners = [];
public LiveEntityLightController(
LiveEntityRuntime liveEntities,
@ -49,8 +50,9 @@ public sealed class LiveEntityLightController : IDisposable
return false;
}
_serverGuidByOwner[entity.Id] = serverGuid;
_presentOwners.Add(entity.Id);
RuntimeEntityKey key = RequireProjectionKey(record);
_trackedOwners.Add(key);
_presentOwners.Add(key);
_lighting.UnregisterOwner(entity.Id, forgetState: false);
_lighting.InitializeOwnerLighting(
entity.Id,
@ -87,7 +89,13 @@ public sealed class LiveEntityLightController : IDisposable
/// <summary>Withdraw cell-scoped presentation but retain logical light state.</summary>
public void Unregister(uint ownerLocalId)
{
_presentOwners.Remove(ownerLocalId);
if (_liveEntities.TryGetRecordByLocalEntityId(
ownerLocalId,
out LiveEntityRecord record)
&& record.ProjectionKey is { } key)
{
_presentOwners.Remove(key);
}
_lighting.UnregisterOwner(ownerLocalId, forgetState: false);
}
@ -105,16 +113,20 @@ public sealed class LiveEntityLightController : IDisposable
}
/// <summary>End logical ownership after leave-world presentation teardown.</summary>
public void Forget(uint ownerLocalId)
public void Forget(LiveEntityRecord record)
{
_presentOwners.Remove(ownerLocalId);
_serverGuidByOwner.Remove(ownerLocalId);
_lighting.UnregisterOwner(ownerLocalId);
ArgumentNullException.ThrowIfNull(record);
if (record.ProjectionKey is not { } key)
return;
_presentOwners.Remove(key);
_trackedOwners.Remove(key);
_lighting.UnregisterOwner(key.LocalEntityId);
}
public void Refresh() => _lighting.RefreshAttachedLights();
internal int TrackedOwnerCount => _serverGuidByOwner.Count;
internal int TrackedOwnerCount => _trackedOwners.Count;
internal int PresentedOwnerCount => _presentOwners.Count;
/// <summary>
@ -127,7 +139,8 @@ public sealed class LiveEntityLightController : IDisposable
if (!_liveEntities.TryGetRecord(serverGuid, out LiveEntityRecord record)
|| record.ProjectionKind is not LiveEntityProjectionKind.Attached
|| record.WorldEntity is not { } entity
|| _presentOwners.Contains(entity.Id))
|| record.ProjectionKey is not { } key
|| _presentOwners.Contains(key))
{
return;
}
@ -136,8 +149,11 @@ public sealed class LiveEntityLightController : IDisposable
private void OnOwnerLightingChanged(uint ownerLocalId, bool enabled)
{
if (!_presentOwners.Contains(ownerLocalId)
|| !_serverGuidByOwner.TryGetValue(ownerLocalId, out uint serverGuid))
if (!_liveEntities.TryGetRecordByLocalEntityId(
ownerLocalId,
out LiveEntityRecord record)
|| record.ProjectionKey is not { } key
|| !_presentOwners.Contains(key))
{
return;
}
@ -148,15 +164,17 @@ public sealed class LiveEntityLightController : IDisposable
return;
}
Register(serverGuid);
Register(record.ServerGuid);
}
public void Dispose()
{
_lighting.OwnerLightingChanged -= OnOwnerLightingChanged;
_liveEntities.ProjectionVisibilityChanged -= OnProjectionVisibilityChanged;
foreach (uint ownerId in _serverGuidByOwner.Keys.ToArray())
Forget(ownerId);
foreach (RuntimeEntityKey key in _trackedOwners)
_lighting.UnregisterOwner(key.LocalEntityId);
_trackedOwners.Clear();
_presentOwners.Clear();
}
private void OnProjectionVisibilityChanged(LiveEntityRecord record, bool visible)
@ -176,4 +194,11 @@ public sealed class LiveEntityLightController : IDisposable
else
Unregister(entity.Id);
}
private static RuntimeEntityKey RequireProjectionKey(
LiveEntityRecord record) =>
record.ProjectionKey
?? throw new InvalidOperationException(
$"Live entity 0x{record.ServerGuid:X8}/{record.Generation} " +
"has no exact projection key.");
}