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.
Summary
UloopPausePointRawCaptureLifecycle.OnPauseStateChanged(Runtime assembly) clears the raw capture holder throughEditorApplication.delayCall. In the Editors measured for #3171,delayCalldid 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
delayCallrunning after the Step transition has settled (Step can unpause and re-pause within one frame); whetherEditorApplication.updategives the same guarantee has not been checked.Where
Packages/src/Runtime/PausePoints/UloopPausePointRawCaptureLifecycle.cs:Effect
UloopPausePointRawCaptureHolderkeeps live object references for the latest pause-point hit so thatexecute-dynamic-codecan inspect them throughUloopPausePoint.TryGetCapturedValue/GetCapturedNameswhile 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 onExitingPlayMode/EnteredEditMode. For an unpause that happens outside the pause-point workflow (the Editor's pause button,control-play-modePlay, Step), thedelayCallabove is the only clear tied to the unpause itself:ClosePauseWindowIfEditorResumedExternally, whichOnEditorUpdateruns every tick, closes the pause window and credits the expiry but does not touch the holder (UloopPausePointRegistry.PauseWindow.cs). While the window is unfocused:UloopPausePoint.TryGetCapturedValuekeeps 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
delayCallrun while unfocused, run 152 ms after the first focus).Constraints for a fix
EditorApplication.updateis available (the class already subscribes to it), but the code's own rationale fordelayCallis that Step can unpause and re-pause before the state settles, and whether anupdatetick observes the settled state has not been checked. A fix has to re-check the settled state itself (for example, a pending flag consumed byOnEditorUpdateonly whenisPausedis 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/srcreturns no lines.