Skip to content

UloopPausePointRawCaptureLifecycle clears raw captures through delayCall, which does not run while the Editor is unfocused #3173

Description

@hatayama

Summary

UloopPausePointRawCaptureLifecycle.OnPauseStateChanged (Runtime assembly) clears the raw capture holder through EditorApplication.delayCall. In the Editors measured for #3171, delayCall did not run while the Editor window was unfocused, which is the normal state when an agent drives Unity. The clear therefore waits until someone focuses the window, or until another clear path runs.

Follow-up to #3168, which moved the six Editor-assembly sites but left this one out on purpose: the Runtime assembly cannot use the Editor main-thread dispatcher, and the code relies on delayCall running after the Step transition has settled (Step can unpause and re-pause within one frame); whether EditorApplication.update gives the same guarantee has not been checked.

Where

Packages/src/Runtime/PausePoints/UloopPausePointRawCaptureLifecycle.cs:

private static void OnPauseStateChanged(PauseState pauseState)
{
    if (pauseState != PauseState.Unpaused)
    {
        return;
    }

    // Why: Step can transiently unpause then re-pause; delayCall re-checks the settled state.
    EditorApplication.delayCall += ClearRawCaptureIfStillUnpaused;
}

Effect

UloopPausePointRawCaptureHolder keeps live object references for the latest pause-point hit so that execute-dynamic-code can inspect them through UloopPausePoint.TryGetCapturedValue / GetCapturedNames while Unity is paused. The holder's documented contract is that the references must not outlive the paused window.

The registry clears the holder on its own paths (ClearAll, ClearLatestHitSnapshot, Clear(id) for the owning id, ResetForTests), and the lifecycle clears it on ExitingPlayMode / EnteredEditMode. For an unpause that happens outside the pause-point workflow (the Editor's pause button, control-play-mode Play, Step), the delayCall above is the only clear tied to the unpause itself: ClosePauseWindowIfEditorResumedExternally, which OnEditorUpdate runs every tick, closes the pause window and credits the expiry but does not touch the holder (UloopPausePointRegistry.PauseWindow.cs). While the window is unfocused:

  • the captured references stay alive until the next focus, the next registry clear, or the end of Play Mode;
  • UloopPausePoint.TryGetCapturedValue keeps answering with values from a pause window that has already ended.

Not measured yet; the stall itself was measured in #3171 (macOS, Unity 2022.3.62f3, two processes, 15–61 s without a delayCall run while unfocused, run 152 ms after the first focus).

Constraints for a fix

  • The Runtime assembly has no dispatcher. EditorApplication.update is available (the class already subscribes to it), but the code's own rationale for delayCall is that Step can unpause and re-pause before the state settles, and whether an update tick observes the settled state has not been checked. A fix has to re-check the settled state itself (for example, a pending flag consumed by OnEditorUpdate only when isPaused is still false on a later tick) and prove the Step case in the harness rather than assume the ordering.
  • Assets/RegressionHarness/ has pause-point scenes; the Step and external-unpause cases should be checked there, not only in EditMode tests.

Acceptance

  • git grep -n 'EditorApplication.delayCall +=' -- Packages/src returns no lines.
  • With the Editor unfocused, an external unpause clears the raw capture within a bounded number of frames, and Step (unpause then re-pause within one frame) does not clear it.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions