feat(runtime): freeze initial placement inbound admission
This commit is contained in:
parent
9d601817b8
commit
30012361e1
9 changed files with 2506 additions and 323 deletions
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue