fix: keep refresh honest while play mode defers compilation - #14
Conversation
- Track a main-thread heartbeat and cache isUpdating/isPlaying plus the Script Changes While Playing preference; report them from /health. - Suspend the two refresh timeout exits while play mode can defer the requested compile, sliding their baselines so the timers restart the moment edit mode returns. - Exit the Compiling phase on the observed idle level instead of a one-shot delayCall continuation that was dropped when the editor was still busy, leaving the phase stuck. - Narrow service-init reconciliation to a persisted Reloading phase. Init reruns on every play mode transition, and forcing every active phase to ready reported ready while a deferred compile was pending. - Bump the package to 2.1.0 (additive health fields, same protocol). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: efdb2ef903
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| #else | ||
| MainThreadRequestRunner.InitializeRuntime(); | ||
| RecordMainThreadHeartbeat(); | ||
| EnsureRuntimeHealthHeartbeat(); |
There was a problem hiding this comment.
Create the runtime heartbeat only once
When InitializeForRuntime is called more than once—such as after a shutdown or from initialization code attached to multiple scenes—this call creates another hidden DontDestroyOnLoad object before InitializeInternal can apply its existing s_Initialized guard. Because the heartbeat objects are never found or destroyed, every repeated initialization permanently adds another per-frame Update; create the component only when absent or move its creation behind the initialization guard.
Useful? React with 👍 / 👎.
| if (s_CompilingQuietUpdates == 0) | ||
| { | ||
| s_CompilingQuietSinceEditorTime = EditorApplication.timeSinceStartup; | ||
| } | ||
|
|
||
| MarkRefreshReady("Script compilation finished without assembly reload"); | ||
| s_CompilingQuietUpdates++; |
There was a problem hiding this comment.
Reset the quiet-window state for each compilation
After one compilation reaches ready, s_CompilingQuietUpdates and s_CompilingQuietSinceEditorTime retain that operation's values. If a subsequent fast compilation starts and finishes through the compilation callbacks between two editor-update samples, the isCompiling branch never resets them, so this increment uses an already-satisfied count and old timestamp and can mark the new operation ready on its first idle update, before a pending reload is observed. Reset both fields when a compilation starts or when a new refresh operation is created.
Useful? React with 👍 / 👎.
Summary
Play mode defers script compilation under the editor's Script Changes While Playing preference, and the refresh phase machine had no defense against that: two timeout paths could report a fake ready, exiting play mode re-ran service init which forced every active phase to ready while the deferred compile was still pending, and the Compiling phase's only exit was a one-shot delayCall that was dropped whenever the editor was still busy.
isUpdating/isPlaying/ the Script Changes While Playing preference on the main thread./healthfields:isUpdating,isPlaying,mainThreadHeartbeatAgeMs,scriptChangesWhilePlaying. Protocol version is unchanged; old clients are unaffected. The service only reports the preference — policy stays client-side.Test plan
Verified live against a Unity 2022.3 editor across all three preference values: deferred-compile hold surfaces honestly and completes once play mode exits (no fake ready while
isCompilingis true), stop-and-recompile and recompile-and-continue flows reach ready normally, a deliberate compile error terminates the cycle withcompileFailedinstead of hanging in Compiling, and the Editor.log build lines agree with every reported outcome.🤖 Generated with Claude Code