feat(runtime): seal dormant SetPosition evaluations
This commit is contained in:
parent
22651c823d
commit
99f867f053
16 changed files with 2298 additions and 62 deletions
|
|
@ -179,6 +179,17 @@ public sealed class PropertyBundle
|
|||
/// </summary>
|
||||
public sealed class ClientObject
|
||||
{
|
||||
private uint? _houseOwnerId;
|
||||
private uint? _monarchId;
|
||||
private HouseRestrictionRecord? _restrictions;
|
||||
|
||||
/// <summary>
|
||||
/// Synchronous table-internal notification for the qualities consulted by
|
||||
/// retail's house-entry restriction gate. Multiple tables may retain the
|
||||
/// same object during isolated evaluation fixtures; each receives the edge.
|
||||
/// </summary>
|
||||
internal event Action<ClientObject>? RestrictionAuthorityChanged;
|
||||
|
||||
public uint ObjectId { get; init; }
|
||||
public uint WeenieClassId { get; set; } // "blueprint"
|
||||
public string Name { get; set; } = "";
|
||||
|
|
@ -273,7 +284,16 @@ public sealed class ClientObject
|
|||
/// object (wire <c>WeenieHeaderFlag.Owner</c>, 0x02000000). Zero or a
|
||||
/// match against the mover's own id admits regardless of the guest list.
|
||||
/// </summary>
|
||||
public uint? HouseOwnerId { get; set; }
|
||||
public uint? HouseOwnerId
|
||||
{
|
||||
get => _houseOwnerId;
|
||||
set
|
||||
{
|
||||
if (_houseOwnerId == value) return;
|
||||
_houseOwnerId = value;
|
||||
RestrictionAuthorityChanged?.Invoke(this);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// AP-129 (Campaign P Slice P4 review fix): retail <c>PublicWeenieDesc
|
||||
/// ._monarch_iid</c> (wire <c>WeenieHeaderFlag.Monarch</c>, 0x40) — this
|
||||
|
|
@ -281,7 +301,16 @@ public sealed class ClientObject
|
|||
/// objects; a player's own value is what <c>RestrictionDB::IsAllowedIn</c>
|
||||
/// compares against a house's <see cref="HouseRestrictionRecord.AllegianceMonarchId"/>.
|
||||
/// </summary>
|
||||
public uint? MonarchId { get; set; }
|
||||
public uint? MonarchId
|
||||
{
|
||||
get => _monarchId;
|
||||
set
|
||||
{
|
||||
if (_monarchId == value) return;
|
||||
_monarchId = value;
|
||||
RestrictionAuthorityChanged?.Invoke(this);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// AP-129 (Campaign P Slice P4 review fix): retail <c>PublicWeenieDesc
|
||||
/// ._db</c> (<c>RestrictionDB*</c>) — the house's own guest/ban list.
|
||||
|
|
@ -291,7 +320,16 @@ public sealed class ClientObject
|
|||
/// object — acdream cannot distinguish the two, and both resolve to
|
||||
/// the same retail-faithful "allow" default.
|
||||
/// </summary>
|
||||
public HouseRestrictionRecord? Restrictions { get; set; }
|
||||
public HouseRestrictionRecord? Restrictions
|
||||
{
|
||||
get => _restrictions;
|
||||
set
|
||||
{
|
||||
if (ReferenceEquals(_restrictions, value)) return;
|
||||
_restrictions = value;
|
||||
RestrictionAuthorityChanged?.Invoke(this);
|
||||
}
|
||||
}
|
||||
public PropertyBundle Properties { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AcDream.Core.Items;
|
||||
|
|
@ -22,11 +23,29 @@ namespace AcDream.Core.Items;
|
|||
/// (0 = dwelling access only, 1 = storage access also). Retail's
|
||||
/// <c>IsAllowedIn</c> only consults key membership for entry; the permission
|
||||
/// value is preserved for wire fidelity but not consulted here.</param>
|
||||
public sealed record HouseRestrictionRecord(
|
||||
bool OpenToPublic,
|
||||
uint AllegianceMonarchId,
|
||||
IReadOnlyDictionary<uint, uint> Guests)
|
||||
public sealed record HouseRestrictionRecord
|
||||
{
|
||||
public HouseRestrictionRecord(
|
||||
bool OpenToPublic,
|
||||
uint AllegianceMonarchId,
|
||||
IReadOnlyDictionary<uint, uint> Guests)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(Guests);
|
||||
this.OpenToPublic = OpenToPublic;
|
||||
this.AllegianceMonarchId = AllegianceMonarchId;
|
||||
this.Guests = Guests.ToFrozenDictionary();
|
||||
}
|
||||
|
||||
public bool OpenToPublic { get; }
|
||||
public uint AllegianceMonarchId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Immutable snapshot of the wire permission table. The parser's mutable
|
||||
/// dictionary must never remain an untracked mutation path into live
|
||||
/// collision-entry authority.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<uint, uint> Guests { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Verbatim port of retail <c>RestrictionDB::IsAllowedIn</c>
|
||||
/// (named-retail pc:444493-444516, 0x005ae8f0):
|
||||
|
|
|
|||
|
|
@ -25,6 +25,14 @@ public sealed class CellArray : ICollection<uint>, IReadOnlyCollection<uint>
|
|||
private readonly List<uint> _order = new();
|
||||
private readonly HashSet<uint> _seen = new();
|
||||
|
||||
/// <summary>
|
||||
/// Optional append-only union target used by one retained SetPosition
|
||||
/// transaction. Clearing this CELLARRAY must not clear the target: retail
|
||||
/// can rebuild the working array repeatedly while every probed cell still
|
||||
/// contributes to the transaction's collision-world authority footprint.
|
||||
/// </summary>
|
||||
internal CellArray? UnionTarget { get; set; }
|
||||
|
||||
public int Count => _order.Count;
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
|
|
@ -34,6 +42,11 @@ public sealed class CellArray : ICollection<uint>, IReadOnlyCollection<uint>
|
|||
/// <summary>Append <paramref name="id"/> iff not already present (retail add_cell dedup).</summary>
|
||||
public void Add(uint id)
|
||||
{
|
||||
if (UnionTarget is { } target
|
||||
&& !ReferenceEquals(target, this))
|
||||
{
|
||||
target.Add(id);
|
||||
}
|
||||
if (_seen.Add(id))
|
||||
_order.Add(id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -169,6 +169,7 @@ public static class CellTransit
|
|||
// Retail CEnvCell::find_transit_cells first asks the loaded
|
||||
// neighbour cell whether the sphere intersects its CellBSP.
|
||||
// The portal-plane side test is only the unloaded-cell load hint.
|
||||
RecordUnionOnlyProbe(candidates, otherId);
|
||||
var otherCell = cache.GetCellStruct(otherId);
|
||||
if (otherCell is not null &&
|
||||
CollisionTraversal.HasCellContainment(cache, otherCell))
|
||||
|
|
@ -446,6 +447,7 @@ public static class CellTransit
|
|||
if (portal.OtherPortalId < 0)
|
||||
continue;
|
||||
|
||||
RecordUnionOnlyProbe(candidates, portal.OtherCellId);
|
||||
var otherCell = cache.GetCellStruct(portal.OtherCellId);
|
||||
if (otherCell is null ||
|
||||
!CollisionTraversal.HasCellContainment(cache, otherCell))
|
||||
|
|
@ -679,8 +681,13 @@ public static class CellTransit
|
|||
/// </para>
|
||||
/// </summary>
|
||||
public static uint FindVisibleChildCell(
|
||||
PhysicsDataCache cache, uint startCellId, Vector3 worldPoint, bool useStabList)
|
||||
PhysicsDataCache cache,
|
||||
uint startCellId,
|
||||
Vector3 worldPoint,
|
||||
bool useStabList,
|
||||
ICollection<uint>? probedCells = null)
|
||||
{
|
||||
probedCells?.Add(startCellId);
|
||||
var start = cache.GetCellStruct(startCellId);
|
||||
if (start is null) return 0u;
|
||||
|
||||
|
|
@ -691,12 +698,17 @@ public static class CellTransit
|
|||
{
|
||||
// arg3 != 0 → iterate stab_list, GetVisible + point_in_cell (:311444-311465)
|
||||
foreach (uint id in start.VisibleCellIds)
|
||||
{
|
||||
probedCells?.Add(id);
|
||||
if (PointInCell(cache, cache.GetCellStruct(id), worldPoint)) return id;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// arg3 == 0 → iterate direct portals, GetOtherCell + point_in_cell (:311411-311434)
|
||||
foreach (var portal in start.Portals)
|
||||
{
|
||||
probedCells?.Add(portal.OtherCellId);
|
||||
if (PointInCell(
|
||||
cache,
|
||||
cache.GetCellStruct(portal.OtherCellId),
|
||||
|
|
@ -704,6 +716,7 @@ public static class CellTransit
|
|||
{
|
||||
return portal.OtherCellId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0u;
|
||||
|
|
@ -1068,7 +1081,11 @@ public static class CellTransit
|
|||
sphereRadius))
|
||||
{
|
||||
uint recovered = FindVisibleChildCell(
|
||||
cache, currentCellId, worldSphereCenter, useStabList: true);
|
||||
cache,
|
||||
currentCellId,
|
||||
worldSphereCenter,
|
||||
useStabList: true,
|
||||
(candidates as CellArray)?.UnionTarget);
|
||||
if (recovered != 0u && recovered != currentCellId)
|
||||
return recovered;
|
||||
}
|
||||
|
|
@ -1078,6 +1095,14 @@ public static class CellTransit
|
|||
return currentCellId;
|
||||
}
|
||||
|
||||
private static void RecordUnionOnlyProbe(
|
||||
ICollection<uint> candidates,
|
||||
uint cellId)
|
||||
{
|
||||
if (candidates is CellArray { UnionTarget: { } queryFootprint })
|
||||
queryFootprint.Add(cellId);
|
||||
}
|
||||
|
||||
private static int EffectiveSphereCount(IReadOnlyList<Sphere> worldSpheres, int numSpheres)
|
||||
{
|
||||
if (numSpheres <= 0 || worldSpheres.Count == 0) return 0;
|
||||
|
|
|
|||
|
|
@ -224,7 +224,22 @@ public sealed class PhysicsEngine
|
|||
/// when the restriction weenie can't be resolved — so production MUST
|
||||
/// wire this to the live table for the fix to actually admit anyone.
|
||||
/// </summary>
|
||||
public ClientObjectTable? Objects { get; set; }
|
||||
private ClientObjectTable? _objects;
|
||||
private ulong _objectsBindingRevision;
|
||||
|
||||
public ClientObjectTable? Objects
|
||||
{
|
||||
get => _objects;
|
||||
set
|
||||
{
|
||||
if (ReferenceEquals(_objects, value))
|
||||
return;
|
||||
_objects = value;
|
||||
_objectsBindingRevision = checked(_objectsBindingRevision + 1UL);
|
||||
}
|
||||
}
|
||||
|
||||
internal ulong ObjectsBindingRevision => _objectsBindingRevision;
|
||||
|
||||
internal sealed record LandblockPhysics(
|
||||
TerrainSurface Terrain,
|
||||
|
|
@ -1553,8 +1568,10 @@ public sealed class PhysicsEngine
|
|||
private AdjustedSetPosition AdjustSetPosition(
|
||||
uint seedCellId,
|
||||
Vector3 cellLocalPosition,
|
||||
Vector3 firstWorldSphereCenter)
|
||||
Vector3 firstWorldSphereCenter,
|
||||
CellArray queryFootprint)
|
||||
{
|
||||
queryFootprint.Add(seedCellId);
|
||||
uint low = seedCellId & 0xFFFFu;
|
||||
bool lowInRange = low is (>= 1u and <= 0x40u)
|
||||
or (>= 0x0100u and <= 0xFFFDu)
|
||||
|
|
@ -1582,7 +1599,8 @@ public sealed class PhysicsEngine
|
|||
cache,
|
||||
seedCellId,
|
||||
firstWorldSphereCenter,
|
||||
useStabList: true);
|
||||
useStabList: true,
|
||||
queryFootprint);
|
||||
if (child != 0u)
|
||||
{
|
||||
return new AdjustedSetPosition(
|
||||
|
|
@ -1606,6 +1624,7 @@ public sealed class PhysicsEngine
|
|||
bool adjusted = LandDefs.AdjustToOutside(
|
||||
ref adjustedCell,
|
||||
ref adjustedLocal);
|
||||
queryFootprint.Add(adjustedCell);
|
||||
bool resident = adjusted
|
||||
&& IsLandblockTerrainResident(adjustedCell);
|
||||
return new AdjustedSetPosition(
|
||||
|
|
@ -1635,33 +1654,54 @@ public sealed class PhysicsEngine
|
|||
}
|
||||
|
||||
Transition transition = RentTransition();
|
||||
CellArray queryFootprint =
|
||||
transition.SpherePath.SetPositionQueryFootprint;
|
||||
try
|
||||
{
|
||||
queryFootprint.Clear();
|
||||
transition.SpherePath.CellCandidates.UnionTarget = queryFootprint;
|
||||
InitializeSetPositionTransition(transition, request);
|
||||
bool randomOnly = request.Flags.HasFlag(
|
||||
PhysicsSetPositionFlags.RandomScatter);
|
||||
PhysicsSetPositionResult result;
|
||||
if (randomOnly)
|
||||
{
|
||||
return SetScatterPositionInternal(
|
||||
result = SetScatterPositionInternal(
|
||||
transition,
|
||||
request,
|
||||
handleCollisions);
|
||||
handleCollisions,
|
||||
queryFootprint);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = SetPositionInternal(
|
||||
transition,
|
||||
request,
|
||||
handleCollisions,
|
||||
queryFootprint);
|
||||
if (result.Error != PhysicsSetPositionError.Ok
|
||||
&& request.Flags.HasFlag(PhysicsSetPositionFlags.Scatter))
|
||||
{
|
||||
result = SetScatterPositionInternal(
|
||||
transition,
|
||||
request,
|
||||
handleCollisions,
|
||||
queryFootprint);
|
||||
}
|
||||
}
|
||||
|
||||
PhysicsSetPositionResult result =
|
||||
SetPositionInternal(transition, request, handleCollisions);
|
||||
if (result.Error != PhysicsSetPositionError.Ok
|
||||
&& request.Flags.HasFlag(PhysicsSetPositionFlags.Scatter))
|
||||
// Scatter retains one append-only query union across every inner
|
||||
// attempt. Materialize it exactly once at the public transaction
|
||||
// boundary; copying it per attempt is quadratic at retail's
|
||||
// maximum retry count.
|
||||
return result with
|
||||
{
|
||||
return SetScatterPositionInternal(
|
||||
transition,
|
||||
request,
|
||||
handleCollisions);
|
||||
}
|
||||
return result;
|
||||
QueriedCellIds = queryFootprint.OrderedIds.ToImmutableArray(),
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
transition.SpherePath.CellCandidates.UnionTarget = null;
|
||||
ReturnTransition(transition);
|
||||
}
|
||||
}
|
||||
|
|
@ -1686,7 +1726,8 @@ public sealed class PhysicsEngine
|
|||
private PhysicsSetPositionResult SetScatterPositionInternal(
|
||||
Transition transition,
|
||||
in PhysicsSetPositionRequest request,
|
||||
Func<PhysicsSetPositionCollisionReport, bool>? handleCollisions)
|
||||
Func<PhysicsSetPositionCollisionReport, bool>? handleCollisions,
|
||||
CellArray queryFootprint)
|
||||
{
|
||||
PhysicsSetPositionResult result = ErrorResult(
|
||||
request,
|
||||
|
|
@ -1706,7 +1747,8 @@ public sealed class PhysicsEngine
|
|||
result = SetPositionInternal(
|
||||
transition,
|
||||
scattered,
|
||||
handleCollisions);
|
||||
handleCollisions,
|
||||
queryFootprint);
|
||||
if (result.Error == PhysicsSetPositionError.Ok)
|
||||
break;
|
||||
}
|
||||
|
|
@ -1716,7 +1758,8 @@ public sealed class PhysicsEngine
|
|||
private PhysicsSetPositionResult SetPositionInternal(
|
||||
Transition transition,
|
||||
in PhysicsSetPositionRequest request,
|
||||
Func<PhysicsSetPositionCollisionReport, bool>? handleCollisions)
|
||||
Func<PhysicsSetPositionCollisionReport, bool>? handleCollisions,
|
||||
CellArray queryFootprint)
|
||||
{
|
||||
transition.SpherePath.CellCandidates.Clear();
|
||||
transition.SpherePath.ClearWalkable();
|
||||
|
|
@ -1731,7 +1774,8 @@ public sealed class PhysicsEngine
|
|||
AdjustedSetPosition adjusted = AdjustSetPosition(
|
||||
request.CellId,
|
||||
request.CellLocalPosition,
|
||||
firstWorldCenter);
|
||||
firstWorldCenter,
|
||||
queryFootprint);
|
||||
if (!adjusted.Resident)
|
||||
{
|
||||
return new PhysicsSetPositionResult(
|
||||
|
|
@ -1849,7 +1893,9 @@ public sealed class PhysicsEngine
|
|||
}
|
||||
if (spherePath.CurCellId == 0u)
|
||||
{
|
||||
return ErrorResult(request, PhysicsSetPositionError.NoCell);
|
||||
return ErrorResult(
|
||||
request,
|
||||
PhysicsSetPositionError.NoCell);
|
||||
}
|
||||
|
||||
bool inContact = collision.ContactPlaneValid;
|
||||
|
|
|
|||
|
|
@ -145,7 +145,8 @@ internal readonly record struct PhysicsSetPositionResult(
|
|||
bool CellChanged = false,
|
||||
PhysicsShadowCommitAction ShadowAction = PhysicsShadowCommitAction.None,
|
||||
ImmutableArray<uint> CrossCellIds = default,
|
||||
ImmutableArray<uint> CollidedObjectIds = default)
|
||||
ImmutableArray<uint> CollidedObjectIds = default,
|
||||
ImmutableArray<uint> QueriedCellIds = default)
|
||||
{
|
||||
internal bool IsSuccessful => Error == PhysicsSetPositionError.Ok;
|
||||
internal bool IsCommitted =>
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ public sealed class ShadowObjectRegistry
|
|||
_collisionWorld.Current.ShadowOwnerFreeSlots;
|
||||
private readonly HashSet<uint> _prefixScratch = new();
|
||||
private readonly List<uint> _removedPrefixScratch = new();
|
||||
private ulong _mutationRevision;
|
||||
internal event Action<uint, ulong>? OwnerMutated;
|
||||
internal event Action<uint, uint>? OwnerPrefixMembershipChanged;
|
||||
|
||||
|
|
@ -101,6 +102,7 @@ public sealed class ShadowObjectRegistry
|
|||
"A populated shadow registry cannot change collision roots.");
|
||||
}
|
||||
_collisionWorld = collisionWorld;
|
||||
AdvanceMutationRevision();
|
||||
}
|
||||
|
||||
internal sealed record RegistrationRecord(
|
||||
|
|
@ -123,8 +125,20 @@ public sealed class ShadowObjectRegistry
|
|||
? version
|
||||
: 0UL;
|
||||
|
||||
/// <summary>
|
||||
/// Monotonic authority for every logical mutation of the active shadow
|
||||
/// collision world. SetPosition evaluation receipts seal this value so a
|
||||
/// later owner insert, removal, move, state change, suspension, reflood,
|
||||
/// or cell-row replacement cannot be committed against a different world.
|
||||
/// </summary>
|
||||
internal ulong MutationRevision => _mutationRevision;
|
||||
|
||||
private void AdvanceMutationRevision() =>
|
||||
_mutationRevision = checked(_mutationRevision + 1UL);
|
||||
|
||||
private void BumpOwnerVersion(uint entityId)
|
||||
{
|
||||
AdvanceMutationRevision();
|
||||
ulong version = checked(GetOwnerVersion(entityId) + 1UL);
|
||||
_ownerVersions[entityId] = version;
|
||||
RefreshOwnerPrefixIndex(entityId);
|
||||
|
|
@ -1308,6 +1322,7 @@ public sealed class ShadowObjectRegistry
|
|||
DeregisterCore(entityId, publishMutation: false);
|
||||
RemoveOwnerPrefixMembership(entityId);
|
||||
_ownerVersions.Remove(entityId);
|
||||
AdvanceMutationRevision();
|
||||
return;
|
||||
}
|
||||
if (!_entityToCells.TryGetValue(entityId, out List<uint>? cells))
|
||||
|
|
@ -1877,6 +1892,13 @@ public sealed class ShadowObjectRegistry
|
|||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
bool mutated = _cells.Count != 0
|
||||
|| _entityToCells.Count != 0
|
||||
|| _entityReg.Count != 0
|
||||
|| _suspendedEntities.Count != 0
|
||||
|| _suspendedEntityCells.Count != 0;
|
||||
if (mutated)
|
||||
AdvanceMutationRevision();
|
||||
_cells.Clear();
|
||||
_entityToCells.Clear();
|
||||
_suspendedEntities.Clear();
|
||||
|
|
|
|||
|
|
@ -719,6 +719,7 @@ public sealed class SpherePath
|
|||
private Vector3[]? _walkableVertexStorage;
|
||||
private Vector3[]? _lastWalkableVertexStorage;
|
||||
internal readonly CellArray CellCandidates = new();
|
||||
internal readonly CellArray SetPositionQueryFootprint = new();
|
||||
internal readonly CellOrderScratchArena OrderedCellScratch = new();
|
||||
|
||||
internal Vector3[]? RetainedWalkableVertexStorage => _walkableVertexStorage;
|
||||
|
|
@ -938,6 +939,8 @@ public sealed class SpherePath
|
|||
if (_lastWalkableVertexStorage is not null)
|
||||
System.Array.Clear(_lastWalkableVertexStorage);
|
||||
CellCandidates.Clear();
|
||||
CellCandidates.UnionTarget = null;
|
||||
SetPositionQueryFootprint.Clear();
|
||||
OrderedCellScratch.ResetForReuse();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue