feat(runtime): atomically replace collision generations

This commit is contained in:
Erik 2026-08-01 17:33:34 +02:00
parent 99bf1751bb
commit 9b0f59bd1b
21 changed files with 2435 additions and 408 deletions

View file

@ -18,9 +18,37 @@ internal interface IHeadlessCollisionNeighborhood
bool IsReady(uint fullCellId);
}
internal static class HeadlessCollisionGenerationTransaction
internal readonly record struct HeadlessCollisionGenerationAdvance(
bool Completed,
bool Progressed,
bool WaitingForProjectionAcknowledgement,
bool YieldToCaller);
internal sealed class HeadlessCollisionGenerationTransaction
{
internal static RuntimeCollisionGenerationCommit Execute(
private readonly RuntimePhysicsState _physics;
private readonly RuntimeCollisionAdmission _admission;
private readonly PreparedLandblockCollisionGeneration _prepared;
private bool _ownerCaptureCommitted;
private int _refreshCursor;
private bool _sealCommitted;
private HeadlessCollisionGenerationTransaction(
RuntimePhysicsState physics,
RuntimeCollisionAdmission admission,
PreparedLandblockCollisionGeneration prepared)
{
_physics = physics;
_admission = admission;
_prepared = prepared;
}
internal uint LandblockId => _admission.LandblockId;
internal bool EngineMutationCommitted { get; private set; }
internal bool CompletionCommitted { get; private set; }
internal bool CancellationRequested { get; private set; }
internal static HeadlessCollisionGenerationTransaction Begin(
RuntimePhysicsState physics,
uint landblockId,
Action<RuntimeCollisionAdmission>? afterAdmission,
@ -33,60 +61,103 @@ internal static class HeadlessCollisionGenerationTransaction
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;
return new HeadlessCollisionGenerationTransaction(
physics,
admission,
prepared);
}
finally
catch (Exception publicationError)
{
if (!committed)
physics.CancelCollisionGeneration(admission, prepared);
if (!physics.CancelCollisionGeneration(admission, prepared))
{
throw new AggregateException(
"Headless collision preparation failed and its pre-engine cancellation did not converge.",
publicationError);
}
throw;
}
}
internal HeadlessCollisionGenerationAdvance Advance()
{
if (CancellationRequested)
throw new InvalidOperationException(
"A cancelled headless collision generation cannot resume.");
if (CompletionCommitted)
return new(true, false, false, false);
if (!EngineMutationCommitted)
{
if (!_ownerCaptureCommitted)
{
RuntimeCollisionOwnerCaptureStep capture =
_physics.AdvanceCollisionRetainedOwnerCapture(
_admission,
_prepared);
_ownerCaptureCommitted = capture.Completed;
return new(false, true, false, false);
}
if (_refreshCursor < _prepared.RetainedOwnerIds.Count)
{
_physics.RefreshCollisionRetainedOwner(
_admission,
_prepared,
_prepared.RetainedOwnerIds[_refreshCursor]);
_refreshCursor++;
return new(false, true, false, false);
}
if (!_sealCommitted)
{
RuntimeCollisionSealStep seal =
_physics.AdvanceCollisionGenerationSeal(
_admission,
_prepared);
_sealCommitted = seal.Completed;
if (seal.Restarted)
{
_ownerCaptureCommitted = false;
_refreshCursor = 0;
}
return new(false, true, false, false);
}
}
RuntimeCollisionGenerationCommit commit =
_physics.CommitCollisionGeneration(_admission, _prepared);
EngineMutationCommitted |= commit.EngineCommitted;
CompletionCommitted = commit.Completed;
bool waiting = !commit.Completed
&& _physics.CaptureOwnership()
.PendingCollisionPrefixProjectionCount != 0;
if (!commit.Completed && !commit.EngineCommitted)
_sealCommitted = false;
return new(
commit.Completed,
Progressed: true,
WaitingForProjectionAcknowledgement: waiting,
YieldToCaller: !commit.Completed);
}
internal bool TryCancel()
{
if (CompletionCommitted)
return true;
CancellationRequested = true;
bool completed = _physics.CancelCollisionGeneration(
_admission,
_prepared);
if (completed && EngineMutationCommitted)
CompletionCommitted = true;
return completed;
}
}
/// <summary>
@ -97,10 +168,23 @@ internal static class HeadlessCollisionGenerationTransaction
internal sealed class HeadlessCollisionNeighborhood
: IHeadlessCollisionNeighborhood
{
private readonly record struct PublicationSpec(
uint LandblockId,
Vector3 Origin,
bool Required);
private readonly GameRuntime _runtime;
private readonly HeadlessProcessContentOwner
.HeadlessProcessContentLease _content;
private readonly HashSet<uint> _resident = [];
private readonly Queue<uint> _retirementQueue = [];
private readonly Queue<PublicationSpec> _publicationQueue = [];
private HeadlessCollisionGenerationTransaction? _pendingPublication;
private bool _pendingPublicationCancellation;
private bool _resetRequired;
private bool _publicationPlanBuilt;
private uint _requestedCenterLandblock;
private uint _requestedFullCell;
private uint _centerLandblock;
internal HeadlessCollisionNeighborhood(
@ -122,6 +206,21 @@ internal sealed class HeadlessCollisionNeighborhood
nameof(fullCellId),
"A collision neighborhood requires a real destination cell.");
}
if (_requestedCenterLandblock != center)
{
_requestedCenterLandblock = center;
_requestedFullCell = fullCellId;
_resetRequired = true;
_publicationPlanBuilt = false;
_publicationQueue.Clear();
_pendingPublicationCancellation =
_pendingPublication is not null;
}
else
{
_requestedFullCell = fullCellId;
}
if (_centerLandblock == center
&& _resident.Contains(center)
&& _runtime.EntityObjects.Physics.Engine
@ -131,55 +230,17 @@ internal sealed class HeadlessCollisionNeighborhood
.UpdatePlayerCurrCell(fullCellId);
return;
}
RetireAll();
int centerX = (int)((center >> 24) & 0xFFu);
int centerY = (int)((center >> 16) & 0xFFu);
try
{
PublishOne(
center,
Vector3.Zero,
fullCellId,
required: true);
for (int dx = -1; dx <= 1; dx++)
{
for (int dy = -1; dy <= 1; dy++)
{
if (dx == 0 && dy == 0)
continue;
int landblockX = centerX + dx;
int landblockY = centerY + dy;
if ((uint)landblockX > byte.MaxValue
|| (uint)landblockY > byte.MaxValue)
{
continue;
}
uint landblockId =
((uint)landblockX << 24)
| ((uint)landblockY << 16)
| 0xFFFFu;
PublishOne(
landblockId,
new Vector3(dx * 192f, dy * 192f, 0f),
fullCellId,
required: false);
}
}
_centerLandblock = center;
_runtime.EntityObjects.Physics.Engine
.UpdatePlayerCurrCell(fullCellId);
}
catch
{
RetireAll();
throw;
}
AdvanceWork();
}
public bool IsReady(uint fullCellId)
{
uint center = CanonicalLandblock(fullCellId);
if (_requestedCenterLandblock == center)
{
_requestedFullCell = fullCellId;
AdvanceWork();
}
if (_centerLandblock != center
|| !_resident.Contains(center)
|| !_runtime.EntityObjects.Physics.Engine
@ -192,7 +253,7 @@ internal sealed class HeadlessCollisionNeighborhood
.GetCellStruct(fullCellId) is not null;
}
private void PublishOne(
private HeadlessCollisionGenerationTransaction? CreatePublication(
uint landblockId,
Vector3 origin,
uint currentCellId,
@ -207,7 +268,7 @@ internal sealed class HeadlessCollisionNeighborhood
throw new InvalidDataException(
$"Required headless landblock 0x{landblockId:X8} is missing.");
}
return;
return null;
}
IReadOnlyList<WorldEntity> staticEntities =
@ -244,7 +305,7 @@ internal sealed class HeadlessCollisionNeighborhood
landblock);
RuntimePhysicsState physics = _runtime.EntityObjects.Physics;
_ = HeadlessCollisionGenerationTransaction.Execute(
return HeadlessCollisionGenerationTransaction.Begin(
physics,
landblockId,
afterAdmission: null,
@ -294,22 +355,113 @@ internal sealed class HeadlessCollisionNeighborhood
collisions,
origin);
});
_resident.Add(CanonicalLandblock(landblockId));
}
private void RetireAll()
private void AdvanceWork()
{
if (_resident.Count == 0)
if (_pendingPublicationCancellation
&& _pendingPublication is { } cancelling)
{
_centerLandblock = 0u;
return;
bool wasCommitted = cancelling.EngineMutationCommitted;
if (!cancelling.TryCancel())
return;
if (wasCommitted || cancelling.EngineMutationCommitted)
_resident.Add(cancelling.LandblockId);
_pendingPublication = null;
_pendingPublicationCancellation = false;
}
uint[] retiring = [.. _resident];
_resident.Clear();
_centerLandblock = 0u;
foreach (uint landblock in retiring)
_ = _runtime.EntityObjects.Physics.WithdrawCollision(landblock);
if (_resetRequired)
{
_retirementQueue.Clear();
foreach (uint landblock in _resident)
_retirementQueue.Enqueue(landblock);
_centerLandblock = 0u;
_resetRequired = false;
}
while (_retirementQueue.TryPeek(out uint retiring))
{
RuntimeCollisionMutationResult result = _runtime.EntityObjects
.Physics.WithdrawCollision(retiring);
if (!result.Completed)
return;
_retirementQueue.Dequeue();
_resident.Remove(retiring);
}
if (!_publicationPlanBuilt)
{
BuildPublicationPlan(
_requestedCenterLandblock,
_publicationQueue);
_publicationPlanBuilt = true;
}
while (true)
{
if (_pendingPublication is null)
{
if (!_publicationQueue.TryDequeue(out PublicationSpec spec))
{
_centerLandblock = _requestedCenterLandblock;
_runtime.EntityObjects.Physics.Engine
.UpdatePlayerCurrCell(_requestedFullCell);
return;
}
_pendingPublication = CreatePublication(
spec.LandblockId,
spec.Origin,
_requestedFullCell,
spec.Required);
if (_pendingPublication is null)
continue;
}
HeadlessCollisionGenerationAdvance advance =
_pendingPublication.Advance();
if (advance.Completed)
{
_resident.Add(_pendingPublication.LandblockId);
_pendingPublication = null;
continue;
}
if (advance.WaitingForProjectionAcknowledgement)
return;
if (advance.YieldToCaller)
return;
if (!advance.Progressed)
throw new InvalidOperationException(
"Headless collision publication made no progress.");
}
}
private static void BuildPublicationPlan(
uint center,
Queue<PublicationSpec> destination)
{
destination.Enqueue(new PublicationSpec(
center,
Vector3.Zero,
Required: true));
int centerX = (int)((center >> 24) & 0xFFu);
int centerY = (int)((center >> 16) & 0xFFu);
for (int dx = -1; dx <= 1; dx++)
{
for (int dy = -1; dy <= 1; dy++)
{
if (dx == 0 && dy == 0)
continue;
int x = centerX + dx;
int y = centerY + dy;
if ((uint)x > byte.MaxValue || (uint)y > byte.MaxValue)
continue;
destination.Enqueue(new PublicationSpec(
((uint)x << 24) | ((uint)y << 16) | 0xFFFFu,
new Vector3(dx * 192f, dy * 192f, 0f),
Required: false));
}
}
}
private static uint CanonicalLandblock(uint fullCellId) =>