C++中的事件分发
本文意在展现一个C++实现的通用事件分发系统,能够灵活的处理各种事件。对于事件处理函数的注册,希望既能注册到普通函数,注册到事件处理类,也能注册到任意类的成员函数。这样在游戏客户端的逻辑处理中,可以非常灵活的处理事件,让普通函数和类的成员函数既能手动调用,又能作为事件响应函数。
游戏是一个交互很强的软件。在客户端中,事件充斥于整个程序的各个角落,玩家操作事件,网络消息事件,音频事件,定时事件等等。这些事件通常参数不一致,分属不同系统,使得消息的处理非常离散,分布于各个UI和系统类中。这使得在设计事件分发系统的时候,需要充分考虑通用性,能够适应各种处理函数需求。服务器在处理网络和数据库消息时,虽然通常是固定的处理格式,但同样需要实现一个统一的事件分发的机制。客户端C#有delegate的存在,因此可以非常方便的将成员函数绑定到delegate进行调用。在unity项目中,利用C#的这一特性,非常容易搭起事件系统框架。但是,C++项目由于常常有成员函数和普通函数并存,因此事件分发时,需要考虑到两种情况。
本文实现的事件分发系统是一个集中注册和分发的系统。消息的注册和分发都由EventManager单例负责,如果需要,可以在EventManager中加入EventQueue实现事件队列,进而缓存消息,异步分发。以下为EventManager类实现。
#ifndef EVENTMANAGER_H
#define EVENTMANAGER_H
enum EventId
{
EVT_TICK = ,
}; struct Event
{
Event(int id) : id_(id)
{}
~Event()
{}
int id_;
//params
}; class EventHandler
{
public:
virtual int HandleEvent(Event *evt)
{
return ;
}
}; class EventManager
{
public:
typedef std::list<EventHandler *> HandlerList;
typedef std::map<int, HandlerList> IdHandlerMap; public:
static EventManager &Instance()
{
static EventManager inst_;
return inst_;
} void AddEventHandler(int id, EventHandler *handler)
{
if (handler == NULL)
return;
auto pair = handler_map_.insert(std::make_pair(id, HandlerList()));
auto it = pair.first;
if (it == handler_map_.end())
return;
it->second.push_back(handler);
} void RemoveEventHandler(int id, EventHandler *handler)
{
if (handler == NULL)
return;
auto it = handler_map_.find(id);
if (it == handler_map_.end())
return;
auto listIt = std::find(it->second.begin(), it->second.end(), handler);
if (listIt != it->second.end())
it->second.erase(listIt);
} void ClearEventHandler(int id)
{
handler_map_.erase(id);
} void DispatchEvent(Event *evt)
{
auto it = handler_map_.find(evt->id_);
if (it == handler_map_.end())
return;
auto &handler_list = it->second;
for (auto listIt = handler_list.begin(); listIt != handler_list.end(); ++listIt)
{
if ((*listIt) == NULL) continue;
(*listIt)->HandleEvent(evt);
}
} private:
EventManager(){}
EventManager(const EventManager&){}
~EventManager(){} IdHandlerMap handler_map_;
}; #endif
其中:
EventId是一个事件类型枚举,以区分不同类型的事件,这里我只定义一个用来举例的时钟TICK事件。
Event定义一个具体事件,它作为事件基类,仅有一个事件类型(id)。实际使用的事件类继承自Event,并添加需要的参数传递给事件处理函数。
EventHandler是事件处理基类,当有事件分发给某个Handler时,它做的就是调用HandleEvent(注意,这里一种Event可以同时注册分发给多个EventHandler依次处理,在客户端中常常需要将消息分发到多个地方处理)。
EventManager是集中的事件管理器,这里作为示例用简单单例实现,可以根据需求扩展。它只有一个成员变量,EventId到它的EventHandler列表的映射。主要功能:注册,解注册Handler和分发事件。
使用时,定义具体的Event类型和处理类,注册事件处理函数,分发事件即可。这里用TickEvent作为例子。
#include "EventManager.h" struct TickEvent : Event
{
TickEvent() : Event(EVT_TICK), time_()
{}
~TickEvent()
{}
time_t time_;
}; class TimeEventHandler : public EventHandler
{
virtual int HandleEvent(Event *evt)
{
TickEvent *tickEvt = (TickEvent*)evt;
printf("time now: %d", (int)tickEvt->time_);
return ;
}
}; TickEvent tickEvt;
TimeEventHandler timeHandler;
EventManager::Instance().AddEventHandler(EVT_TICK, &timeHandler);
EventManager::Instance().DispatchEvent(&tickEvt);
EventManager::Instance().RemoveEventHandler(EVT_TICK, &timeHandler);
TimeEventHandler作为EVT_TICK事件的处理器,在HandleEvent中做相应的处理。
这里是一个专门的事件处理器类,那么普通类的成员函数中如何方便的处理相应的消息呢?如果每个需要处理消息的类都需要继承自TimeEventHandler并且实现一个HandleEvent,那么势必会有局限性,如不能响应多个消息、须多重继承。如果能直接注册成员函数,那真是太好了!
我们来看一个例子,一个UIClass类,OnEvent正是一个处理TICK事件的函数。我们当然可以额外定义一个普通函数作为响应回调,让特定的EventHandler持有对象,并调用该普通函数,在普通函数中再调用对象的成员函数。但是这样做势必使得整个项目的代码很冗余。
template<class Type>
class ClassEventHandler : public EventHandler
{
public:
typedef int (Type::*HandlerFunc)(Event *evt); public:
ClassEventHandler(Type *ptr, HandlerFunc func):
pointer_(ptr),
func_(func)
{
}
virtual int HandleEvent(Event *evt)
{
if (pointer_ && func_)
return (pointer_->*func_)(evt);
return ;
}
private:
Type *pointer_;
HandlerFunc func_;
};
//test eventmanager
class UIClass
{
public:
UIClass()
{
} ~UIClass()
{
} int OnEvent(Event *evt)
{
TickEvent *tickEvt = (TickEvent*)evt;
printf("uiclass event, time now: %d", (int)tickEvt->time_);
return ;
}
}; UIClass ui;
ClassEventHandler<UIClass> evthandler(&ui, &UIClass::OnEvent);
EventManager::Instance().AddEventHandler(EVT_TICK, &evthandler);
EventManager::Instance().DispatchEvent(&tickEvt);
EventManager::Instance().RemoveEventHandler(EVT_TICK, &evthandler);
如上定义一个处理类模板ClassEventHandler,继承自EventHandler,有两个成员变量,持有对象和对象的类成员函数,handler初始化时指定相应的对象和成员函数,HandleEvent时调用该成员函数。如此使用时,除了多一个类模板特例化,几乎不带来任何额外代码成本,不需要对UIClass做任何特殊处理,这正是我们想要看到的~
补上一个实例图,能够更简单的说明一个Event的处理过程:

如上,每个Event有一个特定的EventId,当有Event发送给EventManager处理时,根据EventId索引到相应的EventHandler的列表,借助EventHandler的模板,这些Handler既可以时一般函数,也可以是成员函数。
事件分发系统的实现有多种,也有一些是预先注册好相应的事件内容,分发时直接分发事件id等方式。私以为这种传递Event指针的方式是最灵活和通用的,可以传递任意消息,甚至可以实现消息的嵌套,递归传递消息。在客户端和服务器都能有一致的应用,这对于后期想共用前后端代码是很有好处的。
C++中的事件分发的更多相关文章
- cocos2dx 中触摸事件分发一些解读
触摸事件分发中几个代码解读: 怎么说呢,感觉cocos2dx中的消息分发机制,相对于android中触摸事件分发机制要简单的多.因为android中要做区域判断,过滤器,以及父子组件分发给谁等等的逻辑 ...
- 安卓中的事件分发机制之View控件
前言:Android 中与 Touch 事件相关的方法包括:dispatchTouchEvent(MotionEvent ev).onInterceptTouchEvent(MotionEvent e ...
- 【Unity游戏开发】用C#和Lua实现Unity中的事件分发机制EventDispatcher
一.简介 最近马三换了一家大公司工作,公司制度规范了一些,因此平时的业余时间多了不少.但是人却懒了下来,最近这一个月都没怎么研究新技术,博客写得也是拖拖拉拉,周六周天就躺尸在家看帖子.看小说,要么就是 ...
- Android中的事件分发机制
Android中的事件分发机制 作者:丁明祥 邮箱:2780087178@qq.com 这篇文章这周之内尽量写完 参考资料: Android事件分发机制完全解析,带你从源码的角度彻底理解(上) And ...
- 【转】Android中的事件分发和处理
原文链接:http://www.apkbus.com/home.php?mod=space&uid=705730&do=blog&id=61207 上次跟大家分享了一下自定义V ...
- 第二十五篇:在SOUI中做事件分发处理
不同的SOUI控件可以产生不同的事件.SOUI系统中提供了两种事件处理方式:事件订阅 + 事件处理映射表(参见第八篇:SOUI中控件事件的响应) 事件订阅由于直接将事件及事件处理函数连接,不存在事件分 ...
- Android中的事件分发和处理
上次跟大家分享了一下自定义View的一下要点,这次跟大家聊一下View的事件分发及处理,为什么主题都是View,因为作为一名初级应用层Android工程师,跟我打交道最多的莫过于各种各样的View,只 ...
- Android中的事件分发机制总结
Android 的事件分发机制 一.View的事件分发总结: View的onTouchEvent和OnTouch区别 还是以自定义的TestButton为例. 我们可以通过重写onTouchEven ...
- 浅谈Android中的事件分发机制
View事件分发机制的本质就是就是MotionEvent事件的分发过程,即MotionEvent产生后是怎样在View之间传递及处理的. 首先介绍一下什么是MotionEvent.所谓MotionEv ...
随机推荐
- .NET 串口通信
这段时间做了一个和硬件设备通信的小项目,涉及到扫描头.输送线.称重机.贴标机等硬件.和各设备之间通信使用的是串口或网络(Socket)的方式.扫描头和贴标机使用的网络通信,输送线和称重机使用的是串口通 ...
- Connect() 2016 大会的主题 ---微软大法好
文章首发于微信公众号"dotnet跨平台",欢迎关注,可以扫页面左面的二维码. 今年 Connect 大会的主题是 Big possibilities. Bold technolo ...
- Logstash实践: 分布式系统的日志监控
文/赵杰 2015.11.04 1. 前言 服务端日志你有多重视? 我们没有日志 有日志,但基本不去控制需要输出的内容 经常微调日志,只输出我们想看和有用的 经常监控日志,一方面帮助日志微调,一方面及 ...
- SQLServer文件收缩-图形化+命令
汇总篇:http://www.cnblogs.com/dunitian/p/4822808.html#tsql 收缩前 图形化演示: 不仅仅可以收缩日记文件,数据库文件也是可以收缩的,只不过日记收缩比 ...
- Java 8五大主要功能为开发者提供了哪些便利?
两年前当Java 8发布后,立即受到了业界的欢迎,因为它大大提高了Java的性能.它独特的卖点是,顾及了编程语言的每一个方面,包括JVM(Java虚拟机)和编译器,并且改良了其它帮助系统. Java是 ...
- BPM配置故事之案例6-条件可见与条件必填
小明兴奋的告诉大毛自己独立解决了必填和水印问题,腹黑的大毛决定给小明出一个进阶问题刷一下存在感. 大毛:我再考考你,我把表单改成了这样(下图).怎么做到,预算状态为"预算内"时,不 ...
- 安装devtoolset
在运维的工作内,经常要编译安装各种开源组件,以CentOS 6的用户来说,大部分时候用到gcc的时候都是4.4.7版本的,在绝大多数情况下编译一些东西还是够用的,但还是有个别软件对gcc的版本是有要求 ...
- 【一起学OpenFoam】01 OpenFoam的优势
CFD技术发展到今天,已经超过了大半个世纪了,已经涌现出非常多的CFD软件可供人们使用.通用商业CFD软件譬如Fluent.CFX.Star CCM+等在工业上得到了广泛的应用,另外一些专用的软件(如 ...
- Spring MVC初始化参数绑定
初始化参数绑定与类型转换很类似,初始化绑定时,主要是参数类型 ---单日期 在处理器类中配置绑定方法 使用@InitBinder注解 在这里首先注册一个用户编辑器 参数一为目标类型 proper ...
- 简单的转盘抽奖——CSS动画优化
前言 前两天去一家公司面试,被问到一些小游戏的东西.面试官提到了刷红包还有抽奖这些怎么实现,当时简单说了下思路,回来之后想想还是说的太轻描淡写了,干说不做就是耍流氓,所以就做了一个(Demo & ...