fix(input): focus loss no longer faults on an unpublished movement controller (#356)
Losing window focus calls CameraPointerInputController.HandleFocusChanged -> MouseLookController.EndForLifecycle -> PlayerMovementController .EndMouseLook, and EnsurePublishedForRuntimeOperation throws when the controller exists but is not yet published (mid-login) or already retired (post-logout). A focus callback can land in either window, so a simple alt-tab during the login stream took the whole process down with an unhandled InvalidOperationException. Hit live during the Campaign A listening-session launches. EndAndRestoreCursor already guarded 'no controller'; publication state is the finer-grained form of the same condition, so the guard is completed with the new CanExecuteLiveMovement predicate (the exact lifecycle set EnsurePublishedForRuntimeOperation accepts) rather than wrapping the call in a catch. Cursor restore still runs unconditionally - presentation is always safe. Published-controller behaviour is unchanged. Also files issue #357: the login placement stall this session exposed (reveal ready=True, player Place edge never executes, world never opens). That one is NOT fixed here - full evidence chain, wire capture, and probe output are in the issue. It is a placement-domain bug and blocks the Campaign A listening gate. Suite: green except the known load-dependent measurement flake (RuntimeCollisionReportingStateTests allocation pin), which passes in isolation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
2914e43aa9
commit
972c7ab3b8
3 changed files with 78 additions and 1 deletions
|
|
@ -196,8 +196,14 @@ internal sealed class MouseLookController : IMouseLookInputFrameController
|
|||
bool stateWasActive = _state.Active;
|
||||
_state.Release();
|
||||
|
||||
// The null check alone is not enough: a controller can also be present
|
||||
// but UNPUBLISHED (mid-login) or retired (post-logout), and a focus-loss
|
||||
// callback can land in either window. EndMouseLook faults on both, so a
|
||||
// simple alt-tab during login used to take the process down. Cursor
|
||||
// restore below still runs — that is presentation and always safe.
|
||||
PlayerMovementController? controller = _playerController.Controller;
|
||||
if (controller is not null && controller.EndMouseLook(_movementInput.Capture()))
|
||||
if (controller is { CanExecuteLiveMovement: true }
|
||||
&& controller.EndMouseLook(_movementInput.Capture()))
|
||||
{
|
||||
_outbound.TrySendMovement(
|
||||
_session.CurrentSession,
|
||||
|
|
|
|||
|
|
@ -764,6 +764,18 @@ public sealed class PlayerMovementController
|
|||
internal bool IsRuntimePublished => _publicationLifecycle
|
||||
is PlayerMovementControllerPublicationLifecycle.RuntimePublished;
|
||||
|
||||
/// <summary>
|
||||
/// True when this controller may execute a live movement operation — the
|
||||
/// same predicate <see cref="EnsurePublishedForRuntimeOperation"/> throws
|
||||
/// on. Exposed so LIFECYCLE callers (window focus loss, session teardown)
|
||||
/// can skip the operation instead of faulting: a focus change can arrive at
|
||||
/// any moment, including before the controller is published during login
|
||||
/// and after it is retired on logout, and neither is an error.
|
||||
/// </summary>
|
||||
public bool CanExecuteLiveMovement => _publicationLifecycle
|
||||
is PlayerMovementControllerPublicationLifecycle.StandalonePublished
|
||||
or PlayerMovementControllerPublicationLifecycle.RuntimePublished;
|
||||
|
||||
private bool _dormantSetPositionGroundPhase;
|
||||
|
||||
internal void BeginDormantSetPositionGroundPhase()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue