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指針的方式是最靈活和通用的,可以傳遞任意消息,甚至可以實現消息的嵌套,遞歸傳遞消息。在客戶端和服務器都能有一致的應用,這對於后期想共用前后端代碼是很有好處的。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM