Skip to content

Commit

Permalink
Speed up code mod reloading
Browse files Browse the repository at this point in the history
  • Loading branch information
Popax21 committed Nov 6, 2023
1 parent 9d588c7 commit c6e8512
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 45 deletions.
26 changes: 19 additions & 7 deletions Celeste.Mod.mm/Mod/Everest/Everest.Loader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -798,15 +798,27 @@ void VisitMod(EverestModuleMetadata node) {
}

// Load modules in the reverse order determined before (dependencies before dependents)
foreach (EverestModuleMetadata loadMod in reloadMods.Reverse<EverestModuleMetadata>()) {
if (loadMod.Dependencies.Any(dep => !DependencyLoaded(dep))) {
Logger.Log(LogLevel.Warn, "loader", $"-> skipping reload of mod '{loadMod.Name}' as dependency failed to load");
continue;
// Delay initialization until all mods have been loaded
Trace.Assert(_DelayedModuleInitializationQueue == null);
try {
_DelayedModuleInitializationQueue = new Queue<EverestModule>();

foreach (EverestModuleMetadata loadMod in reloadMods.Reverse<EverestModuleMetadata>()) {
if (loadMod.Dependencies.Any(dep => !DependencyLoaded(dep))) {
Logger.Log(LogLevel.Warn, "loader", $"-> skipping reload of mod '{loadMod.Name}' as dependency failed to load");
continue;
}

Logger.Log(LogLevel.Verbose, "loader", $"-> reloading: {loadMod.Name}");
if (!LoadMod(loadMod))
Logger.Log(LogLevel.Warn, "loader", $"-> failed to reload mod '{loadMod.Name}'!");
}
} finally {
Queue<EverestModule> moduleInitQueue = _DelayedModuleInitializationQueue;
_DelayedModuleInitializationQueue = null;

Logger.Log(LogLevel.Verbose, "loader", $"-> reloading: {loadMod.Name}");
if (!LoadMod(loadMod))
Logger.Log(LogLevel.Warn, "loader", $"-> failed to reload mod '{loadMod.Name}'!");
if (moduleInitQueue.Count > 0)
Everest.LateInitializeMods(moduleInitQueue);
}
}, static () => AssetReloadHelper.ReloadLevel(true));
});
Expand Down
90 changes: 56 additions & 34 deletions Celeste.Mod.mm/Mod/Everest/Everest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -544,40 +544,10 @@ public static void Register(this EverestModule module) {
module.LoadContent(true);
}
if (_Initialized) {
Tracker.Initialize();
module.Initialize();
Input.Initialize();
((Monocle.patch_Commands) Engine.Commands).ReloadCommandsList();

if (SaveData.Instance != null) {
// we are in a save. we are expecting the save data to already be loaded at this point
Logger.Log(LogLevel.Verbose, "core", $"Loading save data slot {SaveData.Instance.FileSlot} for {module.Metadata}");
if (module.SaveDataAsync) {
module.DeserializeSaveData(SaveData.Instance.FileSlot, module.ReadSaveData(SaveData.Instance.FileSlot));
} else {
#pragma warning disable CS0618 // Synchronous save / load IO is obsolete but some mods still override / use it.
module.LoadSaveData(SaveData.Instance.FileSlot);
#pragma warning restore CS0618
}

if (SaveData.Instance.CurrentSession?.InArea ?? false) {
// we are in a level. we are expecting the session to already be loaded at this point
Logger.Log(LogLevel.Verbose, "core", $"Loading session slot {SaveData.Instance.FileSlot} for {module.Metadata}");
if (module.SaveDataAsync) {
module.DeserializeSession(SaveData.Instance.FileSlot, module.ReadSession(SaveData.Instance.FileSlot));
} else {
#pragma warning disable CS0618 // Synchronous save / load IO is obsolete but some mods still override / use it.
module.LoadSession(SaveData.Instance.FileSlot, false);
#pragma warning restore CS0618
}
}
}

// Check if the module defines a PrepareMapDataProcessors method. If this is the case, we want to reload maps so that they are applied.
if (module.GetType().GetMethod("PrepareMapDataProcessors", new Type[] { typeof(MapDataFixup) })?.DeclaringType == module.GetType()) {
Logger.Log(LogLevel.Verbose, "core", $"Module {module.Metadata} has map data processors: reloading maps.");
AssetReloadHelper.ReloadAllMaps();
}
if (_DelayedModuleInitializationQueue != null)
_DelayedModuleInitializationQueue.Enqueue(module);
else
LateInitializeMods(Enumerable.Repeat(module, 1));
}

if (Engine.Instance != null && Engine.Scene is Overworld overworld) {
Expand Down Expand Up @@ -611,6 +581,58 @@ public static void Register(this EverestModule module) {
CheckDependenciesOfDelayedMods();
}

[ThreadStatic]
internal static Queue<EverestModule> _DelayedModuleInitializationQueue = null;

internal static void LateInitializeMods(IEnumerable<EverestModule> modules) {
// Re-initialize the tracker
Tracker.Initialize();

// Initialize mods
foreach (EverestModule module in modules)
module.Initialize();

// Re-initialize inputs + reload commands
Input.Initialize();
((Monocle.patch_Commands) Engine.Commands).ReloadCommandsList();

// If we are in a save, load save data
if (SaveData.Instance != null) {
foreach (EverestModule module in modules) {
// we are in a save. we are expecting the save data to already be loaded at this point
Logger.Log(LogLevel.Verbose, "core", $"Loading save data slot {SaveData.Instance.FileSlot} for {module.Metadata}");
if (module.SaveDataAsync) {
module.DeserializeSaveData(SaveData.Instance.FileSlot, module.ReadSaveData(SaveData.Instance.FileSlot));
} else {
#pragma warning disable CS0618 // Synchronous save / load IO is obsolete but some mods still override / use it.
module.LoadSaveData(SaveData.Instance.FileSlot);
#pragma warning restore CS0618
}

if (SaveData.Instance.CurrentSession?.InArea ?? false) {
// we are in a level. we are expecting the session to already be loaded at this point
Logger.Log(LogLevel.Verbose, "core", $"Loading session slot {SaveData.Instance.FileSlot} for {module.Metadata}");
if (module.SaveDataAsync) {
module.DeserializeSession(SaveData.Instance.FileSlot, module.ReadSession(SaveData.Instance.FileSlot));
} else {
#pragma warning disable CS0618 // Synchronous save / load IO is obsolete but some mods still override / use it.
module.LoadSession(SaveData.Instance.FileSlot, false);
#pragma warning restore CS0618
}
}
}
}

// Check if any module defines a PrepareMapDataProcessors method. If this is the case, we want to reload maps so that they are applied.
foreach (EverestModule module in modules) {
if (module.GetType().GetMethod("PrepareMapDataProcessors", new Type[] { typeof(MapDataFixup) })?.DeclaringType == module.GetType()) {
Logger.Log(LogLevel.Verbose, "core", $"Module {module.Metadata} has map data processors: reloading maps.");
AssetReloadHelper.ReloadAllMaps();
break;
}
}
}

internal static void CheckDependenciesOfDelayedMods() {
// Attempt to load mods after their dependencies have been loaded.
// Only load and lock the delayed list if we're not already loading delayed mods.
Expand Down
9 changes: 5 additions & 4 deletions Celeste.Mod.mm/Mod/Everest/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;

namespace Celeste.Mod {
Expand Down Expand Up @@ -372,12 +373,12 @@ public static bool IsUp(this TouchLocationState state)
=> state == TouchLocationState.Released || state == TouchLocationState.Invalid;

[ThreadStatic]
private static HashSet<string> _SafeTypes;
private static ConditionalWeakTable<Type, object> _SafeTypes;
public static bool IsSafe(this Type type) {
_SafeTypes ??= new HashSet<string>();
_SafeTypes ??= new ConditionalWeakTable<Type, object>();

try {
if (_SafeTypes.Contains(type.AssemblyQualifiedName))
if (!_SafeTypes.TryGetValue(type, out _))
return true;

// "Probe" the type
Expand All @@ -391,7 +392,7 @@ public static bool IsSafe(this Type type) {
if (!type.BaseType?.IsSafe() ?? false)
return false;

_SafeTypes.Add(type.AssemblyQualifiedName);
_SafeTypes.Add(type, new object());
return true;
} catch {
return false;
Expand Down

0 comments on commit c6e8512

Please sign in to comment.