Files
AIC-Project/Client/PackageLocal/com.unity.pipeline@0.4.0-exp.1/Documentation~/hot-reload.md
T

146 lines
6.8 KiB
Markdown

# Hot reload
Apply edited C# to running code without restarting Play Mode or rebuilding the Player. Two flavors are supported: **in-place** reload of a tagged method body, and **override** reload that routes a method through a separately-compiled replacement.
Hot reload runs in **Editor Play Mode and Mono standalone development builds only** — not IL2CPP. Like code eval, it is gated behind `#if UNITY_EDITOR || (UNITY_STANDALONE && DEBUG)`.
## Flavor 1 — in-place (`reload_file`)
Tag the method you want to be reloadable with `[HotReload]`, then edit its body and apply the file. The running instance picks up the new body.
```csharp
using Unity.Pipeline.HotReload;
using UnityEngine;
public class Spinner : MonoBehaviour
{
public float rotationSpeed = 90f;
[HotReload]
void Update()
{
transform.Rotate(Vector3.up, rotationSpeed * Time.deltaTime);
}
}
```
Edit the body of `Update`, then apply it:
```bash
unity command reload_file "<absolute path>/Spinner.cs"
# Emit debug symbols so breakpoints bind (compiles unoptimized):
unity command reload_file "<absolute path>/Spinner.cs" --pdb
```
`reload_file` parameters:
| Arg | Default | Meaning |
|-----|---------|---------|
| `filename` | *(required)* | Source file containing `[HotReload]` methods. |
| `timeout` | `30000` | Compilation timeout (ms). |
| `assemblyDir` | `null` | Optional directory to also save the compiled assembly to disk (default: in-memory only). |
| `pdb` | `false` | Emit a portable PDB mapped to the original source so debugger breakpoints bind. Compiles unoptimized. |
**Constraints (in-place):** the `[HotReload]` method must be a **`void` instance method** that is **`public`**, and reload works on **Mono only**. The CodeGen ILPostProcessor skips static methods and value-returning methods at build time (with a warning), so only matching methods are weaved for dispatch.
## Flavor 2 — override (`reload_file_override`)
When you want to swap behavior from a *separate* file (leaving the original untouched), tag the method with `[HotReloadWithOverrides]` and route it through `HotReloadHelper.ExecuteWithHotReload`:
```csharp
using Unity.Pipeline.HotReload;
using UnityEngine;
public class BossController : MonoBehaviour
{
[HotReloadWithOverrides]
void Update()
{
HotReloadHelper.ExecuteWithHotReload(this, "Update", OriginalUpdate);
}
public void OriginalUpdate()
{
transform.Rotate(45 * Time.deltaTime, 0, 0);
}
}
```
Put the tweaked behavior in a **separate file** as a **`public static` method** tagged `[HotReloadOverrideMethod("Type.Method")]`. The override **takes the target instance as its first parameter**:
```csharp
// BossOverrides.cs — a separate file
using Unity.Pipeline.HotReload;
using UnityEngine;
public static class BossOverrides
{
[HotReloadOverrideMethod("BossController.Update")]
public static void TweakedUpdate(BossController instance)
{
instance.transform.Rotate(0, 90 * Time.deltaTime, 0); // new behaviour
}
}
```
Apply the override file:
```bash
unity command reload_file_override "<absolute path>/BossOverrides.cs"
```
`reload_file_override` parameters:
| Arg | Default | Meaning |
|-----|---------|---------|
| `filename` | *(required)* | The override source file to compile. |
| `timeout` | `30000` | Compilation timeout (ms). |
| `assemblyDir` | `null` | Optional directory to also save the compiled assembly to disk (default: in-memory only). |
The override method's signature must be `public static <ReturnType> Name(<TargetType> instance, ...)` — return type and trailing parameters matching the original. Do **not** redeclare the target type in the override file (a common cause of "signature mismatch"). Until an override is applied, `ExecuteWithHotReload` calls `OriginalUpdate`; once applied, dispatch routes to the override.
`ExecuteWithHotReload` has matching overloads for both value-returning (`Func<object>`) and void (`Action`) originals:
```csharp
public static object ExecuteWithHotReload<T>(T instance, string methodName, Func<object> originalMethod, params object[] parameters);
public static void ExecuteWithHotReload<T>(T instance, string methodName, Action originalMethod, params object[] parameters);
```
## Supporting commands
- `hotreload_status` — show registry stats (which methods have overrides registered).
- `cleanup_hotreload` — remove old hot-reload assemblies and clear the registry.
## Architecture
The pipeline that turns an edited source file into running code:
1. **Compile (Roslyn, in-memory).** `RoslynCompilationService.Compile` parses and compiles the source to a `DynamicallyLinkedLibrary` entirely in memory — IL bytes in one `MemoryStream`, and (when `pdb`/`EmitDebugInformation` is requested) a **portable PDB** in another. **Nothing is written to disk** by default. When emitting debug info it compiles unoptimized (`OptimizationLevel.Debug`) so sequence points survive and breakpoints can bind.
2. **Load into the running AppDomain.** The IL (and optional PDB) bytes are loaded via `PipelineUtils.LoadFromBytes`, which selects the load mechanism by Unity version:
```csharp
public static Assembly LoadFromBytes(byte[] bytes, byte[] pdb = null)
{
#if UNITY_6000_5_OR_NEWER
return UnityEngine.Assemblies.CurrentAssemblies.LoadFromBytes(bytes, pdb);
#else
return System.Reflection.Assembly.Load(bytes, pdb);
#endif
}
```
On Unity 6000.5+ it uses Unity's `CurrentAssemblies.LoadFromBytes`; on older versions it falls back to `Assembly.Load(bytes, pdb)`. Either way the freshly compiled code becomes a **sibling assembly** loaded next to the original in the live AppDomain.
3. **Dispatch.** Calls are routed through `HotReloadRegistry`. The registry keys dispatch on `TypeName.MethodName`; when an override is registered for that id, `TryInvokeHotReload` invokes it with the instance as the first argument (marshaling to the main thread via the injected `Dispatcher` if the override requires it). If no override is registered, the original code runs.
4. **In-place weaving (CodeGen).** For the in-place flavor, the **`HotReloadInPlaceILPostProcessor`** (built on Mono.Cecil, in the `Unity.Pipeline.CodeGen` assembly) weaves a dispatch prologue into every `[HotReload]` method **at build/compile time**. That prologue checks the registry for a reloaded body and calls it instead of the original — which is how an edited `[HotReload]` body takes effect without changing the call sites. The post-processor enforces the void-instance-method constraint, skipping (with a warning) any static or value-returning `[HotReload]` method.
Because hot reload depends on Mono's ability to load a sibling assembly and dispatch into it, it is limited to **Editor Play Mode and Mono standalone dev builds — not IL2CPP**.
## See also
- [Runtime connection & setup](runtime-setup.md) — enabling hot reload in a Player build.
- [Command reference](commands/runtime.md)