求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
分享到
基于id的游戏客户端事件分发(消息队列)
 

发布于2013-5-31

 

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

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 
#include 

#ifdef __APPLE__
#include 
using std::tr1::function;
using std::tr1::bind;
using namespace std::tr1::placeholders;

namespace std {
    template struct _Remove_reference
    {
        typedef _Ty _Type;
    };
    
    template inline typename _Remove_reference<_Ty>::_Type&& move(_Ty&& _Arg)
    {
        return ((typename _Remove_reference<_Ty>::_Type&&)_Arg);
    }
};
#else
#include 
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(m_data);  
return std::move(ret); }; #else #define PARAM_IMPL_RET(t, it, value) operator t () { t ret = value; try { ret = boost::any_cast(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 { 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 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 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 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 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.p
arameters[2], 0); break; case 4: itrCall->second(cmdData.dwCommand, cmdData.parameters[0], cmdData.parameters[1], cmdData.p
arameters[2],
cmdData.parameters[3]); break; } m_queue.pop_front(); } } private: struct COMMAND_DATA { uint32 dwCommand; std::vector parameters; }; MyMutex m_mutex; // 递归锁,处理多线程异步消息抛送 std::deque 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 inline void post_command(uint32 dwCommand, Type data) { CommandQueue::getSingleton().post(dwCommand, data); } template 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 inline void send_command(uint32 dwCommand, Type data) { CommandQueue::getSingleton().send(dwCommand, data); } template 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又多。

相关文章

深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
相关文档

重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
相关课程

基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程
 
分享到
 
 


android人机界面指南
Android手机开发(一)
Android手机开发(二)
Android手机开发(三)
Android手机开发(四)
iPhone消息推送机制实现探讨
手机软件测试用例设计实践
手机客户端UI测试分析
手机软件自动化测试研究报告
更多...   


Android高级移动应用程序
Android应用开发
Android系统开发
手机软件测试
嵌入式软件测试
Android软、硬、云整合


领先IT公司 android开发平台最佳实践
北京 Android开发技术进阶
某新能源领域企业 Android开发技术
某航天公司 Android、IOS应用软件开发
阿尔卡特 Linux内核驱动
艾默生 嵌入式软件架构设计
西门子 嵌入式架构设计
更多...