fix(runtime): restore interaction completion ownership
Preserve prepublication local motion completion, require the PartArray enter-world lifecycle port, and balance deferred Use busy ownership across dispatch and cancellation. Reconcile the completed GameWindow connected gates and add regression coverage. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
5c955c36ec
commit
f9736ece6c
26 changed files with 675 additions and 68 deletions
|
|
@ -21,13 +21,74 @@ internal interface ILocalPlayerControllerSource
|
|||
PlayerMovementController? Controller { get; }
|
||||
}
|
||||
|
||||
internal interface ILocalPlayerMotionSource
|
||||
{
|
||||
MotionInterpreter? Motion { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The one mutable local movement-controller slot. Player-mode lifecycle owns
|
||||
/// assignment; update and presentation owners receive the read-only seam.
|
||||
/// </summary>
|
||||
internal sealed class LocalPlayerControllerSlot : ILocalPlayerControllerSource
|
||||
internal sealed class LocalPlayerControllerSlot
|
||||
: ILocalPlayerControllerSource,
|
||||
ILocalPlayerMotionSource
|
||||
{
|
||||
private PlayerMovementController? _preparingMotionOwner;
|
||||
|
||||
public PlayerMovementController? Controller { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The motion owner visible to the local PartArray completion relay.
|
||||
/// During player-mode construction this is the fully animation-bound
|
||||
/// candidate controller; all other consumers continue to see only the
|
||||
/// committed <see cref="Controller"/>.
|
||||
/// </summary>
|
||||
MotionInterpreter? ILocalPlayerMotionSource.Motion =>
|
||||
_preparingMotionOwner?.Motion ?? Controller?.Motion;
|
||||
|
||||
/// <summary>
|
||||
/// Opens the narrow construction-time ownership seam required by retail's
|
||||
/// CPhysicsObj/PartArray lifetime. Initial placement synchronously queues
|
||||
/// and completes StopCompletely, so its MotionDone relay must reach the
|
||||
/// candidate interpreter before the complete controller is published.
|
||||
/// </summary>
|
||||
public IDisposable BeginMotionPreparation(PlayerMovementController controller)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(controller);
|
||||
if (_preparingMotionOwner is not null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"A local player motion owner is already being prepared.");
|
||||
}
|
||||
|
||||
_preparingMotionOwner = controller;
|
||||
return new MotionPreparation(this, controller);
|
||||
}
|
||||
|
||||
private void EndMotionPreparation(PlayerMovementController controller)
|
||||
{
|
||||
if (!ReferenceEquals(_preparingMotionOwner, controller))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The local player motion preparation owner changed unexpectedly.");
|
||||
}
|
||||
|
||||
_preparingMotionOwner = null;
|
||||
}
|
||||
|
||||
private sealed class MotionPreparation(
|
||||
LocalPlayerControllerSlot owner,
|
||||
PlayerMovementController controller) : IDisposable
|
||||
{
|
||||
private LocalPlayerControllerSlot? _owner = owner;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
LocalPlayerControllerSlot? current = Interlocked.Exchange(ref _owner, null);
|
||||
current?.EndMotionPreparation(controller);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal interface ILocalPlayerPhysicsHostSource
|
||||
|
|
|
|||
|
|
@ -386,6 +386,15 @@ internal sealed class PlayerModeController :
|
|||
new MotionTableDispatchSink(sequencer);
|
||||
}
|
||||
|
||||
// Retail CPhysicsObj owns CMotionInterp and CPartArray throughout
|
||||
// construction. Our split owners preserve that lifetime with a
|
||||
// narrow preparation lease: SetPosition's synchronous type-5
|
||||
// completion reaches this candidate MotionInterpreter, while the
|
||||
// public controller slot remains unpublished until every other
|
||||
// player-mode edge has prepared successfully.
|
||||
using IDisposable motionPreparation =
|
||||
_controllerSlot.BeginMotionPreparation(controller);
|
||||
|
||||
ResolveResult initial = _physics.Resolve(
|
||||
playerEntity.Position,
|
||||
initialCellId,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue