using System.Net; using System.Reflection; using AcDream.Core.Chat; using AcDream.Core.Combat; using AcDream.Core.Items; using AcDream.Core.Net.Messages; using AcDream.Core.Player; using AcDream.Core.Spells; namespace AcDream.Core.Net.Tests; public sealed class WorldSessionWiringOwnershipTests { [Fact] public void ObjectTableWiring_DisposeDetachesEveryExactSessionDelegate() { using var session = NewSession(); var table = new ClientObjectTable(); var player = new LocalPlayerState(); IDisposable registration = ObjectTableWiring.Wire( session, table, playerGuid: () => 0x50000001u, localPlayer: player); Assert.Equal(1, HandlerCount(session, nameof(session.ObjectIntPropertyUpdated))); Assert.Equal(1, HandlerCount(session, nameof(session.PlayerIntPropertyUpdated))); Assert.Equal(1, HandlerCount(session, nameof(session.PlayerInt64PropertyUpdated))); Assert.Equal(1, HandlerCount(session, nameof(session.StackSizeUpdated))); Assert.Equal(1, HandlerCount(session, nameof(session.InventoryObjectRemoved))); registration.Dispose(); registration.Dispose(); Assert.Equal(0, HandlerCount(session, nameof(session.ObjectIntPropertyUpdated))); Assert.Equal(0, HandlerCount(session, nameof(session.PlayerIntPropertyUpdated))); Assert.Equal(0, HandlerCount(session, nameof(session.PlayerInt64PropertyUpdated))); Assert.Equal(0, HandlerCount(session, nameof(session.StackSizeUpdated))); Assert.Equal(0, HandlerCount(session, nameof(session.InventoryObjectRemoved))); } [Fact] public void CombatStateWiring_DisposeDetachesOnlyItsExactDelegate() { using var session = NewSession(); var combat = new CombatState(); Action predecessor = _ => { }; session.PlayerIntPropertyUpdated += predecessor; IDisposable registration = CombatStateWiring.Wire(session, combat); Assert.Equal(2, HandlerCount(session, nameof(session.PlayerIntPropertyUpdated))); registration.Dispose(); registration.Dispose(); Assert.Equal(1, HandlerCount(session, nameof(session.PlayerIntPropertyUpdated))); session.PlayerIntPropertyUpdated -= predecessor; } [Fact] public void CombatStateWiring_ConcurrentDisposeDetachesExactlyOnce() { using var session = NewSession(); IDisposable registration = CombatStateWiring.Wire(session, new CombatState()); Parallel.For(0, 64, _ => registration.Dispose()); Assert.Equal(0, HandlerCount(session, nameof(session.PlayerIntPropertyUpdated))); } [Fact] public void GameEventWiring_DisposeRestoresPreexistingHandlerAndRemovesOwnedSet() { var dispatcher = new GameEventDispatcher(); int predecessorCalls = 0; dispatcher.Register(GameEventType.Tell, _ => predecessorCalls++); IDisposable registration = GameEventWiring.WireAll( dispatcher, new ClientObjectTable(), new CombatState(), new Spellbook(), new ChatLog()); int wiredCount = dispatcher.RegisteredHandlerCount; dispatcher.Dispatch(new GameEventEnvelope(0, 0, GameEventType.Tell, default)); Assert.Equal(0, predecessorCalls); registration.Dispose(); registration.Dispose(); dispatcher.Dispatch(new GameEventEnvelope(0, 0, GameEventType.Tell, default)); Assert.True(wiredCount > 1); Assert.Equal(1, dispatcher.RegisteredHandlerCount); Assert.Equal(1, predecessorCalls); } [Fact] public void GameEventWiring_OnWorldSession_RestoresInternalRegistrations() { using var session = NewSession(); int baselineCount = session.GameEvents.RegisteredHandlerCount; IDisposable registration = GameEventWiring.WireAll( session.GameEvents, new ClientObjectTable(), new CombatState(), new Spellbook(), new ChatLog(), turbineChat: new TurbineChatState()); Assert.True(session.GameEvents.RegisteredHandlerCount > baselineCount); registration.Dispose(); Assert.Equal(baselineCount, session.GameEvents.RegisteredHandlerCount); } private static WorldSession NewSession() => new(new IPEndPoint(IPAddress.Loopback, 9)); private static int HandlerCount(WorldSession session, string eventName) { FieldInfo field = typeof(WorldSession).GetField( eventName, BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new InvalidOperationException($"event field {eventName} not found"); return (field.GetValue(session) as MulticastDelegate)? .GetInvocationList() .Length ?? 0; } }