using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; namespace XWorld.Server.Host { // (gameId,version) -> 已加载模块;按引用计数获取/释放。 // 淘汰有两个来源:①同 gameId 更高版本加载成功;②同 (gameId,version) 磁盘内容指纹变化(覆盖重发)。 // 被淘汰且引用归零即卸载;内容变化时新旧模块并存至旧房间结束(_retired,Release 按实例识别)。 // 生产环境版本不可变、指纹恒命中缓存,行为与旧实现一致。 public sealed class GameModuleRegistry { private sealed class Entry { public LoadedGameModule Module; public int RefCount; public bool Superseded; public string Fingerprint; } private readonly GameModuleLoader _loader; private readonly string _gamesRoot; private readonly Dictionary _current = new Dictionary(); private readonly List _retired = new List(); public GameModuleRegistry(GameModuleLoader loader, string gamesRoot) { _loader = loader; _gamesRoot = gamesRoot; } private static string Key(string gameId, int version) => $"{gameId}@{version}"; private string ModuleDir(string gameId, int version) => Path.Combine(_gamesRoot, gameId, version.ToString()); public LoadedGameModule Acquire(string gameId, int version) { string key = Key(gameId, version); if (_current.TryGetValue(key, out var e)) { // 覆盖重发检测:磁盘内容指纹变化 → 旧条目退役、加载新内容。 // 指纹算不出(目录处于覆盖交换的瞬间窗口/被删)→ 视为未变化,用缓存兜底。 string fp = ComputeFingerprintOrNull(ModuleDir(gameId, version)); if (fp == null || fp == e.Fingerprint) { e.RefCount++; return e.Module; } var reloaded = LoadEntry(gameId, version); // 失败直接抛出:旧条目原样保留,不污染状态 Retire(e); _current[key] = reloaded; SupersedeOlderVersions(gameId, version); reloaded.RefCount++; return reloaded.Module; } var loaded = LoadEntry(gameId, version); _current[key] = loaded; SupersedeOlderVersions(gameId, version); loaded.RefCount++; return loaded.Module; } public void Release(LoadedGameModule module) { string key = Key(module.GameId, module.Version); if (_current.TryGetValue(key, out var e) && ReferenceEquals(e.Module, module)) { if (e.RefCount <= 0) throw new InvalidOperationException($"重复释放模块 {module.GameId}@{module.Version}"); e.RefCount--; if (e.RefCount <= 0 && e.Superseded) { _current.Remove(key); module.Unload(); } return; } // 退役条目:同版本被覆盖重发淘汰后仍被旧房间持有,按实例识别 for (int i = 0; i < _retired.Count; i++) { if (!ReferenceEquals(_retired[i].Module, module)) continue; var r = _retired[i]; if (r.RefCount <= 0) throw new InvalidOperationException($"重复释放模块 {module.GameId}@{module.Version}"); r.RefCount--; if (r.RefCount <= 0) { _retired.RemoveAt(i); module.Unload(); } return; } // 未知模块的释放忽略(与旧实现对不存在 key 直接 return 的行为一致) } public int RefCount(string gameId, int version) => _current.TryGetValue(Key(gameId, version), out var e) ? e.RefCount : 0; public bool IsLoaded(string gameId, int version) => _current.ContainsKey(Key(gameId, version)); private Entry LoadEntry(string gameId, int version) { // 指纹在加载前采样:加载成功即与所载内容对应(单线程 tick actor 下无并发写窗口) string fp = ComputeFingerprintOrNull(ModuleDir(gameId, version)); var loaded = _loader.Load(_gamesRoot, gameId, version); return new Entry { Module = loaded, RefCount = 0, Superseded = false, Fingerprint = fp }; } private void SupersedeOlderVersions(string gameId, int version) { foreach (var kv in _current) if (kv.Value.Module.GameId == gameId && kv.Value.Module.Version < version) kv.Value.Superseded = true; } private void Retire(Entry e) { e.Superseded = true; if (e.RefCount <= 0) e.Module.Unload(); else _retired.Add(e); } // 目录内容指纹:全部 *.dll + game.json 按名 Ordinal 排序拼 name=md5; 再整体 md5; // 读不到(目录不存在/IO 竞态)返回 null 由调用方兜底 private static string ComputeFingerprintOrNull(string dir) { try { if (!Directory.Exists(dir)) return null; var names = Directory.GetFiles(dir, "*.dll") .Select(Path.GetFileName) .Append("game.json") .Where(n => File.Exists(Path.Combine(dir, n))) .Distinct() .OrderBy(n => n, StringComparer.Ordinal); var sb = new StringBuilder(); foreach (var n in names) sb.Append(n).Append('=') .Append(XWorld.PublishTool.Md5Util.OfFile(Path.Combine(dir, n))).Append(';'); return XWorld.PublishTool.Md5Util.OfBytes(Encoding.UTF8.GetBytes(sb.ToString())); } catch (IOException) { return null; } catch (UnauthorizedAccessException) { return null; } } } }