基于id的游戏客户端事件分发(消息队列)

主要目的是完成游戏客户端内的事件处理。这里要明确几件事情:

1、同样一个方案在小项目中可能是漂亮的解决方案,但是在大项目中就是很不灵活的处理方法。游戏客户端无论如何都是小项目,不会像windows一样几千位工程师负责上千个dll,一起开发一个操作系统。所以,对我的需求而言,把模块分的很细,很独立意义不是很大,点到即止。

2、这样一个事件分发机制主要是处理业务逻辑对gui的控制,或者是不同的界面之间的交互。比如服务器下发一个消息给客户端,然后客户端要刷新物品界面;或者是竞技场界面打开后,通知打开玩家列表界面等等。单纯的点击一个按钮,然后响应按钮事件不需要用到这个功能。

3、这个模块就像ios的通知中心一样,可以最大程度的解耦合,与此对应的还有一个方案是基于接口观察者模式事件通知。我个人更倾向于id的方案,如果是接口的话,一方面要维护接口的一致性,另一方面要经常使用到c++最恶心,最容易出问题的多继承(我碰到过好几个非常隐晦的bug就是多继承内存排布引发的)。


基本实现思路是这样的:

1、先实现一个委托机制以方便的实现函数回调,这里我用的是fast_delegate,也可以选择boost::function。

2、写一个CommandQueueMgr来管理所有的id(一个枚举,由开发者进行统一维护,多个开发者可以分配多个id段,也可以写在各自的头文件里面以避免冲突),并提供注册事件,解除注册,响应事件等功能

3、每个需要事件响应的模块(界面)自己在初始化的时候进行注册(如果没有统一的入口,就写个静态变量来调用初始化函数)。这样就有了一个id和函数的对应关系。

4、原本需要调用函数的地方,执行PostMessage函数,发送一个id事件。调用者根本不需要知道被调用者是什么,如果这个id有对应函数就执行,否则就忽略(也可以写上log)。

5、根据实际需要可以写一个异步的PostMessage和一个同步的SendMessage

6、PostMessage可以只支持一个int参数,就像windows一样,这样会更加清晰。也可以支持多个参数,这样就要求开发者在写id的时候把对应参数的注释写好,否则会很混乱

    #pragma once  
      
    #include <deque>  
    #include <boost/any.hpp>  
      
    #ifdef __APPLE__  
    #include <tr1/functional>  
    using std::tr1::function;  
    using std::tr1::bind;  
    using namespace std::tr1::placeholders;  
      
    namespace std {  
        template<class _Ty> struct _Remove_reference  
        {  
            typedef _Ty _Type;  
        };  
          
        template<class _Ty> inline typename _Remove_reference<_Ty>::_Type&& move(_Ty&& _Arg)  
        {  
            return ((typename _Remove_reference<_Ty>::_Type&&)_Arg);  
        }  
    };  
    #else  
    #include <functional>  
    using std::function;  
    using std::bind;  
    using namespace std::placeholders;  
    #endif  
      
    #include "MySingleton.h"  
    #include "MyThread.h"  
      
      
      
    class Parameter  
    {  
    public:  
        #define PARAM_IMPL_INIT(t) Parameter(t para) { m_data = para; }  
      
    #ifdef WIN32  
        // windows下面类型转换失败抛出异常  
        #define PARAM_IMPL_RET(t, it,value) operator t () { t ret = value;  ret = boost::any_cast<it>(m_data);  return std::move(ret); };  
    #else  
        #define PARAM_IMPL_RET(t, it, value) operator t () { t ret = value; try { ret = boost::any_cast<it>(m_data); } catch (...) {} return std::move(ret); };  
    #endif  
          
        PARAM_IMPL_INIT(int);  
        PARAM_IMPL_INIT(long long);  
        PARAM_IMPL_INIT(double);  
        PARAM_IMPL_INIT(const std::string&);  
        PARAM_IMPL_INIT(boost::any);  
      
        PARAM_IMPL_RET(bool, int, false);  
        PARAM_IMPL_RET(char, int, 0);  
        PARAM_IMPL_RET(unsigned char, int, 0);  
        PARAM_IMPL_RET(short, int, 0);  
        PARAM_IMPL_RET(unsigned short, int, 0);  
        PARAM_IMPL_RET(int, int, 0);  
        PARAM_IMPL_RET(unsigned int, int, 0);  
        PARAM_IMPL_RET(long, int, 0);  
        PARAM_IMPL_RET(unsigned long, int, 0);  
        PARAM_IMPL_RET(long long, long long, 0);  
        PARAM_IMPL_RET(unsigned long long, long long, 0);  
        PARAM_IMPL_RET(float, double, 0.0f);  
        PARAM_IMPL_RET(double, double, 0.0);  
        PARAM_IMPL_RET(std::string, std::string, "");  
      
        Parameter(const char* param)  
        {  
            m_data = std::string(param);  
        }  
      
        operator boost::any() {  
            return std::move(m_data);  
        }  
    private:  
    //  boost::variant<std::string, double, float, int> m_data;  
        boost::any m_data;  
    };  
      
    typedef function<void(uint32, Parameter, Parameter, Parameter, Parameter)> FuncCallback;  
    #define LOCK_QUEUE MyLock l(&m_mutex)  
    class CommandQueue : public MySingleton<CommandQueue>  
    {  
    public:  
        CommandQueue() {};  
        ~CommandQueue() {};  
      
        void registerHandler(uint32 command, FuncCallback callback)  
        {  
            m_eventMap[command] = callback;  
        }  
      
        void unRegisterHandler(uint32 command)  
        {  
             auto itr = m_eventMap.find(command);  
             if (itr != m_eventMap.end())  
             {  
                 m_eventMap.erase(itr);  
             }  
        }  
      
        void post(uint32 dwCommand)  
        {  
            COMMAND_DATA cmdData;  
            cmdData.dwCommand = dwCommand;  
      
            LOCK_QUEUE;  
            m_queue.push_back(std::move(cmdData));  
        }  
      
        template<typename Type>  
        void post(uint32 dwCommand, Type data)  
        {  
            COMMAND_DATA cmdData;  
            cmdData.dwCommand = dwCommand;  
            cmdData.parameters.push_back(std::move(data));  
      
            LOCK_QUEUE;  
            m_queue.push_back(std::move(cmdData));  
        };  
      
        template<typename Type1, typename Type2>  
        void post(uint32 dwCommand, Type1 data1, Type2 data2)  
        {  
            COMMAND_DATA cmdData;  
            cmdData.dwCommand = dwCommand;  
            cmdData.parameters.push_back(std::move(data1));  
            cmdData.parameters.push_back(std::move(data2));  
      
            LOCK_QUEUE;  
            m_queue.push_back(std::move(cmdData));  
        };  
      
        template<typename Type1, typename Type2, typename Type3>  
        void post(uint32 dwCommand, Type1 data1, Type2 data2, Type3 data3)  
        {  
            COMMAND_DATA cmdData;  
            cmdData.dwCommand = dwCommand;  
            cmdData.parameters = {data1, data2, data3};  
      
            LOCK_QUEUE;  
            m_queue.push_back(std::move(cmdData));  
        };  
      
        template<typename Type1, typename Type2, typename Type3, typename Type4>  
        void post(uint32 dwCommand, Type1 data1, Type2 data2, Type3 data3, Type4 data4)  
        {  
            COMMAND_DATA cmdData;  
            cmdData.dwCommand = dwCommand;  
            cmdData.parameters = {data1, data2, data3, data4};  
      
            LOCK_QUEUE;  
            m_queue.push_back(cmdData);  
        };  
      
        void send(uint32 dwCommand)  
        {  
            auto itr = m_eventMap.find(dwCommand);  
            if (itr != m_eventMap.end()) {  
                itr->second(dwCommand, 0, 0, 0, 0);  
            }  
        };  
      
        template<typename Type>  
        void send(uint32 dwCommand, Type data)  
        {  
            auto itr = m_eventMap.find(dwCommand);  
            if (itr != m_eventMap.end()) {  
                itr->second(dwCommand, data, 0, 0, 0);  
            }  
        };  
      
        template<typename Type1, typename Type2>  
        void send(uint32 dwCommand, Type1 data1, Type2 data2)  
        {  
            auto itr = m_eventMap.find(dwCommand);  
            if (itr != m_eventMap.end()) {  
                itr->second(dwCommand, data1, data2, 0, 0);  
            }  
        };  
      
        template<typename Type1, typename Type2, typename Type3>  
        void send(uint32 dwCommand, Type1 data1, Type2 data2, Type3 data3)  
        {  
            auto itr = m_eventMap.find(dwCommand);  
            if (itr != m_eventMap.end()) {  
                itr->second(dwCommand, data1, data2, data3, 0);  
            }  
        };  
      
        template<typename Type1, typename Type2, typename Type3, typename Type4>  
        void send(uint32 dwCommand, Type1 data1, Type2 data2, Type3 data3, Type4 data4)  
        {  
            auto itr = m_eventMap.find(dwCommand);  
            if (itr != m_eventMap.end()) {  
                itr->second(dwCommand, data1, data2, data3, data4);  
            }  
        };  
      
        void dispatchAll()  
        {  
            LOCK_QUEUE;  
            while (!m_queue.empty()) {  
                COMMAND_DATA& cmdData = m_queue.front();  
                int size = cmdData.parameters.size();  
                auto itrCall = m_eventMap.find(cmdData.dwCommand);  
                switch (size)  
                {  
                case 0:  
                    itrCall->second(cmdData.dwCommand, 0, 0, 0, 0);  
                    break;  
                case 1:  
                    itrCall->second(cmdData.dwCommand, cmdData.parameters[0], 0, 0, 0);  
                    break;  
                case 2:  
                    itrCall->second(cmdData.dwCommand, cmdData.parameters[0], cmdData.parameters[1], 0, 0);  
                    break;  
                case 3:  
                    itrCall->second(cmdData.dwCommand, cmdData.parameters[0], cmdData.parameters[1], cmdData.parameters[2], 0);  
                    break;  
                case 4:  
                    itrCall->second(cmdData.dwCommand, cmdData.parameters[0], cmdData.parameters[1], cmdData.parameters[2], cmdData.parameters[3]);  
                    break;  
                }  
      
                m_queue.pop_front();  
            }  
        }  
      
    private:  
        struct COMMAND_DATA {  
            uint32 dwCommand;  
            std::vector<Parameter> parameters;  
        };  
      
        MyMutex m_mutex;                            // 递归锁,处理多线程异步消息抛送  
        std::deque<COMMAND_DATA> m_queue;         // post异步command处理队列(都在主线程处理,每帧结束的时候执行)  
        std::map<uint32, FuncCallback> m_eventMap;    // 注册的命令和回调函数映射  
    };  
      
    inline void reg_command(uint32 command, FuncCallback callback)  
    {  
        CommandQueue::getSingleton().registerHandler(command, callback);  
    }  
      
    inline void unreg_command(uint32 command)  
    {  
        CommandQueue::getSingleton().unRegisterHandler(command);  
    }  
      
    inline void post_command(uint32 dwCommand)  
    {  
        CommandQueue::getSingleton().post(dwCommand);  
    }  
      
    template<typename Type>  
    inline void post_command(uint32 dwCommand, Type data)  
    {  
        CommandQueue::getSingleton().post(dwCommand, data);  
    }  
      
    template<typename Type1, typename Type2>  
    inline void post_command(uint32 dwCommand, Type1 data1, Type2 data2)  
    {  
        CommandQueue::getSingleton().post(dwCommand, data1, data2);  
    }  
      
    template<typename Type1, typename Type2, typename Type3>  
    inline void post_command(uint32 dwCommand, Type1 data1, Type2 data2, Type3 data3)  
    {  
        CommandQueue::getSingleton().post(dwCommand, data1, data2, data3);  
    }  
      
    template<typename Type1, typename Type2, typename Type3, typename Type4>  
    inline void post_command(uint32 dwCommand, Type1 data1, Type2 data2, Type3 data3, Type4 data4)  
    {  
        CommandQueue::getSingleton().post(dwCommand, data1, data2, data3, data4);  
    }  
      
      
      
      
    inline void send_command(uint32 dwCommand)  
    {  
        CommandQueue::getSingleton().send(dwCommand);  
    }  
      
    template<typename Type>  
    inline void send_command(uint32 dwCommand, Type data)  
    {  
        CommandQueue::getSingleton().send(dwCommand, data);  
    }  
      
    template<typename Type1, typename Type2>  
    inline void send_command(uint32 dwCommand, Type1 data1, Type2 data2)  
    {  
        CommandQueue::getSingleton().send(dwCommand, data1, data2);  
    }  
      
    template<typename Type1, typename Type2, typename Type3>  
    inline void send_command(uint32 dwCommand, Type1 data1, Type2 data2, Type3 data3)  
    {  
        CommandQueue::getSingleton().send(dwCommand, data1, data2, data3);  
    }  
      
    template<typename Type1, typename Type2, typename Type3, typename Type4>  
    inline void send_command(uint32 dwCommand, Type1 data1, Type2 data2, Type3 data3, Type4 data4)  
    {  
        CommandQueue::getSingleton().send(dwCommand, data1, data2, data3, data4);  
    }  

       这个东西的好处就是完全解耦合,写起来也非常方便,用到什么功能就添一个id就好了。假设服务器下发一个消息通知物品改变了,那么我们需要刷新物品界面。这个时候定义一个id CMD_REFRESH_GOODS  ,然后在界面的初始化代码里面注册这个id (reg_command(CMD_REFRESH_GOODS, std::bind(&MyGoodsDialog::Refresh, this))),Refresh函数可以是任意形式,任意参数。消息处理代码里面只需要调用  send_command(CMD_REFRESH_GOODS),就可以完成界面刷新,无论是界面还是逻辑都不需要包含或继承任何东西,它们只需要知道这个id就可以了。 如果有多个消息会刷新界面那也非常简单,在各自的消息里面调用send_command代码就可以了。

       这里也顺便说一下我对游戏客户端各模块依赖关系的看法,说真心话,游戏客户端真的是小项目。那么作为这个小项目,没有必要过分关注耦合,模块化之类的东西。凡是能让我的代码变得整洁,能让我写代码写的顺手方便的功能就是好功能,即便是约束也乐于遵守。但是如果为了模块化,而让我写代码时写个Impl,再写个provider,那会让人恶心透了,事实证明,有些模块化纯粹是杞人忧天。有些复用性的想法纯粹是自作多情,比如这种--你看我的代码模块化的多好,逻辑模块一行代码不用改就可以用到其他项目。我想说,如果你的游戏赚钱了,我们要再做一个类似的项目,那么我就算把你的ui模块也搬过来又有什么问题。如果我们要做的是一个新的项目,那么我还是要从那一坨代码中找到我想要的可以复用的东西,那还不如让代码变得简单,直接些,我更容易理解。

       作为游戏客户端,有三个主要模块就足够了,逻辑模块、渲染模块、ui模块,所有跟引擎打交道的地方都停留在渲染模块,渲染模块是对引擎的再封装,即便有少量东西扩散到ui模块也应该只停留在ui控件内部。逻辑模块只负责并且负责完全的逻辑,这也是为什么逻辑层不能引入ui元素的原因。

      逻辑层的东西就是一个一个的管理类,负责游戏的各个业务逻辑。  渲染层是对引擎的再封装,比如封装一个人物渲染类,负责渲染人物(逻辑层里还应该有一个人物类来处理业务逻辑,比如姓名、帮派,这个是组合关系)。  ui层就是一个一个的界面。   渲染层封装好后可以几个月不动,逻辑层和ui层一一对应,完成一个业务逻辑就是逻辑层添加一个管理类以及ui层添加几个对话框。他们之间相对独立,有交互就靠上面提到的事件中心来调度。

      这样一个事件中心,看着是非常简单的东西,但是却是整个客户端的基础架构,一个好的架构可以让人更加快速的完成功能(而当架构搭建好后,90%的时间都是在写业务逻辑和界面逻辑),而不好的架构可能让我们写代码又慢,bug又多。

     原文地址:http://blog.youkuaiyun.com/langresser_king/article/details/8580362

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值