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,17 +1,25 @@
|
|||
using AcDream.Core.Net.Messages;
|
||||
using AcDream.App.Input;
|
||||
using AcDream.Runtime.Entities;
|
||||
|
||||
namespace AcDream.App.World;
|
||||
|
||||
internal readonly record struct LiveEntityLivenessSample(
|
||||
RuntimeEntityKey Key,
|
||||
uint ServerGuid,
|
||||
ushort Generation,
|
||||
bool IsConservativelyVisible,
|
||||
bool HasNonWorldRetention);
|
||||
|
||||
internal readonly record struct LiveEntityPruneCandidate(
|
||||
RuntimeEntityKey Key,
|
||||
uint ServerGuid,
|
||||
ushort Generation);
|
||||
ushort Generation)
|
||||
{
|
||||
public LiveEntityPruneCandidate(RuntimeEntityKey key, uint serverGuid)
|
||||
: this(key, serverGuid, key.Incarnation)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deadline state for ACE's retained KnownObjects behavior. Retail
|
||||
|
|
@ -23,7 +31,10 @@ internal sealed class LiveEntityLivenessTracker
|
|||
{
|
||||
internal const double DestructionTimeoutSeconds = 25.0;
|
||||
|
||||
private readonly Dictionary<uint, Deadline> _deadlines = new();
|
||||
private readonly Dictionary<RuntimeEntityKey, double> _deadlines = [];
|
||||
private readonly HashSet<RuntimeEntityKey> _present = [];
|
||||
private readonly List<RuntimeEntityKey> _stale = [];
|
||||
private readonly List<LiveEntityPruneCandidate> _due = [];
|
||||
|
||||
internal int DeadlineCount => _deadlines.Count;
|
||||
|
||||
|
|
@ -31,46 +42,44 @@ internal sealed class LiveEntityLivenessTracker
|
|||
double now,
|
||||
IReadOnlyList<LiveEntityLivenessSample> samples)
|
||||
{
|
||||
var present = new HashSet<uint>(samples.Count);
|
||||
var due = new List<LiveEntityPruneCandidate>();
|
||||
_present.Clear();
|
||||
_due.Clear();
|
||||
for (int i = 0; i < samples.Count; i++)
|
||||
{
|
||||
LiveEntityLivenessSample sample = samples[i];
|
||||
present.Add(sample.ServerGuid);
|
||||
_present.Add(sample.Key);
|
||||
if (sample.IsConservativelyVisible || sample.HasNonWorldRetention)
|
||||
{
|
||||
_deadlines.Remove(sample.ServerGuid);
|
||||
_deadlines.Remove(sample.Key);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_deadlines.TryGetValue(sample.ServerGuid, out Deadline deadline)
|
||||
|| deadline.Generation != sample.Generation)
|
||||
if (!_deadlines.TryGetValue(sample.Key, out double expiresAt))
|
||||
{
|
||||
_deadlines[sample.ServerGuid] = new Deadline(
|
||||
sample.Generation,
|
||||
now + DestructionTimeoutSeconds);
|
||||
_deadlines[sample.Key] = now + DestructionTimeoutSeconds;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (deadline.ExpiresAt > now)
|
||||
if (expiresAt > now)
|
||||
continue;
|
||||
|
||||
due.Add(new LiveEntityPruneCandidate(sample.ServerGuid, sample.Generation));
|
||||
_deadlines.Remove(sample.ServerGuid);
|
||||
_due.Add(new LiveEntityPruneCandidate(sample.Key, sample.ServerGuid));
|
||||
_deadlines.Remove(sample.Key);
|
||||
}
|
||||
|
||||
uint[] stale = _deadlines.Keys
|
||||
.Where(guid => !present.Contains(guid))
|
||||
.ToArray();
|
||||
for (int i = 0; i < stale.Length; i++)
|
||||
_deadlines.Remove(stale[i]);
|
||||
_stale.Clear();
|
||||
foreach (RuntimeEntityKey key in _deadlines.Keys)
|
||||
{
|
||||
if (!_present.Contains(key))
|
||||
_stale.Add(key);
|
||||
}
|
||||
for (int i = 0; i < _stale.Count; i++)
|
||||
_deadlines.Remove(_stale[i]);
|
||||
|
||||
return due;
|
||||
return _due;
|
||||
}
|
||||
|
||||
internal void Clear() => _deadlines.Clear();
|
||||
|
||||
private readonly record struct Deadline(ushort Generation, double ExpiresAt);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -134,8 +143,11 @@ internal sealed class LiveEntityLivenessController
|
|||
|| NonZero(record.Snapshot.WielderId)
|
||||
|| NonZero(record.Snapshot.ParentGuid);
|
||||
_samples.Add(new LiveEntityLivenessSample(
|
||||
record.ProjectionKey
|
||||
?? throw new InvalidOperationException(
|
||||
$"Materialized liveness owner 0x{record.ServerGuid:X8}/" +
|
||||
$"{record.Generation} has no exact projection key."),
|
||||
record.ServerGuid,
|
||||
record.Generation,
|
||||
record.IsSpatiallyVisible
|
||||
|| IsWithinConservativeVisibility(playerPosition, position),
|
||||
retained));
|
||||
|
|
@ -145,8 +157,8 @@ internal sealed class LiveEntityLivenessController
|
|||
for (int i = 0; i < due.Count; i++)
|
||||
{
|
||||
LiveEntityPruneCandidate candidate = due[i];
|
||||
if (_runtime.TryGetRecord(candidate.ServerGuid, out LiveEntityRecord current)
|
||||
&& current.Generation == candidate.Generation)
|
||||
if (_runtime.TryGetRecord(candidate.Key, out LiveEntityRecord current)
|
||||
&& current.ServerGuid == candidate.ServerGuid)
|
||||
{
|
||||
_prune.Prune(candidate);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue