#region License /* Copyright (c) 2010-2014 Danko Kozar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #endregion License using System; using System.Collections.Generic; using UnityEngine; namespace eDriven.Core.Events { /// /// The EventDispatcher /// Base class for all classes that dispatch events /// /// Coded by Danko Kozar public class EventDispatcher : IEventDispatcher, IEventQueue, IDisposable { /// /// Constructor /// public EventDispatcher() { _eventDispatcherTarget = this; } /// /// Constructor /// public EventDispatcher(object target) { if (null == target) throw new Exception("Target cannot be null"); _eventDispatcherTarget = target; } #region Properties #if DEBUG /// /// Debug mode /// public static bool DebugMode; #endif #endregion #region Members private readonly object _eventDispatcherTarget; /// /// A dictionary holding the references to event listeners /// For each event type, more that one event listener could be subscribed (one-to-many relationship) /// private readonly Dictionary> _eventHandlerDict = new Dictionary>(); /// /// The queue used for delayed processing /// private readonly List _queue = new List(); #endregion #region Subscribing / unsubscribing /// /// Adds the event listener /// /// Event type /// Event handler /// Event bubbling phases that we listen to /// Event priority public virtual void AddEventListener(string eventType, EventHandler handler, EventPhase phases, int priority) { var arr = EventPhaseHelper.BreakUpPhases(phases); foreach (EventPhase phase in arr) { EventTypePhase key = new EventTypePhase(eventType, phase); if (!_eventHandlerDict.ContainsKey(key)) // avoid key duplication _eventHandlerDict.Add(key, new List()); var group = _eventHandlerDict[key].Find(delegate(PriorityGroup g) { return g.Priority == priority; }); if (null == group) { //if (0 != priority) // Debug.Log("Creating new group with priority " + priority); group = new PriorityGroup { Priority = priority }; // add and sort _eventHandlerDict[key].Add(group); // if having multiple priorities, sort now if (_eventHandlerDict[key].Count > 0) _eventHandlerDict[key].Sort(PriorityComparer); } if (!group.Contains(handler)) // avoid key + value duplication group.Add(handler); } } /// /// Adds the event listener /// /// Event type /// Event handler /// Event bubbling phases that we listen to public virtual void AddEventListener(string eventType, EventHandler handler, EventPhase phases) { AddEventListener(eventType, handler, phases, 0); } /// /// AddEventListener Overload /// Assumes that useCapturePhase is false /// /// Event type /// Event handler (function) public virtual void AddEventListener(string eventType, EventHandler handler) { AddEventListener(eventType, handler, EventPhase.Target | EventPhase.Bubbling); // | EventPhase.Bubbling added back 20121216 } /// /// Adds the event listener /// /// Event type /// Event handler /// Event priority public virtual void AddEventListener(string eventType, EventHandler handler, int priority) { AddEventListener(eventType, handler, EventPhase.Target | EventPhase.Bubbling, priority); } private List _tempList; /// /// Removes the event listener /// /// Event type /// Event handler (function) /// Event bubbling phases that we listen to public virtual void RemoveEventListener(string eventType, EventHandler handler, EventPhase phases) { var arr = EventPhaseHelper.BreakUpPhases(phases); foreach (EventPhase phase in arr) { EventTypePhase key = new EventTypePhase(eventType, phase); if (_eventHandlerDict.ContainsKey(key)) { foreach (PriorityGroup group in _eventHandlerDict[key]) { if (group.Contains(handler)) group.Remove(handler); if (group.Count == 0) { if (null == _tempList) _tempList = new List(); _tempList.Add(group); } } if (null != _tempList) { foreach (PriorityGroup @group in _tempList) { _eventHandlerDict[key].Remove(@group); // cleanup } _tempList.Clear(); } if (_eventHandlerDict[key].Count == 0) _eventHandlerDict.Remove(key); // cleanup } } } /// /// Removes the event listener /// /// Event type /// Event handler (function) public virtual void RemoveEventListener(string eventType, EventHandler handler) { RemoveEventListener(eventType, handler, EventPhase.Target | EventPhase.Bubbling); // this is the default // | EventPhase.Bubbling added back 20121216 } /// /// Returns true if handler is mapped to any of the specified phases /// /// /// /// /// public virtual bool MappedToAnyPhase(string eventType, EventHandler handler, EventPhase phases) { var arr = EventPhaseHelper.BreakUpPhases(phases); foreach (EventPhase phase in arr) { EventTypePhase key = new EventTypePhase(eventType, phase); //if (_eventHandlerDict.ContainsKey(key) && _eventHandlerDict[key].Contains(handler)) if (_eventHandlerDict.ContainsKey(key)) { var exists = _eventHandlerDict[key].Exists(delegate(PriorityGroup group) { return group.Contains(handler); }); //foreach (PriorityGroup group in _eventHandlerDict[key]) //{ // //if (_eventHandlerDict[key].Contains(handler)) // // return true; //} return exists; } } return false; } /// /// Removes all listeners for the spacified event type (both capture and bubbling phase) /// /// Event type public virtual void RemoveAllListeners(string eventType) { RemoveAllListeners(eventType, EventPhase.Bubbling); RemoveAllListeners(eventType, EventPhase.Target); RemoveAllListeners(eventType, EventPhase.Capture); } /// /// Removes all listeners for the spacified event type and phases /// /// Event type /// /// public virtual void RemoveAllListeners(string eventType, EventPhase phases) { var arr = EventPhaseHelper.BreakUpPhases(phases); foreach (EventPhase phase in arr) { EventTypePhase key = new EventTypePhase(eventType, phase); if (_eventHandlerDict.ContainsKey(key)) { _eventHandlerDict[key].Clear(); _eventHandlerDict.Remove(key); } } } /// /// Returns true if EventDispatcher has any registered listeners for a specific type and phase /// /// /// public bool HasEventListener(string eventType) { return _eventHandlerDict.ContainsKey(new EventTypePhase(eventType, EventPhase.Target)); // TODO: Optimize } /// /// Returns true if there are any subscribers in bubbling hierarchy
/// Override in superclass ///
/// /// public virtual bool HasBubblingEventListener(string eventType) { return HasEventListener(eventType); } #endregion #region Dispatching /// /// Dispatches an event with the option of late processing (immediate = TRUE/FALSE) /// /// Event /// Process immediatelly or delayed? /// If after the event object finishes propagating through the DOM event flow its Event.DefaultPrevented attribute is false, then this method returns true. Otherwise this method returns false. public virtual void DispatchEvent(Event e, bool immediate) { #if DEBUG if (DebugMode) Debug.Log(string.Format("Dispatching event [{0}]", e)); #endif // do nothing if event has already been canceled if (e.Canceled) return; // set target if not already set if (null == e.Target) e.Target = _eventDispatcherTarget; // set current target if not already set if (null == e.CurrentTarget) e.CurrentTarget = e.Target; if (immediate) { /** * 1) Immediate dispatching * The code from the event listener is being run just NOW * */ ProcessEvent(e); } else { /** * 2) Delayed dispatching * Processed when ProcessQueue is called by the external code * */ EnqueueEvent(e); } } /// /// Dispatches an event immediatelly /// /// Event public virtual void DispatchEvent(Event e) { DispatchEvent(e, true); } #endregion #region Event processing /// /// Could be overriden in a subclass (for instance to implement event bubbling) /// /// Event to dispatch protected virtual void ProcessEvent(Event e) { ExecuteListeners(e); } /// /// Executes event handlers listening for a particular event type /// /// Event to dispatch /// NOTE: Public by purpose public void ExecuteListeners(Event e) { // return if event canceled if (e.Canceled) return; EventTypePhase key = new EventTypePhase(e.Type, e.Phase); // find event handlers subscribed for this event if (_eventHandlerDict.ContainsKey(key) && null != _eventHandlerDict[key]) { _eventHandlerDict[key].ForEach( delegate(PriorityGroup group) { group.ForEach(delegate (EventHandler handler) { if (e.Canceled) // the event might have been canceled by the previous listener in the collection return; handler(e); // execute the handler with an event as argument }); } ); } } #endregion #region Queued processing /// /// Adds an event to the queue /// The queue will be processed when ProcessQueue() manually executed /// /// public virtual void EnqueueEvent(Event e) { #if DEBUG if (DebugMode) Debug.Log(string.Format("Enqueueing event [{0}]. Queue count: {1}", e, _queue.Count)); #endif _queue.Add(e); } /// /// If events are added to queue, they are waiting to be fired
/// in the same order they are added ///
public virtual void ProcessQueue() { // return if nothing to process if (0 == _queue.Count) { #if DEBUG if (DebugMode) Debug.Log(string.Format("No enqueued events to process")); #endif return; } // dispatch each event in the queue now _queue.ForEach(delegate(Event e) { ExecuteListeners(e); }); #if DEBUG if (DebugMode) Debug.Log(string.Format("Processed {0} enqueued events", _queue.Count)); #endif // empty the queue _queue.Clear(); } #endregion #region Implementation of IDisposable /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public virtual void Dispose() { List keysToRemove = new List(); foreach (EventTypePhase key in _eventHandlerDict.Keys) { keysToRemove.Add(key); } keysToRemove.ForEach(key => RemoveAllListeners(key.EventType, key.Phase)); _eventHandlerDict.Clear(); } #endregion #region Preventing default /// /// Exposes the cancelable event to the outside if there are listeners for that event type /// If default prevented, returns false /// If not, returns true /// /// /// /// public bool IsDefaultPrevented(string eventType, bool bubbles) { // prevent removing if (HasEventListener(eventType)) { var e = new Event(eventType, bubbles, true); DispatchEvent(e); return e.DefaultPrevented; } return false; // return false because there are no listeners to prevent default } /// /// No-bubbling version /// /// /// public bool IsDefaultPrevented(string eventType) { return IsDefaultPrevented(eventType, false); } #endregion #region Helper /// /// /// private static readonly Comparison PriorityComparer = delegate(PriorityGroup group1, PriorityGroup group2) { if (group1.Priority > group2.Priority) return -1; if (group1.Priority < group2.Priority) return 1; return 0; }; #endregion } internal class PriorityGroup : List { public int Priority; } }