Files
ShrinkModFramework/Runtime/Bootstrap/ShrinkModRuntimeDriver.cs
T
cneicy 78ccae3e07
Publish UPM package / publish (push) Failing after 1s
chore: initialize standalone UPM package
2026-08-26 02:50:31 +08:00

174 lines
5.4 KiB
C#

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<Action> _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<ShrinkModRuntimeDriver>();
_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;
}
}
}