功能:通过全局事件使类与类之间的通信解耦(两个类之间无需知道彼此的存在,类似广播电台,一个类发送,一个类监听)。
特点:规定一个特定的字符串即可作为一个事件。在事件调用时会对参数类型和数量进行判断,若有误则不执行并警告。
性能:注册时事件容器的List扩容可能会产生GC,注册后第一次调用会有List到数组转换的GC,之后调用无额外GC。在内部将string转换为int型的哈希值来作为字典的Key来存储,提高字典的查询效率。
相比Type为Key的优势:支持跨程序集使用。支持读取配置文件的字符数据直接触发事件。
缺点:非强类型安全,参数类型和事件名称必须严格匹配,容易搞错。
*事件名称易错、易忘的问题可以通过定义常量字符串解决。
使用示例:
using Cysharp.Threading.Tasks;
using Ib_Core;
using UnityEngine;
public static class EventNames
{
//为事件名字分类
public static class Test
{
// 测试事件的常量名,防止拼错或遗忘
public const string OnGameLog = "OnGameLog";
}
}
public class Sample_Ib_Messenger_Launcher : MonoBehaviour
{
void Start()
{
//5s后调用OnGameLog事件
Ib_Async.DelayDoSomething(5,TestEvent, this.GetCancellationTokenOnDestroy());
}
void TestEvent()
{
Ib_Log.Info($"[{System.DateTime.Now}] >> {this.GetType().Name} >> {EventNames.Test.OnGameLog}事件被调用");
Ib_Messenger.Invoke(EventNames.Test.OnGameLog, "成功调用");
}
}
using Ib_Core;
using UnityEngine;
public class Sample_Ib_Messenger_Listener : MonoBehaviour
{
void Start()
{
//注册 名为 OnGameLog 并 带有一个string类型参数 的事件
Ib_Messenger.Register<string>(EventNames.Test.OnGameLog,GameLog);
}
void OnDestroy()
{
Ib_Messenger.Deregister<string>(EventNames.Test.OnGameLog,GameLog);
}
private void GameLog(string content)
{
Ib_Log.Info($"[{System.DateTime.Now}] >> {this.GetType().Name} >> {content}");
}
}

*同事件名但不同的参数类型或数量会被内部定义为两个不同的事件。同时会弹出类型或数量无法匹配的警告,只有匹配成功的才会被执行。可以看到两个同为“OnGameLog”的事件各自只被调用了一次,同时对彼此弹出了类型不匹配的警告。
public class Sample_Ib_Messenger_Launcher : MonoBehaviour
{
void Start()
{
//5s后调用OnGameLog事件
Ib_Async.DelayDoSomething(5,TestEvent, this.GetCancellationTokenOnDestroy());
}
void TestEvent()
{
Ib_Log.Info($"[{System.DateTime.Now}] >> {this.GetType().Name} >> {EventNames.Test.OnGameLog}事件被调用");
Ib_Messenger.Invoke(EventNames.Test.OnGameLog, "成功调用");
Ib_Messenger.Invoke(EventNames.Test.OnGameLog, "成功调用", 2);
}
}
using Ib_Core;
using UnityEngine;
public class Sample_Ib_Messenger_Listener : MonoBehaviour
{
void Start()
{
//注册 名为 OnGameLog 并 带有一个string类型参数 的事件
Ib_Messenger.Register<string>(EventNames.Test.OnGameLog,GameLog);
Ib_Messenger.Register<string,int>(EventNames.Test.OnGameLog,GameLog2);
}
void OnDestroy()
{
Ib_Messenger.Deregister<string>(EventNames.Test.OnGameLog,GameLog);
Ib_Messenger.Deregister<string,int>(EventNames.Test.OnGameLog,GameLog2);
}
private void GameLog(string content)
{
Ib_Log.Info($"[{System.DateTime.Now}] >> {this.GetType().Name} >> {content}");
}
private void GameLog2(string content, int id)
{
Ib_Log.Info($"[{System.DateTime.Now}] >> {this.GetType().Name} >> {content} <id:{id}>");
}
}

源码:
namespace Ib_Core
{
using UnityEngine;
using System;
using System.Collections.Generic;
/// <summary>
/// 以string为Key的同步事件中心
/// #使用方法----------------------------------------------------------------------------------
/// 通过Ib_Messenger.Invoke(事件名称, 参数1, 参数2, ...)来触发事件
/// 通过Ib_Messenger.Register<参数类型1, ...>(事件名称, Action<参数类型1, ...>)来注册事件
/// 通过Ib_Messenger.Deregister<参数类型1, ...>(事件名称, Action<参数类型1, ...>)来注销事件
/// #----------------------------------------------------------------------------------------
/// </summary>
public static class Ib_Messenger
{
private static readonly Dictionary<int, Ib_EventContainer> EventsDict = new Dictionary<int, Ib_EventContainer>();
private static readonly object _lock = new object();
/// <summary>
/// 将string转换为哈希值,提高查找效率
/// </summary>
/// <param name="eventName"></param>
/// <returns></returns>
private static int GetHash(string eventName)
{
int hash = Animator.StringToHash(eventName);
return hash;
}
private static Ib_EventContainer GetContainer(int hash, bool createIfNull)
{
if (!EventsDict.TryGetValue(hash, out var container))
{
if (createIfNull)
{
container = new Ib_EventContainer();
EventsDict[hash] = container;
}
}
return container;
}
#region 无参数
public static void Register(string eventName, Action action)
{
int hash = GetHash(eventName);
lock (_lock) GetContainer(hash, true).Add(action);
}
public static void Deregister(string eventName, Action action)
{
int hash = GetHash(eventName);
lock (_lock)
{
var container = GetContainer(hash, false);
if (container != null)
{
container.Remove(action);
if (container.IsEmpty) EventsDict.Remove(hash);
}
}
}
public static void Invoke(string eventName)
{
int hash = GetHash(eventName);
Delegate[] actions = null;
lock (_lock)
{
if (EventsDict.TryGetValue(hash, out var container))
actions = container.GetDelegateArray();
}
if (actions == null || actions.Length == 0) return;
for (int i = 0; i < actions.Length; i++)
{
try
{
if (actions[i] is Action action)
{
action?.Invoke();
}
else
{
#if UNITY_EDITOR || DEVELOPMENT_BUILD
Ib_Log.Warning($"Event '{eventName}' signature mismatch. Target expects: {actions[i].GetType()}");
#endif
}
}
catch (Exception ex)
{
#if UNITY_EDITOR || DEVELOPMENT_BUILD
Ib_Log.Error($"Error invoking '{eventName}': {ex.Message}");
#endif
}
}
}
#endregion
#region 一个参数 <T>
public static void Register<T>(string eventName, Action<T> action)
{
int hash = GetHash(eventName);
lock (_lock) GetContainer(hash, true).Add(action);
}
public static void Deregister<T>(string eventName, Action<T> action)
{
int hash = GetHash(eventName);
lock (_lock)
{
var container = GetContainer(hash, false);
if (container != null)
{
container.Remove(action);
if (container.IsEmpty) EventsDict.Remove(hash);
}
}
}
public static void Invoke<T>(string eventName, T arg)
{
int hash = GetHash(eventName);
Delegate[] actions = null;
lock (_lock)
{
if (EventsDict.TryGetValue(hash, out var container))
actions = container.GetDelegateArray();
}
if (actions == null || actions.Length == 0) return;
for (int i = 0; i < actions.Length; i++)
{
try
{
// 类型匹配
if (actions[i] is Action<T> action)
{
action.Invoke(arg);
}
else
{
#if UNITY_EDITOR || DEVELOPMENT_BUILD
Ib_Log.Warning($"Event '{eventName}' signature mismatch. Target expects: {actions[i].GetType()}, Invoked with: {typeof(T)}");
#endif
}
}
catch (Exception ex)
{
#if UNITY_EDITOR || DEVELOPMENT_BUILD
Ib_Log.Error($"Error invoking '{eventName}' with arg {arg}: {ex.Message}");
#endif
}
}
}
#endregion
#region 两个参数 <T1, T2>
public static void Register<T1, T2>(string eventName, Action<T1, T2> action)
{
int hash = GetHash(eventName);
lock (_lock) GetContainer(hash, true).Add(action);
}
public static void Deregister<T1, T2>(string eventName, Action<T1, T2> action)
{
int hash = GetHash(eventName);
lock (_lock)
{
var container = GetContainer(hash, false);
if (container != null)
{
container.Remove(action);
if (container.IsEmpty) EventsDict.Remove(hash);
}
}
}
public static void Invoke<T1, T2>(string eventName, T1 arg1, T2 arg2)
{
int hash = GetHash(eventName);
Delegate[] actions = null;
lock (_lock)
{
if (EventsDict.TryGetValue(hash, out var container))
actions = container.GetDelegateArray();
}
if (actions == null || actions.Length == 0) return;
for (int i = 0; i < actions.Length; i++)
{
try
{
// 使用 'is' 模式匹配进行安全转换和调用
if (actions[i] is Action<T1, T2> action)
{
action.Invoke(arg1, arg2);
}
else
{
#if UNITY_EDITOR || DEVELOPMENT_BUILD
Ib_Log.Warning($"Event '{eventName}' signature mismatch. Target expects: {actions[i].GetType()}, Invoked with: {typeof(T1)}, {typeof(T2)}");
#endif
}
}
catch (Exception ex)
{
#if UNITY_EDITOR || DEVELOPMENT_BUILD
Ib_Log.Error($"Error invoking '{eventName}': {ex.Message}");
#endif
}
}
}
#endregion
#region 三个参数 <T1, T2, T3>
public static void Register<T1, T2, T3>(string eventName, Action<T1, T2, T3> action)
{
int hash = GetHash(eventName);
lock (_lock) GetContainer(hash, true).Add(action);
}
public static void Deregister<T1, T2, T3>(string eventName, Action<T1, T2, T3> action)
{
int hash = GetHash(eventName);
lock (_lock)
{
var container = GetContainer(hash, false);
if (container != null)
{
container.Remove(action);
if (container.IsEmpty) EventsDict.Remove(hash);
}
}
}
public static void Invoke<T1, T2, T3>(string eventName, T1 arg1, T2 arg2, T3 arg3)
{
int hash = GetHash(eventName);
Delegate[] actions = null;
lock (_lock)
{
if (EventsDict.TryGetValue(hash, out var container))
actions = container.GetDelegateArray();
}
if (actions == null || actions.Length == 0) return;
for (int i = 0; i < actions.Length; i++)
{
try
{
// 使用 'is' 模式匹配进行安全转换和调用
if (actions[i] is Action<T1, T2, T3> action)
{
action.Invoke(arg1, arg2, arg3);
}
else
{
#if UNITY_EDITOR || DEVELOPMENT_BUILD
Ib_Log.Warning($"Event '{eventName}' signature mismatch. Target expects: {actions[i].GetType()}, Invoked with: {typeof(T1)}, {typeof(T2)}, {typeof(T3)}");
#endif
}
}
catch (Exception ex)
{
#if UNITY_EDITOR || DEVELOPMENT_BUILD
Ib_Log.Error($"Error invoking '{eventName}': {ex.Message}");
#endif
}
}
}
#endregion
#region 四个参数 <T1, T2, T3, T4>
public static void Register<T1, T2, T3, T4>(string eventName, Action<T1, T2, T3, T4> action)
{
int hash = GetHash(eventName);
lock (_lock) GetContainer(hash, true).Add(action);
}
public static void Deregister<T1, T2, T3, T4>(string eventName, Action<T1, T2, T3, T4> action)
{
int hash = GetHash(eventName);
lock (_lock)
{
var container = GetContainer(hash, false);
if (container != null)
{
container.Remove(action);
if (container.IsEmpty) EventsDict.Remove(hash);
}
}
}
public static void Invoke<T1, T2, T3, T4>(string eventName, T1 arg1, T2 arg2, T3 arg3, T4 arg4)
{
int hash = GetHash(eventName);
Delegate[] actions = null;
lock (_lock)
{
if (EventsDict.TryGetValue(hash, out var container))
actions = container.GetDelegateArray();
}
if (actions == null || actions.Length == 0) return;
for (int i = 0; i < actions.Length; i++)
{
try
{
// 使用 'is' 模式匹配进行安全转换和调用
if (actions[i] is Action<T1, T2, T3, T4> action)
{
action.Invoke(arg1, arg2, arg3, arg4);
}
else
{
#if UNITY_EDITOR || DEVELOPMENT_BUILD
Ib_Log.Warning($"Event '{eventName}' signature mismatch. Target expects: {actions[i].GetType()}, Invoked with: {typeof(T1)}, {typeof(T2)}, {typeof(T3)}, {typeof(T4)}");
#endif
}
}
catch (Exception ex)
{
#if UNITY_EDITOR || DEVELOPMENT_BUILD
Ib_Log.Error($"Error invoking '{eventName}': {ex.Message}");
#endif
}
}
}
#endregion
#region 五个参数 <T1, T2, T3, T4, T5>
public static void Register<T1, T2, T3, T4, T5>(string eventName, Action<T1, T2, T3, T4, T5> action)
{
int hash = GetHash(eventName);
lock (_lock) GetContainer(hash, true).Add(action);
}
public static void Deregister<T1, T2, T3, T4, T5>(string eventName, Action<T1, T2, T3, T4, T5> action)
{
int hash = GetHash(eventName);
lock (_lock)
{
var container = GetContainer(hash, false);
if (container != null)
{
container.Remove(action);
if (container.IsEmpty) EventsDict.Remove(hash);
}
}
}
public static void Invoke<T1, T2, T3, T4, T5>(string eventName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5)
{
int hash = GetHash(eventName);
Delegate[] actions = null;
lock (_lock)
{
if (EventsDict.TryGetValue(hash, out var container))
actions = container.GetDelegateArray();
}
if (actions == null || actions.Length == 0) return;
for (int i = 0; i < actions.Length; i++)
{
try
{
// 使用 'is' 模式匹配进行安全转换和调用
if (actions[i] is Action<T1, T2, T3, T4, T5> action)
{
action.Invoke(arg1, arg2, arg3, arg4, arg5);
}
else
{
#if UNITY_EDITOR || DEVELOPMENT_BUILD
Ib_Log.Warning($"Event '{eventName}' signature mismatch. Target expects: {actions[i].GetType()}, Invoked with: {typeof(T1)}, {typeof(T2)}, {typeof(T3)}, {typeof(T4)}, {typeof(T5)}");
#endif
}
}
catch (Exception ex)
{
#if UNITY_EDITOR || DEVELOPMENT_BUILD
Ib_Log.Error($"Error invoking '{eventName}': {ex.Message}");
#endif
}
}
}
#endregion
}
}
事件容器:
#region 事件容器
/// <summary>
/// 事件容器,使用写时复制(Copy-on-Write)策略来实现 0 GC 的 Invoke
/// </summary>
internal class Ib_EventContainer
{
// 直接持有数组,Invoke时直接遍历这个数组
private Delegate[] _actionArray = Array.Empty<Delegate>();
public bool IsEmpty => _actionList.Count == 0;
//list和脏标记用于防止同一帧内有大量事件同时注册,导致卡顿
private readonly List<Delegate> _actionList = new List<Delegate>();
private bool _isDirty = false;//脏标记
public void Add(Delegate callback)
{
if (callback == null) return;
_actionList.Add(callback);
_isDirty = true;
}
public void Remove(Delegate callback)
{
if (callback == null) return;
if (_actionList.Remove(callback))
{
_isDirty = true;
}
}
public Delegate[] GetDelegateArray()
{
if (_isDirty)
{
_actionArray = _actionList.ToArray();
_isDirty = false;
}
return _actionArray;
}
}
#endregion
使用Ib_Async可自行添加延迟一帧调用的拓展:
namespace Ib_Core
{
using System.Threading;
public partial class Ib_Messenger
{
public static void InvokeNextFrame(string eventName,CancellationToken cts) => Ib_Async.DelayDoSomething(0, Invoke, eventName,cts);
public static void InvokeNextFrame<T>(string eventName,T arg,CancellationToken cts) => Ib_Async.DelayDoSomething(0, Invoke, eventName,arg,cts);
public static void InvokeNextFrame<T1, T2>(string eventName,T1 arg1,T2 arg2,CancellationToken cts) => Ib_Async.DelayDoSomething(0, Invoke, eventName,arg1,arg2,cts);
public static void InvokeNextFrame<T1, T2, T3>(string eventName,T1 arg1,T2 arg2,T3 arg3,CancellationToken cts) => Ib_Async.DelayDoSomething(0, Invoke, eventName,arg1,arg2,arg3,cts);
public static void InvokeNextFrame<T1, T2, T3, T4>(string eventName,T1 arg1,T2 arg2,T3 arg3,T4 arg4,CancellationToken cts) => Ib_Async.DelayDoSomething(0, Invoke, eventName,arg1,arg2,arg3,arg4,cts);
public static void InvokeNextFrame<T1, T2, T3, T4, T5>(string eventName,T1 arg1,T2 arg2,T3 arg3,T4 arg4,T5 arg5,CancellationToken cts) => Ib_Async.DelayDoSomething(0, Invoke, eventName,arg1,arg2,arg3,arg4,arg5,cts);
}
}
评论(0)
暂无评论