Workbench Per-Session Schema Loading & ALC Reload
Lets you rebuild and re-attach your schema DLL in the Workbench without restarting the host.
Status: โ Implemented ยท Visibility: Internal ยท Category: Schema
๐ฏ What it solves
Iterating on a schema while the Workbench is open normally hits a hard wall: .NET can't unload an assembly that's loaded into the default AssemblyLoadContext, so a rebuilt DLL can't replace the one already in memory without restarting the whole tool. It also doesn't help that a raw load failure, a breaking schema change, or a failed migration would otherwise just throw and tear down the session. This feature lets each Workbench session load your schema DLL in isolation, swap it for a freshly rebuilt one on the next attach, and turns engine-side schema exceptions into a banner state you can act on instead of a crash.
โ๏ธ How it works (in brief)
Each Open/Attach session loads your schema DLL(s) into a fresh, unloadable assembly context scoped to that session; closing the session releases it so the next attach can load the rebuilt binary cleanly. Every [Component]-tagged struct and every concrete type deriving from Archetype<>/Archetype<,> found in the DLL(s) is registered against the engine one at a time โ a failure on one component doesn't stop the others from loading. The aggregate outcome becomes one of three session states: Ready (everything registered cleanly), MigrationRequired (at least one component registered, but something needs attention), or Incompatible (the session isn't navigable โ a schema downgrade, a failed migration function, or total registration failure). Because each rebuild produces new CLR Type instances even for byte-identical source, the engine re-binds them to the same component IDs by matching the [Component(Name, ...)] string across reloads, so re-attaching never inflates component IDs or loses prior registrations.
๐ป Usage
using System.Runtime.InteropServices;
using Typhon.Schema.Definition;
// The Name string ("Game.Player") is the stable key the Workbench uses to re-bind this
// component across rebuilds. Keep it constant even as you change fields or bump Revision.
[Component("Game.Player", revision: 1)]
[StructLayout(LayoutKind.Sequential)]
public struct PlayerComponent
{
public float Health;
public int Level;
}
There's no API to call from application code โ this is the Workbench's own loading behavior. As the application developer you just rebuild your schema project and re-attach (Open/Attach) the session; the Workbench handles the rest. What you control is the schema itself: keep [Component] names stable across edits, and be aware that a breaking field change or a removed migration function will surface as a MigrationRequired or Incompatible banner on the next attach rather than failing silently.
| Session outcome | When it happens | What you can still do |
|---|---|---|
Ready |
Every component registered with no diagnostics | Full session โ browse, query, profile |
MigrationRequired |
At least one component registered; one or more had a breaking change, schema error, or other failure | Browse/inspect the components that loaded; fix and rebuild the rest |
Incompatible |
A schema downgrade, a failed migration function, or zero components registered | Session isn't usable until you fix the binary and re-attach |
โ ๏ธ Guarantees & limits
- A bad or unbuildable DLL never crashes the Kestrel host or other open sessions โ failures are scoped to the session being opened/attached.
- One component failing to register (breaking change, schema error) does not block its siblings from registering โ you keep visibility into everything that's still readable.
- Re-attaching the same schema project after a rebuild reuses the same component IDs and archetype slot bindings โ no ID growth across repeated edit/rebuild/re-attach cycles.
Incompatibleis reserved for unrecoverable cases (schema downgrade, failed migration, or total failure) โ a handful of breaking-change components alongside healthy ones isMigrationRequired, notIncompatible.- A schema DLL placed next to a stale copy of an engine assembly still resolves the engine's own copy (
Typhon.*/System.*/Microsoft.*and anything already loaded are deferred to the host's context) โ you won't silently get zero components from an attribute-identity mismatch. - The session must fully close (and its components released) before the underlying DLL file is unlocked for a fresh build โ keep the Workbench session closed while rebuilding if your build fails with a file-in-use error.
- Multi-file schemas (a main DLL plus sibling helper assemblies) resolve correctly only when the dependency sits in the same directory as the DLL you opened.
๐งช Tests
- SchemaLoaderTests โ missing path (
schema_missing), invalid assembly (schema_load_failed), empty DLL list, real-assembly load through a freshWorkbenchAssemblyLoadContext
๐ Related
- Related feature: Component Family Classification (a separate per-session classification step that runs after loading)
- Related feature: Schema Validation on Reopen (the underlying breaking/compatible classification this feature surfaces as a banner)
- Source:
tools/Typhon.Workbench/Schema/SchemaLoader.cs,tools/Typhon.Workbench/Schema/WorkbenchAssemblyLoadContext.cs,tools/Typhon.Workbench/Schema/SchemaCompatibility.cs,src/Typhon.Engine/Ecs/internals/ArchetypeRegistry.cs