Repository navigation
feat: Hot reload now applies edits on partial types and in global-using assemblies, and the response says whether an edit is live - #3198
Merged
Conversation
…ferenced assembly uses (#3176)
… settle the message after a fallback compile (#3177)
…e's global using, and names unresolved signature types (#3179)
…ype skip reason (#3180)
…urvive a test run (#3181)
…s it counts edits since the last compile (#3182)
…hods in the edited files (#3185)
…ompile instead of on every run (#3187) Hot reload no longer reads the edited assembly's dll and PDB and walks its sequence points on every run to find an edited file's compile-time checksum. - Add HotReloadPdbDocumentIndex: per dll, the distinct documents that sequence points refer to (url, hash algorithm, hash), in walk order, so the first match is the document the old walk returned. It is built from sequence points, not from the PDB's document table, so a file without a method body still reports NoDocumentInPdb. - Reuse each list while the dll's length, write time and MVID and the PDB's length and write time match the files it was read from, and read again otherwise. HotReloadSourceBaseline looks documents up through the shared index; only the At entry points that tests call take an index. - Tests D1-D9 and B1-B2 count reads through LoadCount instead of timing anything; the reuse ones failed before the reuse was wired in. - D10 pins the failure contract: when the files cannot be read, the lookup throws, nothing is stored, and the list kept for the earlier files is not used. One lookup on a 295-file test assembly went from 135-169 ms to about 0.16 ms. The first lookup after a compile walks the whole PDB.
…ibling rows in the Skipped count, and note added test methods (#3188) Hot reload responses now state three things a reader used to have to work out from elsewhere in the response. - Timing reports OtherMs, the time outside the three phases (file resolution, planning, the unchanged-method checks). The timing breakdown refuses a total shorter than its phases; a group processor test helper that passed a total of 0 now times its call instead. - When some Skipped rows belong to sibling files the run re-applied on its own, Message says how many: "Skipped: n (m in sibling files the run re-applied on its own; Outcome does not count those)." The clause is built once and used by both the ordinary applied message and the type-only message. SkippedTotal and Outcome keep their rules. - The transform worker gives the entry of an added method with a test attribute a LifecycleNote saying the Unity Test Runner will not discover it until 'uloop compile', under the same condition as the existing warning. A one-shot lifecycle note still takes precedence. - The output reference documents all three, and comments these changes made inaccurate are updated.
…g skill installs instead of doing nothing (#3189)
…tual Player (#3191) Hot reload sent to a Multiplayer Play Mode Virtual Player no longer answers a missing compiled assembly with "Compile the project first", which never helps there: a player loads the main project's script assemblies and has none under its own root. - The (file) reason now says the Editor is a Virtual Player, hot reload cannot patch it yet, the edit reaches it through a compile, and a patch applied to the main Editor does not reach it. The player is recognized from the project root path alone (<main project>/Library/VP/<player>), and every other project keeps the existing text. - The Multiplayer Play Mode and hot-reload references say the same, including that the compile fallback follows --compile-on-skip (auto holds it while that player is in Play Mode).
…es a name generated at compile time (#3193)
…t as already compiling (#3192)
…t after a domain reload (#3195)
…s the earlier busy answer when its connection drops or times out (#3196)
…member of a type the reload was not given (#3194)
…n internal member of a type it was not given (#3197)
Contributor
|
Important Review skippedToo many files! This PR contains 157 files, which is 7 over the limit of 150. To get a review, reduce the PR to 150 files or fewer by splitting it into smaller PRs or changing its base branch. Upgrade to a paid plan to raise the limit. This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry. ⚙️ Run configuration
⛔ Files ignored due to path filters (35)
📒 Files selected for processing (157)
You can disable this status message by setting the
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…ethod it patches (#3199)
hatayama
marked this pull request as ready for review
October 7, 2026 01:12
This was referenced Oct 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
uloop launchon macOS leaves SIGUSR1/SIGUSR2 as SIG_DFL+SA_SIGINFO in the Editor; IL2CPP builds crash with SIGSEGV in il2cpp #3178) that came out of using hot reload on a large multi-assembly project: the original trial report, two rounds of re-testing by the agent that wrote the report, and problems found while fixing those.global using, method bodies onpartialtypes, test assemblies that reach a plugin DLL only through a referenced assembly, and static methods that usenameofon an instance member.Outcome), how many rows of each kind the run produced, and where the time went (Timing).uloop compileand hot reload's compile fallback now recover when Unity lost the request or rejected it as already compiling, instead of exiting with an error or waiting 10 minutes.Merge status
main.User Impact
Edits that were skipped or failed are now applied
global usingfiles, nearly every edited method wasSkippedwith unrelated reasons (unresolved types, "added generic method", a field "of a different type"), and untouched methods were reported as addedpartialtype wasSkippedpartialtypes are patched. When another part changed and was not passed, or could not be checked, the reason names the file or says to compilepartialtype's body that used aninternalmember of another type wasSkippedwith a reason about a missing generated partSkippedwith a reason that names the type the member is internal tonameofon an instance member failed the whole fileThe response says what happened
Success: trueeven when nothing was applied. Whether the edit was live had to be pieced together from several fields and the messageOutcomeanswers it in one field (Applied,PartiallyApplied,NothingApplied,NothingToApply,Failed,ReplacedByCompile). Per-kind totals (SkippedTotal,AddedTotal, ...) are fields. After a fallback compile succeeds, the message no longer mixes in the "Auto Refresh is held" sentenceTimingreports each phase, the fallback compile, and the time outside the phases (OtherMs)Outcome: Appliednext toSkippedTotal: 1with no hint that the skipped row belonged to a sibling file. An added test method looked live although the test runner needs a compile to see itLifecycleNoteuloop compileFewer edits reported as live that are not
partialtype, a body that used aninternalmember of a type the reload was not given inside a lambda, local function, query, iterator or async method wasPatchedand then threwMethodAccessExceptionorFieldAccessExceptionon the first call. An inherited internal member named withoutthis.failed the whole fileSkipped, with a reasonSpeed
CLI
Success: trueafter about 9 suloop skills installwith no target flag printed guidance and did nothing, although the help said existing installs are refreshedBehaviour changes to weigh before merging
A request that reached Unity after a busy answer but was never registered now waits for its timeout (fix: A command that reached Unity after a busy retry no longer reports the earlier busy answer when its connection drops or times out #3196). Before, the earlier busy answer ended the command at once. The waits are: the pause-point recovery compile, up to its limit (10 minutes by default);
control-play-mode,TimeoutSeconds(180 s by default); PlayModerun-tests,TimeoutSecondsplus 60 s (660 s by default). This is what already happens when the first attempt is lost the same way.uloop compileis not affected, because it sends the request again (fix: Send the compile again when Unity lost the request or rejected it as already compiling #3192). Giving the other commands the same resend is a follow-up.Four kinds of body that work today are
Skippedafter fix: Hot reload no longer patches method bodies that would throw on an internal member of a type it was not given #3197, on non-partialtypes. The analysis cannot tell them from the bodies that throw. All four were measured as patched and working before the change.var seed = new Host().InternalField; Func<int> read = () => seed + 100;. Declaring the local with its type (int seed = ...) keeps the body patched.fromsource expression of a query.nameof(Type.InternalMember)or aninternal constinside a lambda, iterator, async method or a body that runs through a delegating shim.this.in a file that also imports the inherited type withusing static.An alternative was considered and not taken: skip only when the internal member itself is written inside the lambda, iterator or async body. That would keep the four forms patched, but a lambda that reaches an internal member through such a value would still be reported as patched and then throw. A stated skip was judged less harmful than a patch that throws. Switching is a one-condition change.
partialand non-partialtypes skip different forms (fix: Hot reload now patches partial-type bodies that use an internal member of a type the reload was not given #3194, fix: Hot reload no longer patches method bodies that would throw on an internal member of a type it was not given #3197). A method passed as a delegate, an event, and a member named in an object initializer or a property pattern are patched on a non-partialtype andSkippedon apartialtype. Apartialtype opens only the forms a run has shown to work.The response after a fallback compile (feat: Report the hot-reload outcome and per-kind totals as fields and settle the message after a fallback compile #3177). When the fallback compile succeeds,
OutcomeisReplacedByCompile,AutoRefreshHeldis nowfalse, and the message drops the hold sentence. The top-levelSuccessis the compile's, as it was before. All new fields are additive.Two small differences in what stops a run (fix: Hot reload no longer slows down with the number of unchanged methods in the edited files #3185, fix: Hot reload reads each edited assembly's debug symbols once per compile instead of on every run #3187). A row with no patch to remove no longer reads the DLL, so an unreadable DLL does not stop it. Once the debug-symbol table exists, a locked PDB does not stop a run; the first run scans the whole table, so a PDB corrupted part-way raises an error there.
Not covered
InternalsVisibleToand an internal extension method inside a lambda; internal constructors and indexers; an internal overload hidden behind a public overload of the same name (measured: patched, thenMethodAccessExceptioninside a lambda); a nullable struct reached with?.(inferred). These behave as they did before this pull request. The lasting fix is to analyse bodies with internal members visible, which is a follow-up.partialtypes: an internal member of another assembly (throughInternalsVisibleTo) used from apartialtype's body isSkipped.OtherMsgrew to 84–87 s per apply, about 2.3 s per live patch, because every live patch is re-applied until the next compile. The code that runs per patched method outside the three phases is the lifecycle-note caller scan (HotReloadOrchestrator→ApplyOneShotCallerNotes), which this branch does not touch — the same cost exists onmain. It is the first item of the next batch.main), kept separate because it affects everyuloopcommand and touches no hot-reload code.7950674dand is part of this pull request.Included pull requests
Each pull request's description holds its own behaviour changes, input-space table, mutation checks and "Not covered" list.
Four of these did not come from the report or its re-tests. They were found while fixing the reported items: the two speed changes (#3185, #3187), the busy-answer masking (#3196), and the non-
partialinternal-member skip (#3197).Changes
skills installwith no target flag.Verification
ef68b944(manual runs, because pull requests to the integration branch do not trigger the full CI):build-and-test.yml(Go tests on Linux, macOS and Windows): passed.unity-editmode-tests.yml(the full EditMode suite on Unity 2022.3, 6000.3, 6000.5 and 6000.7, plus the Release and concurrency legs): passed, every leg on the first attempt.aee6702d(everything except fix: Hot reload no longer patches method bodies that would throw on an internal member of a type it was not given #3197): both workflows passed. The Unity 6000.7 leg needed a second attempt: on the first, Unity was killed about 37 s after start (exit 137), before any test ran.36876a67: fix: Hot reload no longer fails to resolve plugin DLLs that only a referenced assembly uses #3176 to fix: Hot reload no longer slows down with the number of unchanged methods in the edited files #3185 except feat: Hot reload now applies method edits on partial types #3184): 11 applies gave 6 patched and 3 added methods and 1 skip (a struct method, which is out of scope). The original trial had 7 applies and nothing left applied. The global-using, classification, plugin DLL, outcome, timing, warning and run-tests items were confirmed. It raised six new points: feat: Hot reload responses now report time outside the phases, name sibling rows in the Skipped count, and note added test methods #3188, fix: uloop skills install without a target flag now refreshes existing skill installs instead of doing nothing #3189 and fix: Send the compile again when Unity lost the request or rejected it as already compiling #3192 address five, and the sixth is the time outside the phases listed under "Not covered".f37cf75c: adds feat: Hot reload now applies method edits on partial types #3184 and chore: Hot reload skill now says to read Outcome to tell whether an edit is live #3186):partialtypes were patched. It found five new problems; fix: Hot reload now applies static methods that use nameof on an instance member #3190, fix: Explain that hot reload cannot patch a Multiplayer Play Mode Virtual Player #3191, fix: Hot reload now says to run uloop compile when an added member uses a name generated at compile time #3193, fix: Hot reload now patches partial-type bodies that use an internal member of a type the reload was not given #3194 and fix: Hot reload no longer patches unchanged methods when it runs right after a domain reload #3195 address them.ef68b944: adds fix: Hot reload reads each edited assembly's debug symbols once per compile instead of on every run #3187 to fix: Hot reload no longer patches method bodies that would throw on an internal member of a type it was not given #3197), 26 applies: no blocker, and nothing that rounds 1 and 2 had fixed came back. Confirmed fixed:partialruns that use another type'sinternalmembers with a receiver (patched, and the sibling file is no longer skipped);nameofof an instance member inside an added static method (added); the generated-member reason now says that a compile is the only way; the Virtual Player reason now explains the situation; a compile started by Unity while an apply is in flight no longer makes the command fail (the fallback compile succeeds,ReplacedByCompile). Still open, for a later batch: one specific file keeps reporting "No verified source snapshot" after every compile while its neighbours do not (apartial, source-generated type with#ifinside;OtherMsabout 11 s on those runs);OtherMson the roughly 3,000-file assembly is still 2.1–3.6 s (unchanged from round 2); an apply refused because Unity is compiling could wait for that compile and retry instead of failing into a 64–78 s fallback compile;RecommendedNextActionsays "fix the failed declarations" for failures with nothing to fix (Editor busy, Virtual Player); the "Unity has not answered status polls for 30s, maybe a modal dialog" message also fires when a compile and reload simply take long; theinternal-member reason should lead with the cause rather than the compiler's CS1061 and tailor its last sentence to the form.7e1ba673= this head plus perf: Hot reload no longer re-reads the compiled assembly for every method it patches #3199 and fix: Commands no longer run several times slower while the Unity Editor stays in the background on macOS #3200): with the Editor left in the background for 34 minutes, the second and third applies took 3.2–5.3 s, the same range as right after a compile (fix: Commands no longer run several times slower while the Unity Editor stays in the background on macOS #3200 confirmed). With 37 live patches, the patch phase stayed at 149–223 ms (perf: Hot reload no longer re-reads the compiled assembly for every method it patches #3199 confirmed) whileOtherMsreached 84–87 s — see "Not covered". The "No verified source snapshot" warning did not reproduce on that commit in three attempts; the likely cause of the earlier sightings is a Unity-initiated compile right afteruloop compile. Two new findings for the next batch: changing only a property's getter also reports its unchanged setter as skipped (which under--compile-on-skip autotriggers a fallback compile), and aninternalextension method of another assembly that shadows a public one of the same signature makes the call ambiguous (CS0121) only in the hot-reload compilation, failing the whole file.