zoukankan      html  css  js  c++  java
  • 游戏随笔之事件系统的设计

      新的一年,新的开始,祝愿大家新春快乐!转载请标明出处:http://www.cnblogs.com/zblade/

      今天,开始写新的一年的博客了,我的博客更新的比较随意,也比较缓慢,哈哈。前段时间一直在看一些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

  • 相关阅读:
    git 命令参考手册 git中文命令参考手册大全
    php常用命令大全
    freemarker中的list 前端模板
    jquery-懒加载技术(简称lazyload)
    JavaScript跨域总结与解决办法 什么是跨域
    Ajax+Spring MVC实现跨域请求(JSONP)JSONP 跨域
    jQuery- v1.10.2 源码解读
    css技巧
    前端异常捕获与上报
    兼容性/pollyfill/shim/渐进增强/优雅降级
  • 原文地址:https://www.cnblogs.com/zblade/p/8458322.html
Copyright © 2011-2022 走看看