Event handling

이벤트를 처리하는 방법에 대하여


프로그램 런타임에 Event가 발생하면 EventManager는 이를 모두 Queue에 저장해 놓았다가 특정 시점에서 모두 Dispath한다.
게임엔진은 사용자가 원하는 Event Type을 정의하고, 특정 Event Type을 처리하는 Callback을 등록하도록 인터페이스를 제공할 수 있다.

Event

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/** Engine API **/
class IEvent
{
public:
virtual const EventTypeID GetEventTypeID() const = 0;
};

template <typename T>
class Event : public IEvent
{
public:
static const EventTypeID EVENT_TYPE_ID;

virtual const EventTypeID GetEventTypeID() const override
{
return EVENT_TYPE_ID;
}
};

/** Client **/

class KeydownEvent : public Event<KeydownEvent>
{
...
};

사용자는 Event<T>를 상속하여 새로운 Event Type을 정의할 수 있다.

Event<T> 클래스는 T에 따라서 유니크한 EventTypeID를 static 멤버로 갖는다. 이는 Event를 특정 Type에 따라서 처리해야하는 경우에 사용하고, 주로 EventManager에서 접근하므로 해당 클래스에서 접근할 수 있는 방법을 마련해 놓는다.
여기서는 EVENT_TYPE_ID를 public 으로 공개하여 인스턴스가 없어도 T::EVENT_TYPE_ID로 직접 접근할 수 있고, GetEventTypeID()를 override 하여 인스턴스를 통해서도 접근할 수 있도록 하였다.



Delegate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class IDelegate
{
public:
virtual const EventTypeID GetEventTypeID() const = 0;
virtual void Invoke(const IEvent* const e) = 0;
};

template <typename E, typename C>
class Delegate : public IDelegate
{
typedef (C::* Callback)(const E* const);

public:
Delegate(C* _class, Callback& _callback) :
_class{_class}, _callback{_callback} {}

virtual const EventTypeID GetEventTypeID() const override
{
return E::EVENT_TYPE_ID;
}

virtual void Invoke(const IEvent* const e) override
{
(_class->*_callback)(reinterpret_cast<const E* const>(e));
}

private:
C* _class;
Callback _callback;
};

Delegate<E, C>는 특정 Event Type(E)과 그걸 처리하는 Callback(C의 멤버함수)의 래퍼클래스이다. 만약 멤버함수가 아닌 Callback을 사용하고 싶다면, Delegate<E>와 같이 특수화를 해주면 될 것이다.
Invoke(const IEvent* const e)함수에서는 인자로 받은 IEventE type으로 다운캐스팅 하여 콜백함수를 호출한다.
당연한 이야기지만 콜백함수의 형태는 InputSystem::ProcessKeyDownEvent(const KeydownEvent* const e)와 같이 사용자 정의된 Event Type을 인자로하는 함수이다.



Dispatcher

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class IDispatcher
{
public:
virtual void Dispatch(const IEvent* const) = 0;
virtual void AddEventCallback(IDelegate* const) = 0;
virtual void RemoveEventCallback(IDelegate* const) = 0;
};

template <typename T>
class Dispatcher : public IDispatcher
{
public:
virtual void Dispatch(const IEvent* const e) override
{
for(auto& d : _delegates)
d->Invoke(e);
}

virtual void AddEventCallback(IDelegate* const d) override
{
... // Check same delegate already exists...
_delegates.push_back(d);
}

virtual void RemoveEventCallback(IDelegate* const d) override
{
... // Find d from Delegate list
_delegates.erase(iterator of d);
}

private:
std::vector<IDelegate*> _delegates;
};

Dispatcher는 특정 Event type를 처리하기 위한 Callback 목록을 모아놓은 컨테이너다. 특정 시기(주로 initalize 단계)에 사용할 콜백들을 모두 등록하고 사용한다.

Manager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class EventManager
{
public:
template <typename T>
void AddEventDelegate(IDelegate* const d)
{
const EventTypeID tid{T::EVENT_TYPE_ID};
if _eventDispatcherMap has key tid :
_eventDispatcherMap[tid]->AddEventCallback(d);
else
{
IDispatcher* dispatcher{ new Dispatcher<T> };

_eventDispatcherMap.insert({tid, dispatcher});
dispatcher->AddEventCallback(d);
}
}

void RemoveEventDelegate(IDelegate* const d)
{
const EventTypeID tid{d->GetEventTypeID()};

if _eventDispatcherMap has key tid:
_eventDispatcherMap[tid]->RemoveEventCallback(d);
}

template <typename T, typename... ARGS>
void SendEvent(ARGS&& ...args)
{
const EventTypeID tid{T::EVENT_TYPE_ID};

IEvent* e{new Event<T>(std::forward<ARGS>(args)...)};
_eventQueue.push(e);
}

void DispatchAllEvents()
{
while(_eventQueue.empty())
{
IEvent* e{_eventQueue.front()};
EventTypeID tid{e->GetEventTypeID()};
_eventQueue.pop_front();

if _eventDispatcherMap has key tid:
_eventDispatcherMap[tid]->Dispatch(e);
}
}

private:
std::unordered_map<EventTypeID, IDispatcher*> _eventDispatcherMap;
std::queue<IEvent*> _eventQueue;
};

EventManager에서 모든 이벤트 관련 처리를 담당한다.
Add/RemoveEventDelegate함수를 통해서 특정 Event type에 대한 Dispather를 구성하고, Delegate를 추가한다.
SendEvent함수를 통해서 어디서든 Event를 발생시킬 수 있으며, 해당 이벤트는 _eventQueue에 저장되어 DispathAllEvent가 호출될때 한번에 처리된다.



EventListener

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class IEventListener
{
protected:
template <typename C, typename E>
void RegisterEventCallback(void(C::* Callback)(const E* const))
{
IDelegate* delegate{new Delegate<E, C>((C*)this, Callback)};
EventManager_inst->AddEventDelegate<E>(delegate);
}

template <typename C, typename E>
void UnregisterEventCallback(void(C::* Callback)(const E* const))
{
Delegate<E, C> tempDelegate((C*)this, Callback);
EventManager_inst->RemoveEventDelegate(&tempDelegate);
}
};

// Client
class InputSystem : private IEventListener
{
void Initialize()
{
IEventListener::RegisterEventCallback(&InputSystem::ProcessKeydownEvent);
...
}

...

void ProcessKeydownEvent(const KeydownEvent* const e)
{
...
}
};

IEventListener는 Event 처리 콜백을 등록/해제 하기위한 인터페이스이다. 특정 Event 를 처리하고자 하는 클래스는 IEventListener를 상속하여 Register/UnRegisterEventCallback함수를 통하여 Event 콜백을 등록/해제한다. 해당 함수가 호출되면 콜백에서 처리하고자하는 특정 Event type과 콜백 함수를 랩핑한 Delegate를 생성하고 EventManager에 등록/해제 한다.


Author

Joyus.Gim

Posted on

2022-04-17

Updated on

2022-07-19

Licensed under

Comments