using System; using System.Collections.Generic; using UnityEngine; namespace Uigenx.Actions { /// /// Process wide dispatch for UI actions. Main thread only: handlers run inline on the caller. /// public static class UgxActionBus { static readonly Dictionary>> ByName = new Dictionary>>(StringComparer.Ordinal); static readonly List> AllHandlers = new List>(); // Dispatch snapshots come from this pool so a steady state emit allocates nothing. The pool // is a stack because Emit can re-enter from inside a handler. static readonly Stack>> BufferPool = new Stack>>(); const int MaxPooledBuffers = 4; /// Static state survives a play mode exit when domain reload is disabled. [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] static void ResetOnLoad() => Clear(); public static void Clear() { ByName.Clear(); AllHandlers.Clear(); BufferPool.Clear(); } public static IDisposable Subscribe(string name, Action handler) { if (string.IsNullOrEmpty(name)) throw new ArgumentException("action name is required", nameof(name)); if (handler == null) throw new ArgumentNullException(nameof(handler)); if (!ByName.TryGetValue(name, out var list)) { list = new List>(2); ByName[name] = list; } list.Add(handler); return new Subscription(name, handler); } public static IDisposable SubscribeAll(Action handler) { if (handler == null) throw new ArgumentNullException(nameof(handler)); AllHandlers.Add(handler); return new Subscription(null, handler); } public static void Emit(UgxAction action) { if (string.IsNullOrEmpty(action.Name)) return; if (ByName.TryGetValue(action.Name, out var named) && named.Count > 0) Dispatch(named, action); if (AllHandlers.Count > 0) Dispatch(AllHandlers, action); } public static void Emit(string name, UgxActionPayload payload = default, string screen = null, string nodeId = null) => Emit(new UgxAction(name, payload, screen, nodeId)); public static bool HasSubscribers(string name) => AllHandlers.Count > 0 || (ByName.TryGetValue(name, out var list) && list.Count > 0); static void Dispatch(List> handlers, in UgxAction action) { var buffer = Rent(); buffer.AddRange(handlers); for (int i = 0; i < buffer.Count; i++) { // A faulty listener must never stop the rest of the bus. try { buffer[i](action); } catch (Exception e) { Debug.LogError("[ugx] action handler for '" + action.Name + "' threw: " + e); } } Return(buffer); } static List> Rent() => BufferPool.Count > 0 ? BufferPool.Pop() : new List>(8); static void Return(List> buffer) { buffer.Clear(); if (BufferPool.Count < MaxPooledBuffers) BufferPool.Push(buffer); } static void Unsubscribe(string name, Action handler) { if (name == null) { AllHandlers.Remove(handler); return; } if (!ByName.TryGetValue(name, out var list)) return; list.Remove(handler); if (list.Count == 0) ByName.Remove(name); } sealed class Subscription : IDisposable { string _name; Action _handler; internal Subscription(string name, Action handler) { _name = name; _handler = handler; } public void Dispose() { if (_handler == null) return; Unsubscribe(_name, _handler); _handler = null; _name = null; } } } }