事件广播机制可以说是在各种框架里比较常见的解耦机制了,GF自然也不例外,在本篇文章中我们便来编写Event模块
下面是官网的介绍
在正式开始编写Event模块之前,我们需要完成一个前置的框架基础功能——ReferencePool(引用池),ReferencePool提供了一个IReference接口,只有实现了该接口的对象才会被纳入引用池管理当中,而在GF里,主要由Event模块的事件基类实现该接口,也就是说ReferencePool在框架里主要是负责管理Event模块的事件的引用的
首先在工程的Base文件夹下创建一个ReferencePool文件夹,新建IReference,ReferenceCollection和ReferencePool
其中
IReference上面已经说明过了
ReferenceCollection是引用集合类,负责管理实现了IReference的对象
ReferencePool是引用池类,负责管理所有的ReferenceCollection
打开IReference,将其修改为接口,并添加清理引用的方法
/// <summary> /// 引用池对象接口 /// </summary> public interface IReference{ /// <summary> /// 清理引用 /// </summary> void Clear(); }
接下来打开ReferenceCollection,为其添加字段与构造方法
/// <summary> /// 引用集合 /// </summary> public class ReferenceCollection{ /// <summary> /// 引用队列 /// </summary> private Queue<IReference> m_References; public ReferenceCollection() { m_References = new Queue<IReference>(); } }
并添加引用队列的相关方法
/// <summary> /// 获取引用 /// </summary> public T Acquire<T>() where T : class, IReference, new() { lock (m_References) { if (m_References.Count > 0) { return m_References.Dequeue() as T; } } return new T(); } /// <summary> /// 释放引用 /// </summary> public void Release<T>(T reference) where T : class, IReference { reference.Clear(); lock (m_References) { m_References.Enqueue(reference); } } /// <summary> /// 添加引用 /// </summary> public void Add<T>(int count) where T : class, IReference, new() { lock (m_References) { while (count-- > 0) { m_References.Enqueue(new T()); } } } /// <summary> /// 删除引用 /// </summary> public void Remove<T>(int count) where T : class, IReference { lock (m_References) { if (count > m_References.Count) { count = m_References.Count; } while (count-- > 0) { m_References.Dequeue(); } } } /// <summary> /// 删除所有引用 /// </summary> public void RemoveAll() { lock (m_References) { m_References.Clear(); } }
最后打开ReferencePool,将其修改为静态类,并为其添加字段与属性
/// <summary> /// 引用池 /// </summary> public static class ReferencePool{ /// <summary> /// 引用集合的字典 /// </summary> private static Dictionary<string, ReferenceCollection> s_ReferenceCollections = new Dictionary<string, ReferenceCollection>(); /// <summary> /// 获取引用池的数量 /// </summary> public static int Count { get { return s_ReferenceCollections.Count; } } }
添加获取引用集合与清除所有引用集合的方法
/// <summary> /// 获取引用集合 /// </summary> private static ReferenceCollection GetReferenceCollection(string fullName) { ReferenceCollection referenceCollection = null; lock (s_ReferenceCollections) { if (!s_ReferenceCollections.TryGetValue(fullName, out referenceCollection)) { referenceCollection = new ReferenceCollection(); s_ReferenceCollections.Add(fullName, referenceCollection); } } return referenceCollection; } /// <summary> /// 清除所有引用集合 /// </summary> public static void ClearAll() { lock (s_ReferenceCollections) { foreach (KeyValuePair<string, ReferenceCollection> referenceCollection in s_ReferenceCollections) { referenceCollection.Value.RemoveAll(); } s_ReferenceCollections.Clear(); } }
然后添加引用池的相关方法
追加与移除
/// <summary> /// 向引用集合中追加指定数量的引用 /// </summary> /// <typeparam name="T">引用类型</typeparam> /// <param name="count">追加数量</param> public static void Add<T>(int count) where T : class, IReference, new() { GetReferenceCollection(typeof(T).FullName).Add<T>(count); } /// <summary> /// 从引用集合中移除指定数量的引用 /// </summary> /// <typeparam name="T">引用类型</typeparam> /// <param name="count">移除数量</param> public static void Remove<T>(int count) where T : class, IReference { GetReferenceCollection(typeof(T).FullName).Remove<T>(count); } /// <summary> /// 从引用集合中移除所有的引用 /// </summary> /// <typeparam name="T">引用类型</typeparam> public static void RemoveAll<T>() where T : class, IReference { GetReferenceCollection(typeof(T).FullName).RemoveAll(); }获取与归还
/// <summary> /// 从引用集合获取引用 /// </summary> /// <typeparam name="T">引用类型。</typeparam> public static T Acquire<T>() where T : class, IReference, new() { return GetReferenceCollection(typeof(T).FullName).Acquire<T>(); } /// <summary> /// 将引用归还引用集合 /// </summary> /// <typeparam name="T">引用类型</typeparam> /// <param name="reference">引用</param> public static void Release<T>(T reference) where T : class, IReference { if (reference == null) { Debug.LogError("要归还的引用为空"); } GetReferenceCollection(typeof(T).FullName).Release(reference); }
Ok,到这里ReferencePool功能编写完毕,可以正式开始Event模块的编写了
新建Event文件夹,在其中新建GlobalEventArgs,EventPool与EventManager
GlobalEventArgs是全局事件的基类,EventPool负责将某一类型的GlobalEventArgs封装后进行管理,EventManager负责管理所有的EventPool
首先打开GlobalEventArgs,将其修改为抽象类,继承EventArgs并实现IReference
/// <summary> /// 全局事件基类(继承该类的事件类才能被事件池管理) /// </summary> public abstract class GlobalEventArgs : EventArgs,IReference { // <summary> /// 事件类型ID /// </summary> public abstract int Id { get; } public abstract void Clear(); }
然后打开EventPool,为其添加泛型与约束,并编写一个内部类Event封装GlobalEventArgs,用于进行线程安全的事件抛出
/// <summary> /// 事件池 /// </summary> public class EventPool<T> where T : GlobalEventArgs{ /// <summary> /// 事件结点 /// </summary> private class Event { public Event(object sender, T e) { Sender = sender; EventArgs = e; } /// <summary> /// 事件发送者 /// </summary> public object Sender { get; private set; } /// <summary> /// 事件参数 /// </summary> public T EventArgs { get; private set; } } }
这里之所以要这么封装是因为事件处理方法的委托采用了C#的EventHandler,其形参为object与EvntArgs
接下来添加字段与属性,并在构造方法里进行初始化
/// <summary> /// 事件码与对应处理方法的字典 /// </summary> private Dictionary<int, EventHandler<T>> m_EventHandlers; /// <summary> /// 事件结点队列 /// </summary> private Queue<Event> m_Events; public EventPool() { m_EventHandlers = new Dictionary<int, EventHandler<T>>(); m_Events = new Queue<Event>(); }
添加事件的订阅与取消的方法
/// <summary> /// 检查订阅事件处理方法是否存在 /// </summary> public bool Check(int id, EventHandler<T> handler) { if (handler == null) { Debug.LogError("事件处理方法为空"); return false; } EventHandler<T> handlers = null; if (!m_EventHandlers.TryGetValue(id, out handlers)) { return false; } if (handlers == null) { return false; } //遍历委托里的所有方法 foreach (EventHandler<T> i in handlers.GetInvocationList()) { if (i == handler) { return true; } } return false; } /// <summary> /// 订阅事件 /// </summary> public void Subscribe(int id, EventHandler<T> handler) { if (handler == null) { Debug.LogError("事件处理方法为空,无法订阅"); return; } EventHandler<T> eventHandler = null; //检查是否获取处理方法失败或获取到的为空 if (!m_EventHandlers.TryGetValue(id, out eventHandler) || eventHandler == null) { m_EventHandlers[id] = handler; } //不为空,就检查是否处理方法重复了 else if (Check(id, handler)) { Debug.LogError("要订阅事件的处理方法已存在"); } else { eventHandler += handler; m_EventHandlers[id] = eventHandler; } } /// <summary> /// 取消订阅事件 /// </summary> public void Unsubscribe(int id, EventHandler<T> handler) { if (handler == null) { Debug.LogError("事件处理方法为空,无法取消订阅"); return; } if (m_EventHandlers.ContainsKey(id)) { m_EventHandlers[id] -= handler; } }
添加事件处理方法
/// <summary> /// 处理事件 /// </summary> /// <param name="sender">事件来源</param> /// <param name="e">事件参数</param> private void HandleEvent(object sender, T e) { //尝试获取事件的处理方法 int eventId = e.Id; EventHandler<T> handlers = null; if (m_EventHandlers.TryGetValue(eventId, out handlers)) { if (handlers != null) { handlers(sender, e); } else { Debug.LogError("事件没有对应处理方法:" + eventId); } } } /// <summary> /// 事件池轮询(用于处理线程安全的事件) /// </summary> public void Update(float elapseSeconds, float realElapseSeconds) { while (m_Events.Count > 0) { Event e = null; lock (m_Events) { e = m_Events.Dequeue(); } //从封装的Event中取出事件数据进行处理 HandleEvent(e.Sender, e.EventArgs); } }
添加事件抛出方法
/// <summary> /// 抛出事件(线程安全) /// </summary> /// <param name="sender">事件源。</param> /// <param name="e">事件参数。</param> public void Fire(object sender, T e) { //将事件源和事件参数封装为Event加入队列 Event eventNode = new Event(sender, e); lock (m_Events) { m_Events.Enqueue(eventNode); } } /// <summary> /// 抛出事件(线程不安全) /// </summary> /// <param name="sender">事件源</param> /// <param name="e">事件参数</param> public void FireNow(object sender, T e) { HandleEvent(sender, e); }
最后添加关闭与清理事件池的方法
/// <summary> /// 清理事件。 /// </summary> public void Clear() { lock (m_Events) { m_Events.Clear(); } } /// <summary> /// 关闭并清理事件池。 /// </summary> public void Shutdown() { Clear(); m_EventHandlers.Clear(); }
到这里EventPool的编写就完成了
接下来打开EventManager,继承ManagerBase,添加需要的字段与构造方法
/// <summary> /// 事件管理器 /// </summary> public class EventManager : ManagerBase { /// <summary> /// 事件池 /// </summary> private EventPool<GlobalEventArgs> m_EventPool; public override int Priority { get { return 100; } } public EventManager() { m_EventPool = new EventPool<GlobalEventArgs>(); } public override void Init() { } public override void Shutdown() { //关闭并清理事件池 m_EventPool.Shutdown(); } /// <summary> /// 轮询事件池 /// </summary> public override void Update(float elapseSeconds, float realElapseSeconds) { m_EventPool.Update(elapseSeconds, realElapseSeconds); } }
添加事件的订阅与取消(其实就是对事件池的代理)
/// <summary> /// 检查订阅事件处理方法是否存在 /// </summary> public bool Check(int id, EventHandler<GlobalEventArgs> handler) { return m_EventPool.Check(id, handler); } /// <summary> /// 订阅事件 /// </summary> public void Subscribe(int id, EventHandler<GlobalEventArgs> handler) { m_EventPool.Subscribe(id, handler); } /// <summary> /// 取消订阅事件 /// </summary> public void Unsubscribe(int id, EventHandler<GlobalEventArgs> handler) { m_EventPool.Unsubscribe(id, handler); }
以及事件的抛出
/// <summary> /// 抛出事件(线程安全) /// </summary> public void Fire(object sender, GlobalEventArgs e) { m_EventPool.Fire(sender, e); } /// <summary> /// 抛出事件(线程不安全) /// </summary> public void FireNow(object sender, GlobalEventArgs e) { m_EventPool.FireNow(sender, e); }
很简单的就完成了EventManager的编写,那么就轮到测试环节了
按照惯例,新建好文件夹,测试脚本,以及测试场景
打开EventTestArgs,使其继承GlobalEventArgs,并添加一个string字段
public class EventTestArgs : GlobalEventArgs { public string m_Name; public override int Id { get { return 1; } } public override void Clear() { } }
打开测试脚本,进行事件的订阅与派发编写
public class EventTestMain : MonoBehaviour { private void Start() { //订阅事件 FrameworkEntry.Instance.GetManager<EventManager>().Subscribe(1, EventTestMethod); } private void Update() { if (Input.GetMouseButtonDown(0)) { EventTestArgs e = new EventTestArgs { m_Name = "EventTest" }; //派发事件 FrameworkEntry.Instance.GetManager<EventManager>().Fire(this, e); } } /// <summary> /// 事件处理方法 /// </summary> private void EventTestMethod(object sender, GlobalEventArgs e) { EventTestArgs args = e as EventTestArgs; Debug.Log(args.m_Name); } }
然后启动游戏,点击左键,控制台成功输出了EventTest