diff --git a/Robust.Client/Player/LocalPlayer.cs b/Robust.Client/Player/LocalPlayer.cs index 0adad5a9a..3b99aa6b1 100644 --- a/Robust.Client/Player/LocalPlayer.cs +++ b/Robust.Client/Player/LocalPlayer.cs @@ -5,6 +5,7 @@ using Robust.Client.Interfaces.GameObjects; using Robust.Client.Interfaces.GameObjects.Components; using Robust.Shared; using Robust.Shared.Enums; +using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.Configuration; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects.Components; @@ -89,7 +90,7 @@ namespace Robust.Client.Player entity.SendMessage(null, new PlayerAttachedMsg()); // notify ECS Systems - ControlledEntity.EntityManager.RaiseEvent(this, new PlayerAttachSysMessage(ControlledEntity)); + ControlledEntity.EntityManager.EventBus.RaiseEvent(this, new PlayerAttachSysMessage(ControlledEntity)); } /// @@ -104,7 +105,7 @@ namespace Robust.Client.Player previous.SendMessage(null, new PlayerDetachedMsg()); // notify ECS Systems - previous.EntityManager.RaiseEvent(this, new PlayerAttachSysMessage(null)); + previous.EntityManager.EventBus.RaiseEvent(this, new PlayerAttachSysMessage(null)); } ControlledEntity = null; diff --git a/Robust.Server/GameObjects/Components/Container/Container.cs b/Robust.Server/GameObjects/Components/Container/Container.cs index 346a667c9..ff58f5411 100644 --- a/Robust.Server/GameObjects/Components/Container/Container.cs +++ b/Robust.Server/GameObjects/Components/Container/Container.cs @@ -134,7 +134,7 @@ namespace Robust.Server.GameObjects.Components.Container { DebugTools.Assert(!Deleted); - Owner.EntityManager.RaiseEvent(Owner, new EntInsertedIntoContainerMessage(toinsert, this)); + Owner.EntityManager.EventBus.RaiseEvent(Owner, new EntInsertedIntoContainerMessage(toinsert, this)); Manager.Owner.SendMessage(Manager, new ContainerContentsModifiedMessage(this, toinsert, false)); } @@ -192,7 +192,8 @@ namespace Robust.Server.GameObjects.Components.Container DebugTools.Assert(Manager != null); DebugTools.Assert(toremove != null && toremove.IsValid()); - Owner?.EntityManager.RaiseEvent(Owner, new EntRemovedFromContainerMessage(toremove, this)); + Owner?.EntityManager.EventBus.RaiseEvent(Owner, new EntRemovedFromContainerMessage(toremove, this)); + Manager.Owner.SendMessage(Manager, new ContainerContentsModifiedMessage(this, toremove, true)); } diff --git a/Robust.Server/Player/PlayerSession.cs b/Robust.Server/Player/PlayerSession.cs index a09190eef..620d9650b 100644 --- a/Robust.Server/Player/PlayerSession.cs +++ b/Robust.Server/Player/PlayerSession.cs @@ -91,7 +91,7 @@ namespace Robust.Server.Player actorComponent.playerSession = this; AttachedEntity = a; a.SendMessage(actorComponent, new PlayerAttachedMsg(this)); - a.EntityManager.RaiseEvent(this, new PlayerAttachSystemMessage(a, this)); + a.EntityManager.EventBus.RaiseEvent(this, new PlayerAttachSystemMessage(a, this)); SetAttachedEntityName(); UpdatePlayerState(); } @@ -110,7 +110,7 @@ namespace Robust.Server.Player if (AttachedEntity.TryGetComponent(out var actor)) { AttachedEntity.SendMessage(actor, new PlayerDetachedMsg(this)); - AttachedEntity.EntityManager.RaiseEvent(this, new PlayerDetachedSystemMessage(AttachedEntity)); + AttachedEntity.EntityManager.EventBus.RaiseEvent(this, new PlayerDetachedSystemMessage(AttachedEntity)); AttachedEntity.RemoveComponent(); AttachedEntity = null; UpdatePlayerState(); diff --git a/Robust.Shared/GameObjects/Components/Transform/TransformComponent.cs b/Robust.Shared/GameObjects/Components/Transform/TransformComponent.cs index 4e8a0a45a..47a075074 100644 --- a/Robust.Shared/GameObjects/Components/Transform/TransformComponent.cs +++ b/Robust.Shared/GameObjects/Components/Transform/TransformComponent.cs @@ -120,7 +120,7 @@ var entMessage = new EntParentChangedMessage(Owner, Parent?.Owner); var compMessage = new ParentChangedMessage(value?.Owner, Parent?.Owner); _parent = value?.Owner.Uid ?? EntityUid.Invalid; - Owner.EntityManager.RaiseEvent(Owner, entMessage); + Owner.EntityManager.EventBus.RaiseEvent(Owner, entMessage); Owner.SendMessage(this, compMessage); } } diff --git a/Robust.Shared/GameObjects/Entity.cs b/Robust.Shared/GameObjects/Entity.cs index 099522b36..94846993c 100644 --- a/Robust.Shared/GameObjects/Entity.cs +++ b/Robust.Shared/GameObjects/Entity.cs @@ -186,20 +186,20 @@ namespace Robust.Shared.GameObjects public void SubscribeEvent(EntityEventHandler evh, IEntityEventSubscriber s) where T : EntityEventArgs { - EntityManager.SubscribeEvent(evh, s); + EntityManager.EventBus.SubscribeEvent(evh, s); } /// public void UnsubscribeEvent(IEntityEventSubscriber s) where T : EntityEventArgs { - EntityManager.UnsubscribeEvent(s); + EntityManager.EventBus.UnsubscribeEvent(s); } /// public void RaiseEvent(EntityEventArgs toRaise) { - EntityManager.RaiseEvent(this, toRaise); + EntityManager.EventBus.RaiseEvent(this, toRaise); } #endregion Entity Events diff --git a/Robust.Shared/GameObjects/EntityEventBus.cs b/Robust.Shared/GameObjects/EntityEventBus.cs new file mode 100644 index 000000000..9e8d25821 --- /dev/null +++ b/Robust.Shared/GameObjects/EntityEventBus.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Robust.Shared.Utility; + +namespace Robust.Shared.GameObjects +{ + /// + /// Provides a central event bus that EntitySystems can subscribe to. This is the main way that + /// EntitySystems communicate with each other. + /// + public interface IEventBus + { + /// + /// Subscribes an event handler for a event type. + /// + /// Event type to subscribe to. + /// Delegate that handles the event. + /// Subscriber that owns the handler. + void SubscribeEvent(EntityEventHandler eventHandler, IEntityEventSubscriber subscriber) + where T : EntityEventArgs; + + /// + /// Unsubscribes all event handlers of a given type. + /// + /// Event type being unsubscribed from. + /// Subscriber that owns the handlers. + void UnsubscribeEvent(IEntityEventSubscriber subscriber) + where T : EntityEventArgs; + + /// + /// Immediately raises an event onto the bus. + /// + /// Object that raised the event. + /// Event being raised. + void RaiseEvent(object sender, EntityEventArgs toRaise); + + /// + /// Queues an event to be raised at a later time. + /// + /// Object that raised the event. + /// Event being raised. + void QueueEvent(object sender, EntityEventArgs toRaise); + } + + /// + internal interface IEntityEventBus : IEventBus + { + /// + /// Unsubscribes all event handlers for a given subscriber. + /// + /// Owner of the handlers being removed. + void UnsubscribeEvents(IEntityEventSubscriber subscriber); + + /// + /// Raises all queued events onto the event bus. This needs to be called often. + /// + void ProcessEventQueue(); + } + + /// + internal class EntityEventBus : IEntityEventBus + { + private readonly Dictionary> _eventSubscriptions + = new Dictionary>(); + + private readonly Dictionary> _inverseEventSubscriptions + = new Dictionary>(); + + private readonly Queue<(object sender, EntityEventArgs eventArgs)> _eventQueue + = new Queue<(object, EntityEventArgs)>(); + + /// + public void UnsubscribeEvents(IEntityEventSubscriber subscriber) + { + if (!_inverseEventSubscriptions.TryGetValue(subscriber, out var val)) + return; + + // UnsubscribeEvent modifies _inverseEventSubscriptions, requires val to be cached + foreach (var (type, @delegate) in val.ToList()) + { + UnsubscribeEvent(type, @delegate, subscriber); + } + } + + /// + public void ProcessEventQueue() + { + while (_eventQueue.Count != 0) + { + ProcessSingleEvent(_eventQueue.Dequeue()); + } + } + + /// + public void SubscribeEvent(EntityEventHandler eventHandler, IEntityEventSubscriber subscriber) + where T : EntityEventArgs + { + if (eventHandler == null) + throw new ArgumentNullException(nameof(eventHandler)); + + if(subscriber == null) + throw new ArgumentNullException(nameof(subscriber)); + + var eventType = typeof(T); + if (!_eventSubscriptions.TryGetValue(eventType, out var subscriptions)) + _eventSubscriptions.Add(eventType, new List {eventHandler}); + else if (!subscriptions.Contains(eventHandler)) + subscriptions.Add(eventHandler); + + if (!_inverseEventSubscriptions.TryGetValue(subscriber, out var inverseSubscription)) + { + inverseSubscription = new Dictionary + { + {eventType, eventHandler} + }; + + _inverseEventSubscriptions.Add( + subscriber, + inverseSubscription + ); + } + + else if (!inverseSubscription.ContainsKey(eventType)) + { + inverseSubscription.Add(eventType, eventHandler); + } + } + + /// + public void UnsubscribeEvent(IEntityEventSubscriber subscriber) + where T : EntityEventArgs + { + var eventType = typeof(T); + + if (_inverseEventSubscriptions.TryGetValue(subscriber, out var inverse) + && inverse.TryGetValue(eventType, out var @delegate)) + UnsubscribeEvent(eventType, @delegate, subscriber); + } + + /// + public void RaiseEvent(object sender, EntityEventArgs toRaise) + { + if(toRaise == null) + throw new ArgumentNullException(nameof(toRaise)); + + ProcessSingleEvent((sender, toRaise)); + } + + /// + public void QueueEvent(object sender, EntityEventArgs toRaise) + { + if(toRaise == null) + throw new ArgumentNullException(nameof(toRaise)); + + _eventQueue.Enqueue((sender, toRaise)); + } + + private void UnsubscribeEvent(Type eventType, Delegate evh, IEntityEventSubscriber s) + { + if (_eventSubscriptions.TryGetValue(eventType, out var subscriptions) && subscriptions.Contains(evh)) + subscriptions.Remove(evh); + + if (_inverseEventSubscriptions.TryGetValue(s, out var inverse) && inverse.ContainsKey(eventType)) + inverse.Remove(eventType); + } + + private void ProcessSingleEvent((object sender, EntityEventArgs eventArgs) argsTuple) + { + var (sender, eventArgs) = argsTuple; + var eventType = eventArgs.GetType(); + + if (!_eventSubscriptions.TryGetValue(eventType, out var subs)) + return; + + foreach (var handler in subs) + { + handler.DynamicInvoke(sender, eventArgs); + } + } + } +} diff --git a/Robust.Shared/GameObjects/EntityManager.cs b/Robust.Shared/GameObjects/EntityManager.cs index ab4e24c71..b2fef06bf 100644 --- a/Robust.Shared/GameObjects/EntityManager.cs +++ b/Robust.Shared/GameObjects/EntityManager.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Robust.Shared.GameObjects.Components.Transform; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; @@ -67,21 +66,17 @@ namespace Robust.Shared.GameObjects protected int NextUid = (int)EntityUid.FirstUid; - private readonly Dictionary> _eventSubscriptions - = new Dictionary>(); + private readonly IEntityEventBus _eventBus = new EntityEventBus(); - private readonly Dictionary> _inverseEventSubscriptions - = new Dictionary>(); - - private readonly Queue<(object sender, EntityEventArgs eventArgs)> _eventQueue - = new Queue<(object, EntityEventArgs)>(); + /// + public IEventBus EventBus => _eventBus; public bool Started { get; protected set; } public virtual void Initialize() { EntityNetworkManager.SetupNetworking(); - _componentManager.ComponentRemoved += (sender, args) => RemoveSubscribedEvents(args.Component); + _componentManager.ComponentRemoved += (sender, args) => _eventBus.UnsubscribeEvents(args.Component); } public virtual void Startup() @@ -100,7 +95,7 @@ namespace Robust.Shared.GameObjects { ProcessMessageBuffer(); EntitySystemManager.Update(frameTime); - ProcessEventQueue(); + _eventBus.ProcessEventQueue(); CullDeletedEntities(); } @@ -109,16 +104,6 @@ namespace Robust.Shared.GameObjects EntitySystemManager.FrameUpdate(frameTime); } - /// - /// Retrieves template with given name from prototypemanager. - /// - /// name of the template - /// Template - public EntityPrototype GetTemplate(string prototypeName) - { - return PrototypeManager.Index(prototypeName); - } - #region Entity Management public abstract IEntity SpawnEntity(string protoName); @@ -338,118 +323,6 @@ namespace Robust.Shared.GameObjects #endregion Entity Management - #region Entity Events - - public void SubscribeEvent(EntityEventHandler eventHandler, IEntityEventSubscriber s) - where T : EntityEventArgs - { - // adding a null handler delegate should do nothing, since trying to invoke it would throw a NullRefException - if(eventHandler == null) - return; - - var eventType = typeof(T); - if (!_eventSubscriptions.TryGetValue(eventType, out var subscriptions)) - { - _eventSubscriptions.Add(eventType, new List { eventHandler }); - } - else if (!subscriptions.Contains(eventHandler)) - { - subscriptions.Add(eventHandler); - } - - if (!_inverseEventSubscriptions.TryGetValue(s, out var inverseSubscription)) - { - inverseSubscription = new Dictionary - { - {eventType, eventHandler} - }; - - _inverseEventSubscriptions.Add( - s, - inverseSubscription - ); - } - - else if (!inverseSubscription.ContainsKey(eventType)) - { - inverseSubscription.Add(eventType, eventHandler); - } - } - - public void UnsubscribeEvent(IEntityEventSubscriber s) - where T : EntityEventArgs - { - var eventType = typeof(T); - - if (_inverseEventSubscriptions.TryGetValue(s, out var inverse) - && inverse.TryGetValue(eventType, out var @delegate)) - { - UnsubscribeEvent(eventType, @delegate, s); - } - } - - private void UnsubscribeEvent(Type eventType, Delegate evh, IEntityEventSubscriber s) - { - if (_eventSubscriptions.TryGetValue(eventType, out var subscriptions) && subscriptions.Contains(evh)) - { - subscriptions.Remove(evh); - } - - if (_inverseEventSubscriptions.TryGetValue(s, out var inverse) && inverse.ContainsKey(eventType)) - { - inverse.Remove(eventType); - } - } - - public void RaiseEvent(object sender, EntityEventArgs toRaise) - { - ProcessSingleEvent((sender, toRaise)); - } - - public void QueueEvent(object sender, EntityEventArgs toRaise) - { - _eventQueue.Enqueue((sender, toRaise)); - } - - public void RemoveSubscribedEvents(IEntityEventSubscriber subscriber) - { - if (!_inverseEventSubscriptions.TryGetValue(subscriber, out var val)) - { - return; - } - - foreach (var (type, @delegate) in val.ToList()) - { - UnsubscribeEvent(type, @delegate, subscriber); - } - } - - private void ProcessEventQueue() - { - while (_eventQueue.Count != 0) - { - var current = _eventQueue.Dequeue(); - ProcessSingleEvent(current); - } - } - - private void ProcessSingleEvent((object sender, EntityEventArgs eventArgs) argsTuple) - { - var (sender, eventArgs) = argsTuple; - var eventType = eventArgs.GetType(); - if (!_eventSubscriptions.TryGetValue(eventType, out var subs)) - { - return; - } - - foreach (var handler in subs) - { - handler.DynamicInvoke(sender, eventArgs); - } - } - - #endregion Entity Events - #region message processing /// diff --git a/Robust.Shared/GameObjects/Systems/EntitySystem.cs b/Robust.Shared/GameObjects/Systems/EntitySystem.cs index 5521f0e4b..4083bbdc5 100644 --- a/Robust.Shared/GameObjects/Systems/EntitySystem.cs +++ b/Robust.Shared/GameObjects/Systems/EntitySystem.cs @@ -78,29 +78,29 @@ namespace Robust.Shared.GameObjects.Systems protected void SubscribeEvent(EntityEventHandler evh) where T : EntitySystemMessage { - EntityManager.SubscribeEvent(evh, this); + EntityManager.EventBus.SubscribeEvent(evh, this); } protected void SubscribeEvent(EntityEventHandler evh) where T : EntitySystemMessage { - EntityManager.SubscribeEvent(evh, this); + EntityManager.EventBus.SubscribeEvent(evh, this); } protected void UnsubscribeEvent() where T : EntitySystemMessage { - EntityManager.UnsubscribeEvent(this); + EntityManager.EventBus.UnsubscribeEvent(this); } protected void RaiseEvent(EntitySystemMessage message) { - EntityManager.RaiseEvent(this, message); + EntityManager.EventBus.RaiseEvent(this, message); } protected void QueueEvent(EntitySystemMessage message) { - EntityManager.QueueEvent(this, message); + EntityManager.EventBus.QueueEvent(this, message); } protected void RaiseNetworkEvent(EntitySystemMessage message) diff --git a/Robust.Shared/Interfaces/GameObjects/IEntityManager.cs b/Robust.Shared/Interfaces/GameObjects/IEntityManager.cs index ea9f7abe7..04b751025 100644 --- a/Robust.Shared/Interfaces/GameObjects/IEntityManager.cs +++ b/Robust.Shared/Interfaces/GameObjects/IEntityManager.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Maths; @@ -27,6 +26,7 @@ namespace Robust.Shared.Interfaces.GameObjects IComponentManager ComponentManager { get; } IEntityNetworkManager EntityNetManager { get; } + IEventBus EventBus { get; } #region Entity Management @@ -110,17 +110,6 @@ namespace Robust.Shared.Interfaces.GameObjects #region ComponentEvents - void SubscribeEvent(EntityEventHandler eventHandler, IEntityEventSubscriber s) - where T : EntityEventArgs; - - void UnsubscribeEvent(IEntityEventSubscriber s) - where T : EntityEventArgs; - - void RaiseEvent(object sender, EntityEventArgs toRaise); - void QueueEvent(object sender, EntityEventArgs toRaise); - - void RemoveSubscribedEvents(IEntityEventSubscriber subscriber); - /// /// Converts a raw NetIncomingMessage to an IncomingEntityMessage object /// diff --git a/Robust.UnitTesting/Shared/GameObjects/EntityEventBus_Tests.cs b/Robust.UnitTesting/Shared/GameObjects/EntityEventBus_Tests.cs new file mode 100644 index 000000000..5e4a2192f --- /dev/null +++ b/Robust.UnitTesting/Shared/GameObjects/EntityEventBus_Tests.cs @@ -0,0 +1,354 @@ +using System; +using NUnit.Framework; +using Robust.Shared.GameObjects; + +namespace Robust.UnitTesting.Shared.GameObjects +{ + [TestFixture, Parallelizable, TestOf(typeof(EntityEventBus))] + public class EntityEventBus_Tests + { + /// + /// Trying to subscribe a null handler causes a to be thrown. + /// + [Test] + public void SubscribeEvent_NullHandler_NullArgumentException() + { + // Arrange + var bus = new EntityEventBus(); + var subscriber = new TestEventSubscriber(); + + // Act + void Code() => bus.SubscribeEvent((EntityEventHandler) null, subscriber); + + //Assert + Assert.Throws(Code); + } + + /// + /// Trying to subscribe with a null subscriber causes a to be thrown. + /// + [Test] + public void SubscribeEvent_NullSubscriber_NullArgumentException() + { + // Arrange + var bus = new EntityEventBus(); + + // Act + void Code() => bus.SubscribeEvent((sender, ev) => {}, null); + + //Assert: this should do nothing + Assert.Throws(Code); + } + + /// + /// Unlike C# events, the set of event handler delegates is unique. + /// Subscribing the same delegate multiple times will only call the handler once. + /// + [Test] + public void SubscribeEvent_DuplicateSubscription_RaisedOnce() + { + // Arrange + var bus = new EntityEventBus(); + var subscriber = new TestEventSubscriber(); + + int delegateCallCount = 0; + void Handler(object sender, TestEventArgs ev) => delegateCallCount++; + + // 2 subscriptions 1 handler + bus.SubscribeEvent(Handler, subscriber); + bus.SubscribeEvent(Handler, subscriber); + + // Act + bus.RaiseEvent(null, new TestEventArgs()); + + //Assert + Assert.That(delegateCallCount, Is.EqualTo(1)); + } + + /// + /// Subscribing two different delegates to a single event type causes both events + /// to be raised in an indeterminate order. + /// + [Test] + public void SubscribeEvent_MultipleDelegates_BothRaised() + { + // Arrange + var bus = new EntityEventBus(); + var subscriber = new TestEventSubscriber(); + + int delFooCount = 0; + int delBarCount = 0; + + bus.SubscribeEvent((sender, ev) => delFooCount++, subscriber); + bus.SubscribeEvent((sender, ev) => delBarCount++, subscriber); + + // Act + bus.RaiseEvent(null, new TestEventArgs()); + + // Assert + Assert.That(delFooCount, Is.EqualTo(1)); + Assert.That(delBarCount, Is.EqualTo(1)); + } + + /// + /// A subscriber's handlers are properly called only when the specified event type is raised. + /// + [Test] + public void SubscribeEvent_MultipleSubscriptions_IndividuallyCalled() + { + // Arrange + var bus = new EntityEventBus(); + var subscriber = new TestEventSubscriber(); + + int delFooCount = 0; + int delBarCount = 0; + + bus.SubscribeEvent((sender, ev) => delFooCount++, subscriber); + bus.SubscribeEvent((sender, ev) => delBarCount++, subscriber); + + // Act & Assert + bus.RaiseEvent(null, new TestEventArgs()); + Assert.That(delFooCount, Is.EqualTo(1)); + Assert.That(delBarCount, Is.EqualTo(0)); + + delFooCount = delBarCount = 0; + + bus.RaiseEvent(null, new TestEventTwoArgs()); + Assert.That(delFooCount, Is.EqualTo(0)); + Assert.That(delBarCount, Is.EqualTo(1)); + } + + /// + /// Unsubscribing a handler twice does nothing. + /// + [Test] + public void UnsubscribeEvent_DoubleUnsubscribe_Nop() + { + // Arrange + var bus = new EntityEventBus(); + var subscriber = new TestEventSubscriber(); + + void Handler(object sender, TestEventArgs ev) { } + + bus.SubscribeEvent(Handler, subscriber); + bus.UnsubscribeEvent(subscriber); + + // Act + bus.UnsubscribeEvent(subscriber); + + // Assert: Does not throw + } + + /// + /// Unsubscribing a handler that was never subscribed in the first place does nothing. + /// + [Test] + public void UnsubscribeEvent_NoSubscription_Nop() + { + // Arrange + var bus = new EntityEventBus(); + var subscriber = new TestEventSubscriber(); + + // Act + bus.UnsubscribeEvent(subscriber); + + // Assert: Does not throw + } + + /// + /// Trying to unsubscribe with a null subscriber causes a to be thrown. + /// + [Test] + public void UnsubscribeEvent_NullSubscriber_NullArgumentException() + { + // Arrange + var bus = new EntityEventBus(); + + // Act + void Code() => bus.UnsubscribeEvent(null); + + // Assert + Assert.Throws(Code); + } + + /// + /// Trying to queue a null event causes a to be thrown. + /// + [Test] + public void RaiseEvent_NullEvent_ArgumentNullException() + { + // Arrange + var bus = new EntityEventBus(); + + // Act + void Code() => bus.RaiseEvent(null, null); + + // Assert + Assert.Throws(Code); + } + + /// + /// Raising an event with no handlers subscribed to it does nothing. + /// + [Test] + public void RaiseEvent_NoSubscriptions_Nop() + { + // Arrange + var bus = new EntityEventBus(); + var subscriber = new TestEventSubscriber(); + + int delCalledCount = 0; + bus.SubscribeEvent(((sender, ev) => delCalledCount++), subscriber); + + // Act + bus.RaiseEvent(null, new TestEventArgs()); + + // Assert + Assert.That(delCalledCount, Is.EqualTo(0)); + } + + /// + /// Raising an event when a handler has been unsubscribed no longer calls the handler. + /// + [Test] + public void RaiseEvent_Unsubscribed_Nop() + { + // Arrange + var bus = new EntityEventBus(); + var subscriber = new TestEventSubscriber(); + + int delCallCount = 0; + void Handler(object sender, TestEventArgs ev) => delCallCount++; + + bus.SubscribeEvent(Handler, subscriber); + bus.UnsubscribeEvent(subscriber); + + // Act + bus.RaiseEvent(null, new TestEventArgs()); + + // Assert + Assert.That(delCallCount, Is.EqualTo(0)); + } + + /// + /// Trying to unsubscribe all of a null subscriber's events causes a to be thrown. + /// + [Test] + public void UnsubscribeEvents_NullSubscriber_NullArgumentException() + { + // Arrange + var bus = new EntityEventBus(); + + // Act + void Code() => bus.UnsubscribeEvents(null); + + // Assert + Assert.Throws(Code); + } + + /// + /// Unsubscribing a subscriber with no subscriptions does nothing. + /// + [Test] + public void UnsubscribeEvents_NoSubscriptions_Nop() + { + // Arrange + var bus = new EntityEventBus(); + var subscriber = new TestEventSubscriber(); + + // Act + bus.UnsubscribeEvents(subscriber); + + // Assert: no exception + } + + /// + /// The subscriber's handlers are not raised after they are unsubscribed. + /// + [Test] + public void UnsubscribeEvents_UnsubscribedHandler_Nop() + { + // Arrange + var bus = new EntityEventBus(); + var subscriber = new TestEventSubscriber(); + + int delCallCount = 0; + void Handler(object sender, TestEventArgs ev) => delCallCount++; + + bus.SubscribeEvent(Handler, subscriber); + bus.UnsubscribeEvents(subscriber); + + // Act + bus.RaiseEvent(null, new TestEventArgs()); + + // Assert + Assert.That(delCallCount, Is.EqualTo(0)); + } + + /// + /// Trying to queue a null event causes a to be thrown. + /// + [Test] + public void QueueEvent_NullEvent_ArgumentNullException() + { + // Arrange + var bus = new EntityEventBus(); + + // Act + void Code() => bus.QueueEvent(null, null); + + // Assert + Assert.Throws(Code); + } + + /// + /// Queuing an event does not immediately raise the event unless the queue is processed. + /// + [Test] + public void QueueEvent_EventQueued_DoesNotImmediatelyRaise() + { + // Arrange + var bus = new EntityEventBus(); + var subscriber = new TestEventSubscriber(); + + int delCallCount = 0; + void Handler(object sender, TestEventArgs ev) => delCallCount++; + + bus.SubscribeEvent(Handler, subscriber); + + // Act + bus.QueueEvent(null, new TestEventArgs()); + + // Assert + Assert.That(delCallCount, Is.EqualTo(0)); + } + + /// + /// Queued events are raised when the queue is processed. + /// + [Test] + public void ProcessQueue_EventQueued_HandlerRaised() + { + // Arrange + var bus = new EntityEventBus(); + var subscriber = new TestEventSubscriber(); + + int delCallCount = 0; + void Handler(object sender, TestEventArgs ev) => delCallCount++; + + bus.SubscribeEvent(Handler, subscriber); + bus.QueueEvent(null, new TestEventArgs()); + + // Act + bus.ProcessEventQueue(); + + // Assert + Assert.That(delCallCount, Is.EqualTo(1)); + } + } + + internal class TestEventSubscriber : IEntityEventSubscriber { } + + internal class TestEventArgs : EntityEventArgs { } + internal class TestEventTwoArgs : EntityEventArgs { } +} diff --git a/Robust.UnitTesting/Shared/GameObjects/EntityManager_Tests.cs b/Robust.UnitTesting/Shared/GameObjects/EntityManager_Tests.cs deleted file mode 100644 index 114aabaca..000000000 --- a/Robust.UnitTesting/Shared/GameObjects/EntityManager_Tests.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using NUnit.Framework; -using Robust.Shared.GameObjects; -using Robust.Shared.Interfaces.GameObjects; -using Robust.Shared.Map; - -namespace Robust.UnitTesting.Shared.GameObjects -{ - [TestFixture, Parallelizable, TestOf(typeof(EntityManager))] - public class EntityManager_Tests - { - /// - /// Raising a null C# delegate does not generate a NullReferenceException. - /// - [Test] - public void SubscribeEvent_NullEvent_NoNullException() - { - // Arrange - var manager = new TestEntityManager(); - var subscriber = new TestEventSubscriber(); - - manager.SubscribeEvent((EntityEventHandler) null, subscriber); - - // Act - manager.RaiseEvent(null, new TestEventArgs()); - - //Assert: this should do nothing - } - } - - internal class TestEventSubscriber : IEntityEventSubscriber { } - - internal class TestEntityManager : EntityManager - { - public override IEntity SpawnEntity(string protoName) - { - throw new NotImplementedException(); - } - - public override IEntity SpawnEntityNoMapInit(string protoName) - { - throw new NotImplementedException(); - } - - public override IEntity SpawnEntityAt(string entityType, GridCoordinates coordinates) - { - throw new NotImplementedException(); - } - } - - internal class TestEventArgs : EntityEventArgs { } -}