feat(runtime): freeze initial placement inbound admission

This commit is contained in:
Erik 2026-08-01 21:00:03 +02:00
parent 9d601817b8
commit 30012361e1
9 changed files with 2506 additions and 323 deletions

View file

@ -1,3 +1,4 @@
using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
@ -17,12 +18,119 @@ public sealed class ParentAttachmentState
private readonly Dictionary<uint, ParentAttachmentRelation> _recoveryByChild = new();
private readonly Dictionary<uint, ParentAttachmentRelation> _lastAcceptedByChild = new();
private readonly Dictionary<ParentIncarnation, List<uint>> _committedChildrenByParent = new();
private readonly Dictionary<uint, Queue<DeferredParentCreate>>
_deferredCreatesByParent = [];
private ulong _nextDeferredCreateAdmissionId;
public int UnresolvedRelationCount =>
_unresolvedByChild.Values.Sum(queue => queue.Count);
public int StagedRelationCount => _stagedByChild.Count;
public int RecoveryRelationCount => _recoveryByChild.Count;
public int CommittedRelationCount => _lastAcceptedByChild.Count;
internal int DeferredCreateCount =>
_deferredCreatesByParent.Values.Sum(queue => queue.Count);
/// <summary>
/// Retains the complete unaccepted CreateObject packet when its nonzero
/// parent is not addressable. Retail queues the raw blob before object
/// lookup and before any child timestamp is consumed; storing a decoded
/// relation after AcceptCreate would partially admit the child.
/// </summary>
internal void EnqueueDeferredCreate(
WorldSession.EntitySpawn spawn,
bool isLocalPlayer)
{
uint parentGuid = spawn.ParentGuid
?? spawn.Physics?.Parent?.Guid
?? 0u;
if (spawn.Guid == 0u || parentGuid == 0u)
{
throw new ArgumentException(
"A deferred parent CreateObject requires nonzero child and parent GUIDs.",
nameof(spawn));
}
if (_nextDeferredCreateAdmissionId == ulong.MaxValue)
{
throw new InvalidOperationException(
"The deferred parent CreateObject admission sequence is exhausted.");
}
if (!_deferredCreatesByParent.TryGetValue(
parentGuid,
out Queue<DeferredParentCreate>? queue))
{
queue = new Queue<DeferredParentCreate>();
_deferredCreatesByParent.Add(parentGuid, queue);
}
ulong admissionId = _nextDeferredCreateAdmissionId + 1UL;
queue.Enqueue(new DeferredParentCreate(
admissionId,
RuntimeInitialCreateAdmissionFreezer.Freeze(spawn),
isLocalPlayer));
_nextDeferredCreateAdmissionId = admissionId;
}
internal bool TryPeekDeferredCreate(
uint parentGuid,
out DeferredParentCreate deferred)
{
if (!_deferredCreatesByParent.TryGetValue(
parentGuid,
out Queue<DeferredParentCreate>? queue)
|| !queue.TryPeek(out deferred))
{
deferred = default;
return false;
}
return true;
}
internal bool ConsumeDeferredCreate(
uint parentGuid,
in DeferredParentCreate expected)
{
if (!_deferredCreatesByParent.TryGetValue(
parentGuid,
out Queue<DeferredParentCreate>? queue)
|| !queue.TryPeek(out DeferredParentCreate current)
|| current != expected)
{
return false;
}
_ = queue.Dequeue();
if (queue.Count == 0)
_deferredCreatesByParent.Remove(parentGuid);
return true;
}
internal bool ContainsDeferredCreate(
uint childGuid,
ushort instanceSequence)
{
foreach (Queue<DeferredParentCreate> queue
in _deferredCreatesByParent.Values)
{
if (queue.Any(candidate =>
candidate.Spawn.Guid == childGuid
&& candidate.Spawn.InstanceSequence == instanceSequence))
{
return true;
}
}
return false;
}
/// <summary>
/// Cancels only the raw, still-unaccepted child generation addressed by a
/// terminal packet. Instance zero is a normal retail timestamp and is not
/// treated as an empty sentinel.
/// </summary>
internal void CancelDeferredChildGeneration(
uint childGuid,
ushort terminalInstanceSequence) => FilterDeferredCreates(
candidate => candidate.Spawn.Guid != childGuid
|| PhysicsTimestampGate.IsNewer(
terminalInstanceSequence,
candidate.Spawn.InstanceSequence));
public void AcceptCreateObjectRelation(ParentAttachmentRelation relation)
{
@ -270,6 +378,7 @@ public sealed class ParentAttachmentState
public void RemoveObject(uint guid)
{
RemoveDeferredChildCreates(guid);
_stagedByChild.Remove(guid);
_recoveryByChild.Remove(guid);
RemoveCommittedChild(guid);
@ -298,6 +407,12 @@ public sealed class ParentAttachmentState
/// </summary>
public void EndGeneration(uint guid, ushort replacementGeneration)
{
FilterDeferredCreates(candidate =>
candidate.Spawn.Guid != guid
|| candidate.Spawn.InstanceSequence == replacementGeneration
|| PhysicsTimestampGate.IsNewer(
replacementGeneration,
candidate.Spawn.InstanceSequence));
FilterChildCandidates(
guid,
relation => relation.WaitOwner is ParentAttachmentWaitOwner.Parent);
@ -322,6 +437,7 @@ public sealed class ParentAttachmentState
/// </summary>
public void DeleteGeneration(uint guid, ushort deletedGeneration)
{
CancelDeferredChildGeneration(guid, deletedGeneration);
FilterChildCandidates(
guid,
relation => relation.WaitOwner is ParentAttachmentWaitOwner.Parent);
@ -351,6 +467,7 @@ public sealed class ParentAttachmentState
public void RemoveChild(uint childGuid)
{
RemoveDeferredChildCreates(childGuid);
_stagedByChild.Remove(childGuid);
_recoveryByChild.Remove(childGuid);
RemoveCommittedChild(childGuid);
@ -359,6 +476,7 @@ public sealed class ParentAttachmentState
public void Clear()
{
_deferredCreatesByParent.Clear();
_unresolvedByChild.Clear();
_stagedByChild.Clear();
_recoveryByChild.Clear();
@ -368,6 +486,26 @@ public sealed class ParentAttachmentState
_committedChildrenByParent.Clear();
}
private void RemoveDeferredChildCreates(uint childGuid)
=> FilterDeferredCreates(
candidate => candidate.Spawn.Guid != childGuid);
private void FilterDeferredCreates(
Func<DeferredParentCreate, bool> retain)
{
uint[] parents = _deferredCreatesByParent.Keys.ToArray();
for (int index = 0; index < parents.Length; index++)
{
uint parentGuid = parents[index];
Queue<DeferredParentCreate> retained = new(
_deferredCreatesByParent[parentGuid].Where(retain));
if (retained.Count == 0)
_deferredCreatesByParent.Remove(parentGuid);
else
_deferredCreatesByParent[parentGuid] = retained;
}
}
private void RemoveCommittedChild(uint childGuid)
{
if (!_lastAcceptedByChild.Remove(
@ -462,6 +600,15 @@ public sealed class ParentAttachmentState
ushort InstanceSequence);
}
internal readonly record struct DeferredParentCreate(
ulong AdmissionId,
WorldSession.EntitySpawn Spawn,
bool IsLocalPlayer)
{
internal bool IsValid => AdmissionId != 0UL
&& Spawn.Guid != 0u;
}
public readonly record struct ParentAttachmentRelation(
uint ParentGuid,
uint ChildGuid,