Last active
March 25, 2022 05:20
-
-
Save FleshMobProductions/3594d07e4a0dded611fe58de401fca7c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
namespace FMPUtils | |
{ | |
/// <summary> | |
/// Extension of the messenger from Wokarol (found at https://github.com/Wokarol/Messenger) using integer tags by utilizing | |
/// bitmasks | |
/// Tags can be any number between 0 and 63, the broadcaster of an event can specify something akin to a layermask | |
/// and all subscribers that have a tag number that is inside this mask will receive the event | |
/// Sending a message for a tag requires a tag mask, so if we want all receivers for tag 3 and | |
/// the type go get the event, we would pass (1L << 3). | |
/// Subscribing to an event only requires the tag index between 0 and 63. Users can make their own Tag class that holds | |
/// Integer constants as key for example | |
/// </summary> | |
public class TaggedMessenger | |
{ | |
public static TaggedMessenger Default { get; } = new TaggedMessenger(); | |
private Dictionary<Type, Delegate[]> messageMappings = new Dictionary<Type, Delegate[]>(); | |
private const int delegateArrayLength = 64; | |
#if UNITY_EDITOR | |
// Account for disabled domain reloading | |
[UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.SubsystemRegistration)] | |
private static void ResetDefault() | |
{ | |
if (Default != null) | |
Default.messageMappings.Clear(); | |
} | |
#endif | |
/// <summary> | |
/// Adds listener for event T | |
/// </summary> | |
/// <typeparam name="T"></typeparam> | |
/// <param name="handler"></param> | |
/// <param name="tagNum"></param> | |
public void AddListener<T>(Action<T> handler, int tagNum) | |
{ | |
var type = typeof(T); | |
AddListener(handler, type, tagNum); | |
} | |
/// <summary> | |
/// Sends message of type T to all listeners | |
/// </summary> | |
/// <typeparam name="T"></typeparam> | |
/// <param name="message"></param> | |
/// <param name="tagMask"></param> | |
public void SendMessage<T>(T message, long tagMask) | |
{ | |
var type = typeof(T); | |
SendMessage(message, type, tagMask); | |
} | |
/// <summary> | |
/// Removes given handler from event | |
/// </summary> | |
/// <typeparam name="T"></typeparam> | |
/// <param name="handler"></param> | |
/// <param name="tagNum"></param> | |
public void RemoveListener<T>(Action<T> handler, int tagNum) | |
{ | |
Type type = typeof(T); | |
RemoveListener(handler, type, tagNum); | |
} | |
/// <summary> | |
/// Removes all listeners from all events for a given target | |
/// </summary> | |
public void RemoveAllListenersFor(object target) | |
{ | |
// Stores all keys to iterate over (iterating over them directly causes error becase d[k] = x is treated as changing a collection) | |
Type[] keys = new Type[messageMappings.Count]; | |
RemoveAllListenersFor(target, keys); | |
} | |
private void AddListener<T>(Action<T> handler, Type type, int tagNum) | |
{ | |
if (tagNum < 0 || tagNum >= delegateArrayLength) { | |
throw new ArgumentException("tagNum"); | |
} | |
// Adds delegate to dictionary if there's no of given type | |
if (!messageMappings.ContainsKey(type)) { | |
messageMappings.Add(type, new Delegate[delegateArrayLength]); | |
} | |
// Add new delegate to old one | |
messageMappings[type][tagNum] = Delegate.Combine(messageMappings[type][tagNum], handler); | |
} | |
private void SendMessage<T>(T message, Type type, long tagMask) | |
{ | |
if (!messageMappings.ContainsKey(type)) return; | |
// Null checks only if type is class, otherwise it generates 17 B of Garbage for each call | |
if (type.IsClass && message == null) throw new ArgumentNullException(); | |
// Casts and invokes delegate assuming it's an Action<T> (should always be) | |
for (int i = 0; i < delegateArrayLength; i++) { | |
if ((tagMask & (1L << i)) != 0) { | |
((Action<T>)messageMappings[type][i])?.Invoke(message); | |
} | |
} | |
} | |
private void RemoveListener<T>(Action<T> handler, Type type, int tagNum) | |
{ | |
if (tagNum < 0 || tagNum >= delegateArrayLength) { | |
throw new ArgumentException("tagNum"); | |
} | |
if (!messageMappings.ContainsKey(type)) return; | |
// Removes handler from old delegate | |
var newDelegate = Delegate.Remove(messageMappings[type][tagNum], handler); | |
messageMappings[type][tagNum] = newDelegate; | |
} | |
private bool IsDelegateArrayEmpty(Type messageMapKey) { | |
var delegates = messageMappings[messageMapKey]; | |
for (int i = 0; i < delegates.Length; i++) { | |
if (delegates[i] != null) { | |
return false; | |
} | |
} | |
return true; | |
} | |
private void RemoveAllListenersFor(object target, Type[] keys) | |
{ | |
messageMappings.Keys.CopyTo(keys, 0); | |
// Stores all keys that shoudl be removed after for loop iteration | |
List<Type> keysToClear = new List<Type>(); | |
// Iterates over every type of event | |
for (int i = 0; i < keys.Length; i++) { | |
Type key = keys[i]; | |
var typeDelegates = messageMappings[key]; | |
for (int delegateIndex = 0; i < typeDelegates.Length; i++) { | |
var currentDelegate = typeDelegates[delegateIndex]; | |
if (currentDelegate != null) { | |
var invocations = currentDelegate.GetInvocationList(); | |
var newDelegate = currentDelegate; | |
// Check if any delegate matches target and if so removes it | |
for (int j = 0; j < invocations.Length; j++) { | |
if (invocations[j].Target == target) newDelegate = Delegate.Remove(newDelegate, invocations[j]); | |
} | |
messageMappings[key][delegateIndex] = newDelegate; | |
// Add key to "toClear" list if delegate has no listeners after last operation | |
if (newDelegate == null) { | |
if (IsDelegateArrayEmpty(key)) { | |
keysToClear.Add(key); | |
} | |
} | |
} | |
} | |
} | |
// Removes all now empty keys | |
for (int i = 0; i < keysToClear.Count; i++) { | |
messageMappings.Remove(keysToClear[i]); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment