mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
EventBus Refactor (#909)
* Extracts the ECS event system out of EntityManager, and into a new class called EventBus. XML Docs and full test coverage for EventBus.
This commit is contained in:
@@ -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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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<BasicActorComponent>(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<BasicActorComponent>();
|
||||
AttachedEntity = null;
|
||||
UpdatePlayerState();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,20 +186,20 @@ namespace Robust.Shared.GameObjects
|
||||
public void SubscribeEvent<T>(EntityEventHandler<EntityEventArgs> evh, IEntityEventSubscriber s)
|
||||
where T : EntityEventArgs
|
||||
{
|
||||
EntityManager.SubscribeEvent<T>(evh, s);
|
||||
EntityManager.EventBus.SubscribeEvent(evh, s);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UnsubscribeEvent<T>(IEntityEventSubscriber s)
|
||||
where T : EntityEventArgs
|
||||
{
|
||||
EntityManager.UnsubscribeEvent<T>(s);
|
||||
EntityManager.EventBus.UnsubscribeEvent<T>(s);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RaiseEvent(EntityEventArgs toRaise)
|
||||
{
|
||||
EntityManager.RaiseEvent(this, toRaise);
|
||||
EntityManager.EventBus.RaiseEvent(this, toRaise);
|
||||
}
|
||||
|
||||
#endregion Entity Events
|
||||
|
||||
182
Robust.Shared/GameObjects/EntityEventBus.cs
Normal file
182
Robust.Shared/GameObjects/EntityEventBus.cs
Normal file
@@ -0,0 +1,182 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a central event bus that EntitySystems can subscribe to. This is the main way that
|
||||
/// EntitySystems communicate with each other.
|
||||
/// </summary>
|
||||
public interface IEventBus
|
||||
{
|
||||
/// <summary>
|
||||
/// Subscribes an event handler for a event type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Event type to subscribe to.</typeparam>
|
||||
/// <param name="eventHandler">Delegate that handles the event.</param>
|
||||
/// <param name="subscriber">Subscriber that owns the handler.</param>
|
||||
void SubscribeEvent<T>(EntityEventHandler<T> eventHandler, IEntityEventSubscriber subscriber)
|
||||
where T : EntityEventArgs;
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribes all event handlers of a given type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Event type being unsubscribed from.</typeparam>
|
||||
/// <param name="subscriber">Subscriber that owns the handlers.</param>
|
||||
void UnsubscribeEvent<T>(IEntityEventSubscriber subscriber)
|
||||
where T : EntityEventArgs;
|
||||
|
||||
/// <summary>
|
||||
/// Immediately raises an event onto the bus.
|
||||
/// </summary>
|
||||
/// <param name="sender">Object that raised the event.</param>
|
||||
/// <param name="toRaise">Event being raised.</param>
|
||||
void RaiseEvent(object sender, EntityEventArgs toRaise);
|
||||
|
||||
/// <summary>
|
||||
/// Queues an event to be raised at a later time.
|
||||
/// </summary>
|
||||
/// <param name="sender">Object that raised the event.</param>
|
||||
/// <param name="toRaise">Event being raised.</param>
|
||||
void QueueEvent(object sender, EntityEventArgs toRaise);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
internal interface IEntityEventBus : IEventBus
|
||||
{
|
||||
/// <summary>
|
||||
/// Unsubscribes all event handlers for a given subscriber.
|
||||
/// </summary>
|
||||
/// <param name="subscriber">Owner of the handlers being removed.</param>
|
||||
void UnsubscribeEvents(IEntityEventSubscriber subscriber);
|
||||
|
||||
/// <summary>
|
||||
/// Raises all queued events onto the event bus. This needs to be called often.
|
||||
/// </summary>
|
||||
void ProcessEventQueue();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
internal class EntityEventBus : IEntityEventBus
|
||||
{
|
||||
private readonly Dictionary<Type, List<Delegate>> _eventSubscriptions
|
||||
= new Dictionary<Type, List<Delegate>>();
|
||||
|
||||
private readonly Dictionary<IEntityEventSubscriber, Dictionary<Type, Delegate>> _inverseEventSubscriptions
|
||||
= new Dictionary<IEntityEventSubscriber, Dictionary<Type, Delegate>>();
|
||||
|
||||
private readonly Queue<(object sender, EntityEventArgs eventArgs)> _eventQueue
|
||||
= new Queue<(object, EntityEventArgs)>();
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ProcessEventQueue()
|
||||
{
|
||||
while (_eventQueue.Count != 0)
|
||||
{
|
||||
ProcessSingleEvent(_eventQueue.Dequeue());
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SubscribeEvent<T>(EntityEventHandler<T> 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<Delegate> {eventHandler});
|
||||
else if (!subscriptions.Contains(eventHandler))
|
||||
subscriptions.Add(eventHandler);
|
||||
|
||||
if (!_inverseEventSubscriptions.TryGetValue(subscriber, out var inverseSubscription))
|
||||
{
|
||||
inverseSubscription = new Dictionary<Type, Delegate>
|
||||
{
|
||||
{eventType, eventHandler}
|
||||
};
|
||||
|
||||
_inverseEventSubscriptions.Add(
|
||||
subscriber,
|
||||
inverseSubscription
|
||||
);
|
||||
}
|
||||
|
||||
else if (!inverseSubscription.ContainsKey(eventType))
|
||||
{
|
||||
inverseSubscription.Add(eventType, eventHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UnsubscribeEvent<T>(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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RaiseEvent(object sender, EntityEventArgs toRaise)
|
||||
{
|
||||
if(toRaise == null)
|
||||
throw new ArgumentNullException(nameof(toRaise));
|
||||
|
||||
ProcessSingleEvent((sender, toRaise));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Type, List<Delegate>> _eventSubscriptions
|
||||
= new Dictionary<Type, List<Delegate>>();
|
||||
private readonly IEntityEventBus _eventBus = new EntityEventBus();
|
||||
|
||||
private readonly Dictionary<IEntityEventSubscriber, Dictionary<Type, Delegate>> _inverseEventSubscriptions
|
||||
= new Dictionary<IEntityEventSubscriber, Dictionary<Type, Delegate>>();
|
||||
|
||||
private readonly Queue<(object sender, EntityEventArgs eventArgs)> _eventQueue
|
||||
= new Queue<(object, EntityEventArgs)>();
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves template with given name from prototypemanager.
|
||||
/// </summary>
|
||||
/// <param name="prototypeName">name of the template</param>
|
||||
/// <returns>Template</returns>
|
||||
public EntityPrototype GetTemplate(string prototypeName)
|
||||
{
|
||||
return PrototypeManager.Index<EntityPrototype>(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<T>(EntityEventHandler<T> 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<Delegate> { eventHandler });
|
||||
}
|
||||
else if (!subscriptions.Contains(eventHandler))
|
||||
{
|
||||
subscriptions.Add(eventHandler);
|
||||
}
|
||||
|
||||
if (!_inverseEventSubscriptions.TryGetValue(s, out var inverseSubscription))
|
||||
{
|
||||
inverseSubscription = new Dictionary<Type, Delegate>
|
||||
{
|
||||
{eventType, eventHandler}
|
||||
};
|
||||
|
||||
_inverseEventSubscriptions.Add(
|
||||
s,
|
||||
inverseSubscription
|
||||
);
|
||||
}
|
||||
|
||||
else if (!inverseSubscription.ContainsKey(eventType))
|
||||
{
|
||||
inverseSubscription.Add(eventType, eventHandler);
|
||||
}
|
||||
}
|
||||
|
||||
public void UnsubscribeEvent<T>(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
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -78,29 +78,29 @@ namespace Robust.Shared.GameObjects.Systems
|
||||
protected void SubscribeEvent<T>(EntityEventHandler<EntitySystemMessage> evh)
|
||||
where T : EntitySystemMessage
|
||||
{
|
||||
EntityManager.SubscribeEvent<T>(evh, this);
|
||||
EntityManager.EventBus.SubscribeEvent(evh, this);
|
||||
}
|
||||
|
||||
protected void SubscribeEvent<T>(EntityEventHandler<T> evh)
|
||||
where T : EntitySystemMessage
|
||||
{
|
||||
EntityManager.SubscribeEvent<T>(evh, this);
|
||||
EntityManager.EventBus.SubscribeEvent(evh, this);
|
||||
}
|
||||
|
||||
protected void UnsubscribeEvent<T>()
|
||||
where T : EntitySystemMessage
|
||||
{
|
||||
EntityManager.UnsubscribeEvent<T>(this);
|
||||
EntityManager.EventBus.UnsubscribeEvent<T>(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)
|
||||
|
||||
@@ -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<T>(EntityEventHandler<T> eventHandler, IEntityEventSubscriber s)
|
||||
where T : EntityEventArgs;
|
||||
|
||||
void UnsubscribeEvent<T>(IEntityEventSubscriber s)
|
||||
where T : EntityEventArgs;
|
||||
|
||||
void RaiseEvent(object sender, EntityEventArgs toRaise);
|
||||
void QueueEvent(object sender, EntityEventArgs toRaise);
|
||||
|
||||
void RemoveSubscribedEvents(IEntityEventSubscriber subscriber);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a raw NetIncomingMessage to an IncomingEntityMessage object
|
||||
/// </summary>
|
||||
|
||||
354
Robust.UnitTesting/Shared/GameObjects/EntityEventBus_Tests.cs
Normal file
354
Robust.UnitTesting/Shared/GameObjects/EntityEventBus_Tests.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Trying to subscribe a null handler causes a <see cref="ArgumentNullException"/> to be thrown.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void SubscribeEvent_NullHandler_NullArgumentException()
|
||||
{
|
||||
// Arrange
|
||||
var bus = new EntityEventBus();
|
||||
var subscriber = new TestEventSubscriber();
|
||||
|
||||
// Act
|
||||
void Code() => bus.SubscribeEvent((EntityEventHandler<TestEventArgs>) null, subscriber);
|
||||
|
||||
//Assert
|
||||
Assert.Throws<ArgumentNullException>(Code);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trying to subscribe with a null subscriber causes a <see cref="ArgumentNullException"/> to be thrown.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void SubscribeEvent_NullSubscriber_NullArgumentException()
|
||||
{
|
||||
// Arrange
|
||||
var bus = new EntityEventBus();
|
||||
|
||||
// Act
|
||||
void Code() => bus.SubscribeEvent<TestEventArgs>((sender, ev) => {}, null);
|
||||
|
||||
//Assert: this should do nothing
|
||||
Assert.Throws<ArgumentNullException>(Code);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unlike C# events, the set of event handler delegates is unique.
|
||||
/// Subscribing the same delegate multiple times will only call the handler once.
|
||||
/// </summary>
|
||||
[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<TestEventArgs>(Handler, subscriber);
|
||||
bus.SubscribeEvent<TestEventArgs>(Handler, subscriber);
|
||||
|
||||
// Act
|
||||
bus.RaiseEvent(null, new TestEventArgs());
|
||||
|
||||
//Assert
|
||||
Assert.That(delegateCallCount, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribing two different delegates to a single event type causes both events
|
||||
/// to be raised in an indeterminate order.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void SubscribeEvent_MultipleDelegates_BothRaised()
|
||||
{
|
||||
// Arrange
|
||||
var bus = new EntityEventBus();
|
||||
var subscriber = new TestEventSubscriber();
|
||||
|
||||
int delFooCount = 0;
|
||||
int delBarCount = 0;
|
||||
|
||||
bus.SubscribeEvent<TestEventArgs>((sender, ev) => delFooCount++, subscriber);
|
||||
bus.SubscribeEvent<TestEventArgs>((sender, ev) => delBarCount++, subscriber);
|
||||
|
||||
// Act
|
||||
bus.RaiseEvent(null, new TestEventArgs());
|
||||
|
||||
// Assert
|
||||
Assert.That(delFooCount, Is.EqualTo(1));
|
||||
Assert.That(delBarCount, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A subscriber's handlers are properly called only when the specified event type is raised.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void SubscribeEvent_MultipleSubscriptions_IndividuallyCalled()
|
||||
{
|
||||
// Arrange
|
||||
var bus = new EntityEventBus();
|
||||
var subscriber = new TestEventSubscriber();
|
||||
|
||||
int delFooCount = 0;
|
||||
int delBarCount = 0;
|
||||
|
||||
bus.SubscribeEvent<TestEventArgs>((sender, ev) => delFooCount++, subscriber);
|
||||
bus.SubscribeEvent<TestEventTwoArgs>((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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribing a handler twice does nothing.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void UnsubscribeEvent_DoubleUnsubscribe_Nop()
|
||||
{
|
||||
// Arrange
|
||||
var bus = new EntityEventBus();
|
||||
var subscriber = new TestEventSubscriber();
|
||||
|
||||
void Handler(object sender, TestEventArgs ev) { }
|
||||
|
||||
bus.SubscribeEvent<TestEventArgs>(Handler, subscriber);
|
||||
bus.UnsubscribeEvent<TestEventArgs>(subscriber);
|
||||
|
||||
// Act
|
||||
bus.UnsubscribeEvent<TestEventArgs>(subscriber);
|
||||
|
||||
// Assert: Does not throw
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribing a handler that was never subscribed in the first place does nothing.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void UnsubscribeEvent_NoSubscription_Nop()
|
||||
{
|
||||
// Arrange
|
||||
var bus = new EntityEventBus();
|
||||
var subscriber = new TestEventSubscriber();
|
||||
|
||||
// Act
|
||||
bus.UnsubscribeEvent<TestEventArgs>(subscriber);
|
||||
|
||||
// Assert: Does not throw
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trying to unsubscribe with a null subscriber causes a <see cref="ArgumentNullException"/> to be thrown.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void UnsubscribeEvent_NullSubscriber_NullArgumentException()
|
||||
{
|
||||
// Arrange
|
||||
var bus = new EntityEventBus();
|
||||
|
||||
// Act
|
||||
void Code() => bus.UnsubscribeEvent<TestEventArgs>(null);
|
||||
|
||||
// Assert
|
||||
Assert.Throws<ArgumentNullException>(Code);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trying to queue a null event causes a <see cref="ArgumentNullException"/> to be thrown.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void RaiseEvent_NullEvent_ArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
var bus = new EntityEventBus();
|
||||
|
||||
// Act
|
||||
void Code() => bus.RaiseEvent(null, null);
|
||||
|
||||
// Assert
|
||||
Assert.Throws<ArgumentNullException>(Code);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raising an event with no handlers subscribed to it does nothing.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void RaiseEvent_NoSubscriptions_Nop()
|
||||
{
|
||||
// Arrange
|
||||
var bus = new EntityEventBus();
|
||||
var subscriber = new TestEventSubscriber();
|
||||
|
||||
int delCalledCount = 0;
|
||||
bus.SubscribeEvent<TestEventTwoArgs>(((sender, ev) => delCalledCount++), subscriber);
|
||||
|
||||
// Act
|
||||
bus.RaiseEvent(null, new TestEventArgs());
|
||||
|
||||
// Assert
|
||||
Assert.That(delCalledCount, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raising an event when a handler has been unsubscribed no longer calls the handler.
|
||||
/// </summary>
|
||||
[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<TestEventArgs>(Handler, subscriber);
|
||||
bus.UnsubscribeEvent<TestEventArgs>(subscriber);
|
||||
|
||||
// Act
|
||||
bus.RaiseEvent(null, new TestEventArgs());
|
||||
|
||||
// Assert
|
||||
Assert.That(delCallCount, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trying to unsubscribe all of a null subscriber's events causes a <see cref="ArgumentNullException"/> to be thrown.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void UnsubscribeEvents_NullSubscriber_NullArgumentException()
|
||||
{
|
||||
// Arrange
|
||||
var bus = new EntityEventBus();
|
||||
|
||||
// Act
|
||||
void Code() => bus.UnsubscribeEvents(null);
|
||||
|
||||
// Assert
|
||||
Assert.Throws<ArgumentNullException>(Code);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribing a subscriber with no subscriptions does nothing.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void UnsubscribeEvents_NoSubscriptions_Nop()
|
||||
{
|
||||
// Arrange
|
||||
var bus = new EntityEventBus();
|
||||
var subscriber = new TestEventSubscriber();
|
||||
|
||||
// Act
|
||||
bus.UnsubscribeEvents(subscriber);
|
||||
|
||||
// Assert: no exception
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The subscriber's handlers are not raised after they are unsubscribed.
|
||||
/// </summary>
|
||||
[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<TestEventArgs>(Handler, subscriber);
|
||||
bus.UnsubscribeEvents(subscriber);
|
||||
|
||||
// Act
|
||||
bus.RaiseEvent(null, new TestEventArgs());
|
||||
|
||||
// Assert
|
||||
Assert.That(delCallCount, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trying to queue a null event causes a <see cref="ArgumentNullException"/> to be thrown.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void QueueEvent_NullEvent_ArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
var bus = new EntityEventBus();
|
||||
|
||||
// Act
|
||||
void Code() => bus.QueueEvent(null, null);
|
||||
|
||||
// Assert
|
||||
Assert.Throws<ArgumentNullException>(Code);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queuing an event does not immediately raise the event unless the queue is processed.
|
||||
/// </summary>
|
||||
[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<TestEventArgs>(Handler, subscriber);
|
||||
|
||||
// Act
|
||||
bus.QueueEvent(null, new TestEventArgs());
|
||||
|
||||
// Assert
|
||||
Assert.That(delCallCount, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queued events are raised when the queue is processed.
|
||||
/// </summary>
|
||||
[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<TestEventArgs>(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 { }
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Raising a null C# delegate does not generate a NullReferenceException.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void SubscribeEvent_NullEvent_NoNullException()
|
||||
{
|
||||
// Arrange
|
||||
var manager = new TestEntityManager();
|
||||
var subscriber = new TestEventSubscriber();
|
||||
|
||||
manager.SubscribeEvent((EntityEventHandler<TestEventArgs>) 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 { }
|
||||
}
|
||||
Reference in New Issue
Block a user