Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using System;
- using System.Collections.Generic;
- using UnityEngine;
- public interface PubsubEvent { }
- // For global events.
- public class GlobalPubSub {
- private static PubSub ps = new PubSub();
- public static void Subscribe<T>(object o, Action<T> callback) where T : PubsubEvent {
- ps.Subscribe<T>(o, callback);
- }
- public static void Unsubscribe<T>(object o) where T : PubsubEvent {
- ps.Unsubscribe<T>(o);
- }
- public static void UnsubscribeAll(object o) {
- ps.UnsubscribeAll(o);
- }
- public static void WipeSubscribers() {
- ps.WipeSubscribers();
- }
- public static bool IsSubscribed<T>(object o) where T : PubsubEvent {
- return ps.IsSubscribed<T>(o);
- }
- public static void Publish<T>(T evt) where T : PubsubEvent {
- ps.Publish<T>(evt);
- }
- }
- // For events that are local to a single GameObject.
- public class LocalPubSub : MonoBehaviour {
- private PubSub ps = new PubSub();
- void OnDestroy() {
- ps.WipeSubscribers();
- }
- public static LocalPubSub GetOrCreate(GameObject g) {
- var lps = g.GetComponent<LocalPubSub>();
- if (lps == null) {
- lps = g.AddComponent<LocalPubSub>();
- }
- return lps;
- }
- public void Subscribe<T>(object o, Action<T> callback) where T : PubsubEvent {
- ps.Subscribe<T>(o, callback);
- }
- public void Unsubscribe<T>(object o) where T : PubsubEvent {
- ps.Unsubscribe<T>(o);
- }
- public void UnsubscribeAll(object o) {
- ps.UnsubscribeAll(o);
- }
- public void WipeSubscribers() {
- ps.WipeSubscribers();
- }
- public bool IsSubscribed<T>(object o) where T : PubsubEvent {
- return ps.IsSubscribed<T>(o);
- }
- public void Publish<T>(T evt) where T : PubsubEvent {
- ps.Publish<T>(evt);
- }
- }
- public class PubSub {
- // Maps event type to subscriber to callback.
- private Dictionary<Type, Registry> subscribers = new Dictionary<Type, Registry>(100);
- public void Subscribe<T>(object o, Action<T> callback) where T : PubsubEvent {
- var typ = typeof(T);
- if (!subscribers.ContainsKey(typ)) {
- subscribers[typ] = new Registry();
- }
- subscribers[typ].Subscribe(o, callback);
- }
- public void Unsubscribe<T>(object o) where T : PubsubEvent {
- var typ = typeof(T);
- if (subscribers.ContainsKey(typ)) {
- subscribers[typ].Unsubscribe(o);
- }
- }
- public void UnsubscribeAll(object o) {
- foreach (var typ in subscribers.Keys) {
- subscribers[typ].Unsubscribe(o);
- }
- }
- // Remove all subscribers, e.g. because we're changing scenes.
- public void WipeSubscribers() {
- foreach (var key in subscribers.Keys) {
- subscribers[key].Clear();
- }
- }
- public bool IsSubscribed<T>(object o) where T : PubsubEvent {
- var typ = typeof(T);
- return subscribers.ContainsKey(typ) && subscribers[typ].IsSubscribed(o);
- }
- // To avoid allocations, we retain a data structure that we stuff the
- // current set of subscribers for a given event type into. But because
- // events firing may cause other events to fire, we have a stack of
- // lists to use.
- private Stack<List<(int, object)>> subscriberStack = new Stack<List<(int, object)>>();
- public void Publish<T>(T evt) where T : PubsubEvent {
- // HACK: not in the editor.
- if (!Application.isPlaying) {
- return;
- }
- if (!subscribers.ContainsKey(typeof(T))) {
- // Nobody to publish to.
- return;
- }
- var typ = typeof(T);
- subscribers[typ].Publish(evt, subscriberStack);
- }
- // For one given event type, tracks subscribers and keeps them ordered.
- // Ordering is important to enforce consistent execution order. Objects
- // often subscribe to events in Awake or Start, and the order in which
- // this happens is not consistent (though it may be consistent in the
- // editor!). So we sort subscribers by the hashcode of their class name,
- // on the assumption that two instances of the same class should not
- // care which of them subscribes first.
- private class Registry {
- // Maps subscriber to their event handling function
- private Dictionary<object, object> subscriberToCallback = new Dictionary<object, object>(100);
- // Keeps subscribers ordered by their code.
- private List<(int, object)> orderedSubscribers = new List<(int, object)>(100);
- public void Subscribe(object subscriber, object callback) {
- subscriberToCallback[subscriber] = callback;
- int code = subscriber.GetType().Name.GetHashCode();
- for (int i = 0; i < orderedSubscribers.Count; ++i) {
- var pair = orderedSubscribers[i];
- if (pair.Item1 > code) {
- orderedSubscribers.Insert(i, (code, subscriber));
- return;
- }
- }
- orderedSubscribers.Insert(orderedSubscribers.Count, (code, subscriber));
- }
- public void Unsubscribe(object subscriber) {
- if (!subscriberToCallback.ContainsKey(subscriber)) {
- return;
- }
- subscriberToCallback.Remove(subscriber);
- for (int i = 0; i < orderedSubscribers.Count; ++i) {
- if (orderedSubscribers[i].Item2 == subscriber) {
- orderedSubscribers.RemoveAt(i);
- return;
- }
- }
- Debug.LogError($"Unsubscribe `{subscriber}` was in our callbacks, but not in the ordered list; this should be impossible!");
- }
- public void Publish<T>(T evt, Stack<List<(int, object)>> subscriberStack) where T : PubsubEvent {
- if (subscriberStack.Count == 0) {
- for (int i = 0; i < 5; ++i) {
- subscriberStack.Push(new List<(int, object)>(100)); // Arbitrary initial capacity that should be pretty decent.
- }
- }
- var subs = subscriberStack.Pop();
- // Use a separate list because subscribers might unsubscribe as a result
- // of receiving this event.
- subs.Clear();
- subs.AddRange(orderedSubscribers);
- foreach (var pair in subs) {
- if (!subscriberToCallback.ContainsKey(pair.Item2)) {
- // Subscriber unsubscribed while we were in the middle of iteration.
- continue;
- }
- var action = subscriberToCallback[pair.Item2];
- try {
- ((Action<T>)action)(evt);
- }
- catch (Exception e) {
- // Do not propagate exceptions up the stack trace as the code that published the event probably doesn't
- // care that subscribers had problems.
- Debug.LogError($"Error publishing event {evt} to {pair.Item2}: {e}");
- }
- }
- subs.Clear();
- subscriberStack.Push(subs);
- }
- public void Clear() {
- subscriberToCallback.Clear();
- orderedSubscribers.Clear();
- }
- public bool IsSubscribed(object o) {
- return subscriberToCallback.ContainsKey(o);
- }
- }
- }
Add Comment
Please, Sign In to add comment