#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;
}
}