This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace ShrinkModFramework
|
||||
{
|
||||
[DefaultExecutionOrder(-2100)]
|
||||
public sealed class ShrinkModBootstrap : MonoBehaviour
|
||||
{
|
||||
[Header("Override (留空则只使用组件上的选项)")]
|
||||
[SerializeField] private ShrinkModFrameworkSettings settingsOverride;
|
||||
|
||||
[Header("Bootstrap")]
|
||||
[SerializeField] private bool autoLoadOnAwake = true;
|
||||
|
||||
private static bool _bootstrapped;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_bootstrapped)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
_bootstrapped = true;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
|
||||
var shouldAutoLoad = settingsOverride ? settingsOverride.autoLoadOnStartup : autoLoadOnAwake;
|
||||
if (shouldAutoLoad)
|
||||
ShrinkModLoader.LoadAll(settingsOverride);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 23fd762abde4cfe48bf93f9ff86ffdd5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,69 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace ShrinkModFramework
|
||||
{
|
||||
[CreateAssetMenu(fileName = "ShrinkModFrameworkSettings", menuName = "ShrinkModFramework/Settings")]
|
||||
public class ShrinkModFrameworkSettings : ScriptableObject
|
||||
{
|
||||
private static ShrinkModFrameworkSettings _instance;
|
||||
|
||||
public static ShrinkModFrameworkSettings Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance) return _instance;
|
||||
|
||||
_instance = Resources.Load<ShrinkModFrameworkSettings>("ShrinkModFrameworkSettings");
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!_instance)
|
||||
{
|
||||
var guids = UnityEditor.AssetDatabase.FindAssets("t:ShrinkModFrameworkSettings");
|
||||
if (guids.Length > 0)
|
||||
{
|
||||
var path = UnityEditor.AssetDatabase.GUIDToAssetPath(guids[0]);
|
||||
_instance = UnityEditor.AssetDatabase.LoadAssetAtPath<ShrinkModFrameworkSettings>(path);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!_instance)
|
||||
{
|
||||
_instance = CreateInstance<ShrinkModFrameworkSettings>();
|
||||
Debug.LogWarning(
|
||||
"[ShrinkModFramework] 未找到 ShrinkModFrameworkSettings 配置文件,当前使用默认配置。");
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
internal set => _instance = value;
|
||||
}
|
||||
|
||||
[Header("Bootstrap")]
|
||||
public bool autoLoadOnStartup = true;
|
||||
[Tooltip("使用 ShrinkContextHost 协调模组组件;关闭时回退到旧的只增不减 ShrinkModLoader。")]
|
||||
public bool useContextHost = true;
|
||||
|
||||
[Header("Logging")]
|
||||
public bool verboseLogging = true;
|
||||
|
||||
[Header("Discovery")]
|
||||
public string[] assemblyNamePrefixes = new string[0];
|
||||
|
||||
[Header("External Mods")]
|
||||
public bool enableExternalDllMods = true;
|
||||
public bool autoCreateExternalModsDirectory = true;
|
||||
public string externalModsFolderName = "Mods";
|
||||
public bool watchExternalModsDirectory = true;
|
||||
public float externalModsReloadDelaySeconds = 0.5f;
|
||||
[Min(1)]
|
||||
[Tooltip("Mono 下外部程序集 revision 会常驻;历史数量达到该软阈值后提示 Domain Reload/重启。")]
|
||||
public int externalAssemblyRevisionSoftLimit = 16;
|
||||
|
||||
[Header("Harmony")]
|
||||
public bool enableHarmonyPatching = true;
|
||||
|
||||
[Header("Networking")]
|
||||
public bool enableNetworkSync = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f9516fe9a338fb448308a9205c3d448
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
|
||||
namespace ShrinkModFramework
|
||||
{
|
||||
/// <summary>把文件系统的 burst 事件折叠为一次主线程 reload。</summary>
|
||||
internal sealed class ShrinkModReloadDebouncer
|
||||
{
|
||||
private bool _pending;
|
||||
private float _reloadAt;
|
||||
|
||||
public bool IsPending => _pending;
|
||||
|
||||
public void Schedule(float now, float delaySeconds)
|
||||
{
|
||||
_pending = true;
|
||||
_reloadAt = now + Math.Max(0.05f, delaySeconds);
|
||||
}
|
||||
|
||||
public bool TryConsume(float now)
|
||||
{
|
||||
if (!_pending || now < _reloadAt)
|
||||
return false;
|
||||
|
||||
_pending = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_pending = false;
|
||||
_reloadAt = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ec113876ec861b64cb8391eed726108b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace ShrinkModFramework
|
||||
{
|
||||
public static class ShrinkModRuntimeBootstrap
|
||||
{
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void ResetStaticStateForPlayMode()
|
||||
{
|
||||
ShrinkModLoader.ResetForDomainReload();
|
||||
ShrinkModNetworkManager.ResetForDomainReload();
|
||||
}
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
|
||||
private static void AutoLoad()
|
||||
{
|
||||
var settings = ShrinkModFrameworkSettings.Instance;
|
||||
ShrinkModRuntimeDriver.EnsureCreated(settings);
|
||||
|
||||
if (settings && !settings.autoLoadOnStartup)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
ShrinkModLoader.LoadAll(settings);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
Debug.LogError($"[ShrinkModFramework] 自动启动失败:{ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 329c6f80f39fd4840b987c16a6124b1c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,173 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9a850cdc47a8b5b41a7559c3d1fd9efc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user