Guest User

Untitled

a guest
May 7th, 2023
60
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 7.39 KB | None | 0 0
  1. using System;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4.  
  5. public interface PubsubEvent { }
  6.  
  7. // For global events.
  8. public class GlobalPubSub {
  9.     private static PubSub ps = new PubSub();
  10.     public static void Subscribe<T>(object o, Action<T> callback) where T : PubsubEvent {
  11.         ps.Subscribe<T>(o, callback);
  12.     }
  13.     public static void Unsubscribe<T>(object o) where T : PubsubEvent {
  14.         ps.Unsubscribe<T>(o);
  15.     }
  16.  
  17.     public static void UnsubscribeAll(object o) {
  18.         ps.UnsubscribeAll(o);
  19.     }
  20.  
  21.     public static void WipeSubscribers() {
  22.         ps.WipeSubscribers();
  23.     }
  24.     public static bool IsSubscribed<T>(object o) where T : PubsubEvent {
  25.         return ps.IsSubscribed<T>(o);
  26.     }
  27.     public static void Publish<T>(T evt) where T : PubsubEvent {
  28.         ps.Publish<T>(evt);
  29.     }
  30. }
  31.  
  32. // For events that are local to a single GameObject.
  33. public class LocalPubSub : MonoBehaviour {
  34.     private PubSub ps = new PubSub();
  35.  
  36.     void OnDestroy() {
  37.         ps.WipeSubscribers();
  38.     }
  39.  
  40.     public static LocalPubSub GetOrCreate(GameObject g) {
  41.         var lps = g.GetComponent<LocalPubSub>();
  42.         if (lps == null) {
  43.             lps = g.AddComponent<LocalPubSub>();
  44.         }
  45.         return lps;
  46.     }
  47.  
  48.     public void Subscribe<T>(object o, Action<T> callback) where T : PubsubEvent {
  49.         ps.Subscribe<T>(o, callback);
  50.     }
  51.     public void Unsubscribe<T>(object o) where T : PubsubEvent {
  52.         ps.Unsubscribe<T>(o);
  53.     }
  54.     public void UnsubscribeAll(object o) {
  55.         ps.UnsubscribeAll(o);
  56.     }
  57.     public void WipeSubscribers() {
  58.         ps.WipeSubscribers();
  59.     }
  60.     public bool IsSubscribed<T>(object o) where T : PubsubEvent {
  61.         return ps.IsSubscribed<T>(o);
  62.     }
  63.     public void Publish<T>(T evt) where T : PubsubEvent {
  64.         ps.Publish<T>(evt);
  65.     }
  66. }
  67.  
  68. public class PubSub {
  69.     // Maps event type to subscriber to callback.
  70.     private Dictionary<Type, Registry> subscribers = new Dictionary<Type, Registry>(100);
  71.  
  72.     public void Subscribe<T>(object o, Action<T> callback) where T : PubsubEvent {
  73.         var typ = typeof(T);
  74.         if (!subscribers.ContainsKey(typ)) {
  75.             subscribers[typ] = new Registry();
  76.         }
  77.         subscribers[typ].Subscribe(o, callback);
  78.     }
  79.  
  80.     public void Unsubscribe<T>(object o) where T : PubsubEvent {
  81.         var typ = typeof(T);
  82.         if (subscribers.ContainsKey(typ)) {
  83.             subscribers[typ].Unsubscribe(o);
  84.         }
  85.     }
  86.  
  87.     public void UnsubscribeAll(object o) {
  88.         foreach (var typ in subscribers.Keys) {
  89.             subscribers[typ].Unsubscribe(o);
  90.         }
  91.     }
  92.  
  93.     // Remove all subscribers, e.g. because we're changing scenes.
  94.     public void WipeSubscribers() {
  95.         foreach (var key in subscribers.Keys) {
  96.             subscribers[key].Clear();
  97.         }
  98.     }
  99.  
  100.     public bool IsSubscribed<T>(object o) where T : PubsubEvent {
  101.         var typ = typeof(T);
  102.         return subscribers.ContainsKey(typ) && subscribers[typ].IsSubscribed(o);
  103.     }
  104.  
  105.     // To avoid allocations, we retain a data structure that we stuff the
  106.     // current set of subscribers for a given event type into. But because
  107.     // events firing may cause other events to fire, we have a stack of
  108.     // lists to use.
  109.     private Stack<List<(int, object)>> subscriberStack = new Stack<List<(int, object)>>();
  110.     public void Publish<T>(T evt) where T : PubsubEvent {
  111.         // HACK: not in the editor.
  112.         if (!Application.isPlaying) {
  113.             return;
  114.         }
  115.         if (!subscribers.ContainsKey(typeof(T))) {
  116.             // Nobody to publish to.
  117.             return;
  118.         }
  119.         var typ = typeof(T);
  120.         subscribers[typ].Publish(evt, subscriberStack);
  121.     }
  122.  
  123.     // For one given event type, tracks subscribers and keeps them ordered.
  124.     // Ordering is important to enforce consistent execution order. Objects
  125.     // often subscribe to events in Awake or Start, and the order in which
  126.     // this happens is not consistent (though it may be consistent in the
  127.     // editor!). So we sort subscribers by the hashcode of their class name,
  128.     // on the assumption that two instances of the same class should not
  129.     // care which of them subscribes first.
  130.     private class Registry {
  131.         // Maps subscriber to their event handling function
  132.         private Dictionary<object, object> subscriberToCallback = new Dictionary<object, object>(100);
  133.         // Keeps subscribers ordered by their code.
  134.         private List<(int, object)> orderedSubscribers = new List<(int, object)>(100);
  135.  
  136.         public void Subscribe(object subscriber, object callback) {
  137.             subscriberToCallback[subscriber] = callback;
  138.             int code = subscriber.GetType().Name.GetHashCode();
  139.             for (int i = 0; i < orderedSubscribers.Count; ++i) {
  140.                 var pair = orderedSubscribers[i];
  141.                 if (pair.Item1 > code) {
  142.                     orderedSubscribers.Insert(i, (code, subscriber));
  143.                     return;
  144.                 }
  145.             }
  146.             orderedSubscribers.Insert(orderedSubscribers.Count, (code, subscriber));
  147.         }
  148.  
  149.         public void Unsubscribe(object subscriber) {
  150.             if (!subscriberToCallback.ContainsKey(subscriber)) {
  151.                 return;
  152.             }
  153.             subscriberToCallback.Remove(subscriber);
  154.             for (int i = 0; i < orderedSubscribers.Count; ++i) {
  155.                 if (orderedSubscribers[i].Item2 == subscriber) {
  156.                     orderedSubscribers.RemoveAt(i);
  157.                     return;
  158.                 }
  159.             }
  160.             Debug.LogError($"Unsubscribe `{subscriber}` was in our callbacks, but not in the ordered list; this should be impossible!");
  161.         }
  162.  
  163.         public void Publish<T>(T evt, Stack<List<(int, object)>> subscriberStack) where T : PubsubEvent {
  164.             if (subscriberStack.Count == 0) {
  165.                 for (int i = 0; i < 5; ++i) {
  166.                     subscriberStack.Push(new List<(int, object)>(100)); // Arbitrary initial capacity that should be pretty decent.
  167.                 }
  168.             }
  169.             var subs = subscriberStack.Pop();
  170.             // Use a separate list because subscribers might unsubscribe as a result
  171.             // of receiving this event.
  172.             subs.Clear();
  173.             subs.AddRange(orderedSubscribers);
  174.             foreach (var pair in subs) {
  175.                 if (!subscriberToCallback.ContainsKey(pair.Item2)) {
  176.                     // Subscriber unsubscribed while we were in the middle of iteration.
  177.                     continue;
  178.                 }
  179.                 var action = subscriberToCallback[pair.Item2];
  180.                 try {
  181.                     ((Action<T>)action)(evt);
  182.                 }
  183.                 catch (Exception e) {
  184.                     // Do not propagate exceptions up the stack trace as the code that published the event probably doesn't
  185.                     // care that subscribers had problems.
  186.                     Debug.LogError($"Error publishing event {evt} to {pair.Item2}: {e}");
  187.                 }
  188.             }
  189.             subs.Clear();
  190.             subscriberStack.Push(subs);
  191.         }
  192.         public void Clear() {
  193.             subscriberToCallback.Clear();
  194.             orderedSubscribers.Clear();
  195.         }
  196.  
  197.         public bool IsSubscribed(object o) {
  198.             return subscriberToCallback.ContainsKey(o);
  199.         }
  200.     }
  201. }
Add Comment
Please, Sign In to add comment