diff --git a/Blarg.GameFramework/Blarg.GameFramework.csproj b/Blarg.GameFramework/Blarg.GameFramework.csproj index de74197..63f1692 100644 --- a/Blarg.GameFramework/Blarg.GameFramework.csproj +++ b/Blarg.GameFramework/Blarg.GameFramework.csproj @@ -141,6 +141,10 @@ + + + + @@ -155,6 +159,7 @@ + diff --git a/Blarg.GameFramework/Events/Event.cs b/Blarg.GameFramework/Events/Event.cs new file mode 100644 index 0000000..3f9969c --- /dev/null +++ b/Blarg.GameFramework/Events/Event.cs @@ -0,0 +1,11 @@ +using System; +using Blarg.GameFramework.Support; + +namespace Blarg.GameFramework.Events +{ + public abstract class Event : IPoolable + { + public abstract void Reset(); + } +} + diff --git a/Blarg.GameFramework/Events/EventHandler.cs b/Blarg.GameFramework/Events/EventHandler.cs new file mode 100644 index 0000000..76a4112 --- /dev/null +++ b/Blarg.GameFramework/Events/EventHandler.cs @@ -0,0 +1,30 @@ +using System; + +namespace Blarg.GameFramework.Events +{ + public abstract class EventHandler : IEventListener + { + public readonly EventManager EventManager; + + public EventHandler(EventManager eventManager) + { + if (eventManager == null) + throw new ArgumentNullException("eventManager"); + + EventManager = eventManager; + } + + public bool ListenFor() where T : Event + { + return EventManager.AddListener(this); + } + + public bool StopListeningFor() where T : Event + { + return EventManager.RemoveListener(this); + } + + public abstract bool Handle(Event e); + } +} + diff --git a/Blarg.GameFramework/Events/EventManager.cs b/Blarg.GameFramework/Events/EventManager.cs new file mode 100644 index 0000000..af3be77 --- /dev/null +++ b/Blarg.GameFramework/Events/EventManager.cs @@ -0,0 +1,227 @@ +using System; +using System.Collections.Generic; +using Blarg.GameFramework.Support; + +namespace Blarg.GameFramework.Events +{ + using EventListenerList = IList; + using EventTypeSet = ISet; + using EventListenerTable = IList; + using EventListenerMap = IDictionary>; + using EventQueue = LinkedList; + + public class EventManager + { + const int NumEventQueues = 2; + + EventTypeSet _typeList; + EventListenerMap _registry; + EventQueue[] _queues; + int _activeQueue; + + public EventManager() + { + _typeList = new HashSet(); + _registry = new Dictionary(); + _queues = new EventQueue[NumEventQueues]; + for (int i = 0; i < _queues.Length; ++i) + _queues[i] = new LinkedList(); + + _activeQueue = 0; + } + + public bool AddListener(IEventListener listener) where T : Event + { + if (listener == null) + throw new ArgumentNullException("listener"); + + var type = typeof(T); + EventListenerTable listenerTable = null; + + _registry.TryGetValue(type, out listenerTable); + if (listenerTable == null) + { + // need to register this listener for the given type + listenerTable = new List(); + _registry.Add(type, listenerTable); + } + + // prevent duplicate listeners from being registered + if (listenerTable.Contains(listener)) + throw new InvalidOperationException("Duplicate event listener registration."); + + listenerTable.Add(listener); + Platform.Logger.Debug("EventManager", "Added {0} as a listener for event type {1}", listener.GetType().Name, type.Name); + + // also update the list of currently registered event types + _typeList.Add(type); + + return true; + } + + public bool RemoveListener(IEventListener listener) where T : Event + { + if (listener == null) + throw new ArgumentNullException("listener"); + + var type = typeof(T); + + // get the list of listeners for the given event type + EventListenerTable listenersForType; + _registry.TryGetValue(type, out listenersForType); + if (listenersForType == null) + return false; // either no listeners for this type, or the listener wasn't registered with us + + if (listenersForType.Contains(listener)) + { + listenersForType.Remove(listener); + Platform.Logger.Debug("EventManager", "Removed {0} as a listener for event type {1}", listener.GetType().Name, type.Name); + + // if there are no more listeners for this type, remove the type + // from the list of registered event types + if (listenersForType.Count == 0) + _typeList.Remove(type); + + return true; + } + else + return false; + } + + public bool Trigger(Event e) + { + if (e == null) + throw new ArgumentNullException("e"); + + var type = e.GetType(); + + // find the listeners for the event type provided + EventListenerTable listenersForType; + _registry.TryGetValue(type, out listenersForType); + if (listenersForType == null) + return false; // no listeners for this event type have been registered -- we can't handle the event + + bool result = false; + + // trigger the event in each listener + foreach (var listener in listenersForType) + { + if (listener.Handle(e)) + { + // don't let other listeners handle the event if this one signals it handled it + result = true; + break; + } + } + + // TODO: maybe, for Trigger() only, it's better to force the calling code + // to "putback" the event object being triggered? since we handle the + // event immediately, unlike with Queue() where it makes a lot more + // sense for us to place it back in the pool ourselves ... + Free(e); + + // a result of "false" merely indicates that no listener indicates + // it "handled" the event + return result; + } + + public bool Queue(Event e) + { + if (e == null) + throw new ArgumentNullException("e"); + + // validate that there is infact a listener for this event type + // (otherwise, we don't queue this event) + var type = e.GetType(); + if (!_typeList.Contains(type)) + return false; + + _queues[_activeQueue].AddLast(e); + + return true; + } + + public bool Abort(bool stopAfterFirstRemoval = true) where T : Event + { + // validate that there is infact a listener for this event type + // (otherwise, we don't queue this event) + var type = typeof(T); + if (!_typeList.Contains(type)) + return false; + + bool result = false; + + // walk through the queue and remove matching events + // NOTE: foreach not used because we need to remove items while inside the loop + var queue = _queues[_activeQueue]; + var node = queue.First; + while (node != null) + { + // grab the next node first (so we have it before potentially removing + // this node and then losing the link to the next one) + var nextNode = node.Next; + + if (node.Value.GetType() == type) + { + // found a match, remove it + var e = node.Value; + queue.Remove(node); + Free(e); + result = true; + + if (stopAfterFirstRemoval) + break; + } + + node = nextNode; + } + + return result; + } + + public bool ProcessQueue() + { + // swap active queues and empty the new queue + int queueToProcess = _activeQueue; + _activeQueue = (_activeQueue + 1) % NumEventQueues; + _queues[_activeQueue].Clear(); + + // process the queue + var queue = _queues[queueToProcess]; + while (queue.Count > 0) + { + // pop the next event off the queue + var e = queue.First.Value; + queue.RemoveFirst(); + + var type = e.GetType(); + + EventListenerTable listenersForType; + _registry.TryGetValue(type, out listenersForType); + if (listenersForType != null) + { + foreach (var listener in listenersForType) + { + if (listener.Handle(e)) + break; // don't let other listeners handle the event if this one signals it handled it + } + } + + Free(e); + } + + return true; + } + + public T Create() where T : Event + { + return ObjectPools.Take(); + } + + public void Free(T e) where T : Event + { + ObjectPools.Free(e); + } + } +} + diff --git a/Blarg.GameFramework/Events/IEventListener.cs b/Blarg.GameFramework/Events/IEventListener.cs new file mode 100644 index 0000000..71b4869 --- /dev/null +++ b/Blarg.GameFramework/Events/IEventListener.cs @@ -0,0 +1,10 @@ +using System; + +namespace Blarg.GameFramework.Events +{ + public interface IEventListener + { + bool Handle(Event e); + } +} +