feat(runtime): own deferred set-position residence

This commit is contained in:
Erik 2026-07-31 22:32:49 +02:00
parent e84a388e6f
commit 4c02ac4259
18 changed files with 5557 additions and 34 deletions

View file

@ -33,6 +33,8 @@ public sealed class ShadowObjectRegistry
_collisionWorld.Current.ShadowEntityCells; // for deregistration
private HashSet<uint> _suspendedEntities =>
_collisionWorld.Current.SuspendedShadowEntities;
private Dictionary<uint, List<uint>> _suspendedEntityCells =>
_collisionWorld.Current.SuspendedShadowEntityCells;
// Rows withdrawn because a touched landblock streamed out. The owner may
// be seeded in an adjacent still-resident landblock, so its remaining rows
// cannot by themselves tell RefloodLandblock that this prefix needs repair.
@ -691,6 +693,218 @@ public sealed class ShadowObjectRegistry
reg.State, reg.Flags, seedCellId, reg.IsStatic);
}
/// <summary>
/// Installs the shadow-list suffix of one canonical retail
/// <c>CPhysicsObj::SetPositionInternal</c> commit. The transition has
/// already selected whether retail recalculates, replaces, or preserves
/// the owner's cross-cell list; this method consumes that decision
/// without running a second placement/flood oracle.
/// </summary>
internal void CommitSetPosition(
uint entityId,
Vector3 worldPosition,
Quaternion worldRotation,
uint seedCellId,
float worldOffsetX,
float worldOffsetY,
PhysicsShadowCommitAction action,
System.Collections.Immutable.ImmutableArray<uint> crossCellIds)
{
if (!_entityReg.TryGetValue(
entityId,
out RegistrationRecord? registration))
{
return;
}
switch (action)
{
case PhysicsShadowCommitAction.None:
RefreshPositionRows(
entityId,
registration,
worldPosition,
worldRotation,
seedCellId);
return;
case PhysicsShadowCommitAction.Recalculate:
UpdatePosition(
entityId,
worldPosition,
worldRotation,
worldOffsetX,
worldOffsetY,
landblockId: seedCellId & 0xFFFF0000u,
seedCellId);
return;
case PhysicsShadowCommitAction.Replace:
if (crossCellIds.IsDefaultOrEmpty)
{
RefreshPositionRows(
entityId,
registration,
worldPosition,
worldRotation,
seedCellId);
return;
}
ReplacePositionRows(
entityId,
registration,
worldPosition,
worldRotation,
seedCellId,
crossCellIds);
return;
case PhysicsShadowCommitAction.Preserve:
RefreshPositionRows(
entityId,
registration,
worldPosition,
worldRotation,
seedCellId);
return;
default:
throw new ArgumentOutOfRangeException(nameof(action));
}
}
private void RefreshPositionRows(
uint entityId,
RegistrationRecord registration,
Vector3 worldPosition,
Quaternion worldRotation,
uint seedCellId)
{
if ((_entityToCells.TryGetValue(
entityId,
out List<uint>? retainedCells)
|| _suspendedEntityCells.TryGetValue(
entityId,
out retainedCells))
&& retainedCells.Count != 0)
{
ReplacePositionRows(
entityId,
registration,
worldPosition,
worldRotation,
seedCellId,
retainedCells);
return;
}
_entityReg[entityId] = registration with
{
SeedCellId = seedCellId,
EntityWorldPos = worldPosition,
EntityWorldRot = worldRotation,
};
BumpOwnerVersion(entityId);
}
private void ReplacePositionRows(
uint entityId,
RegistrationRecord registration,
Vector3 worldPosition,
Quaternion worldRotation,
uint seedCellId,
IReadOnlyList<uint> cellIds)
{
if (_entityToCells.TryGetValue(
entityId,
out List<uint>? previousCells))
{
for (int index = 0; index < previousCells.Count; index++)
{
if (_cells.TryGetValue(
previousCells[index],
out List<ShadowEntry>? entries))
{
RemoveOwnerRows(entries, entityId);
}
}
}
_suspendedEntities.Remove(entityId);
_suspendedEntityCells.Remove(entityId);
_entityReg[entityId] = registration with
{
SeedCellId = seedCellId,
EntityWorldPos = worldPosition,
EntityWorldRot = worldRotation,
};
var exactCells = new List<uint>(cellIds.Count);
for (int index = 0; index < cellIds.Count; index++)
{
uint cellId = cellIds[index];
if (cellId == 0u || exactCells.Contains(cellId))
continue;
exactCells.Add(cellId);
}
if (registration.IsMultiPart
&& _entityShapes.TryGetValue(
entityId,
out IReadOnlyList<ShadowShape>? shapes))
{
foreach (ShadowShape shape in shapes)
{
Vector3 partWorldPosition = worldPosition
+ Vector3.Transform(shape.LocalPosition, worldRotation);
Quaternion partWorldRotation = worldRotation
* shape.LocalRotation;
var entry = new ShadowEntry(
entityId,
shape.GfxObjId,
partWorldPosition,
partWorldRotation,
shape.Radius,
shape.CollisionType,
shape.CylHeight,
shape.Scale,
registration.State,
registration.Flags,
shape.LocalPosition,
shape.LocalRotation);
for (int index = 0; index < exactCells.Count; index++)
AddEntryToCell(entry, exactCells[index]);
}
}
else
{
var entry = new ShadowEntry(
entityId,
registration.GfxObjId,
worldPosition,
worldRotation,
registration.Radius,
registration.CollisionType,
registration.CylHeight,
registration.Scale,
registration.State,
registration.Flags);
for (int index = 0; index < exactCells.Count; index++)
AddEntryToCell(entry, exactCells[index]);
}
if (exactCells.Count == 0)
_entityToCells.Remove(entityId);
else
_entityToCells[entityId] = exactCells;
if (_withdrawnPrefixesByOwner.TryGetValue(
entityId,
out HashSet<uint>? withdrawn))
{
for (int index = 0; index < exactCells.Count; index++)
withdrawn.Remove(exactCells[index] & 0xFFFF0000u);
if (withdrawn.Count == 0)
_withdrawnPrefixesByOwner.Remove(entityId);
}
BumpOwnerVersion(entityId);
}
/// <summary>
/// Removes an entity from every cell collision list while retaining the
/// exact registration and shape payload needed to restore it later.
@ -706,6 +920,7 @@ public sealed class ShadowObjectRegistry
if (_entityToCells.TryGetValue(entityId, out var cellIds))
{
_suspendedEntityCells[entityId] = new List<uint>(cellIds);
foreach (uint cellId in cellIds)
{
if (_cells.TryGetValue(cellId, out var list))
@ -924,7 +1139,8 @@ public sealed class ShadowObjectRegistry
bool existed = _entityReg.ContainsKey(entityId)
|| _entityToCells.ContainsKey(entityId)
|| _entityShapes.ContainsKey(entityId)
|| _suspendedEntities.Contains(entityId);
|| _suspendedEntities.Contains(entityId)
|| _suspendedEntityCells.ContainsKey(entityId);
if (_entityToCells.TryGetValue(entityId, out var cellIds))
{
foreach (var cellId in cellIds)
@ -937,6 +1153,7 @@ public sealed class ShadowObjectRegistry
_entityShapes.Remove(entityId);
_entityReg.Remove(entityId);
_suspendedEntities.Remove(entityId);
_suspendedEntityCells.Remove(entityId);
_withdrawnPrefixesByOwner.Remove(entityId);
if (existed && publishMutation)
{
@ -1065,6 +1282,7 @@ public sealed class ShadowObjectRegistry
_entityShapes.Remove(eid);
_entityReg.Remove(eid);
_suspendedEntities.Remove(eid);
_suspendedEntityCells.Remove(eid);
_withdrawnPrefixesByOwner.Remove(eid);
}
}
@ -1367,6 +1585,9 @@ public sealed class ShadowObjectRegistry
return false;
}
_entityToCells.TryGetValue(entityId, out List<uint>? cells);
_suspendedEntityCells.TryGetValue(
entityId,
out List<uint>? suspendedCells);
_entityShapes.TryGetValue(
entityId,
out IReadOnlyList<ShadowShape>? shapes);
@ -1394,6 +1615,7 @@ public sealed class ShadowObjectRegistry
cells is null ? null : new List<uint>(cells),
rows,
_suspendedEntities.Contains(entityId),
suspendedCells is null ? null : new List<uint>(suspendedCells),
withdrawn is null ? null : new HashSet<uint>(withdrawn));
return true;
}
@ -1405,6 +1627,8 @@ public sealed class ShadowObjectRegistry
_entityShapes[state.EntityId] = state.Shapes;
if (state.Suspended)
_suspendedEntities.Add(state.EntityId);
if (state.SuspendedCellIds is not null)
_suspendedEntityCells[state.EntityId] = state.SuspendedCellIds;
if (state.WithdrawnPrefixes is not null)
{
_withdrawnPrefixesByOwner[state.EntityId] = state.WithdrawnPrefixes;
@ -1614,6 +1838,7 @@ public sealed class ShadowObjectRegistry
List<uint>? CellIds,
IReadOnlyList<PreparedShadowCellRows> Rows,
bool Suspended,
List<uint>? SuspendedCellIds,
HashSet<uint>? WithdrawnPrefixes);
internal sealed record PreparedShadowCellRows(
@ -1629,6 +1854,7 @@ public sealed class ShadowObjectRegistry
_cells.Clear();
_entityToCells.Clear();
_suspendedEntities.Clear();
_suspendedEntityCells.Clear();
_withdrawnPrefixesByOwner.Clear();
_entityShapes.Clear();
_entityReg.Clear();