Add hot reload for live mod code reloading#92
Add hot reload for live mod code reloading#92elliotttate wants to merge 5 commits intoAlchyr:developfrom
Conversation
Reload mod assemblies at runtime without restarting the game. Change a card's damage, fix a relic's effect, or add new entities — rebuild and type `hotreload MyMod` in the dev console. The system loads the new assembly into Godot's ALC, removes old entities from ModelDb, creates fresh instances (which auto-register in pools via BaseLib's constructors), reloads localization, and refreshes live card/relic/power nodes in the scene tree. Unchanged entities are detected via IL signature hashing and skipped for speed. Build integration via BaseLib.props stamps each Debug build with a unique assembly name (e.g. MyMod_hr143052789) so the ALC accepts it alongside the previous version. The stamped filename also avoids file lock conflicts with the running game. Mods using the BaseLib NuGet package get this automatically. Console commands: hotreload, hotreload_list, hotreload_status, hotreload_test. Optional file watcher for automatic reload on build (enable in BaseLib settings). 73 startup self-tests verify all reflection targets on every game launch.
- Fix CTS leak in ModFileWatcher (old CancellationTokenSource was never disposed) - Add logging to LiveInstanceRefresher catch blocks (was silently swallowing errors) - Expand rollback to clear ModelDb caches and pool instance caches on entity failure - Skip types without parameterless constructors instead of crashing - Guard against null from Activator.CreateInstance - Protect file watcher against file deletion between debounce and read - Warn when old assembly count exceeds 10 (memory accumulation) - Log stale patch removal failures instead of silently ignoring - Add 11 new integration tests: bad path/mod ID error handling, InheritsFromByName vs IsSubclassOf consistency, hash stability, assembly count health check, RemoveByAssembly safety with unknown assemblies, edge case mod names, and more
Resolve conflicts in BaseLibMain.cs, BaseLibConfig.cs, ContentPatches.cs, and NodeFactory.cs. Adopt upstream's _convertingNodes HashSet tracking and CreateFromNode rename while preserving hot reload additions (HotReloadEngine, RemoveByAssembly, self-tests, CustomModelCounts, EnableFileWatcher config).
|
I've been using this for a while now without any issues, but please let me know if there's anything that could be improved or some edge case that isn't working right |
RunSelfTests() was called from NodeFactory.Init() which runs before harmony.PatchAll(). The 3 tests that call scene.Instantiate() need the SceneConversionPatch postfix to be in place. Move the call to BaseLibMain after PatchAll() so all 17 tests can pass.
…zation Upstream renamed Patches/Utils/ModelLocPatch.cs to Patches/Localization/ModelLocPatch.cs, changing its namespace. Add the new using to HotReloadPipeline.cs so it compiles.
|
I'd like to know how big of an impact on performance this has and if it breaks mod interoperability. This seems like a pretty invasive change |
|
I haven't noticed any, but I have a pretty powerful PC and it needs more testing from others. It's more intended for development though than something you use for released mods. |
|
ngl this should probably be a separate mod, live reloading is completely out of the scope of an end user. baselib is not just for developing mods and it seems like less impact if this is a modding tool for modders and not a part of the baselib every mod on relies upon. |
Summary
Adds a built-in hot reload system that lets modders reload their mod's code while the game is running — no restart needed. Change a card's damage, fix a relic's effect, add a new entity, rebuild, and type
hotreload MyModin the dev console. Changes take effect in seconds.Activator.CreateInstance(), so no explicit pool re-registration is neededMyMod_hr143052789), solving both ALC name conflicts and file locking. Mods using the BaseLib NuGet package get this for free.hotreload,hotreload_list,hotreload_status,hotreload_testKey technical findings during development
IsolatedComponentLoadContext, notAssemblyLoadContext.Default, for mod assemblies. Hot-reloaded DLLs must be loaded into the same ALC as sts2.dll orIsSubclassOf/ischecks fail.AbstractModelconstructor throwsDuplicateModelExceptionif a canonical model already exists. Old entities must be removed from_contentByIdbeforeActivator.CreateInstance().ModManager._modsis the current field name (not_loadedMods).What modders need to do
Nothing special if they already use the BaseLib NuGet package and have
Sts2Pathset in their .csproj. Build in Debug, runhotreload ModNamein the console. Full guide indocs/hot_reload.md.Files
HotReload/HotReloadPipeline.csHotReload/HotReloadEngine.csReload()/ReloadByModId()APIHotReload/HotReloadResult.csTypeSignatureHasher.cs,AssemblyStamper.cs,SerializationCacheSnapshot.cs,HotReloadSession.csLiveInstanceRefresher.csModFileWatcher.csCommands/HotReloadCommand.csHotReloadSelfTests.csbuild/BaseLib.propsdocs/hot_reload.mdRemoveByAssembly()hooks and extracted reusable methodsTest plan
hotreloadconsole command works by mod folder name