using System.Collections.Concurrent;
using System.Reflection;
using System.Runtime.Loader;
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Interface.Addons;
using Milimoe.FunGame.Core.Interface.Base.Addons;
using Milimoe.FunGame.Core.Library.Common.Addon;
using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Core.Library.Exception;
namespace Milimoe.FunGame.Core.Service
{
///
/// 支持热更新、可卸载、可重载插件/模组
/// 支持插件跨 DLL 引用模组,通过全局访问最新实例
///
internal static class HotLoadAddonManager
{
///
/// 已加载的插件DLL名称对应的路径
///
internal static Dictionary PluginFilePaths { get; } = [];
///
/// 已加载的模组DLL名称对应的路径
///
internal static Dictionary ModuleFilePaths { get; } = [];
///
/// 已加载的插件
///
internal static Dictionary Plugins { get; } = [];
///
/// 已加载的模组
///
internal static Dictionary Modules { get; } = [];
///
/// key = 文件路径(全小写),value = 当前加载上下文 + 程序集 + 根实例
///
private static readonly ConcurrentDictionary _loadedDLLs = new(StringComparer.OrdinalIgnoreCase);
///
/// 即将清除的上下文
///
private static readonly List> _contextsToClean = [];
///
/// 尝试加载或重新加载某个 DLL,返回是否成功加载/更新
///
/// DLL 完整路径
/// 新加载的实例列表
/// 是否为插件
/// 是否为模组
/// 是否成功(无变化不成功)
private static bool TryLoadOrReload(string fullPath, out List newInstances, bool isPlugin, bool isModule)
{
newInstances = [];
if (!File.Exists(fullPath)) return false;
string key = fullPath.ToLowerInvariant();
DateTime currentWriteTime = File.GetLastWriteTimeUtc(fullPath);
// 文件无变化 → 返回现有实例
if (_loadedDLLs.TryGetValue(key, out DLLAddonEntry? entry) && entry.LastWriteTimeUtc == currentWriteTime)
{
foreach (AddonSubEntry sub in entry.Addons)
{
if (sub.Weak.TryGetTarget(out IAddon? target) && target != null)
newInstances.Add(target);
}
return false;
}
// 需要卸载旧 DLL
if (_loadedDLLs.TryRemove(key, out DLLAddonEntry? oldEntry))
{
try
{
foreach (AddonSubEntry sub in oldEntry.Addons)
{
if (sub.Instance is IHotReloadAware aware)
{
aware.OnBeforeUnload();
}
sub.Instance.UnLoad();
Plugins.Remove(sub.Name);
Modules.Remove(sub.Name);
}
oldEntry.Context.Unload();
GC.Collect(2, GCCollectionMode.Forced, true);
}
catch (Exception e)
{
throw new AddonUnloadException(fullPath, e);
}
finally
{
lock (_contextsToClean)
{
_contextsToClean.Add(new WeakReference(oldEntry.Context));
}
}
}
// 加载新 DLL
try
{
string dllName = Path.GetFileNameWithoutExtension(fullPath);
AddonLoadContext ctx = new(dllName, fullPath);
using FileStream fs = new(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read);
Assembly assembly = ctx.LoadFromStream(fs);
string filename = assembly.GetName().Name?.Trim() ?? "";
Type[] addonTypes = [.. assembly.GetTypes().Where(t => !t.IsAbstract && typeof(IAddon).IsAssignableFrom(t) && (typeof(Plugin).IsAssignableFrom(t) || typeof(ServerPlugin).IsAssignableFrom(t) ||
typeof(WebAPIPlugin).IsAssignableFrom(t) || typeof(GameModule).IsAssignableFrom(t) || typeof(GameModuleServer).IsAssignableFrom(t) || typeof(CharacterModule).IsAssignableFrom(t) ||
typeof(SkillModule).IsAssignableFrom(t) || typeof(ItemModule).IsAssignableFrom(t) || typeof(GameMap).IsAssignableFrom(t)))];
DLLAddonEntry newEntry = new()
{
Context = ctx,
Assembly = assembly,
LastWriteTimeUtc = currentWriteTime
};
foreach (Type addonType in addonTypes)
{
try
{
if (Activator.CreateInstance(addonType) is not IAddon instance) continue;
// 为了安全起见,未实现此接口的不允许使用热更新模式加载
if (instance is not IHotReloadAware aware) continue;
string addonName = instance.Name?.Trim() ?? addonType.Name;
AddonSubEntry sub = new()
{
Instance = instance,
Weak = new WeakReference(instance),
Name = addonName
};
newEntry.Addons.Add(sub);
newInstances.Add(instance);
if (isPlugin) Plugins[addonName] = instance;
if (isModule) Modules[addonName] = instance;
}
catch (Exception e)
{
TXTHelper.AppendErrorLog(e.GetErrorInfo());
}
}
if (newInstances.Count > 0)
{
_loadedDLLs[key] = newEntry;
if (isPlugin) PluginFilePaths[filename] = key;
if (isModule) ModuleFilePaths[filename] = key;
}
return true;
}
catch (Exception e)
{
throw new AddonLoadException(fullPath, e);
}
}
///
/// 从 plugins 目录加载所有插件
///
/// 插件字典
/// 委托字典
/// 其他参数
/// 被更新的插件列表
internal static List LoadPlugins(Dictionary plugins, Dictionary delegates, params object[] otherobjs)
{
List updated = [];
if (!Directory.Exists(ReflectionSet.PluginFolderPath)) return updated;
string[] dlls = Directory.GetFiles(ReflectionSet.PluginFolderPath, "*.dll", SearchOption.AllDirectories);
foreach (string dll in dlls)
{
bool loaded = TryLoadOrReload(dll, out List instances, true, false);
foreach (IAddon instance in instances)
{
string name = instance.Name.Trim();
if (instance is Plugin pluginInstance)
{
// 热更新无变化的文件时,不会再触发 Load 方法
if ((loaded || !plugins.ContainsKey(name)) && pluginInstance.Load(otherobjs))
{
pluginInstance.Controller = new(pluginInstance, delegates);
updated.Add(pluginInstance);
}
plugins[name] = pluginInstance;
}
}
}
return updated;
}
///
/// 从 plugins 目录加载所有 Server 插件
///
/// 插件字典
/// 委托字典
/// 其他参数
/// 被更新的插件列表
internal static List LoadServerPlugins(Dictionary plugins, Dictionary delegates, params object[] otherobjs)
{
List updated = [];
if (!Directory.Exists(ReflectionSet.PluginFolderPath)) return updated;
string[] dlls = Directory.GetFiles(ReflectionSet.PluginFolderPath, "*.dll", SearchOption.AllDirectories);
foreach (string dll in dlls)
{
bool loaded = TryLoadOrReload(dll, out List instances, true, false);
foreach (IAddon instance in instances)
{
string name = instance.Name.Trim();
if (instance is ServerPlugin pluginInstance)
{
// 热更新无变化的文件时,不会再触发 Load 方法
if ((loaded || !plugins.ContainsKey(name)) && pluginInstance.Load(otherobjs))
{
pluginInstance.Controller = new(pluginInstance, delegates);
updated.Add(pluginInstance);
}
plugins[name] = pluginInstance;
}
}
}
return updated;
}
///
/// 从 plugins 目录加载所有 WebAPI 插件
///
/// 插件字典
/// 委托字典
/// 其他参数
/// 被更新的插件列表
internal static List LoadWebAPIPlugins(Dictionary plugins, Dictionary delegates, params object[] otherobjs)
{
List updated = [];
if (!Directory.Exists(ReflectionSet.PluginFolderPath)) return updated;
string[] dlls = Directory.GetFiles(ReflectionSet.PluginFolderPath, "*.dll", SearchOption.AllDirectories);
foreach (string dll in dlls)
{
bool loaded = TryLoadOrReload(dll, out List instances, true, false);
foreach (IAddon instance in instances)
{
string name = instance.Name.Trim();
if (instance is WebAPIPlugin pluginInstance)
{
// 热更新无变化的文件时,不会再触发 Load 方法
if ((loaded || !plugins.ContainsKey(name)) && pluginInstance.Load(otherobjs))
{
pluginInstance.Controller = new(pluginInstance, delegates);
updated.Add(pluginInstance);
}
plugins[name] = pluginInstance;
}
}
}
return updated;
}
///
/// 从 modules 目录加载所有模组
///
/// 模组字典
/// 角色字典
/// 技能字典
/// 物品字典
/// 委托字典
/// 其他参数
/// 被更新的模组列表
internal static List LoadGameModules(Dictionary modules, Dictionary characters, Dictionary skills, Dictionary items, Dictionary delegates, params object[] otherobjs)
{
List updated = [];
if (!Directory.Exists(ReflectionSet.GameModuleFolderPath)) return updated;
string[] dlls = Directory.GetFiles(ReflectionSet.GameModuleFolderPath, "*.dll", SearchOption.AllDirectories);
foreach (string dll in dlls)
{
bool loaded = TryLoadOrReload(dll, out List instances, false, true);
foreach (IAddon instance in instances)
{
string name = instance.Name.Trim();
if (instance is GameModule moduleInstance)
{
// 热更新无变化的文件时,不会再触发 Load 方法
if ((loaded || !modules.ContainsKey(name)) && instance.Load(otherobjs))
{
moduleInstance.Controller = new(moduleInstance, delegates);
}
modules[name] = moduleInstance;
}
else if (instance is CharacterModule charInstance)
{
if (loaded || !characters.ContainsKey(name))
{
instance.Load(otherobjs);
}
characters[name] = charInstance;
}
else if (instance is SkillModule skillInstance)
{
if (loaded || !skills.ContainsKey(name))
{
instance.Load(otherobjs);
}
skills[name] = skillInstance;
}
else if (instance is ItemModule itemInstance)
{
if (loaded || !items.ContainsKey(name))
{
instance.Load(otherobjs);
}
items[name] = itemInstance;
}
}
}
return updated;
}
///
/// 从 modules 目录加载所有适用于服务器的模组
///
/// 服务器模组字典
/// 角色字典
/// 技能字典
/// 物品字典
/// 委托字典
/// 其他参数
/// 被更新的服务器模组列表
internal static List LoadGameModulesForServer(Dictionary servers, Dictionary characters, Dictionary skills, Dictionary items, Dictionary delegates, params object[] otherobjs)
{
List updated = [];
if (!Directory.Exists(ReflectionSet.GameModuleFolderPath)) return updated;
string[] dlls = Directory.GetFiles(ReflectionSet.GameModuleFolderPath, "*.dll", SearchOption.AllDirectories);
foreach (string dll in dlls)
{
bool loaded = TryLoadOrReload(dll, out List instances, false, true);
foreach (IAddon instance in instances)
{
string name = instance.Name.Trim();
if (instance is GameModuleServer serversInstance)
{
// 热更新无变化的文件时,不会再触发 Load 方法
if ((loaded || !servers.ContainsKey(name)) && instance.Load(otherobjs))
{
serversInstance.Controller = new(serversInstance, delegates);
}
servers[name] = serversInstance;
}
else if (instance is CharacterModule charInstance)
{
if (loaded || !characters.ContainsKey(name))
{
instance.Load(otherobjs);
}
characters[name] = charInstance;
}
else if (instance is SkillModule skillInstance)
{
if (loaded || !skills.ContainsKey(name))
{
instance.Load(otherobjs);
}
skills[name] = skillInstance;
}
else if (instance is ItemModule itemInstance)
{
if (loaded || !items.ContainsKey(name))
{
instance.Load(otherobjs);
}
items[name] = itemInstance;
}
}
}
return updated;
}
///
/// 从 maps 目录加载所有地图
///
/// 地图字典
/// 其他参数
/// 被更新的地图列表
internal static List LoadGameMaps(Dictionary maps, params object[] objs)
{
List updated = [];
if (!Directory.Exists(ReflectionSet.GameMapFolderPath)) return updated;
string[] dlls = Directory.GetFiles(ReflectionSet.GameMapFolderPath, "*.dll", SearchOption.AllDirectories);
foreach (string dll in dlls)
{
bool loaded = TryLoadOrReload(dll, out List instances, false, true);
foreach (IAddon instance in instances)
{
string name = instance.Name.Trim();
if (instance is GameMap mapInstance)
{
// 热更新无变化的文件时,不会再触发 Load 方法
if ((loaded || !maps.ContainsKey(name)) && mapInstance.Load(objs))
{
updated.Add(mapInstance);
}
maps[name] = mapInstance;
}
}
}
return updated;
}
///
/// 在任务计划中定期执行
///
internal static void CleanUnusedContexts()
{
lock (_contextsToClean)
{
for (int i = _contextsToClean.Count - 1; i >= 0; i--)
{
if (!_contextsToClean[i].TryGetTarget(out AssemblyLoadContext? ctx) || ctx.IsCollectible == false)
{
_contextsToClean.RemoveAt(i);
continue;
}
GC.Collect();
GC.WaitForPendingFinalizers();
if (!ctx.IsCollectible)
{
_contextsToClean.RemoveAt(i);
}
}
}
}
///
/// 热更新 DLL
///
/// DLL 完整路径
/// 是否成功热更新
internal static bool HotReload(string filePath)
{
return TryLoadOrReload(filePath, out _, isPlugin: Directory.GetParent(filePath)?.FullName == ReflectionSet.PluginFolderPath, isModule: Directory.GetParent(filePath)?.FullName == ReflectionSet.GameModuleFolderPath);
}
///
/// 尝试获取当前最新的实例
///
/// 预期类型
/// 插件/模组名称
/// 最新的实例
/// 是否找到
internal static bool TryGetLiveInstance(string addonName, out T? instance) where T : class, IAddon
{
instance = null;
// 所有的 Plugin 都继承自 IPlugin -> IAddon,除此之外都是 IAddon,所以先从 Plugin 里找
if (typeof(T).IsSubclassOf(typeof(IPlugin)))
{
if (Plugins.FirstOrDefault(kv => kv.Key == addonName).Value is T plugin)
{
instance = plugin;
}
}
else if (Modules.FirstOrDefault(kv => kv.Key == addonName).Value is T module)
{
instance = module;
}
if (instance != null)
{
return true;
}
return false;
}
///
/// 每个加载项都拥有独立的可收集 AssemblyLoadContext
///
private class AddonLoadContext(string addonName, string filePath) : AssemblyLoadContext($"{addonName}_{Guid.NewGuid():N}", isCollectible: true)
{
public string AddonName { get; } = addonName;
public string FilePath { get; } = filePath;
public DateTime LastWriteTimeUtc { get; } = File.GetLastWriteTimeUtc(filePath);
public AssemblyDependencyResolver Resolver { get; } = new(filePath);
///
/// 读取加载项的依赖项
///
protected override Assembly? Load(AssemblyName assemblyName)
{
string? assemblyPath = Resolver.ResolveAssemblyToPath(assemblyName);
if (assemblyPath != null)
{
return LoadFromAssemblyPath(assemblyPath);
}
return null;
}
///
/// 读取 native dll 依赖项
///
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
string? nativePath = Resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
if (nativePath != null)
{
return LoadUnmanagedDllFromPath(nativePath);
}
return base.LoadUnmanagedDll(unmanagedDllName);
}
}
///
/// 记录 DLL 信息
///
private class DLLAddonEntry
{
public required AddonLoadContext Context { get; set; }
public required Assembly Assembly { get; set; }
public required DateTime LastWriteTimeUtc { get; set; }
public List Addons { get; } = [];
}
///
/// 记录加载项信息
///
private class AddonSubEntry
{
public required IAddon Instance { get; set; }
public required WeakReference Weak { get; set; }
public required string Name { get; set; }
}
}
}