--- name: uloop-hot-reload toolName: hot-reload description: "Apply C# edits to the running Editor without a compile or domain reload, keeping Play Mode state. Use instead of compile after editing method bodies or adding methods, fields, or new classes." --- # 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 ```bash uloop 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 `Added` rows (supported field-like events of a class are listed in `AddedFields`), 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 a `Warnings` line naming the reason. Use from another assembly or from files outside the reload, reflection, serialization, and Unity message discovery still need `uloop compile`. - Signature changes: a return-type change is `Skipped` unless 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 `Failed` method leaves that file unapplied, a `Failed` type 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`/`Failed` tables, 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` — `Patched` but 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`, `Methods` rows, `Warnings`, totals.