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