fix(physics): activate collision generations atomically
This commit is contained in:
parent
3e0f3b6206
commit
be94bc9b06
18 changed files with 1402 additions and 80 deletions
|
|
@ -12,7 +12,7 @@ namespace AcDream.App.Streaming;
|
|||
/// publication. The render publisher commits buildings and EnvCells between
|
||||
/// these stages without recomputing the captured origin.
|
||||
/// </summary>
|
||||
public sealed class LandblockPhysicsPublication
|
||||
public sealed class LandblockPhysicsPublication : IDisposable
|
||||
{
|
||||
internal LandblockPhysicsPublication(
|
||||
object owner,
|
||||
|
|
@ -21,7 +21,8 @@ public sealed class LandblockPhysicsPublication
|
|||
uint currentCellId,
|
||||
BuildingInfo[] buildings,
|
||||
uint[] priorStaticOwnerIds,
|
||||
RuntimeCollisionAdmission collisionAdmission)
|
||||
RuntimeCollisionAdmission collisionAdmission,
|
||||
PreparedLandblockCollisionGeneration preparedGeneration)
|
||||
{
|
||||
Owner = owner;
|
||||
Build = build;
|
||||
|
|
@ -30,6 +31,7 @@ public sealed class LandblockPhysicsPublication
|
|||
Buildings = buildings;
|
||||
PriorStaticOwnerIds = priorStaticOwnerIds;
|
||||
CollisionAdmission = collisionAdmission;
|
||||
PreparedGeneration = preparedGeneration;
|
||||
}
|
||||
|
||||
internal object Owner { get; }
|
||||
|
|
@ -38,6 +40,9 @@ public sealed class LandblockPhysicsPublication
|
|||
internal BuildingInfo[] Buildings { get; }
|
||||
internal uint[] PriorStaticOwnerIds { get; }
|
||||
internal RuntimeCollisionAdmission CollisionAdmission { get; }
|
||||
internal PreparedLandblockCollisionGeneration PreparedGeneration { get; }
|
||||
internal PhysicsDataCache StagingCache => PreparedGeneration.DataCache;
|
||||
internal PhysicsEngine StagingEngine => PreparedGeneration.Engine;
|
||||
internal SortedSet<uint> GfxObjectIdSet { get; } = new();
|
||||
internal uint[] GfxObjectIds { get; set; } = Array.Empty<uint>();
|
||||
internal int PreparationCursor { get; set; }
|
||||
|
|
@ -64,6 +69,12 @@ public sealed class LandblockPhysicsPublication
|
|||
internal bool BeginCommitted { get; set; }
|
||||
internal bool CompletionCommitted { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!CompletionCommitted)
|
||||
PreparedGeneration.Dispose();
|
||||
}
|
||||
|
||||
public uint LandblockId => Build.Landblock.LandblockId;
|
||||
public Vector3 Origin { get; }
|
||||
}
|
||||
|
|
@ -204,6 +215,8 @@ public sealed class LandblockPhysicsPublisher
|
|||
build.Landblock.PhysicsDats ?? PhysicsDatBundle.Empty;
|
||||
BuildingInfo[] buildings = datBundle.Info?.Buildings.ToArray()
|
||||
?? Array.Empty<BuildingInfo>();
|
||||
RuntimeCollisionAdmission collisionAdmission =
|
||||
_physics.BeginCollisionAdmission(build.Landblock.LandblockId);
|
||||
var publication = new LandblockPhysicsPublication(
|
||||
_receiptOwner,
|
||||
build,
|
||||
|
|
@ -212,11 +225,14 @@ public sealed class LandblockPhysicsPublisher
|
|||
buildings,
|
||||
_physicsEngine.ShadowObjects.CaptureStaticOwnersForLandblock(
|
||||
build.Landblock.LandblockId),
|
||||
_physics.BeginCollisionAdmission(
|
||||
build.Landblock.LandblockId));
|
||||
collisionAdmission,
|
||||
_physics.PrepareCollisionGeneration(collisionAdmission));
|
||||
publication.SetupObjectIds = build.Collisions is { } collisions
|
||||
? [.. collisions.SetupIds]
|
||||
: datBundle.Setups.Keys.Order().ToArray();
|
||||
publication.PreparedGeneration.SetAssetClosure(
|
||||
publication.GfxObjectIds,
|
||||
publication.SetupObjectIds);
|
||||
return publication;
|
||||
}
|
||||
|
||||
|
|
@ -237,6 +253,9 @@ public sealed class LandblockPhysicsPublisher
|
|||
if (publication.Build.Collisions is { } collisions)
|
||||
{
|
||||
publication.GfxObjectIds = [.. collisions.GfxObjIds];
|
||||
publication.PreparedGeneration.SetAssetClosure(
|
||||
publication.GfxObjectIds,
|
||||
publication.SetupObjectIds);
|
||||
publication.PreparationCursor = entities.Count;
|
||||
publication.PreparationCommitted = true;
|
||||
return true;
|
||||
|
|
@ -256,6 +275,9 @@ public sealed class LandblockPhysicsPublisher
|
|||
}
|
||||
|
||||
publication.GfxObjectIds = publication.GfxObjectIdSet.ToArray();
|
||||
publication.PreparedGeneration.SetAssetClosure(
|
||||
publication.GfxObjectIds,
|
||||
publication.SetupObjectIds);
|
||||
publication.PreparationCommitted = true;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -299,9 +321,9 @@ public sealed class LandblockPhysicsPublisher
|
|||
// CacheCellStruct/CacheBuilding use first-wins semantics within one
|
||||
// publication, so the replacement pass starts with one exact
|
||||
// landblock-scoped withdrawal.
|
||||
_physicsDataCache.RemoveCellsForLandblock(landblock.LandblockId);
|
||||
_physicsDataCache.RemoveBuildingsForLandblock(landblock.LandblockId);
|
||||
_physicsDataCache.CellGraph.RemoveEnvCellsForLandblock(
|
||||
publication.StagingCache.RemoveCellsForLandblock(landblock.LandblockId);
|
||||
publication.StagingCache.RemoveBuildingsForLandblock(landblock.LandblockId);
|
||||
publication.StagingCache.CellGraph.RemoveEnvCellsForLandblock(
|
||||
landblock.LandblockId);
|
||||
publication.PriorCacheRemoved = true;
|
||||
}
|
||||
|
|
@ -330,6 +352,7 @@ public sealed class LandblockPhysicsPublisher
|
|||
PublishBuilding(
|
||||
landblock,
|
||||
datBundle,
|
||||
publication.StagingCache,
|
||||
publication.TerrainSurface,
|
||||
origin,
|
||||
publication.Buildings[publication.BuildingCursor]);
|
||||
|
|
@ -337,8 +360,9 @@ public sealed class LandblockPhysicsPublisher
|
|||
}
|
||||
else if (!publication.BaseCommitted)
|
||||
{
|
||||
_physics.AdmitCollisionAssets(
|
||||
_physics.StageCollisionAssets(
|
||||
publication.CollisionAdmission,
|
||||
publication.PreparedGeneration,
|
||||
new RuntimeLandblockCollisionAssets(
|
||||
landblock.LandblockId,
|
||||
publication.TerrainSurface,
|
||||
|
|
@ -409,7 +433,7 @@ public sealed class LandblockPhysicsPublisher
|
|||
gfxObjectId,
|
||||
out FlatGfxObjCollisionAsset? prepared) == true)
|
||||
{
|
||||
_physicsDataCache.CacheGfxObj(gfxObjectId, prepared);
|
||||
publication.StagingCache.CacheGfxObj(gfxObjectId, prepared);
|
||||
}
|
||||
else if (datBundle.GfxObjs.TryGetValue(
|
||||
gfxObjectId,
|
||||
|
|
@ -417,7 +441,7 @@ public sealed class LandblockPhysicsPublisher
|
|||
{
|
||||
// Graph-oracle fixture seam. Production near builds always
|
||||
// carry the strict prepared closure.
|
||||
_physicsDataCache.CacheGfxObj(gfxObjectId, source);
|
||||
publication.StagingCache.CacheGfxObj(gfxObjectId, source);
|
||||
}
|
||||
publication.GfxCursor++;
|
||||
_gfxCacheTicks += Stopwatch.GetTimestamp() - cacheStarted;
|
||||
|
|
@ -430,19 +454,19 @@ public sealed class LandblockPhysicsPublisher
|
|||
setupId,
|
||||
out FlatSetupCollision? prepared) == true)
|
||||
{
|
||||
_physicsDataCache.CacheSetup(setupId, prepared);
|
||||
publication.StagingCache.CacheSetup(setupId, prepared);
|
||||
}
|
||||
else if (datBundle.Setups.TryGetValue(setupId, out var source))
|
||||
{
|
||||
// Graph-oracle fixture seam only.
|
||||
_physicsDataCache.CacheSetup(setupId, source);
|
||||
publication.StagingCache.CacheSetup(setupId, source);
|
||||
}
|
||||
publication.SetupCursor++;
|
||||
}
|
||||
else if (publication.PriorStaticCursor
|
||||
< publication.PriorStaticOwnerIds.Length)
|
||||
{
|
||||
_physicsEngine.ShadowObjects.DeregisterStaticOwnerForLandblock(
|
||||
publication.StagingEngine.ShadowObjects.DeregisterStaticOwnerForLandblock(
|
||||
publication.PriorStaticOwnerIds[
|
||||
publication.PriorStaticCursor],
|
||||
landblock.LandblockId);
|
||||
|
|
@ -458,15 +482,17 @@ public sealed class LandblockPhysicsPublisher
|
|||
else if (publication.RefloodOwnerIds is null)
|
||||
{
|
||||
publication.RefloodOwnerIds =
|
||||
_physicsEngine.ShadowObjects
|
||||
.CaptureRefloodOwnersForLandblock(landblock.LandblockId);
|
||||
_physics.CaptureCollisionDynamicOwners(
|
||||
publication.CollisionAdmission,
|
||||
publication.PreparedGeneration);
|
||||
}
|
||||
else if (publication.RefloodCursor
|
||||
< publication.RefloodOwnerIds.Length)
|
||||
{
|
||||
_physicsEngine.ShadowObjects.RefloodOwnerForLandblock(
|
||||
publication.RefloodOwnerIds[publication.RefloodCursor],
|
||||
landblock.LandblockId);
|
||||
_physics.RefreshCollisionDynamicOwner(
|
||||
publication.CollisionAdmission,
|
||||
publication.PreparedGeneration,
|
||||
publication.RefloodOwnerIds[publication.RefloodCursor]);
|
||||
publication.RefloodCursor++;
|
||||
}
|
||||
else if (!publication.RefloodCommitted)
|
||||
|
|
@ -478,14 +504,24 @@ public sealed class LandblockPhysicsPublisher
|
|||
$"lb 0x{landblock.LandblockId:X8}: scenery tried={publication.SceneryTried} " +
|
||||
$"(outdoorNone={publication.NoCollisionCount})");
|
||||
}
|
||||
LogMissingSceneryBounds(landblock);
|
||||
_refloodCount++;
|
||||
LogMissingSceneryBounds(landblock, publication.StagingCache);
|
||||
publication.RefloodCommitted = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_physics.CompleteCollisionAdmission(
|
||||
publication.CollisionAdmission);
|
||||
RuntimeCollisionGenerationCommit commit =
|
||||
_physics.CommitCollisionGeneration(
|
||||
publication.CollisionAdmission,
|
||||
publication.PreparedGeneration);
|
||||
if (!commit.Committed)
|
||||
{
|
||||
publication.RefloodOwnerIds = commit.DirtyDynamicOwnerIds;
|
||||
publication.RefloodCursor = 0;
|
||||
publication.RefloodCommitted = false;
|
||||
_completePublishTicks += Stopwatch.GetTimestamp() - started;
|
||||
return false;
|
||||
}
|
||||
_refloodCount++;
|
||||
_staticBspOwnerCount += publication.BspOwnerCount;
|
||||
_staticCylinderOwnerCount += publication.CylinderOwnerCount;
|
||||
publication.CompletionCommitted = true;
|
||||
|
|
@ -545,7 +581,7 @@ public sealed class LandblockPhysicsPublisher
|
|||
Matrix4x4.CreateFromQuaternion(rotation)
|
||||
* Matrix4x4.CreateTranslation(cellOriginWorld);
|
||||
|
||||
_physicsDataCache.CacheCellStruct(
|
||||
publication.StagingCache.CacheCellStruct(
|
||||
envCellId,
|
||||
envCell,
|
||||
physicsCellTransform,
|
||||
|
|
@ -623,7 +659,7 @@ public sealed class LandblockPhysicsPublisher
|
|||
Matrix4x4 physicsCellTransform =
|
||||
Matrix4x4.CreateFromQuaternion(rotation)
|
||||
* Matrix4x4.CreateTranslation(cellOriginWorld);
|
||||
_physicsDataCache.CacheCellStruct(
|
||||
publication.StagingCache.CacheCellStruct(
|
||||
envCellId,
|
||||
envCell,
|
||||
cellStruct,
|
||||
|
|
@ -687,6 +723,7 @@ public sealed class LandblockPhysicsPublisher
|
|||
private void PublishBuilding(
|
||||
LoadedLandblock landblock,
|
||||
PhysicsDatBundle datBundle,
|
||||
PhysicsDataCache cache,
|
||||
TerrainSurface terrainSurface,
|
||||
Vector3 origin,
|
||||
BuildingInfo building)
|
||||
|
|
@ -720,7 +757,7 @@ public sealed class LandblockPhysicsPublisher
|
|||
? setup.Parts[0]
|
||||
: 0u;
|
||||
}
|
||||
_physicsDataCache.CacheBuilding(
|
||||
cache.CacheBuilding(
|
||||
landcellId,
|
||||
portals,
|
||||
buildingTransform,
|
||||
|
|
@ -752,11 +789,11 @@ public sealed class LandblockPhysicsPublisher
|
|||
ShadowShapeBuilder.FromLandblockBspParts(
|
||||
entity.MeshRefs,
|
||||
entity.IsBuildingShell,
|
||||
_physicsDataCache.GetGfxObj);
|
||||
publication.StagingCache.GetGfxObj);
|
||||
entityBspCount = bspShapes.Count;
|
||||
if (entityBspCount > 0)
|
||||
{
|
||||
_physicsEngine.ShadowObjects.RegisterMultiPart(
|
||||
publication.StagingEngine.ShadowObjects.RegisterMultiPart(
|
||||
entity.Id,
|
||||
entity.Position,
|
||||
entity.Rotation,
|
||||
|
|
@ -772,9 +809,9 @@ public sealed class LandblockPhysicsPublisher
|
|||
}
|
||||
|
||||
FlatSetupCollision? setup =
|
||||
_physicsDataCache.GetFlatSetup(entity.SourceGfxObjOrSetupId);
|
||||
publication.StagingCache.GetFlatSetup(entity.SourceGfxObjOrSetupId);
|
||||
if (setup is null
|
||||
&& _physicsDataCache.GetSetup(
|
||||
&& publication.StagingCache.GetSetup(
|
||||
entity.SourceGfxObjOrSetupId) is { } graphSetup)
|
||||
{
|
||||
// Graph-oracle fixture seam only. Production Setup publication is
|
||||
|
|
@ -858,7 +895,7 @@ public sealed class LandblockPhysicsPublisher
|
|||
|
||||
if (setupShapes.Count > 0)
|
||||
{
|
||||
_physicsEngine.ShadowObjects.RegisterMultiPart(
|
||||
publication.StagingEngine.ShadowObjects.RegisterMultiPart(
|
||||
entity.Id,
|
||||
entity.Position,
|
||||
entity.Rotation,
|
||||
|
|
@ -917,7 +954,9 @@ public sealed class LandblockPhysicsPublisher
|
|||
}
|
||||
}
|
||||
|
||||
private void LogMissingSceneryBounds(LoadedLandblock landblock)
|
||||
private static void LogMissingSceneryBounds(
|
||||
LoadedLandblock landblock,
|
||||
PhysicsDataCache cache)
|
||||
{
|
||||
if (!PhysicsDiagnostics.ProbeBuildingEnabled)
|
||||
return;
|
||||
|
|
@ -933,7 +972,7 @@ public sealed class LandblockPhysicsPublisher
|
|||
foreach (MeshRef meshRef in entity.MeshRefs)
|
||||
{
|
||||
GfxObjVisualBounds? bounds =
|
||||
_physicsDataCache.GetVisualBounds(meshRef.GfxObjId);
|
||||
cache.GetVisualBounds(meshRef.GfxObjId);
|
||||
if (bounds is not null && bounds.Radius > 0f)
|
||||
{
|
||||
hasBounds = true;
|
||||
|
|
|
|||
|
|
@ -215,6 +215,18 @@ public sealed class LandblockPresentationPipeline
|
|||
public IReadOnlyList<LandblockStreamResult> GetPendingPublicationResults() =>
|
||||
_publications.Keys.ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Cancels retained publication receipts during a generation reset. A
|
||||
/// collision receipt owns only its private staging world until activation,
|
||||
/// so cancellation cannot withdraw or partially replace the active world.
|
||||
/// </summary>
|
||||
internal void CancelPendingPublications()
|
||||
{
|
||||
foreach (PublicationTransaction transaction in _publications.Values)
|
||||
transaction.PhysicsPublication?.Dispose();
|
||||
_publications.Clear();
|
||||
}
|
||||
|
||||
public void ResumePublication(LandblockStreamResult result)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(result);
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ public sealed class StreamingController
|
|||
public bool GenerationAdvanced;
|
||||
public bool PendingLoadsCleared;
|
||||
public bool CompletionQueueCleared;
|
||||
public bool PendingPublicationsCleared;
|
||||
public bool RegionCleared;
|
||||
public bool SpatialGenerationDetached;
|
||||
public bool PreparationCommitted;
|
||||
|
|
@ -45,6 +46,7 @@ public sealed class StreamingController
|
|||
public bool GenerationAdvanced;
|
||||
public bool PendingLoadsCleared;
|
||||
public bool CompletionQueueCleared;
|
||||
public bool PendingPublicationsCleared;
|
||||
public bool RegionCleared;
|
||||
public List<uint>? ResidentIds;
|
||||
public IEnumerator<uint>? ResidentEnumerator;
|
||||
|
|
@ -1358,6 +1360,22 @@ public sealed class StreamingController
|
|||
}
|
||||
if (!transaction.RegionCleared)
|
||||
{
|
||||
if (!transaction.PendingPublicationsCleared)
|
||||
{
|
||||
if (!TryRunStreamingWork(
|
||||
meter,
|
||||
new StreamingWorkCost(EntityOperations: 1),
|
||||
"recenter-cancel-publications",
|
||||
() =>
|
||||
{
|
||||
_presentation.CancelPendingPublications();
|
||||
transaction.PendingPublicationsCleared = true;
|
||||
return true;
|
||||
}))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!TryRunStreamingWork(
|
||||
meter,
|
||||
new StreamingWorkCost(EntityOperations: 1),
|
||||
|
|
@ -1499,6 +1517,22 @@ public sealed class StreamingController
|
|||
|
||||
if (!transaction.RegionCleared)
|
||||
{
|
||||
if (!transaction.PendingPublicationsCleared)
|
||||
{
|
||||
if (!TryRunStreamingWork(
|
||||
meter,
|
||||
new StreamingWorkCost(EntityOperations: 1),
|
||||
"reload-cancel-publications",
|
||||
() =>
|
||||
{
|
||||
_presentation.CancelPendingPublications();
|
||||
transaction.PendingPublicationsCleared = true;
|
||||
return true;
|
||||
}))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!TryRunStreamingWork(
|
||||
meter,
|
||||
new StreamingWorkCost(EntityOperations: 1),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue