Detach old-world spatial ownership atomically, prioritize destination retirement dependencies, and reveal the viewport at the retail transition edge. Give private paperdoll views independent mesh ownership and retain dormant ACE entities so portal revisits preserve server objects without extending active GPU lifetimes.
123 lines
3.7 KiB
C#
123 lines
3.7 KiB
C#
using AcDream.Core.Net;
|
|
using AcDream.Core.Net.Messages;
|
|
using AcDream.Core.Physics;
|
|
|
|
namespace AcDream.App.World;
|
|
|
|
internal enum DormantCreateDisposition
|
|
{
|
|
NoDormantRecord,
|
|
ExistingGeneration,
|
|
NewGeneration,
|
|
StaleGeneration,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cold, data-only ownership for ACE objects that left the client's active
|
|
/// visibility set. Active render, physics, animation, effect, and collision
|
|
/// owners are torn down before a snapshot enters this store.
|
|
///
|
|
/// ACE retains KnownObjects across ordinary teleports and does not reliably
|
|
/// repeat CreateObject when a destination is revisited. Keeping the accepted
|
|
/// CreateObject snapshot therefore preserves the server object's identity
|
|
/// without retaining any frame-time or GPU work.
|
|
/// </summary>
|
|
internal sealed class DormantLiveEntityStore
|
|
{
|
|
private readonly Dictionary<uint, WorldSession.EntitySpawn> _spawns = new();
|
|
|
|
public int Count => _spawns.Count;
|
|
|
|
public DormantCreateDisposition ClassifyCreate(
|
|
WorldSession.EntitySpawn incoming)
|
|
{
|
|
if (!_spawns.TryGetValue(incoming.Guid, out WorldSession.EntitySpawn retained))
|
|
return DormantCreateDisposition.NoDormantRecord;
|
|
|
|
if (PhysicsTimestampGate.IsNewer(
|
|
retained.InstanceSequence,
|
|
incoming.InstanceSequence))
|
|
{
|
|
return DormantCreateDisposition.NewGeneration;
|
|
}
|
|
|
|
if (PhysicsTimestampGate.IsNewer(
|
|
incoming.InstanceSequence,
|
|
retained.InstanceSequence))
|
|
{
|
|
return DormantCreateDisposition.StaleGeneration;
|
|
}
|
|
|
|
return DormantCreateDisposition.ExistingGeneration;
|
|
}
|
|
|
|
public void Retain(WorldSession.EntitySpawn snapshot)
|
|
{
|
|
if (_spawns.TryGetValue(snapshot.Guid, out WorldSession.EntitySpawn current)
|
|
&& PhysicsTimestampGate.IsNewer(
|
|
snapshot.InstanceSequence,
|
|
current.InstanceSequence))
|
|
{
|
|
return;
|
|
}
|
|
|
|
_spawns[snapshot.Guid] = snapshot;
|
|
}
|
|
|
|
public bool RemoveThroughAcceptedCreate(WorldSession.EntitySpawn accepted)
|
|
{
|
|
DormantCreateDisposition disposition = ClassifyCreate(accepted);
|
|
if (disposition is DormantCreateDisposition.StaleGeneration
|
|
or DormantCreateDisposition.NoDormantRecord)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return _spawns.Remove(accepted.Guid);
|
|
}
|
|
|
|
public bool RemoveExact(DeleteObject.Parsed delete)
|
|
{
|
|
if (!_spawns.TryGetValue(delete.Guid, out WorldSession.EntitySpawn retained)
|
|
|| retained.InstanceSequence != delete.InstanceSequence)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return _spawns.Remove(delete.Guid);
|
|
}
|
|
|
|
public WorldSession.EntitySpawn[] SnapshotLandblock(uint landblockId)
|
|
{
|
|
uint canonical = CanonicalLandblock(landblockId);
|
|
return _spawns.Values
|
|
.Where(spawn => spawn.Position is { } position
|
|
&& CanonicalLandblock(position.LandblockId) == canonical
|
|
&& spawn.SetupTableId is not null)
|
|
.ToArray();
|
|
}
|
|
|
|
public bool Contains(uint guid, ushort generation) =>
|
|
_spawns.TryGetValue(guid, out WorldSession.EntitySpawn spawn)
|
|
&& spawn.InstanceSequence == generation;
|
|
|
|
public bool TryGetExact(
|
|
uint guid,
|
|
ushort generation,
|
|
out WorldSession.EntitySpawn snapshot)
|
|
{
|
|
if (_spawns.TryGetValue(guid, out snapshot)
|
|
&& snapshot.InstanceSequence == generation)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
snapshot = default;
|
|
return false;
|
|
}
|
|
|
|
public void Clear() => _spawns.Clear();
|
|
|
|
private static uint CanonicalLandblock(uint cellId) =>
|
|
(cellId & 0xFFFF0000u) | 0xFFFFu;
|
|
}
|