.cs or .razor file, s&box recompiles your project and attempts to live-reload the changed assembly. You can iterate on most code changes without ever restarting the editor.
How it works
When a type definition changes, s&box walks the heap starting from static fields, recursing into instance fields to find and upgrade every live instance of the changed types.IL hotload (fast hotload)
If you only change method bodies — not type definitions — s&box patches in the new instructions directly without walking the heap. This is nearly instant and avoids most of the pitfalls described below. IL hotload is enabled by default. You can disable it in Editor Settings > General.Optimizing hotload speed
Heap walking can be slow when there are many instances. These tips apply to full hotloads, not IL hotloads.Diagnose slow hotloads
Enterhotload_log 2 in the console before saving a file. On the next hotload, s&box prints a table showing how long it spent on each type and how many instances it found, sorted by processing time. The biggest slowdown appears at the top.
If instance counts grow after each hotload, you likely have a leak — for example, a static list you add to but never clear. The table also shows which static fields instances were discovered through.
Use value-type arrays
s&box has a fast path for arrays and lists whose element type is a value type with no reference fields:Skip processing with [SkipHotload]
s&box automatically skips types and fields that can’t possibly contain user-defined types. You can force a skip with [SkipHotload]:
Pitfalls
These edge cases apply to full hotloads only, not IL hotloads.Removing or renaming types
Removing or renaming types
If you remove or rename a type, all references to instances of that type become
null. The engine removes components whose types disappear, but you may need to handle this in your own code too.Renamed types are treated as removed — s&box cannot detect that a rename happened. Restart the editor if you start seeing many errors after a rename.Changing default field values
Changing default field values
Hotload copies the runtime value of fields into the new assembly. This means changes to default field values have no effect until you restart the editor.The exception is expression-bodied properties,
const fields, and fields decorated with [SkipHotload]:Dictionary and HashSet
Dictionary and HashSet
If you make a code change that causes two previously non-equal instances to compare as equal, dictionaries and sets keyed on those instances can enter an invalid state. s&box emits a warning when it detects this. You may need to restart the editor.
Static fields in generic types
Static fields in generic types
s&box cannot process static fields inside generic types during hotload. It emits a compile-time warning for any such fields. Their values are lost on hotload. Suppress the warning with
[SkipHotload] if you are intentionally accepting that behavior.Delegates and lambdas
Delegates and lambdas
Hotload attempts to preserve
Delegate instances, but lambdas can fail to survive if methods that contain them are reordered or significantly restructured. When hotload cannot safely process a delegate, it replaces it with one that logs a warning when invoked.Reflection caches
Reflection caches
If you cache results from reflection (e.g.
Type.GetProperties()), those caches can become stale after a hotload because types change. Mark cached fields with [SkipHotload] and repopulate the cache after hotload.Concurrency
Concurrency
Hotload suspends managed threads that could touch anything being processed. Worker thread tasks (
GameTask.RunInThreadAsync, etc.) are allowed to finish their current yield point first. Write async tasks to yield often so hotload is not blocked for long.Unit testing
If you add aUnitTests/ directory to your project folder, s&box automatically generates a unit test project for you. Restart the editor after creating the directory for the project to appear.
Writing your first test
dotnet test from the CLI, or use the Test Explorer in Visual Studio.
Testing components
If your test needs engine functionality — for example to create aScene and add components — initialize the engine in a shared setup file: