zoukankan      html  css  js  c++  java
  • C++中的事件分发

      本文意在展现一个C++实现的通用事件分发系统,能够灵活的处理各种事件。对于事件处理函数的注册,希望既能注册到普通函数,注册到事件处理类,也能注册到任意类的成员函数。这样在游戏客户端的逻辑处理中,可以非常灵活的处理事件,让普通函数和类的成员函数既能手动调用,又能作为事件响应函数。

      游戏是一个交互很强的软件。在客户端中,事件充斥于整个程序的各个角落,玩家操作事件,网络消息事件,音频事件,定时事件等等。这些事件通常参数不一致,分属不同系统,使得消息的处理非常离散,分布于各个UI和系统类中。这使得在设计事件分发系统的时候,需要充分考虑通用性,能够适应各种处理函数需求。服务器在处理网络和数据库消息时,虽然通常是固定的处理格式,但同样需要实现一个统一的事件分发的机制。客户端C#有delegate的存在,因此可以非常方便的将成员函数绑定到delegate进行调用。在unity项目中,利用C#的这一特性,非常容易搭起事件系统框架。但是,C++项目由于常常有成员函数和普通函数并存,因此事件分发时,需要考虑到两种情况。

      本文实现的事件分发系统是一个集中注册和分发的系统。消息的注册和分发都由EventManager单例负责,如果需要,可以在EventManager中加入EventQueue实现事件队列,进而缓存消息,异步分发。以下为EventManager类实现。

     1 #ifndef EVENTMANAGER_H
     2 #define EVENTMANAGER_H
     3 enum EventId
     4 {
     5     EVT_TICK = 0,
     6 };
     7 
     8 struct Event
     9 {
    10     Event(int id) : id_(id)
    11     {}
    12     ~Event()
    13     {}
    14     int id_;
    15     //params
    16 };
    17 
    18 class EventHandler
    19 {
    20 public:
    21     virtual int HandleEvent(Event *evt)
    22     {
    23         return 0;
    24     }
    25 };
    26 
    27 class EventManager
    28 {
    29 public:
    30     typedef std::list<EventHandler *> HandlerList;
    31     typedef std::map<int, HandlerList> IdHandlerMap;
    32     
    33 public:
    34     static EventManager &Instance()
    35     {
    36         static EventManager inst_;
    37         return inst_;
    38     }
    39     
    40     void AddEventHandler(int id, EventHandler *handler)
    41     {
    42         if (handler == NULL)
    43             return;
    44         auto pair = handler_map_.insert(std::make_pair(id, HandlerList()));
    45         auto it = pair.first;
    46         if (it == handler_map_.end())
    47             return;
    48         it->second.push_back(handler);
    49     }
    50     
    51     void RemoveEventHandler(int id, EventHandler *handler)
    52     {
    53         if (handler == NULL)
    54             return;
    55         auto it = handler_map_.find(id);
    56         if (it == handler_map_.end())
    57             return;
    58         auto listIt = std::find(it->second.begin(), it->second.end(), handler);
    59         if (listIt != it->second.end())
    60             it->second.erase(listIt);
    61      }
    62     
    63     void ClearEventHandler(int id)
    64     {
    65         handler_map_.erase(id);
    66     }
    67     
    68     void DispatchEvent(Event *evt)
    69     {
    70         auto it = handler_map_.find(evt->id_);
    71         if (it == handler_map_.end())
    72             return;
    73         auto &handler_list = it->second;
    74         for (auto listIt = handler_list.begin(); listIt != handler_list.end(); ++listIt)
    75         {
    76             if ((*listIt) == NULL) continue;
    77             (*listIt)->HandleEvent(evt);
    78         }
    79     }
    80     
    81 private:
    82     EventManager(){}
    83     EventManager(const EventManager&){}
    84     ~EventManager(){}
    85 
    86     IdHandlerMap handler_map_;
    87 };
    88 
    89 #endif

      其中:

      EventId是一个事件类型枚举,以区分不同类型的事件,这里我只定义一个用来举例的时钟TICK事件。

      Event定义一个具体事件,它作为事件基类,仅有一个事件类型(id)。实际使用的事件类继承自Event,并添加需要的参数传递给事件处理函数。

      EventHandler是事件处理基类,当有事件分发给某个Handler时,它做的就是调用HandleEvent(注意,这里一种Event可以同时注册分发给多个EventHandler依次处理,在客户端中常常需要将消息分发到多个地方处理)。

      EventManager是集中的事件管理器,这里作为示例用简单单例实现,可以根据需求扩展。它只有一个成员变量,EventId到它的EventHandler列表的映射。主要功能:注册,解注册Handler和分发事件。

      使用时,定义具体的Event类型和处理类,注册事件处理函数,分发事件即可。这里用TickEvent作为例子。

     1 #include "EventManager.h"
     2 
     3 struct TickEvent : Event
     4 {
     5     TickEvent() : Event(EVT_TICK), time_(0)
     6     {}
     7     ~TickEvent()
     8     {}
     9     time_t time_;
    10 };
    11 
    12 class TimeEventHandler : public EventHandler
    13 {
    14     virtual int HandleEvent(Event *evt)
    15     {
    16         TickEvent *tickEvt = (TickEvent*)evt;
    17         printf("time now: %d", (int)tickEvt->time_);
    18         return 0;
    19     }
    20 };
    21 
    22 TickEvent tickEvt;
    23 TimeEventHandler timeHandler;
    24 EventManager::Instance().AddEventHandler(EVT_TICK, &timeHandler);
    25 EventManager::Instance().DispatchEvent(&tickEvt);
    26 EventManager::Instance().RemoveEventHandler(EVT_TICK, &timeHandler);

      TimeEventHandler作为EVT_TICK事件的处理器,在HandleEvent中做相应的处理。

      这里是一个专门的事件处理器类,那么普通类的成员函数中如何方便的处理相应的消息呢?如果每个需要处理消息的类都需要继承自TimeEventHandler并且实现一个HandleEvent,那么势必会有局限性,如不能响应多个消息、须多重继承。如果能直接注册成员函数,那真是太好了!

      我们来看一个例子,一个UIClass类,OnEvent正是一个处理TICK事件的函数。我们当然可以额外定义一个普通函数作为响应回调,让特定的EventHandler持有对象,并调用该普通函数,在普通函数中再调用对象的成员函数。但是这样做势必使得整个项目的代码很冗余。

     1 template<class Type>
     2 class ClassEventHandler : public EventHandler
     3 {
     4 public:
     5     typedef int (Type::*HandlerFunc)(Event *evt);
     6     
     7 public:
     8     ClassEventHandler(Type *ptr, HandlerFunc func):
     9     pointer_(ptr),
    10     func_(func)
    11     {
    12     }
    13     virtual int HandleEvent(Event *evt)
    14     {
    15         if (pointer_ && func_)
    16             return (pointer_->*func_)(evt);
    17         return 0;
    18     }
    19 private:
    20     Type *pointer_;
    21     HandlerFunc func_;
    22 };
    23 //test eventmanager
    24 class UIClass
    25 {
    26 public:
    27     UIClass()
    28     {
    29     }
    30     
    31     ~UIClass()
    32     {
    33     }
    34     
    35     int OnEvent(Event *evt)
    36     {
    37         TickEvent *tickEvt = (TickEvent*)evt;
    38         printf("uiclass event, time now: %d", (int)tickEvt->time_);
    39         return 0;
    40     }
    41 };
    42 
    43 UIClass ui;
    44 ClassEventHandler<UIClass> evthandler(&ui, &UIClass::OnEvent);
    45 EventManager::Instance().AddEventHandler(EVT_TICK, &evthandler);
    46 EventManager::Instance().DispatchEvent(&tickEvt);
    47 EventManager::Instance().RemoveEventHandler(EVT_TICK, &evthandler);

      如上定义一个处理类模板ClassEventHandler,继承自EventHandler,有两个成员变量,持有对象和对象的类成员函数,handler初始化时指定相应的对象和成员函数,HandleEvent时调用该成员函数。如此使用时,除了多一个类模板特例化,几乎不带来任何额外代码成本,不需要对UIClass做任何特殊处理,这正是我们想要看到的~

      补上一个实例图,能够更简单的说明一个Event的处理过程:

      如上,每个Event有一个特定的EventId,当有Event发送给EventManager处理时,根据EventId索引到相应的EventHandler的列表,借助EventHandler的模板,这些Handler既可以时一般函数,也可以是成员函数。

      事件分发系统的实现有多种,也有一些是预先注册好相应的事件内容,分发时直接分发事件id等方式。私以为这种传递Event指针的方式是最灵活和通用的,可以传递任意消息,甚至可以实现消息的嵌套,递归传递消息。在客户端和服务器都能有一致的应用,这对于后期想共用前后端代码是很有好处的。

  • 相关阅读:
    201571030330&201571030307《小学四则运算练习软件软件需求说明》结对项目报告
    201571030330 & 201571030307《小学四则运算练习软件》结对项目报告
    201571030307 四则远算
    个人学习总结
    201571030301 /201571030302《小学四则运算练习软件软件需求说明》结对项目报告
    201571030301/201571030302《小学生四则运算练习软件》结对项目报告
    201571030301 四则运算
    初读思考《构建之法-现代软件工程》
    个人学期总结
    201571030313/201571030312《小学四则运算练习软件软件需求说明》结对项目报告
  • 原文地址:https://www.cnblogs.com/kevonyang/p/5965825.html
Copyright © 2011-2022 走看看