Code/Internals/GameObjectEventManager.cs
#if SANDBOX
using System.Collections;
namespace Sandbox.Reactivity.Internals;
file sealed class EventList<T> : List<Func<T, bool>>;
/// <summary>
/// Maintains the list of registered event callbacks on a game object.
/// </summary>
internal sealed class GameObjectEventManager(GameObject go)
{
/// <summary>
/// The map of game objects to their event managers.
/// </summary>
private static readonly Dictionary<GameObject, GameObjectEventManager> RegisteredObjects = [];
/// <summary>
/// The map of types to the list of callbacks to run when used as an event.
/// </summary>
private readonly Dictionary<Type, IList> _handledEvents = [];
/// <summary>
/// The game object this event manager belongs to.
/// </summary>
private readonly GameObject _owningObject = go;
/// <summary>
/// Returns the event manager for the given game object.
/// </summary>
/// <param name="go">The game object to get the event handler for.</param>
/// <returns>The event manager that corresponds to the given game object, or <c>null</c> if not found.</returns>
public static GameObjectEventManager? Get(GameObject go)
{
return RegisteredObjects.GetValueOrDefault(go);
}
/// <summary>
/// Returns an event manager for the given game object. If it doesn't already have one, it will be created.
/// </summary>
/// <param name="go">The game object to get an event handler for.</param>
/// <returns>An event handler that corresponds to the given game object.</returns>
public static GameObjectEventManager GetOrCreate(GameObject go)
{
if (!RegisteredObjects.TryGetValue(go, out var handler))
{
handler = new GameObjectEventManager(go);
RegisteredObjects[go] = handler;
}
return handler;
}
/// <summary>
/// Adds a function to run when an event is received.
/// </summary>
/// <param name="callback">The function to run.</param>
/// <typeparam name="T">
/// The event type the function accepts. The function will also run for any events that are assignable to this type.
/// </typeparam>
public void Add<T>(Func<T, bool> callback)
{
foreach (var type in TypeHierarchy<T>.Types)
{
if (!_handledEvents.TryGetValue(type, out var eventList))
{
eventList = new EventList<T>();
_handledEvents[type] = eventList;
}
((EventList<T>)eventList).Add(callback);
}
}
/// <summary>
/// Removes a function from running when an event is received.
/// </summary>
/// <param name="callback">The function to remove.</param>
/// <typeparam name="T">The event type the function accepts.</typeparam>
public void Remove<T>(Func<T, bool> callback)
{
foreach (var type in TypeHierarchy<T>.Types)
{
if (!_handledEvents.TryGetValue(type, out var eventList))
{
continue;
}
var callbacks = (EventList<T>)eventList;
if (callbacks.Remove(callback) && callbacks.Count == 0)
{
_handledEvents.Remove(type);
}
}
if (_handledEvents.Count == 0)
{
RegisteredObjects.Remove(_owningObject);
}
}
/// <summary>
/// Runs any functions on this handler with the given event.
/// </summary>
/// <param name="eventData">The event object to use when running a function.</param>
/// <typeparam name="T">
/// The event type to pass to this event handler's functions. This will call any functions that accept a type that
/// is assignable to this one.
/// </typeparam>
/// <returns>Whether a function has requested to stop propagation to other game objects.</returns>
public bool Dispatch<T>(T eventData)
{
if (!_handledEvents.TryGetValue(typeof(T), out var eventList))
{
return false;
}
var stopPropagation = false;
using (new UntrackScope())
{
foreach (var callback in (EventList<T>)eventList)
{
stopPropagation = callback(eventData) || stopPropagation;
}
}
return stopPropagation;
}
}
#endif