perf(streaming): cursor publication across frame budgets

This commit is contained in:
Erik 2026-07-24 19:10:18 +02:00
parent bb16f74fd4
commit 98f1ac8934
32 changed files with 2215 additions and 823 deletions

View file

@ -455,6 +455,18 @@ public sealed class ShadowObjectRegistry
/// cells hydrated after a server spawn landed.
/// </summary>
public void RefloodLandblock(uint landblockId)
{
uint[] owners = CaptureRefloodOwnersForLandblock(landblockId);
for (int i = 0; i < owners.Length; i++)
RefloodOwnerForLandblock(owners[i], landblockId);
}
/// <summary>
/// Captures the stable ordered owner set touched by one landblock reflood.
/// App-layer streaming can retain this receipt and advance one owner per
/// frame without changing the collision registry's ownership rules.
/// </summary>
public uint[] CaptureRefloodOwnersForLandblock(uint landblockId)
{
uint lbPrefix = landblockId & 0xFFFF0000u;
var toReflood = new HashSet<uint>();
@ -487,39 +499,81 @@ public sealed class ShadowObjectRegistry
}
}
foreach (uint entityId in toReflood)
uint[] ordered = toReflood.ToArray();
Array.Sort(ordered);
return ordered;
}
/// <summary>
/// Re-runs one owner from a retained landblock-reflood receipt. A removed,
/// suspended, or otherwise superseded owner is an idempotent no-op.
/// </summary>
public void RefloodOwnerForLandblock(uint entityId, uint landblockId)
{
uint lbPrefix = landblockId & 0xFFFF0000u;
if (_suspendedEntities.Contains(entityId)
|| !_entityReg.TryGetValue(
entityId,
out RegistrationRecord? reg))
{
var reg = _entityReg[entityId];
_withdrawnPrefixesByOwner.TryGetValue(entityId, out var withdrawnBeforeReflood);
if (reg.IsMultiPart && _entityShapes.TryGetValue(entityId, out var shapes))
{
RegisterMultiPart(entityId, reg.EntityWorldPos, reg.EntityWorldRot, shapes,
reg.State, reg.Flags, 0f, 0f, lbPrefix,
reg.SeedCellId, reg.IsStatic);
}
else
{
Register(entityId, reg.GfxObjId, reg.EntityWorldPos, reg.EntityWorldRot,
reg.Radius, 0f, 0f, lbPrefix,
reg.CollisionType, reg.CylHeight, reg.Scale,
reg.State, reg.Flags, reg.SeedCellId, reg.IsStatic);
}
return;
}
// Register is also the authoritative movement/replacement API and
// therefore clears obsolete markers. Only this streaming reflood
// operation preserves the still-missing prefixes across replacement.
if (withdrawnBeforeReflood is not null)
_withdrawnPrefixesByOwner[entityId] = withdrawnBeforeReflood;
_withdrawnPrefixesByOwner.TryGetValue(
entityId,
out var withdrawnBeforeReflood);
if (reg.IsMultiPart
&& _entityShapes.TryGetValue(entityId, out var shapes))
{
RegisterMultiPart(
entityId,
reg.EntityWorldPos,
reg.EntityWorldRot,
shapes,
reg.State,
reg.Flags,
0f,
0f,
lbPrefix,
reg.SeedCellId,
reg.IsStatic);
}
else
{
Register(
entityId,
reg.GfxObjId,
reg.EntityWorldPos,
reg.EntityWorldRot,
reg.Radius,
0f,
0f,
lbPrefix,
reg.CollisionType,
reg.CylHeight,
reg.Scale,
reg.State,
reg.Flags,
reg.SeedCellId,
reg.IsStatic);
}
if (_entityToCells.TryGetValue(entityId, out var refreshedCells)
&& refreshedCells.Exists(cell =>
(cell & 0xFFFF0000u) == lbPrefix)
&& _withdrawnPrefixesByOwner.TryGetValue(entityId, out var withdrawn))
{
withdrawn.Remove(lbPrefix);
if (withdrawn.Count == 0)
_withdrawnPrefixesByOwner.Remove(entityId);
}
// Register is also the authoritative movement/replacement API and
// therefore clears obsolete markers. Only this streaming reflood
// operation preserves the still-missing prefixes across replacement.
if (withdrawnBeforeReflood is not null)
_withdrawnPrefixesByOwner[entityId] = withdrawnBeforeReflood;
if (_entityToCells.TryGetValue(entityId, out var refreshedCells)
&& refreshedCells.Exists(cell =>
(cell & 0xFFFF0000u) == lbPrefix)
&& _withdrawnPrefixesByOwner.TryGetValue(
entityId,
out var withdrawn))
{
withdrawn.Remove(lbPrefix);
if (withdrawn.Count == 0)
_withdrawnPrefixesByOwner.Remove(entityId);
}
}
@ -596,6 +650,17 @@ public sealed class ShadowObjectRegistry
/// are deliberately retained for spatial reflood after streaming changes.
/// </summary>
public void DeregisterStaticOwnersForLandblock(uint landblockId)
{
uint[] owners = CaptureStaticOwnersForLandblock(landblockId);
for (int i = 0; i < owners.Length; i++)
DeregisterStaticOwnerForLandblock(owners[i], landblockId);
}
/// <summary>
/// Captures a stable ordered receipt for static collision owners rooted in
/// one landblock.
/// </summary>
public uint[] CaptureStaticOwnersForLandblock(uint landblockId)
{
uint prefix = landblockId & 0xFFFF0000u;
var owners = new List<uint>();
@ -607,9 +672,27 @@ public sealed class ShadowObjectRegistry
owners.Add(entityId);
}
}
owners.Sort();
return owners.ToArray();
}
foreach (uint entityId in owners)
/// <summary>
/// Removes one static owner from a retained landblock receipt. If the
/// owner was already removed or rebound elsewhere, the operation no-ops.
/// </summary>
public void DeregisterStaticOwnerForLandblock(
uint entityId,
uint landblockId)
{
uint prefix = landblockId & 0xFFFF0000u;
if (_entityReg.TryGetValue(
entityId,
out RegistrationRecord? registration)
&& registration.IsStatic
&& (registration.SeedCellId & 0xFFFF0000u) == prefix)
{
Deregister(entityId);
}
}
/// <summary>