refactor(runtime): own per-session physics simulation
Move the sole PhysicsEngine, production cache, collision admissions, canonical bodies and hosts, remote components, ordinary/remote worksets, simulation, cell commits, and shadow synchronization under RuntimeEntityObjectLifetime. Keep App as the prepared-asset, animation-input, and render-projection adapter while preserving the named-retail update and collision order. Add exact-incarnation, object-clock, callback-reentrancy, GUID-reuse, two-runtime isolation, source ownership, collision publication, and graphical projection coverage. Release build and the complete 8,588-test solution pass. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
parent
0dc3bfdeff
commit
7e6033d0ad
39 changed files with 3685 additions and 1722 deletions
272
src/AcDream.Runtime/Physics/RuntimeOrdinaryPhysicsUpdater.cs
Normal file
272
src/AcDream.Runtime/Physics/RuntimeOrdinaryPhysicsUpdater.cs
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
using System.Numerics;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.Physics.Motion;
|
||||
using AcDream.Runtime.Entities;
|
||||
using DatReaderWriter.Types;
|
||||
|
||||
namespace AcDream.Runtime.Physics;
|
||||
|
||||
internal readonly record struct RuntimePhysicsFrameSnapshot(
|
||||
Vector3 Position,
|
||||
Quaternion Orientation,
|
||||
uint FullCellId);
|
||||
|
||||
internal sealed class RuntimeOrdinaryPhysicsCommit
|
||||
{
|
||||
internal required RuntimeOrdinaryPhysicsUpdater Owner { get; init; }
|
||||
internal required RuntimeEntityRecord Record { get; init; }
|
||||
internal required PhysicsBody Body { get; init; }
|
||||
internal required ulong ObjectClockEpoch { get; init; }
|
||||
internal required bool FrameChanged { get; init; }
|
||||
internal required Func<bool>? ExternalOwnerValid { get; init; }
|
||||
internal bool Completed { get; set; }
|
||||
internal RuntimePhysicsFrameSnapshot Snapshot { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Presentation-free, body-backed branch of retail
|
||||
/// <c>CPhysicsObj::UpdateObjectInternal</c> (`0x005156B0`). App supplies the
|
||||
/// completed animation root frame and acknowledges the resulting projection;
|
||||
/// Runtime owns integration, transition, canonical-body state, and shadows.
|
||||
/// </summary>
|
||||
internal sealed class RuntimeOrdinaryPhysicsUpdater
|
||||
{
|
||||
private readonly RuntimePhysicsState _physics;
|
||||
|
||||
internal RuntimeOrdinaryPhysicsUpdater(RuntimePhysicsState physics)
|
||||
{
|
||||
_physics = physics ?? throw new ArgumentNullException(nameof(physics));
|
||||
}
|
||||
|
||||
internal bool TryBegin(
|
||||
RuntimeEntityRecord record,
|
||||
Frame rootFrame,
|
||||
float objectScale,
|
||||
float quantum,
|
||||
float radius,
|
||||
float height,
|
||||
ulong objectClockEpoch,
|
||||
AnimationSequencer? sequencer,
|
||||
Action<uint, AnimationSequencer> captureAnimationHooks,
|
||||
Func<bool>? externalOwnerValid,
|
||||
out RuntimeOrdinaryPhysicsCommit commit)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(record);
|
||||
ArgumentNullException.ThrowIfNull(rootFrame);
|
||||
ArgumentNullException.ThrowIfNull(captureAnimationHooks);
|
||||
if (record.PhysicsBody is not { } body
|
||||
|| !IsCurrent(
|
||||
record,
|
||||
body,
|
||||
objectClockEpoch,
|
||||
externalOwnerValid))
|
||||
{
|
||||
commit = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
body.State = record.FinalPhysicsState;
|
||||
Vector3 priorPosition = body.Position;
|
||||
Quaternion priorOrientation = body.Orientation;
|
||||
bool previousContact = body.InContact;
|
||||
bool previousOnWalkable = body.OnWalkable;
|
||||
|
||||
// UpdatePositionInternal 0x00512CA1: scale root translation only while
|
||||
// OnWalkable. Complete root orientation composes regardless.
|
||||
Vector3 candidatePosition = priorPosition;
|
||||
if (body.OnWalkable && rootFrame.Origin != Vector3.Zero)
|
||||
{
|
||||
candidatePosition += Vector3.Transform(
|
||||
rootFrame.Origin * objectScale,
|
||||
priorOrientation);
|
||||
}
|
||||
|
||||
Quaternion candidateOrientation = priorOrientation;
|
||||
if (!rootFrame.Orientation.IsIdentity)
|
||||
{
|
||||
candidateOrientation = FrameOps.SetRotate(
|
||||
candidatePosition,
|
||||
priorOrientation,
|
||||
priorOrientation * rootFrame.Orientation);
|
||||
}
|
||||
|
||||
body.SetFrameInCurrentCell(candidatePosition, candidateOrientation);
|
||||
body.calc_acceleration();
|
||||
body.UpdatePhysicsInternal(quantum);
|
||||
body.SetFrameInCurrentCell(body.Position, body.Orientation);
|
||||
|
||||
if (sequencer is not null)
|
||||
{
|
||||
uint localId = record.LocalEntityId
|
||||
?? throw new InvalidOperationException(
|
||||
$"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} has no local identity.");
|
||||
captureAnimationHooks(localId, sequencer);
|
||||
}
|
||||
if (!IsCurrent(
|
||||
record,
|
||||
body,
|
||||
objectClockEpoch,
|
||||
externalOwnerValid))
|
||||
{
|
||||
commit = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector3 integratedPosition = body.Position;
|
||||
uint sourceCellId = record.FullCellId;
|
||||
uint resolvedCellId = sourceCellId;
|
||||
bool frameChanged = integratedPosition != priorPosition
|
||||
|| body.Orientation != priorOrientation;
|
||||
uint movingEntityId = record.LocalEntityId ?? 0u;
|
||||
|
||||
if (integratedPosition != priorPosition
|
||||
&& sourceCellId != 0
|
||||
&& radius >= 0.05f
|
||||
&& _physics.Engine.LandblockCount > 0)
|
||||
{
|
||||
ResolveResult resolved = _physics.Engine.ResolveWithTransition(
|
||||
priorPosition,
|
||||
integratedPosition,
|
||||
sourceCellId,
|
||||
radius,
|
||||
height,
|
||||
stepUpHeight: 0.4f,
|
||||
stepDownHeight: 0.4f,
|
||||
isOnGround: previousOnWalkable,
|
||||
body: body,
|
||||
moverFlags: IsPlayerGuid(record.ServerGuid)
|
||||
? ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide
|
||||
: ObjectInfoState.EdgeSlide,
|
||||
movingEntityId: movingEntityId);
|
||||
|
||||
if (resolved.Ok)
|
||||
{
|
||||
resolvedCellId = resolved.CellId != 0
|
||||
? resolved.CellId
|
||||
: sourceCellId;
|
||||
body.CommitTransitionPosition(
|
||||
resolvedCellId,
|
||||
resolved.Position);
|
||||
PhysicsObjUpdate.CommitSetPositionTransition(
|
||||
body,
|
||||
resolved.InContact,
|
||||
resolved.OnWalkable,
|
||||
resolved.CollisionNormalValid,
|
||||
resolved.CollisionNormal,
|
||||
previousContact,
|
||||
previousOnWalkable);
|
||||
body.CachedVelocity = quantum > 0f
|
||||
? (body.Position - priorPosition) / quantum
|
||||
: Vector3.Zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
body.CachedVelocity = Vector3.Zero;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
body.CachedVelocity = Vector3.Zero;
|
||||
}
|
||||
|
||||
if (!IsCurrent(
|
||||
record,
|
||||
body,
|
||||
objectClockEpoch,
|
||||
externalOwnerValid))
|
||||
{
|
||||
commit = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
commit = new RuntimeOrdinaryPhysicsCommit
|
||||
{
|
||||
Owner = this,
|
||||
Record = record,
|
||||
Body = body,
|
||||
ObjectClockEpoch = objectClockEpoch,
|
||||
FrameChanged = frameChanged,
|
||||
ExternalOwnerValid = externalOwnerValid,
|
||||
Snapshot = new RuntimePhysicsFrameSnapshot(
|
||||
body.Position,
|
||||
body.Orientation,
|
||||
resolvedCellId),
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
internal bool Complete(
|
||||
RuntimeOrdinaryPhysicsCommit commit,
|
||||
int liveCenterX,
|
||||
int liveCenterY,
|
||||
Func<RuntimePhysicsFrameSnapshot, bool> acknowledgeProjection)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(commit);
|
||||
ArgumentNullException.ThrowIfNull(acknowledgeProjection);
|
||||
if (!ReferenceEquals(commit.Owner, this))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"An ordinary-physics commit belongs to another Runtime owner.");
|
||||
}
|
||||
if (commit.Completed)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"An ordinary-physics commit has already completed.");
|
||||
}
|
||||
commit.Completed = true;
|
||||
|
||||
if (!IsCurrent(
|
||||
commit.Record,
|
||||
commit.Body,
|
||||
commit.ObjectClockEpoch,
|
||||
commit.ExternalOwnerValid)
|
||||
|| !_physics.CommitOrdinaryCell(
|
||||
commit.Record,
|
||||
commit.Body,
|
||||
commit.ObjectClockEpoch,
|
||||
commit.Snapshot.FullCellId,
|
||||
commit.ExternalOwnerValid)
|
||||
|| !acknowledgeProjection(commit.Snapshot)
|
||||
|| !IsCurrent(
|
||||
commit.Record,
|
||||
commit.Body,
|
||||
commit.ObjectClockEpoch,
|
||||
commit.ExternalOwnerValid))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (commit.FrameChanged
|
||||
&& commit.Record.FullCellId != 0)
|
||||
{
|
||||
ShadowPositionSynchronizer.Sync(
|
||||
_physics.Engine.ShadowObjects,
|
||||
commit.Record.LocalEntityId ?? 0u,
|
||||
commit.Body.Position,
|
||||
commit.Body.Orientation,
|
||||
commit.Record.FullCellId,
|
||||
liveCenterX,
|
||||
liveCenterY);
|
||||
}
|
||||
|
||||
return IsCurrent(
|
||||
commit.Record,
|
||||
commit.Body,
|
||||
commit.ObjectClockEpoch,
|
||||
commit.ExternalOwnerValid);
|
||||
}
|
||||
|
||||
private bool IsCurrent(
|
||||
RuntimeEntityRecord record,
|
||||
PhysicsBody body,
|
||||
ulong objectClockEpoch,
|
||||
Func<bool>? externalOwnerValid) =>
|
||||
_physics.IsSpatialRoot(record)
|
||||
&& record.ObjectClockEpoch == objectClockEpoch
|
||||
&& ReferenceEquals(record.PhysicsBody, body)
|
||||
&& record.RemoteMotion is null
|
||||
&& (externalOwnerValid?.Invoke() ?? true);
|
||||
|
||||
private static bool IsPlayerGuid(uint guid) =>
|
||||
(guid & 0xFF000000u) == 0x50000000u;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue