I am trying to create an event system that would be quite easy to use. The idea is that a user would only need to create a custom function and tell which event should fire that function. It works as is, but I was wondering if there is a more efficient way to accomplish this of which I'm not aware.
The main classes of the system
Event
EventListener
EventManager
EventType (size_t enum class)
Event
class Event
{
friend class EventListener;
public:
virtual EventType GetEventType() const = 0;
virtual ~Event() { }
inline bool IsHandled() const
{
return m_Handled;
}
protected:
bool m_Handled = false;
};
EventListener
using EventBehavior = std::function<bool(const Event& e)>;
class EventListener
{
public:
EventListener()
{
EventManager::AddListener(this);
}
template<class T>
void Listen(EventBehavior& behavior)
{
ASSERT(std::is_base_of<Event, T>, "Can't listen to non Event!");
m_ListeningTo[(size_t)T::GetStaticType()] = true;
m_RegisteredEvents[(size_t)T::GetStaticType()] = behavior;
}
template<class T>
void StopListening()
{
ASSERT(std::is_base_of<Event, T>, "Can't stop listening to non Event!");
m_ListeningTo[(size_t)T::GetStaticType()] = false;
}
void React(Event& event)
{
if (m_ListeningTo[(size_t)event.GetEventType()])
{
event.m_Handled = m_RegisteredEvents[(size_t)event.GetEventType()](event);
}
}
private:
std::bitset<MaxEvents> m_ListeningTo;
std::array<EventBehavior, MaxEvents> m_RegisteredEvents;
};
EventManager
class EventManager
{
public:
static void Post(Event* event)
{
m_EventBuffer.push_back(event);
}
static void Dispatch()
{
for (unsigned int i = 0; i < m_EventBuffer.size(); i++)
{
for (EventListener* listener : m_Listeners)
{
if (!m_EventBuffer[i]->IsHandled())
{
listener->React(*m_EventBuffer[i]);
}
else if(m_EventBuffer[i]->IsHandled())
{
delete m_EventBuffer[i];
m_EventBuffer.erase(m_EventBuffer.begin() + i);
break;
}
}
}
}
static void AddListener(EventListener* listener)
{
m_Listeners.push_back(listener);
}
static void ClearBuffer()
{
for (unsigned int i = 0; i < m_EventBuffer.size(); i++)
{
delete m_EventBuffer[i];
}
m_EventBuffer.clear();
}
private:
static std::vector<Event*> m_EventBuffer;
static std::vector<EventListener*> m_Listeners;
};
Example event
//Base class for all key events
class KeyEvent : public Event
{
public:
inline int GetKeyCode() const { return m_KeyCode; }
protected:
KeyEvent(int keycode)
: m_KeyCode(keycode), m_Mods(0) {}
KeyEvent(int keycode, int mods)
: m_KeyCode(keycode), m_Mods(mods) {}
int m_KeyCode;
int m_Mods;
};
class KeyPressedEvent : public KeyEvent
{
public:
KeyPressedEvent(int keycode, int repeatCount)
: KeyEvent(keycode, 0), m_RepeatCount(repeatCount) {}
KeyPressedEvent(int keycode, int repeatCount, int scancode, int mods)
: KeyEvent(keycode, mods), m_RepeatCount(repeatCount) {}
inline int GetRepeatCount() const { return m_RepeatCount; }
//EventType is a size_t enum class
static EventType GetStaticType() { return EventType::KeyPressed; }
virtual EventType GetEventType() const override { return GetStaticType(); }
private:
int m_RepeatCount;
};
The way the user uses the system
bool keyPressed(const Event& event)
{
const KeyPressedEvent& kpe = static_cast<const KeyPressedEvent&>(event);
//Do something
return true;
}
class Sandbox : private EventListener
{
public:
Sandbox()
{
this->Listen<KeyPressedEvent>(EventBehavior(keyPressed));
}
~Sandbox()
{
}
};
My main questions
- Is there a way to pass the
Listenmethod a function that would acceptKeyPressedEventand thus removing the need fordynamic_cast(I realize this will require a code change and I am glad to do that as long as the way the user would use the system would remain the same) - Is the current system efficient as it is or is it a complete mess?
- What would be some similar alternatives (I tried making the
EventBehaviora template, but then ran into problems trying to store it inside the array)?