Skip to content

Instantly share code, notes, and snippets.

@LambdaSix
Created January 24, 2024 00:56
Show Gist options
  • Save LambdaSix/bfbce4b5f24573be073db95e9eee6c0a to your computer and use it in GitHub Desktop.
Save LambdaSix/bfbce4b5f24573be073db95e9eee6c0a to your computer and use it in GitHub Desktop.
Unity EventManager
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using UnityEngine;
public abstract class GameEvent
{
}
public class EventManager : MonoBehaviour, IDisposable
{
public bool LimitQueueProcessing { get; set; } = false;
public float QueueProcessTime { get; set; } = 0.0f;
private static EventManager _instance = null;
private ConcurrentQueue<GameEvent> _eventQueue = new();
public delegate void EventDelegate<T>(T e) where T : GameEvent;
private delegate void EventDelegate (GameEvent e);
private ConcurrentDictionary<Type, EventDelegate> delegates = new();
private ConcurrentDictionary<Delegate, EventDelegate> delegateLookup = new();
private ConcurrentDictionary<Delegate, Delegate> onceLookups = new();
public static EventManager Instance =>
_instance ??= GameObject.FindObjectOfType(typeof(EventManager)) as EventManager;
private EventDelegate Add<T>(EventDelegate<T> @delegate) where T : GameEvent
{
if (delegateLookup.ContainsKey(@delegate))
return null;
EventDelegate internalDelegate = (e) => @delegate((T)e);
delegateLookup[@delegate] = internalDelegate;
if (delegates.TryGetValue(typeof(T), out var tempDel))
{
delegates[typeof(T)] = tempDel += internalDelegate;
}
else
{
delegates[typeof(T)] = internalDelegate;
}
return internalDelegate;
}
public void Subscribe<T>(EventDelegate<T> callback) where T : GameEvent
{
Add(callback);
}
public void SubscribeOnce<T>(EventDelegate<T> callback) where T : GameEvent
{
var result = Add(callback);
if (result != null)
onceLookups[result] = callback;
}
public void Unsubscribe<T>(EventDelegate<T> callback) where T : GameEvent
{
if (delegateLookup.TryGetValue(callback, out var internalDelegate))
{
if (delegates.TryGetValue(typeof(T), out var tempDel))
{
tempDel -= internalDelegate;
if (tempDel is null)
{
delegates.Remove(typeof(T), out _);
}
else
{
delegates[typeof(T)] = tempDel;
}
}
delegateLookup.Remove(callback, out _);
}
}
public void UnsubscribeAll()
{
delegates.Clear();
delegateLookup.Clear();
onceLookups.Clear();
}
public bool HasListener<T>(EventDelegate<T> callback) where T : GameEvent
=> delegateLookup.ContainsKey(callback);
public void Trigger(GameEvent e)
{
var eType = e.GetType();
if (delegates.TryGetValue(eType, out var callback))
{
callback?.Invoke(e);
foreach (EventDelegate k in delegates[eType].GetInvocationList())
{
delegates[eType] -= k;
if (delegates[eType] is null)
{
delegates.Remove(eType, out _);
}
delegateLookup.Remove(onceLookups[k], out _);
onceLookups.Remove(k, out _);
}
}
else
{
Debug.LogWarning($"EventManager: Event : {eType} has no listeners.");
}
}
public bool Enqueue(GameEvent evt)
{
if (!delegates.ContainsKey(evt.GetType()))
{
Debug.LogWarning($"EventManager: QueueEvent() failed due to no listeners for event: {evt.GetType()}");
return false;
}
_eventQueue.Enqueue(evt);
return true;
}
// Once per update cycle process the queue. Optionally limit processing time
void Update()
{
float elapsedTime = 0.0f;
while (_eventQueue.Count > 0)
{
if (LimitQueueProcessing && elapsedTime > QueueProcessTime)
return;
if (_eventQueue.TryDequeue(out var evt))
{
Trigger(evt);
if (LimitQueueProcessing)
elapsedTime += Time.deltaTime;
}
}
}
public void OnApplicationQuit()
{
Dispose();
}
public void Dispose()
{
this.UnsubscribeAll();
_eventQueue.Clear();
_instance = null;
GC.SuppressFinalize(this);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment