Summary
WarpClock.App loads theme plug-ins in-process, each into its own collectible AssemblyLoadContext (ThemePluginLoader.PluginLoadContext, isCollectible: true), deferring shared contracts (WarpClock.Abstractions, WarpToolkit.WinForms.DirectX, runtime) to the default context so plug-in IClockTheme types share identity with the host. This part of the design is already correct.
However, the collectible contexts are never unloaded, so true hot-reload does not work today.
Current behavior
ThemePluginLoader accumulates every PluginLoadContext in _contexts and never disposes/unloads them.
_loadedPaths guards against re-loading a path that was already loaded. On a FileSystemWatcher-triggered change, a modified DLL at an already-seen path is skipped — its new content is ignored until the app restarts.
- Net effect: "load-once" semantics. Dropping a new DLL works; changing an existing one does not hot-reload.
Proposed work (future)
Implement real unload-and-reload semantics using the collectible ALCs we already create:
- Track the
PluginLoadContext (and discovered themes) per source path.
- On a watcher change for an already-loaded path:
- Remove/dispose any live theme instances originating from that assembly.
- Call
AssemblyLoadContext.Unload() on the old context and drop it from _contexts.
- Remove the path from
_loadedPaths.
- Reload the assembly into a fresh collectible context.
- Handle the asynchronous nature of ALC unload (GC-driven) — references must be released before the context can collect; surface/await accordingly.
- Keep everything serialized through the existing
_loadGate so the worker-thread initial load and watcher-triggered reloads can never race.
Notes
- This is intentionally deferred; the current demo ships with "load-once" behavior on purpose.
- Relevant files:
src/WinForms/NET11/WarpClock/WarpClock.App/ThemePluginLoader.cs
src/WinForms/NET11/WarpClock/WarpClock.App/FormMain.cs (watcher + _loadGate + LoadPluginsAsync)
Summary
WarpClock.Apploads theme plug-ins in-process, each into its own collectibleAssemblyLoadContext(ThemePluginLoader.PluginLoadContext,isCollectible: true), deferring shared contracts (WarpClock.Abstractions,WarpToolkit.WinForms.DirectX, runtime) to the default context so plug-inIClockThemetypes share identity with the host. This part of the design is already correct.However, the collectible contexts are never unloaded, so true hot-reload does not work today.
Current behavior
ThemePluginLoaderaccumulates everyPluginLoadContextin_contextsand never disposes/unloads them._loadedPathsguards against re-loading a path that was already loaded. On aFileSystemWatcher-triggered change, a modified DLL at an already-seen path is skipped — its new content is ignored until the app restarts.Proposed work (future)
Implement real unload-and-reload semantics using the collectible ALCs we already create:
PluginLoadContext(and discovered themes) per source path.AssemblyLoadContext.Unload()on the old context and drop it from_contexts._loadedPaths._loadGateso the worker-thread initial load and watcher-triggered reloads can never race.Notes
src/WinForms/NET11/WarpClock/WarpClock.App/ThemePluginLoader.cssrc/WinForms/NET11/WarpClock/WarpClock.App/FormMain.cs(watcher +_loadGate+LoadPluginsAsync)