using System; using System.Runtime.CompilerServices; using Xunit; using XWorld.Framework; using XWorld.Server.Host; namespace XWorld.Server.Host.Tests { public class AlcUnloadTests { private sealed class NullOutput : IRoomOutput { public void Broadcast(NetMessage m) { } public void SendTo(int playerId, NetMessage m) { } } // 在独立方法里加载/运行/卸载,确保局部强引用随方法返回而消失 [MethodImpl(MethodImplOptions.NoInlining)] private static (WeakReference alc, WeakReference asm) LoadRunAndUnload() { var module = new GameModuleLoader().Load(TestGames.Root, "rps", 1); IGameServerRoom room = module.CreateRoom(); room.OnRoomStart( new[] { new PlayerInfo { PlayerId = 1, Name = "H", IsAI = false }, new PlayerInfo { PlayerId = -1, Name = "AI", IsAI = true }, }, new RoomCtx(new DeterministicRandom(1), new ManualClock(), new ConsoleLogger(), new InMemoryStorage(), new NullOutput(), _ => { })); room.OnTick(1f); // dt=1 < 5s,仍在 Choosing,仅广播一次快照 var asmWeak = new WeakReference(room.GetType().Assembly); // 游戏 ALC 内加载的程序集 room = null; return (module.Unload(), asmWeak); } [Fact] public void GameModule_Unload_ReclaimsMemory() { var (alcWeak, asmWeak) = LoadRunAndUnload(); for (int i = 0; i < 12 && (alcWeak.IsAlive || asmWeak.IsAlive); i++) { GC.Collect(); GC.WaitForPendingFinalizers(); } Assert.False(alcWeak.IsAlive, "游戏模块 ALC 未被回收 —— 存在泄漏引用"); Assert.False(asmWeak.IsAlive, "游戏程序集未被回收 —— 存在泄漏引用"); } } }