diff --git a/src/Core/Log.cs b/src/Core/Log.cs index 310e23f..01bb7a8 100644 --- a/src/Core/Log.cs +++ b/src/Core/Log.cs @@ -1,3 +1,4 @@ +using System; using UnityModManagerNet; namespace S3.Core; @@ -8,11 +9,56 @@ namespace S3.Core; /// public static class Log { + private const int RingN = 200; + private static readonly object RingLock = new(); + private static readonly string[] Ring = new string[RingN]; + private static int _ringCount; + private static UnityModManager.ModEntry.ModLogger? _logger; public static void Init(UnityModManager.ModEntry modEntry) => _logger = modEntry.Logger; - public static void Info(string msg) => _logger?.Log(msg); - public static void Warn(string msg) => _logger?.Warning(msg); - public static void Error(string msg) => _logger?.Error(msg); + public static void Info(string msg) + { + Push("INF", msg); + _logger?.Log(msg); + } + + public static void Warn(string msg) + { + Push("WRN", msg); + _logger?.Warning(msg); + } + + public static void Error(string msg) + { + Push("ERR", msg); + _logger?.Error(msg); + } + + public static string[] Tail(int count) + { + if (count < 1) count = 1; + if (count > RingN) count = RingN; + lock (RingLock) + { + int have = Math.Min(_ringCount, RingN); + int take = Math.Min(count, have); + var result = new string[take]; + int start = _ringCount - take; + for (int i = 0; i < take; i++) + result[i] = Ring[(start + i) % RingN]; + return result; + } + } + + static void Push(string level, string msg) + { + string line = DateTime.Now.ToString("HH:mm:ss") + " [" + level + "] " + msg; + lock (RingLock) + { + Ring[_ringCount % RingN] = line; + _ringCount++; + } + } } diff --git a/src/Core/ModuleRegistry.cs b/src/Core/ModuleRegistry.cs index 9133aca..1c34e72 100644 --- a/src/Core/ModuleRegistry.cs +++ b/src/Core/ModuleRegistry.cs @@ -10,6 +10,7 @@ namespace S3.Core; public sealed class ModuleRegistry { private readonly List _modules = new(); + private readonly HashSet _active = new(); public IReadOnlyList Modules => _modules; @@ -29,6 +30,7 @@ public sealed class ModuleRegistry try { m.OnEnable(); + _active.Add(m); Log.Info($"[{m.Id}] enabled."); } catch (Exception e) @@ -38,6 +40,66 @@ public sealed class ModuleRegistry } } + public bool IsActive(IModule module) => _active.Contains(module); + + public bool IsActive(string id) + { + IModule? module = Find(id); + return module != null && _active.Contains(module); + } + + public IModule? Find(string id) + { + foreach (IModule module in _modules) + if (string.Equals(module.Id, id, StringComparison.OrdinalIgnoreCase)) + return module; + return null; + } + + /// + /// Changes a module's live state without changing its persisted Enabled setting. + /// Callers that want the change persisted must update Enabled and SaveSettings. + /// + public bool TrySetActive(string id, bool active, out string message) + { + IModule? module = Find(id); + if (module == null) + { + message = "unknown module: " + id; + return false; + } + + bool current = _active.Contains(module); + if (current == active) + { + message = $"{module.Id} active={active} (unchanged)"; + return true; + } + + try + { + if (active) + { + module.OnEnable(); + _active.Add(module); + } + else + { + module.OnDisable(); + _active.Remove(module); + } + message = $"{module.Id} active={active}"; + Log.Info($"[{module.Id}] live active={active}."); + return true; + } + catch (Exception e) + { + message = $"{module.Id} active={current}; transition failed: {e.Message}"; + Log.Error($"[{module.Id}] live transition to active={active} failed: {e}"); + return false; + } + } + /// Persist every module's settings (called from UMM's OnSaveGUI). public void SaveAll() { diff --git a/src/Main.cs b/src/Main.cs index faef95c..127fa84 100644 --- a/src/Main.cs +++ b/src/Main.cs @@ -16,6 +16,7 @@ namespace S3; public static class Main { internal static UnityModManager.ModEntry ModEntry { get; private set; } = null!; + public static ModuleRegistry Registry { get; private set; } = null!; private static ModuleRegistry _registry = null!; @@ -26,6 +27,7 @@ public static class Main SettingsStore.Init(modEntry.Path); _registry = new ModuleRegistry(); + Registry = _registry; // Modules are registered here. Each module loads its own settings in its // constructor. Order here is display order in the settings panel. _registry.Register(new Modules.PhysicsOptimizer.PhysicsOptimizerModule());