fix(physics): seal collision generations before activation

This commit is contained in:
Erik 2026-07-31 15:53:05 +02:00
parent be94bc9b06
commit d94145e6b8
15 changed files with 1556 additions and 410 deletions

View file

@ -18,6 +18,77 @@ internal interface IHeadlessCollisionNeighborhood
bool IsReady(uint fullCellId);
}
internal static class HeadlessCollisionGenerationTransaction
{
internal static RuntimeCollisionGenerationCommit Execute(
RuntimePhysicsState physics,
uint landblockId,
Action<RuntimeCollisionAdmission>? afterAdmission,
Action<RuntimeCollisionAdmission,
PreparedLandblockCollisionGeneration> stage)
{
ArgumentNullException.ThrowIfNull(physics);
ArgumentNullException.ThrowIfNull(stage);
RuntimeCollisionAdmission admission =
physics.BeginCollisionAdmission(landblockId);
PreparedLandblockCollisionGeneration? prepared = null;
bool committed = false;
try
{
// This hook exists so the exact post-admission/pre-prepare failure
// boundary remains covered. Production does not install one.
afterAdmission?.Invoke(admission);
prepared = physics.PrepareCollisionGeneration(admission);
stage(admission, prepared);
RuntimeCollisionOwnerCaptureStep ownerCapture;
do
{
ownerCapture = physics.AdvanceCollisionRetainedOwnerCapture(
admission,
prepared);
}
while (!ownerCapture.Completed);
foreach (uint ownerId in prepared.RetainedOwnerIds)
{
physics.RefreshCollisionRetainedOwner(
admission,
prepared,
ownerId);
}
RuntimeCollisionSealStep seal;
do
{
seal = physics.AdvanceCollisionGenerationSeal(
admission,
prepared);
}
while (!seal.Completed && !seal.Restarted);
if (!seal.Completed)
{
throw new InvalidOperationException(
"Headless collision owner set changed during synchronous sealing.");
}
RuntimeCollisionGenerationCommit result =
physics.CommitCollisionGeneration(admission, prepared);
if (!result.Committed)
{
throw new InvalidOperationException(
"Headless collision generation changed during synchronous publication.");
}
committed = true;
return result;
}
finally
{
if (!committed)
physics.CancelCollisionGeneration(admission, prepared);
}
}
}
/// <summary>
/// Per-session mutable collision publication over process-shared immutable
/// DAT and pak inputs. Every session retains its own engine, data cache,
@ -172,81 +243,58 @@ internal sealed class HeadlessCollisionNeighborhood
_content.PreparedCollision,
landblock);
RuntimePhysicsState physics =
_runtime.EntityObjects.Physics;
RuntimeCollisionAdmission admission =
physics.BeginCollisionAdmission(landblockId);
using PreparedLandblockCollisionGeneration prepared =
physics.PrepareCollisionGeneration(admission);
prepared.SetAssetClosure(
[.. collisions.GfxObjIds],
[.. collisions.SetupIds]);
PhysicsDataCache cache = prepared.DataCache;
TerrainSurface terrain =
LandblockPhysicsContentBuilder.BuildTerrainSurface(
landblock,
_content.HeightTable.AsSpan());
var cellSurfaces = new List<CellSurface>();
var portalPlanes = new List<PortalPlane>();
LandblockPhysicsContentBuilder.PublishPreparedCells(
cache,
landblock,
collisions,
origin,
cellSurfaces,
portalPlanes);
LandblockPhysicsContentBuilder.CacheBuildings(
cache,
landblock,
terrain,
origin);
LandblockPhysicsContentBuilder.CachePreparedObjects(
cache,
collisions);
try
{
physics.StageCollisionAssets(
admission,
prepared,
new RuntimeLandblockCollisionAssets(
landblockId,
terrain,
cellSurfaces,
portalPlanes,
origin.X,
origin.Y,
currentCellId));
_ = LandblockPhysicsContentBuilder
.PublishStaticCollision(
prepared.Engine,
RuntimePhysicsState physics = _runtime.EntityObjects.Physics;
_ = HeadlessCollisionGenerationTransaction.Execute(
physics,
landblockId,
afterAdmission: null,
(admission, prepared) =>
{
prepared.SetAssetClosure(
[.. collisions.GfxObjIds],
[.. collisions.SetupIds]);
PhysicsDataCache cache = prepared.DataCache;
TerrainSurface terrain =
LandblockPhysicsContentBuilder.BuildTerrainSurface(
landblock,
_content.HeightTable.AsSpan());
var cellSurfaces = new List<CellSurface>();
var portalPlanes = new List<PortalPlane>();
LandblockPhysicsContentBuilder.PublishPreparedCells(
cache,
landblock,
collisions,
origin,
cellSurfaces,
portalPlanes);
LandblockPhysicsContentBuilder.CacheBuildings(
cache,
landblock,
terrain,
origin);
foreach (uint ownerId in physics.CaptureCollisionDynamicOwners(
admission,
prepared))
{
physics.RefreshCollisionDynamicOwner(
LandblockPhysicsContentBuilder.CachePreparedObjects(
cache,
collisions);
physics.StageCollisionAssets(
admission,
prepared,
ownerId);
}
RuntimeCollisionGenerationCommit commit =
physics.CommitCollisionGeneration(admission, prepared);
if (!commit.Committed)
{
throw new InvalidOperationException(
"Headless collision generation changed during synchronous publication.");
}
_resident.Add(CanonicalLandblock(landblockId));
}
catch
{
_ = physics.WithdrawCollision(landblockId);
throw;
}
new RuntimeLandblockCollisionAssets(
landblockId,
terrain,
cellSurfaces,
portalPlanes,
origin.X,
origin.Y,
currentCellId));
_ = LandblockPhysicsContentBuilder
.PublishStaticCollision(
prepared.Engine,
cache,
landblock,
collisions,
origin);
});
_resident.Add(CanonicalLandblock(landblockId));
}
private void RetireAll()