using System; using System.Collections.Concurrent; using System.IO; using UnityEngine; namespace ShrinkModFramework { [DefaultExecutionOrder(-2099)] internal sealed class ShrinkModRuntimeDriver : MonoBehaviour { private static ShrinkModRuntimeDriver _instance; private readonly ConcurrentQueue _mainThreadActions = new(); private readonly ShrinkModReloadDebouncer _externalReloadDebouncer = new(); private FileSystemWatcher _watcher; private ShrinkModFrameworkSettings _settings; public static void EnsureCreated(ShrinkModFrameworkSettings settings) { if (_instance) { _instance.ApplySettings(settings); return; } var go = new GameObject("ShrinkModRuntimeDriver"); if (Application.isPlaying) DontDestroyOnLoad(go); _instance = go.AddComponent(); _instance.ApplySettings(settings); } public static void Enqueue(Action action) { if (action == null) return; if (_instance == null) throw new InvalidOperationException("ShrinkModRuntimeDriver 尚未创建。"); _instance._mainThreadActions.Enqueue(action); } internal static ShrinkModRuntimeDriver InstanceForTesting => _instance; internal bool HasWatcherForTesting => _watcher != null; internal void PumpForTesting() => Update(); private void Update() { while (_mainThreadActions.TryDequeue(out var action)) { try { action(); } catch (Exception ex) { Debug.LogException(ex); } } if (!_externalReloadDebouncer.TryConsume(Time.realtimeSinceStartup)) return; try { ShrinkModLoader.LoadNewExternalMods(_settings); } catch (Exception ex) { Debug.LogException(ex); Debug.LogError($"[ShrinkModFramework] 自动热加载外部模组失败:{ex.Message}"); } } private void OnDestroy() { DisposeWatcher(); if (_instance == this) _instance = null; } private void ApplySettings(ShrinkModFrameworkSettings settings) { _settings = settings ?? ShrinkModFrameworkSettings.Instance; if (_settings == null || !_settings.enableExternalDllMods || !_settings.watchExternalModsDirectory) { DisposeWatcher(); return; } TrySetupWatcher(); } private void TrySetupWatcher() { #if ENABLE_IL2CPP && !UNITY_EDITOR return; #else var directory = ShrinkExternalModAssemblyLoader.GetExternalModsDirectory(_settings); if (_settings.autoCreateExternalModsDirectory) Directory.CreateDirectory(directory); if (!Directory.Exists(directory)) return; if (_watcher != null && string.Equals(_watcher.Path, directory, StringComparison.OrdinalIgnoreCase)) return; DisposeWatcher(); try { _watcher = new FileSystemWatcher(directory, "*.dll") { IncludeSubdirectories = true, NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.CreationTime }; _watcher.Created += OnExternalModsChanged; _watcher.Changed += OnExternalModsChanged; _watcher.Deleted += OnExternalModsChanged; _watcher.Renamed += OnExternalModsRenamed; _watcher.EnableRaisingEvents = true; } catch (Exception ex) { Debug.LogWarning($"[ShrinkModFramework] 外部模组目录监听启动失败:{ex.Message}"); DisposeWatcher(); } #endif } private void OnExternalModsChanged(object sender, FileSystemEventArgs args) { ScheduleExternalReload(args.FullPath); } private void OnExternalModsRenamed(object sender, RenamedEventArgs args) { ScheduleExternalReload(args.OldFullPath); ScheduleExternalReload(args.FullPath); } private void ScheduleExternalReload(string path) { if (string.IsNullOrWhiteSpace(path)) return; _mainThreadActions.Enqueue(() => { var delay = _settings ? Mathf.Max(0.05f, _settings.externalModsReloadDelaySeconds) : 0.5f; _externalReloadDebouncer.Schedule(Time.realtimeSinceStartup, delay); }); } private void DisposeWatcher() { if (_watcher == null) return; _watcher.EnableRaisingEvents = false; _watcher.Created -= OnExternalModsChanged; _watcher.Changed -= OnExternalModsChanged; _watcher.Deleted -= OnExternalModsChanged; _watcher.Renamed -= OnExternalModsRenamed; _watcher.Dispose(); _watcher = null; } } }