今天, 开始写新的一年的博客了, 我的博客更新的比较随意, 也比较缓慢, 哈哈前段时间一直在看一些 D3D11 的博客, 自己也在摸索各个 API 的使用, 不过今天要写的是一篇游戏中的事件系统的设计, 具体的事件系统的设计模式(观察者模式), 我就不再赘述了, 网上有很多相关资料, 可以自行查阅学习了解一下相关的原理, 比较简单易懂
在游戏开发中, 我们经常会和服务器进行信息的交互, 比如玩家点击了商城里面的领取奖励的按钮, 此时客户端会发送一个领取的协议给服务器, 服务器校验通过后, 会下发给客户端, 客户端在收到消息后, 会重新刷新一下相关的数据, 那么这些数据的刷新怎么表现在 UI 上面呢, 就需要我们通过事件系统来通知 UI 层进行相关的刷新操作了
最常见的事件系统, 就是我上面说到的, 将数据层和 UI 层进行分离, 通过事件系统的调用来实现耦合的拆分这在游戏中有较多的应用, 那么, 我们今天先说一下这种事件系统的设计模式吧
一常见事件系统的设计
要设计一个事件系统, 一般会暴露最常见的三个接口: Add/Remove/Trigger, 分别负责事件的添加, 移除和触发依然用 Lua 来实现这样的一个接口, 我们可以用几个 table 来实现一个基本的事件系统的三个接口:
- local events = {}
- local eventHandleId = 0
- local eventHandles = {}
-- 设置两个内部函数操作增加和删除
- local function AddListener(name, listener)
- local listeners = events[name]
- if not listeners then
- listeners = {}
- events[name] = listeners
- end
- local handleId = listeners[listener]
- if not handleId then
- handleId = eventHandleId + 1
- eventHandleId = handleId
-- 此处插入, 用 function 来用作 key 值
- listeners[listener] = handleId
- eventHandles[handleId] = { name, listener }
- end
- return handleId
- end
- local function RemoveListener(name, listener, handles)
- local listeners = events[name]
- if listeners then
- if not listener then
-- 该 name 下对应的所有都清空
- for _, handleId in pairs(listeners) do
- eventHandles[handleId] = nil
- if handles then
- handles[handleId] = nil
- end
- end
- events[name] = nil
- else
-- 特定对应的某个 listner 删除
- local handleId = listeners[listener]
- if handleId then
- listeners[listener] = nil
- eventHandles[handleId] = nil
- if handles then
- handles[handleId] = nil
- end
- end
- end
- end
- end
-- 指定删除某个 handleId 对应的事件
- local function RemoveHandle(handleId)
- local entry = eventHandles[handleId]
- if entry then
- RemoveListener(entry[1], entry[2])
- end
- end
- --
- local EventManager = {}
-- 增加
- function EventManager.Add(name, listener)
- AddListener(name, listener)
- end
-- 删除
- function EventManager.Remove(name, listener)
- RemoveListener(name, listener)
- end
-- 清空
- function EventManager.Clear()
- events = {}
- eventHandles = {}
- end
-- 触发
- function EventManager.Dispatch(name, ...)
- local listeners = events[name]
- if listeners then
- for listener, _ in pairs(listeners) do
-- 基于 key 值 (实质为函数) 来执行触发操作
- listener(name, ...)
- end
- end
- end
巧用 lua 中的 table, 我们可以实现一个最基本的事件系统, 具体的原理可以参看 lua 代码来理解, 不是很难这是最基本的事件系统设计, 在此基础上, 我们可以进一步的优化我们的事件系统我们在进行事件系统的注册删除和触发的时候, 并没有考虑到并发性比如同时有多个消息过来, 要求我们对同一个事件进行处理, 这时候事件系统就需要考虑并发性的设计了
二处理并发性的事件系统设计
对于并发性的处理, 许多常见的思路都给出了不同的处理办法, 比如加锁就是一个比较好的处理办法在执行触发的操作的时候, 这是就对添加的函数进行滞后处理, 这样可以避免在执行触发操作的时候, 又塞入一个新的监听, 造成触发隐藏问题可以这样处理:
- local events = {}
- local eventHandleId = 0
- local eventHandles = {}
- local function AddListener(name, listener)
- local listeners = events[name]
- if not listeners then
--listeners 不再是一个简单的 table, 通过多个标识符来标识
- listeners = { insert = {}, dirty = false, executing = false, destroyed = false }
- events[name] = listeners
- end
- local handleId = listeners[listener] or listeners.insert[listener]
- if not handleId then
- handleId = eventHandleId + 1
- eventHandleId = handleId
-- 如果在塞入的时候, 该 listeners 正在被触发, 则不执行立即塞入的操作, 等下一个触发到来的时候执行
- if listeners.executing then
- listeners.insert[listener] = handleId
- listeners.dirty = true
- else
- listeners[listener] = handleId
- end
- eventHandles[handleId] = { name, listener }
- end
- return handleId
- end
- local function RemoveListener(name, listener, handles)
- local listeners = events[name]
- if listeners then
- if not listener then
- for _, handleId in pairs(listeners) do
- eventHandles[handleId] = nil
- if handles then
- handles[handleId] = nil
- end
- end
-- 标记其已经 destroyed
- listeners.destroyed = true
- events[name] = nil
- else
- local handleId = listeners[listener] or listeners.insert[listener]
- if handleId then
- listeners[listener] = nil
- listeners.insert[listener] = nil
- eventHandles[handleId] = nil
- if handles then
- handles[handleId] = nil
- end
- end
- end
- end
- end
- local function RemoveHandle(handleId)
- local entry = eventHandles[handleId]
- if entry then
- RemoveListener(entry[1], entry[2])
- end
- end
- local EventManager = {}
- function EventManager.Add(name, listener)
- AddListener(name, listener)
- end
- function EventManager.Remove(name, listener)
- RemoveListener(name, listener)
- end
- function EventManager.Clear()
- events = {}
- eventHandles = {}
- end
- function EventManager.Dispatch(name, ...)
- local listeners = events[name]
- if listeners then
- listeners.executing = true
- for listener, _ in pairs(listeners) do
- if type(listener) == "function" then
- listener(name, ...)
- end
-- 可能在执行 listener 的过程中回调执行了 remove, 所以需要检测一次是否退出
- if listeners.destroyed then
- return
- end
- end
-- 触发完后, 再执行缓存的塞入检测
- if listeners.dirty then
- for listener, handleId in pairs(listeners.insert) do
- listeners[listener] = handleId
- listeners.insert[listener] = nil
- end
- listeners.dirty = false
- end
- listeners.executing = false
- end
- end
如果不用 lua, 改用 c# 来实现, 则需要巧妙的运用 c# 中的链表来实现对应的操作, 这儿我也给出一份 c# 链表的相关实现吧:)
- using System;
- using System.Collections.Generic;
- public class EventListener
- {
- public string name;
- public Delegate action;
- }
- public static class EventManger
- {
- private static Dictionary<string, List<EventListener>> listenList = new Dictionary<string, List<EventListener>>();
- private static Dictionary<string, int> listenerStatus = new Dictionary<string, int>();
- private static Dictionary<string, List<EventListener>> addList = new Dictionary<string, List<EventListener>>();
- private static Dictionary<string, List<EventListener>> removeList = new Dictionary<string, List<EventListener>>();
- // 查找监听
- public static EventListener GetListener(string name, Delegate action)
- {
- if (action == null) return null;
- List<EventListener> listEvent = null;
- if (!listenList.TryGetValue(name, out listEvent) || listEvent.Count < 1)
- return null;
- var ls = listEvent.Find(l => l.action.Method == action.Method);
- return ls;
- }
- // 添加事件监听
- public static EventListener AddEventListener(string name, Delegate action)
- {
- if (action == null) return null;
- // 判断是否已经有
- var listener = GetListener(name, action);
- if (listener != null)
- return listener;
- //new 可以用资源池代替
- listener = new EventListener();
- listener.name = name;
- listener.action = action;
- // 第一次创建, 则建立对应的 dic
- if(!listenList.ContainsKey(name))
- {
- listenList.Add(name, new List<EventListener>());
- addList.Add(name, new List<EventListener>());
- removeList.Add(name, new List<EventListener>());
- listenerStatus.Add(name, 0);
- }
- // 处于事件触发, 则滞后处理
- if (listenerStatus[name] > 0)
- addList[name].Add(listener);
- else
- listenList[name].Add(listener);
- return listener;
- }
- // 删除事件监听
- public static void RemoveListener(string name, EventListener listener)
- {
- List<EventListener> deleteList = null;
- if(listenList.TryGetValue(name, out deleteList))
- {
- // 首先删除 addList 中的监听
- addList[name].Remove(listener);
- if(deleteList.Contains(listener))
- {
- // 如果正在触发, 则滞后
- if (listenerStatus[name] > 0)
- removeList[name].Add(listener);
- else
- {
- deleteList.Remove(listener);
- // 归还到资源池去........
- }
- }
- }
- }
- // 事件触发
- public static void DispatchEvent(string name)
- {
- //...............
- List<EventListener> ls = null;
- if(listenList.TryGetValue(name, out ls))
- {
- // 标记正在触发
- listenerStatus[name]++;
- //
- foreach(var listener in ls)
- {
- listener.action.DynamicInvoke(null);
- }
- var count = listenerStatus[name] - 1;
- listenerStatus[name] = count;
- // 此时执行滞后的增删操作
- if(count < 1)
- {
- var list = addList[name];
- ls.AddRange(list);
- list.Clear();
- list = removeList[name];
- foreach (var listener in list)
- ls.Remove(listener);
- list.Clear();
- }
- //.......
- }
- }
- }
用 listenerStatus 来标志是否处于事件触发的状态, 可以较为巧妙的避开同时对链表的操作, 当然实际的应用在还会添加一些限定条件, 判定条件, 避免某些不符合常规的操作带来的风险, 具体需要结合项目来进行相关的实现即可好了, 今天的文章就写到这儿, 后续再继续更新 :D
来源: https://www.cnblogs.com/zblade/p/8458322.html