refactor(world): separate live lifetime from spatial buckets
Introduce LiveEntityRuntime as the canonical owner of each accepted server-object incarnation, stable local identity, timestamped state, parent relations, runtime components, and exactly-once teardown. Split logical registration from rebucketing so pending landblocks, equipment attachment, pickup re-entry, and GUID replacement reuse the same entity and effect owners. Keep canonical materialized and visible target/radar views distinct, preserve retail leave_world versus exit_world semantics, gate root simulation while cell-less, and track transitional pre-Create F754 owners through delete and session reset. Remove stale-spawn rehydration and make GpuWorldState spatial-only for live objects. Add lifecycle, generation, pending, unload, attachment, event-publication, local-ID, rollback, and effect-cleanup coverage; update architecture, milestones, memory, and the divergence register. Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
parent
8a5d77f7f4
commit
8dd996053d
24 changed files with 2449 additions and 631 deletions
|
|
@ -1,288 +0,0 @@
|
|||
using AcDream.Core.Net.Messages;
|
||||
using AcDream.Core.Physics;
|
||||
|
||||
namespace AcDream.App.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// Update-thread state machine for parent relations that may arrive before
|
||||
/// either CreateObject. Unaccepted wire events remain in arrival order;
|
||||
/// rollback history contains only relations that passed the canonical retail
|
||||
/// timestamp gate.
|
||||
/// </summary>
|
||||
public sealed class ParentAttachmentState
|
||||
{
|
||||
private readonly Dictionary<uint, Queue<ParentAttachmentRelation>> _unresolvedByChild = new();
|
||||
private readonly Dictionary<uint, ParentAttachmentRelation> _projectionByChild = new();
|
||||
private readonly Dictionary<uint, ParentAttachmentRelation> _lastAcceptedByChild = new();
|
||||
|
||||
public void AcceptCreateObjectRelation(ParentAttachmentRelation relation)
|
||||
{
|
||||
_projectionByChild[relation.ChildGuid] = relation;
|
||||
_lastAcceptedByChild[relation.ChildGuid] = relation;
|
||||
}
|
||||
|
||||
public void Enqueue(ParentEvent.Parsed update)
|
||||
{
|
||||
if (!_unresolvedByChild.TryGetValue(update.ChildGuid, out Queue<ParentAttachmentRelation>? queue))
|
||||
{
|
||||
queue = new Queue<ParentAttachmentRelation>();
|
||||
_unresolvedByChild.Add(update.ChildGuid, queue);
|
||||
}
|
||||
|
||||
queue.Enqueue(new ParentAttachmentRelation(
|
||||
update.ParentGuid,
|
||||
update.ChildGuid,
|
||||
update.ParentLocation,
|
||||
update.PlacementId,
|
||||
update.ParentInstanceSequence,
|
||||
update.ChildPositionSequence));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves every now-addressable relation for one child in wire order.
|
||||
/// A future parent generation remains queued; an older generation or a
|
||||
/// rejected child POSITION_TS is discarded. Every accepted relation
|
||||
/// supersedes the preceding render projection.
|
||||
/// </summary>
|
||||
public void Resolve(
|
||||
uint childGuid,
|
||||
Func<uint, bool> isObjectKnown,
|
||||
Func<uint, ushort?> resolveInstance,
|
||||
Func<ParentEvent.Parsed, bool> accept)
|
||||
{
|
||||
if (!_unresolvedByChild.TryGetValue(childGuid, out Queue<ParentAttachmentRelation>? queue))
|
||||
return;
|
||||
int candidateCount = queue.Count;
|
||||
for (int i = 0; i < candidateCount; i++)
|
||||
{
|
||||
ParentAttachmentRelation relation = queue.Dequeue();
|
||||
ushort? parentInstance = resolveInstance(relation.ParentGuid);
|
||||
if (parentInstance is null)
|
||||
{
|
||||
queue.Enqueue(relation with { WaitOwner = ParentAttachmentWaitOwner.Parent });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (parentInstance.Value != relation.ParentInstanceSequence)
|
||||
{
|
||||
// Current parent newer than the packet: discard the stale
|
||||
// relation. Packet newer than current: retain it until that
|
||||
// parent generation is constructed.
|
||||
if (PhysicsTimestampGate.IsNewer(
|
||||
relation.ParentInstanceSequence,
|
||||
parentInstance.Value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
queue.Enqueue(relation with { WaitOwner = ParentAttachmentWaitOwner.Parent });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isObjectKnown(childGuid))
|
||||
{
|
||||
queue.Enqueue(relation with { WaitOwner = ParentAttachmentWaitOwner.Child });
|
||||
continue;
|
||||
}
|
||||
|
||||
var update = new ParentEvent.Parsed(
|
||||
relation.ParentGuid,
|
||||
relation.ChildGuid,
|
||||
relation.ParentLocation,
|
||||
relation.PlacementId,
|
||||
relation.ParentInstanceSequence,
|
||||
relation.ChildPositionSequence);
|
||||
if (!accept(update))
|
||||
continue;
|
||||
|
||||
_projectionByChild[childGuid] = relation;
|
||||
_lastAcceptedByChild[childGuid] = relation;
|
||||
}
|
||||
|
||||
if (queue.Count == 0)
|
||||
_unresolvedByChild.Remove(childGuid);
|
||||
}
|
||||
|
||||
public bool TryGetProjection(uint childGuid, out ParentAttachmentRelation relation) =>
|
||||
_projectionByChild.TryGetValue(childGuid, out relation);
|
||||
|
||||
public void MarkProjected(uint childGuid) => _projectionByChild.Remove(childGuid);
|
||||
|
||||
public bool RestoreLastAccepted(uint childGuid)
|
||||
{
|
||||
if (!_lastAcceptedByChild.TryGetValue(childGuid, out ParentAttachmentRelation relation))
|
||||
return false;
|
||||
_projectionByChild[childGuid] = relation;
|
||||
return true;
|
||||
}
|
||||
|
||||
public IReadOnlyList<uint> ChildrenWaitingForParent(uint parentGuid)
|
||||
{
|
||||
var result = new HashSet<uint>();
|
||||
foreach ((uint childGuid, ParentAttachmentRelation relation) in _projectionByChild)
|
||||
{
|
||||
if (relation.ParentGuid == parentGuid)
|
||||
result.Add(childGuid);
|
||||
}
|
||||
|
||||
foreach ((uint childGuid, Queue<ParentAttachmentRelation> queue) in _unresolvedByChild)
|
||||
{
|
||||
if (queue.Any(relation => relation.ParentGuid == parentGuid))
|
||||
result.Add(childGuid);
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
public void RemoveObject(uint guid)
|
||||
{
|
||||
_projectionByChild.Remove(guid);
|
||||
_lastAcceptedByChild.Remove(guid);
|
||||
_unresolvedByChild.Remove(guid);
|
||||
|
||||
RemoveParentReferences(_projectionByChild, guid);
|
||||
RemoveParentReferences(_lastAcceptedByChild, guid);
|
||||
|
||||
uint[] children = _unresolvedByChild.Keys.ToArray();
|
||||
for (int i = 0; i < children.Length; i++)
|
||||
{
|
||||
uint childGuid = children[i];
|
||||
Queue<ParentAttachmentRelation> retained = new(
|
||||
_unresolvedByChild[childGuid].Where(relation => relation.ParentGuid != guid));
|
||||
if (retained.Count == 0)
|
||||
_unresolvedByChild.Remove(childGuid);
|
||||
else
|
||||
_unresolvedByChild[childGuid] = retained;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends one logical incarnation without discarding unresolved wire events
|
||||
/// that may explicitly address the replacement/future parent generation.
|
||||
/// </summary>
|
||||
public void EndGeneration(uint guid, ushort replacementGeneration)
|
||||
{
|
||||
FilterChildCandidates(
|
||||
guid,
|
||||
relation => relation.WaitOwner is ParentAttachmentWaitOwner.Parent);
|
||||
_projectionByChild.Remove(guid);
|
||||
_lastAcceptedByChild.Remove(guid);
|
||||
RemoveParentReferences(_projectionByChild, guid);
|
||||
RemoveParentReferences(_lastAcceptedByChild, guid);
|
||||
FilterParentCandidates(
|
||||
guid,
|
||||
relation => relation.ParentInstanceSequence == replacementGeneration
|
||||
|| PhysicsTimestampGate.IsNewer(
|
||||
replacementGeneration,
|
||||
relation.ParentInstanceSequence));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes one exact incarnation. Child-addressed candidates die with the
|
||||
/// child; candidates addressed to a strictly newer parent incarnation
|
||||
/// survive for retail's post-Create blob replay.
|
||||
/// </summary>
|
||||
public void DeleteGeneration(uint guid, ushort deletedGeneration)
|
||||
{
|
||||
FilterChildCandidates(
|
||||
guid,
|
||||
relation => relation.WaitOwner is ParentAttachmentWaitOwner.Parent);
|
||||
_projectionByChild.Remove(guid);
|
||||
_lastAcceptedByChild.Remove(guid);
|
||||
RemoveParentReferences(_projectionByChild, guid);
|
||||
RemoveParentReferences(_lastAcceptedByChild, guid);
|
||||
FilterParentCandidates(
|
||||
guid,
|
||||
relation => PhysicsTimestampGate.IsNewer(
|
||||
deletedGeneration,
|
||||
relation.ParentInstanceSequence));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears an accepted attachment projection after Pickup or a world
|
||||
/// Position without destroying fresher unresolved ParentEvents.
|
||||
/// </summary>
|
||||
public void EndChildProjection(uint childGuid)
|
||||
{
|
||||
_projectionByChild.Remove(childGuid);
|
||||
_lastAcceptedByChild.Remove(childGuid);
|
||||
}
|
||||
|
||||
public void RemoveChild(uint childGuid)
|
||||
{
|
||||
_projectionByChild.Remove(childGuid);
|
||||
_lastAcceptedByChild.Remove(childGuid);
|
||||
_unresolvedByChild.Remove(childGuid);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_unresolvedByChild.Clear();
|
||||
_projectionByChild.Clear();
|
||||
_lastAcceptedByChild.Clear();
|
||||
}
|
||||
|
||||
private static void RemoveParentReferences(
|
||||
Dictionary<uint, ParentAttachmentRelation> relations,
|
||||
uint parentGuid)
|
||||
{
|
||||
uint[] children = relations
|
||||
.Where(pair => pair.Value.ParentGuid == parentGuid)
|
||||
.Select(pair => pair.Key)
|
||||
.ToArray();
|
||||
for (int i = 0; i < children.Length; i++)
|
||||
relations.Remove(children[i]);
|
||||
}
|
||||
|
||||
private void FilterParentCandidates(
|
||||
uint parentGuid,
|
||||
Func<ParentAttachmentRelation, bool> retain)
|
||||
{
|
||||
uint[] children = _unresolvedByChild.Keys.ToArray();
|
||||
for (int i = 0; i < children.Length; i++)
|
||||
{
|
||||
uint childGuid = children[i];
|
||||
Queue<ParentAttachmentRelation> queue = _unresolvedByChild[childGuid];
|
||||
var retained = new Queue<ParentAttachmentRelation>(
|
||||
queue.Where(relation => relation.ParentGuid != parentGuid || retain(relation)));
|
||||
if (retained.Count == 0)
|
||||
_unresolvedByChild.Remove(childGuid);
|
||||
else
|
||||
_unresolvedByChild[childGuid] = retained;
|
||||
}
|
||||
}
|
||||
|
||||
private void FilterChildCandidates(
|
||||
uint childGuid,
|
||||
Func<ParentAttachmentRelation, bool> retain)
|
||||
{
|
||||
if (!_unresolvedByChild.TryGetValue(
|
||||
childGuid,
|
||||
out Queue<ParentAttachmentRelation>? queue))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var retained = new Queue<ParentAttachmentRelation>(queue.Where(retain));
|
||||
if (retained.Count == 0)
|
||||
_unresolvedByChild.Remove(childGuid);
|
||||
else
|
||||
_unresolvedByChild[childGuid] = retained;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly record struct ParentAttachmentRelation(
|
||||
uint ParentGuid,
|
||||
uint ChildGuid,
|
||||
uint ParentLocation,
|
||||
uint PlacementId,
|
||||
ushort ParentInstanceSequence,
|
||||
ushort ChildPositionSequence,
|
||||
ParentAttachmentWaitOwner WaitOwner = ParentAttachmentWaitOwner.Unknown);
|
||||
|
||||
public enum ParentAttachmentWaitOwner
|
||||
{
|
||||
Unknown,
|
||||
Parent,
|
||||
Child,
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue