boxmoe_header_banner_img

菜就多练喵

文章导读

[C#][Unity][Ib_Core]Ib_Messenger——以string为Key的同步事件中心 v0.1


avatar
Ib_Mccf 2025年12月19日 41

功能:通过全局事件使类与类之间的通信解耦(两个类之间无需知道彼此的存在,类似广播电台,一个类发送,一个类监听)。

特点:规定一个特定的字符串即可作为一个事件。在事件调用时会对参数类型和数量进行判断,若有误则不执行并警告。

性能:注册时事件容器的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)

查看评论列表

暂无评论


发表评论

表情 颜文字

插入代码