事件广播机制可以说是在各种框架里比较常见的解耦机制了,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 IReference Acquire(Type referenceType)
{
lock (m_References)
{
if (m_References.Count > 0)
{
return m_References.Dequeue();
}
}
return (IReference)Activator.CreateInstance(referenceType);
}
/// <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>
public static T Acquire<T>() where T : class, IReference, new()
{
return GetReferenceCollection(typeof(T).FullName).Acquire<T>();
}
/// <summary>
/// 从引用池获取引用
/// </summary>
public static IReference Acquire(Type referenceType)
{
return GetReferenceCollection(referenceType.FullName).Acquire(referenceType);
}
/// <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);
}
}
//向引用池归还事件引用
ReferencePool.Release(e);
}
/// <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()
{
m_Name = string.Empty;
}
/// <summary>
/// 事件填充
/// </summary>
public EventTestArgs Fill(string name)
{
m_Name = name;
return this;
}
}
打开测试脚本,进行事件的订阅与派发编写
public class EventTestMain : MonoBehaviour {
private void Start()
{
//订阅事件
FrameworkEntry.Instance.GetManager<EventManager>().Subscribe(1, EventTestMethod);
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
EventTestArgs e = ReferencePool.Acquire<EventTestArgs>();
//派发事件
FrameworkEntry.Instance.GetManager<EventManager>().Fire(this, e.Fill("EventArgs"));
}
}
/// <summary>
/// 事件处理方法
/// </summary>
private void EventTestMethod(object sender, GlobalEventArgs e)
{
EventTestArgs args = e as EventTestArgs;
Debug.Log(args.m_Name);
}
}
然后启动游戏,点击左键,控制台成功输出了EventTest