Skip to content

Instantly share code, notes, and snippets.

@FleshMobProductions
Last active March 25, 2022 05:20
Show Gist options
  • Save FleshMobProductions/3594d07e4a0dded611fe58de401fca7c to your computer and use it in GitHub Desktop.
Save FleshMobProductions/3594d07e4a0dded611fe58de401fca7c to your computer and use it in GitHub Desktop.
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