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;
}
}
}
}