feat(runtime): seal dormant SetPosition evaluations
This commit is contained in:
parent
22651c823d
commit
99f867f053
16 changed files with 2298 additions and 62 deletions
|
|
@ -116,11 +116,71 @@ public sealed class ClientObjectTable
|
|||
private readonly ConcurrentDictionary<uint, Container> _containers = new();
|
||||
private readonly Dictionary<uint, List<uint>> _containerIndex = new();
|
||||
private readonly Dictionary<uint, List<uint>> _equipmentIndex = new();
|
||||
private readonly HashSet<ClientObject> _restrictionObservedObjects =
|
||||
new(ReferenceEqualityComparer.Instance);
|
||||
|
||||
// B-Drag: pre-move snapshots for optimistic inventory moves. itemId → (container, slot, equip) BEFORE
|
||||
// the optimistic MoveItem; restored by RollbackMove on InventoryServerSaveFailed (0x00A0),
|
||||
// cleared by ConfirmMove on the InventoryPutObjInContainer (0x0022) echo.
|
||||
private readonly Dictionary<uint, (ClientObjectPlacement placement, int outstanding)> _pendingMoves = new();
|
||||
private ulong _mutationRevision;
|
||||
|
||||
public ClientObjectTable()
|
||||
{
|
||||
// Keep one conservative authority over every object mutation that can
|
||||
// affect physics entry restrictions. These handlers are registered
|
||||
// before any consumer can subscribe, so re-entrant observers see the
|
||||
// advanced revision before they can evaluate or publish a receipt.
|
||||
ObjectAdded += _ => AdvanceMutationRevision();
|
||||
ObjectMoved += _ => AdvanceMutationRevision();
|
||||
ObjectRemoved += _ => AdvanceMutationRevision();
|
||||
ObjectUpdated += _ => AdvanceMutationRevision();
|
||||
Cleared += AdvanceMutationRevision;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Monotonic authority for live object-table qualities used by physics,
|
||||
/// including house ownership/restrictions and the mover's monarch.
|
||||
/// </summary>
|
||||
internal ulong MutationRevision => _mutationRevision;
|
||||
|
||||
private void AdvanceMutationRevision() =>
|
||||
_mutationRevision = checked(_mutationRevision + 1UL);
|
||||
|
||||
private void RetainObject(ClientObject item)
|
||||
{
|
||||
if (_objects.TryGetValue(item.ObjectId, out ClientObject? prior)
|
||||
&& !ReferenceEquals(prior, item))
|
||||
{
|
||||
UnbindRestrictionAuthority(prior);
|
||||
}
|
||||
|
||||
_objects[item.ObjectId] = item;
|
||||
BindRestrictionAuthority(item);
|
||||
}
|
||||
|
||||
private void BindRestrictionAuthority(ClientObject item)
|
||||
{
|
||||
if (!_restrictionObservedObjects.Add(item)) return;
|
||||
item.RestrictionAuthorityChanged += OnRestrictionAuthorityChanged;
|
||||
}
|
||||
|
||||
private void UnbindRestrictionAuthority(ClientObject item)
|
||||
{
|
||||
if (!_restrictionObservedObjects.Remove(item)) return;
|
||||
item.RestrictionAuthorityChanged -= OnRestrictionAuthorityChanged;
|
||||
}
|
||||
|
||||
private void OnRestrictionAuthorityChanged(ClientObject item)
|
||||
{
|
||||
// Unbinding is synchronous, but keep the exact-reference check as a
|
||||
// defensive lifetime gate against a stale/replaced object callback.
|
||||
if (_objects.TryGetValue(item.ObjectId, out ClientObject? retained)
|
||||
&& ReferenceEquals(retained, item))
|
||||
{
|
||||
AdvanceMutationRevision();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Fires when an object is first added to the session.</summary>
|
||||
public event Action<ClientObject>? ObjectAdded;
|
||||
|
|
@ -270,7 +330,7 @@ public sealed class ClientObjectTable
|
|||
ClientObjectPlacement previous = prior is null
|
||||
? default
|
||||
: ClientObjectPlacement.From(prior);
|
||||
_objects[item.ObjectId] = item;
|
||||
RetainObject(item);
|
||||
UpdateEquipmentIndex(item.ObjectId, previous, ClientObjectPlacement.From(item));
|
||||
if (!existed) ObjectAdded?.Invoke(item);
|
||||
else ObjectUpdated?.Invoke(item);
|
||||
|
|
@ -615,6 +675,7 @@ public sealed class ClientObjectTable
|
|||
bool notifyObjectRemoved)
|
||||
{
|
||||
if (!_objects.TryRemove(itemId, out var item)) return false;
|
||||
UnbindRestrictionAuthority(item);
|
||||
List<uint>? changedContainers = RemoveFromOtherContainerIndexes(
|
||||
itemId,
|
||||
exceptContainerId: 0u);
|
||||
|
|
@ -690,7 +751,7 @@ public sealed class ClientObjectTable
|
|||
if (!existed || item is null)
|
||||
{
|
||||
item = new ClientObject { ObjectId = guid };
|
||||
_objects[guid] = item;
|
||||
RetainObject(item);
|
||||
}
|
||||
foreach (var kv in incoming.Ints) item.Properties.Ints[kv.Key] = kv.Value;
|
||||
foreach (var kv in incoming.Int64s) item.Properties.Int64s[kv.Key] = kv.Value;
|
||||
|
|
@ -800,7 +861,7 @@ public sealed class ClientObjectTable
|
|||
if (!existed || obj is null) // keep: satisfies nullable flow analysis
|
||||
{
|
||||
obj = new ClientObject { ObjectId = d.Guid };
|
||||
_objects[d.Guid] = obj;
|
||||
RetainObject(obj);
|
||||
}
|
||||
uint oldContainer = obj.ContainerId;
|
||||
ClientObjectPlacement previous = ClientObjectPlacement.From(obj);
|
||||
|
|
@ -876,7 +937,7 @@ public sealed class ClientObjectTable
|
|||
if (!existed || obj is null) // keep: satisfies nullable flow analysis
|
||||
{
|
||||
obj = new ClientObject { ObjectId = guid };
|
||||
_objects[guid] = obj;
|
||||
RetainObject(obj);
|
||||
}
|
||||
uint oldContainer = obj.ContainerId;
|
||||
ClientObjectPlacement previous = ClientObjectPlacement.From(obj);
|
||||
|
|
@ -955,7 +1016,7 @@ public sealed class ClientObjectTable
|
|||
if (!existed || obj is null)
|
||||
{
|
||||
obj = new ClientObject { ObjectId = entry.Guid };
|
||||
_objects[entry.Guid] = obj;
|
||||
RetainObject(obj);
|
||||
}
|
||||
|
||||
ClientObjectPlacement previous = ClientObjectPlacement.From(obj);
|
||||
|
|
@ -1249,7 +1310,7 @@ public sealed class ClientObjectTable
|
|||
if (!existed || obj is null)
|
||||
{
|
||||
obj = new ClientObject { ObjectId = entry.Guid };
|
||||
_objects[entry.Guid] = obj;
|
||||
RetainObject(obj);
|
||||
}
|
||||
obj.ContainerTypeHint = entry.ContainerType;
|
||||
if (!existed) added.Add(obj);
|
||||
|
|
@ -1361,7 +1422,7 @@ public sealed class ClientObjectTable
|
|||
if (!existed || obj is null)
|
||||
{
|
||||
obj = new ClientObject { ObjectId = entry.Guid };
|
||||
_objects[entry.Guid] = obj;
|
||||
RetainObject(obj);
|
||||
}
|
||||
|
||||
ClientObjectPlacement previous = ClientObjectPlacement.From(obj);
|
||||
|
|
@ -1426,6 +1487,8 @@ public sealed class ClientObjectTable
|
|||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
foreach (ClientObject item in _restrictionObservedObjects.ToArray())
|
||||
UnbindRestrictionAuthority(item);
|
||||
_objects.Clear();
|
||||
_containers.Clear();
|
||||
_containerIndex.Clear();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue