uloop hot-reload
Replaces method bodies in the running Editor (EditMode or PlayMode) directly from edited
project source files — no domain reload, no attributes, no source markers. Private/internal
member access, static methods, return values, async methods, and iterators all work within
the limits below, including private access inside async, iterator, lambda, local-function,
and LINQ-query bodies. Methods that cannot be patched are reported as Skipped or Failed;
one unpatchable method never aborts the rest of the run.
Outcome says whether the edit is live (ReplacedByCompile: a fallback compile succeeded, so
it is compiled in and no patch is active).
Usage
bashuloop hot-reload --files Assets/Scripts/Enemy.cs uloop hot-reload --files Assets/Scripts/Enemy.cs,Assets/Scripts/Boss.cs uloop hot-reload uloop hot-reload --files Assets/Scripts/Enemy.cs --compile-on-skip off uloop hot-reload --revert-all
Multiple files are one comma-separated value (or a JSON array); array options consume exactly one value token.
A script under a brand-new .asmdef cannot be hot-reloaded before its first import: Unity
has not created that assembly yet. Run uloop compile once, then iterate with hot reload.
A new file under an existing .asmdef can be hot-reloaded, but it is never selected
automatically — pass it with --files.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
--files | array | - | Project-relative .cs paths to hot-reload (method bodies, added members, and new top-level types). When omitted or empty on apply, selects compiled snapshot sources only — those whose bytes changed since the last compile snapshot, capped at 50 changed files per assembly with a warning when the cap trims the list; a file that has never been compiled is never selected and must be passed explicitly, except an introduced type's file after Play Mode entry or --revert-all, which is selected again; run uloop compile first when no snapshot exists, or pass explicit paths when no changed source is found |
--revert-all | flag | - | Remove every active hot-reload patch and added member and clear the ledger; introduced types stay loaded until the next domain reload. When set, --files is ignored |
--status | flag | - | Lists the currently active changes (patched methods, added members, and introduced types) without applying or reverting anything. |
--compile-on-skip | enum | auto | When edits stay unapplied (Skipped/Failed methods, Failed types; a re-bound sibling's Skipped rows aside), run uloop compile in this command: auto only in Edit Mode (never stops a Play session), on always unless Unity holds compiles until Play ends, off never. The response's CompileFallback says which; when the compile ran, Compile carries its result and Success is the compile's. |
Status
uloop hot-reload --status lists the currently active changes; it cannot be combined with
--files or --revert-all. Every change is static Editor state, so after a domain reload
it reports zero. Each Active/Added row's InvocationCount counts calls
into its body since it was applied — a reachability signal only while the code is
being driven.
How It Works
The edited files are grouped by the compiled assembly they belong to. Per group an
out-of-process Roslyn worker rewrites every editable body into a static shim, the shims
compile into one shim assembly and load into the Editor domain, and each original method is
patched with a Harmony transpiler. Because a group shares one shim assembly, a body edited in
one file can call a member added in another edited file of the same assembly. Re-running after
a real edit replaces the patch; an unchanged file reports AlreadyActive and changes nothing
unless a sibling of the same assembly is in the reload, in which case it is re-applied so every
active patch binds to the newest shim. With a compile-time baseline only bodies that actually
changed are patched (UnchangedTotal counts the rest).
Scope in Brief
- Patched: ordinary method bodies and property getters with a body.
- Added members: new methods, fields, and supported properties apply as
Addedrows (supported field-like events of a class are listed inAddedFields), visible to edited code of the same reload within the same assembly (pass the declaring file and its callers together), and gone on any compile or domain reload. - New types: a top-level class, struct, enum, or interface declared in an edited
file is introduced by that reload and listed in
IntroducedTypes; other valid shapes are refused with aWarningsline naming the reason. Use from another assembly or from files outside the reload, reflection, serialization, and Unity message discovery still needuloop compile. - Signature changes: a return-type change is
Skippedunless this or an earlier reload patched every live compiled caller, none in another assembly; a rename or parameter change applies as an added method and warns about call sites left on the old signature. - Constructors, operators, struct methods, compiled setter/init/indexer accessors, and event
accessors are
Skipped; finalizers and interface members are silently not applied. - A reload applies each file all-or-nothing: a
Failedmethod leaves that file unapplied, aFailedtype leaves every file of its assembly unapplied.
Workflow
Treat hot reload as the exploration phase and uloop compile as the landing phase:
keep edits inside the edited files, collect structural changes, and compile once —
every compile drops all patches and pause points and resets the PlayMode session (the compile response's Warning says how many).
While hot-reload changes are active, AutoRefreshHeld is true so returning focus does not
recompile; uloop compile releases the hold, and --revert-all only when no introduced type
remains.
One-shot methods (Awake, Start, init helpers) patch but show no effect on the call that
already ran; the response marks them with LifecycleNote. To re-run an edited compiled
OnEnable, set enabled = false with execute-dynamic-code, reload, then set it back to true
(Unity calls the old OnDisable, then the patched OnEnable). An edited OnDisable runs the
next time the component is disabled; to run it now, toggle enabled after the reload.
To tune a value while playing, expose a static property getter instead of a const; its
body is patched on compiled and introduced types alike.
Reference Guides
All files live in references/ beside this skill; read the one whose trigger matches:
references/scope-and-limits.md— full scope rules: added members, signature changes,Skipped/Failedtables, source baselines, one-shot code, tunable getters.references/mechanism-and-lifecycle.md— patch mechanism, convergence, what survives which reload, Editor-code iteration without PlayMode.references/troubleshooting.md—Patchedbut no behavior change, JIT inlining,--status, stack frames without lines.references/pause-point-interaction.md— how patches re-target or suppress armed pause points; one-way reachability checks.references/introduced-types.md— types a reload can introduce: supported shapes, internal access, refusal wording, identity and lifetime, why a new file is never selected automatically.references/added-field-wiring.md— putting a value into an added field without a compile.references/output.md— "Is my edit live?" (Outcome),Timing, every response field:ErrorCode,NextActions,Methodsrows,Warnings, totals.


