I am excited to share that I have developed an event system in C++. I have always been passionate about programming and have long aspired to create a low-level game engine solely using C++, OpenGL, and GLFW. This project has been a personal challenge and really fun too.
To begin with, I created my own Event System. The functioning of this system involves pushing events to the event queue. The event manager then publishes the events from the queue to the listeners. Each listener checks if the event is of the type it is interested in, and if so, it handles the event. Once an event is handled, it is removed from the queue.
I am open to any feedback or suggestions regarding the design or any other aspect of the system. Your input would be greatly appreciated.
In addition to any general feedback, I have a few specific questions I'd like to address:
1. In the Event.h file, can you explain the justification for using macros?
2. Although I am aware that .cpp files should be used, there are several objects in this codebase that either are or contain templated methods. Can you elaborate on how this should be handled?
3. Considering the principles of Object-Oriented programming, could you evaluate whether this system is well-written?
For those who are curious, I have also included the source code below:
Event.h
#include <string>
#define EVENT_TYPE_FUNCTION(type) \
static const EventType getStaticType() { return EventType::##type; } \
virtual const EventType getType() const override { return EventType::##type; } \
virtual const std::string getTypeName() const override { return #type; }
#define EVENT_CATEGORY_FUNCTION(category) \
virtual ~category##Event() override = default; \
virtual const EventCategory getCategory() const override { return EventCategory::##category; } \
virtual const EventType getType() const override{ return EventType::None; }
enum class EventCategory
{
None,
Input,
Window,
Mouse,
Keyboard,
Application,
};
enum class EventType
{
None,
WindowResize, WindowLostFocus, WindowGainedFocus,
WindowClose, WindowCursorEnter, WindowCursorLeave,
MouseMove, MousePress, MouseRelease, MouseScroll,
KeyboardPress, KeyboardRepeat, KeyboardRelease, CharPress,
};
class Event
{
public:
virtual ~Event() = default;
bool isHandeled() const { return handeled; }
bool handle() { handeled = true; }
virtual const EventCategory getCategory() const = 0;
virtual const EventType getType() const = 0;
virtual const std::string getTypeName() const = 0;
private:
bool handeled = false;
};
class NoneEvent : public Event
{
const EventCategory getCategory() const override { return EventCategory::None; }
EVENT_TYPE_FUNCTION(None)
};
Keyboardevent.h
#include "Event.h"
#include <Stdint.h>
class KeyboardEvent : public Event
{
protected:
KeyboardEvent(int keyCode, int scancode, int mods)
: keyCode(keyCode), scancode(scancode), mods(mods) { }
public:
int getKeyCode() const { return keyCode; }
int getScanCode() const { return scancode; }
int getMods() const { return mods; }
EVENT_CATEGORY_FUNCTION(Keyboard)
private:
int keyCode;
int scancode;
int mods;
};
class KeyboardPressEvent : public KeyboardEvent
{
public:
KeyboardPressEvent(const int keyCode, const int scancode, const int mods)
: KeyboardEvent(keyCode, scancode, mods) { }
EVENT_TYPE_FUNCTION(KeyboardPress)
};
class KeyboardRepeatEvent : public KeyboardEvent
{
KeyboardRepeatEvent(const int keyCode, const int scancode, const int mods)
: KeyboardEvent(keyCode, scancode, mods) { }
EVENT_TYPE_FUNCTION(KeyboardRepeat)
};
class KeyboardReleaseEvent : public KeyboardEvent
{
public:
KeyboardReleaseEvent(const int keyCode, const int scancode, const int mods)
: KeyboardEvent(keyCode, scancode, mods) { }
EVENT_TYPE_FUNCTION(KeyboardRelease)
};
class CharPressEvent : public Event
{
public:
CharPressEvent(const int codePoint)
: codePoint(codePoint) { }
const int getCodePoint() const { return codePoint; }
const EventCategory getCategory() const override { return EventCategory::Keyboard; }
EVENT_TYPE_FUNCTION(CharPress)
private:
const int codePoint;
};
MouseEvent.h
#pragma once
#include "Event.h"
class MouseEvent : public Event
{
public:
EVENT_CATEGORY_FUNCTION(Mouse)
};
// ===================== MOTION UPCOMING ==============================
class MouseMotionEvent : public MouseEvent
{
protected:
MouseMotionEvent(const double mouseX, const double mouseY)
: mouseX(mouseX), mouseY(mouseY) { }
public:
const double getMouseX() const { return mouseX; }
const double getMouseY() const { return mouseY; }
private:
const double mouseX, mouseY;
};
class MouseMoveEvent : public MouseMotionEvent
{
public:
MouseMoveEvent(const double mouseX, const double mouseY)
: MouseMotionEvent(mouseX, mouseY) { }
EVENT_TYPE_FUNCTION(MouseMove)
};
class MouseScrollEvent : public MouseMotionEvent
{
public:
MouseScrollEvent(const double mouseX, const double mouseY)
: MouseMotionEvent(mouseX, mouseY) { }
EVENT_TYPE_FUNCTION(MouseScroll)
};
// ===================== BUTTON UPCOMING ==============================
class MouseButtonEvent : public MouseEvent
{
protected:
MouseButtonEvent(const int button, const int mods)
: button(button), mods(mods) { }
public:
const int getButton() const { return button; }
const int getMods() const { return mods; }
private:
const int button, mods;
};
class MousePressEvent : public MouseButtonEvent
{
public:
MousePressEvent(const int button, const int mods)
: MouseButtonEvent(button, mods) { }
EVENT_TYPE_FUNCTION(MousePress)
};
class MouseReleaseEvent : public MouseButtonEvent
{
public:
MouseReleaseEvent(const int button, const int mods)
: MouseButtonEvent(button, mods) { }
EVENT_TYPE_FUNCTION(MouseRelease)
};
WindowEvent.h
#pragma once
#include "Event.h"
class WindowEvent : public Event
{
public:
EVENT_CATEGORY_FUNCTION(Window)
};
class WindowResizeEvent : public WindowEvent
{
public:
WindowResizeEvent(const int width, const int height)
: width(width), height(height) { }
const int getWidth() const { return width; }
const int getHeight() const { return height; }
EVENT_TYPE_FUNCTION(WindowResize)
private:
const int width, height;
};
class WindowCloseEvent : public WindowEvent
{
public:
EVENT_TYPE_FUNCTION(WindowClose)
};
class WindowLostFocusEvent : public WindowEvent
{
public:
EVENT_TYPE_FUNCTION(WindowLostFocus)
};
class WindowGainedFocusEvent : public WindowEvent
{
public:
EVENT_TYPE_FUNCTION(WindowGainedFocus)
};
class WindowCursorEnterEvent : public WindowEvent
{
public:
EVENT_TYPE_FUNCTION(WindowCursorEnter)
};
class WindowCursorLeaveEvent : public WindowEvent
{
public:
EVENT_TYPE_FUNCTION(WindowCursorLeave)
};
EventListener.h
#pragma once
#include <functional>
#include "MouseEvent.h"
#include "KeyboardEvent.h"
#include "WindowEvent.h"
class BaseEventListener
{
public:
virtual ~BaseEventListener() = default;
virtual const void dispatchEvent(const std::shared_ptr<Event>&) const = 0;
virtual bool isEventType(const std::shared_ptr<Event>& event) const = 0;
virtual const int getID() const = 0;
};
template<typename EventType>
class EventListener : public BaseEventListener
{
public:
using EventCallBackFn = std::function<void(const std::shared_ptr<EventType>&)>;
explicit EventListener(const EventCallBackFn& callBack, const int id) : callBack(callBack), id(id) { }
const void dispatchEvent(const std::shared_ptr<Event>& event) const override { callBack(std::static_pointer_cast<EventType>(event)); }
bool isEventType(const std::shared_ptr<Event>& event) const override { return std::dynamic_pointer_cast<EventType>(event) != nullptr; }
const int getID() const override { return id; }
private:
const EventCallBackFn callBack;
const int id;
};
EventListenerRegister.h
#pragma once
#include <vector>
#include <memory>
#include <algorithm>
#include "EventListener.h"
class EventListenerRegister
{
public:
using ListenerList = std::vector<std::unique_ptr<BaseEventListener>>;
template<typename EventType>
int registerListenerFor(const typename EventListener<EventType>::EventCallBackFn& callBack)
{
static int listenerID = 0;
std::unique_ptr<BaseEventListener> listener = std::make_unique<EventListener<EventType>>(callBack, listenerID);
listeners.push_back(std::move(listener));
return listenerID++;
}
void unregisterListener(const int listenerID)
{
listeners.erase(
std::remove_if(listeners.begin(), listeners.end(), [listenerID](const auto& listener) {
return listenerID == listener->getID();
}),
listeners.end()
);
}
ListenerList::iterator begin() { return listeners.begin(); }
ListenerList::const_iterator begin() const { return listeners.begin(); }
ListenerList::iterator end() { return listeners.end(); }
ListenerList::const_iterator end() const { return listeners.end(); }
private:
ListenerList listeners;
};
EventQueue.h
#pragma once
#include <queue>
#include <memory>
#include "MouseEvent.h"
#include "KeyboardEvent.h"
#include "WindowEvent.h"
class EventQueue
{
public:
void push(const std::shared_ptr<Event>& event) { eventQueue.push(event); }
std::shared_ptr<Event> pop()
{
if (eventQueue.empty())
return nullptr;
std::shared_ptr<Event> event = std::move(eventQueue.front());
eventQueue.pop();
return event;
}
bool isEmpty() const { return eventQueue.empty(); }
private:
std::queue<std::shared_ptr<Event>> eventQueue;
};
EventBus.h
#pragma once
#include <memory>
#include "EventListenerRegister.h"
#include "EventQueue.h"
class EventBus
{
public:
template<typename EventType>
int registerListenerFor(const typename EventListener<EventType>::EventCallBackFn& callBack)
{
return listeners.registerListenerFor<EventType>(callBack);
}
void unregisterListener(const int listenerID)
{
listeners.unregisterListener(listenerID);
}
void publishEvent(EventQueue& eventQueue) const
{
while(const auto& event = eventQueue.pop()) {
for (const auto& listener : listeners) {
if (listener->isEventType(event))
listener->dispatchEvent(event);
if (event->isHandeled())
break;
}
}
}
private:
EventListenerRegister listeners;
};
EventManager.h
#pragma once
#include "EventBus.h"
#include "EventQueue.h"
class EventManager
{
public:
template<typename EventType>
int registerListenerFor(const typename EventListener<EventType>::EventCallBackFn& callBack)
{
return eventBus.registerListenerFor<EventType>(callBack);
}
void unregisterListener(const int listenerID)
{
eventBus.unregisterListener(listenerID);
}
void pushEvent(const std::shared_ptr<Event>& event)
{
eventQueue.push(event);
}
void publishEvents()
{
eventBus.publishEvent(eventQueue);
}
private:
EventBus eventBus;
EventQueue eventQueue;
};