Event Definitions
Event definitions are used to enable the Dispatcher to consume specific events. Defining an event definition can be done via the following macro:
#pragma once
#include <obscura/core/core.hxx>
#include <obscura/event_system/events/event.hxx>
// NOLINTBEGIN
// This section is raising quite some linting errors.
// Unfortunately there is not much choice than to code it this way as far as I know.
/**
* A class that can be used to pass string literals as template parameters.
* @tparam N Number of characters. The template parameter is deduced automatically.
*/
template<size_t N>
struct StringLiteral {
constexpr StringLiteral(const char (&str)[N]) {
std::copy_n(str, N, value);
}
char value[N] {};
};
// NOLINTEND
template<class Listener, class EventType, void (Listener::*CallbackPointer)(const EventType&)>
class CallbackWrapper {
public:
inline static void call(Listener* listener, const EventType& event) {
(listener->*CallbackPointer)(event);
}
};
template<StringLiteral Name, class EventType, template<class Listener> class Callback>
requires std::is_base_of_v<obscura::Event, EventType>
struct EventDefinition {
using Event = EventType;
static constexpr obscura::EventCategory Category = Event::Category;
static constexpr obscura::EventCause Cause = Event::Cause;
template<class Listener>
static constexpr auto getCallback(Listener& listener) -> decltype(auto) {
if constexpr (hasCallback<Listener>) {
return [&listener](const Event& event) {
Callback<Listener>::call(&listener, event);
};
} else {
return []([[maybe_unused]] const Event& event) {
};
}
}
template<class Listener>
static constexpr bool hasCallback =
requires(Listener& listener, const EventType& event) { Callback<Listener>::call(&listener, event); };
inline static constexpr auto getEventCode() -> std::size_t {
return eventCode(Category, Cause);
}
inline static constexpr auto getName() -> std::string_view {
return std::string_view { Name.value };
}
};
template<class T>
concept EventDefinitionConcept = requires(T definition) {
{ EventDefinition { definition } } -> std::same_as<T>;
};
Key Event Definition
The event definition for key events are implemented as follows:
#pragma once
#include <obscura/event_system/event_definitions/event_definition.hxx>
#include <obscura/event_system/events/key_events.hxx>
namespace obscura {
template<class Listener>
using KeyPressedCallback = CallbackWrapper<Listener, KeyPressedEvent, &Listener::onKeyPressed>;
using KeyPressedDefinition = EventDefinition<"KeyPressedDefinition", KeyPressedEvent, KeyPressedCallback>;
template<class Listener>
using KeyReleasedCallback = CallbackWrapper<Listener, KeyReleasedEvent, &Listener::onKeyReleased>;
using KeyReleasedDefinition = EventDefinition<"KeyReleasedDefinition", KeyReleasedEvent, KeyReleasedCallback>;
template<class Listener>
using KeyRepeatedCallback = CallbackWrapper<Listener, KeyRepeatedEvent, &Listener::onKeyRepeated>;
using KeyRepeatedDefinition = EventDefinition<"KeyRepeatedDefinition", KeyRepeatedEvent, KeyRepeatedCallback>;
} // namespace obscura
Example
This example shows how key events can be used. The example implements a listener for key events, registers it with the dispatcher and then sends various events to the dispatcher and dispatches them.
#include <obscura/obscura.hxx>
using namespace obscura;
using namespace std::chrono_literals;
class Listener {
public:
static void onKeyPressed(const KeyPressedEvent& event) {
logInfo("Key {} pressed", event.getKey());
}
static void onKeyReleased(const KeyReleasedEvent& event) {
logInfo("Key {} released", event.getKey());
}
static void onKeyRepeated(const KeyRepeatedEvent& event) {
logInfo("Key {} repeated", event.getKey());
}
static void onWindowClosed(const WindowEvent& /*unused*/) {
Scheduler<Dispatcher>::getInstance().stopMainLoop();
}
// Only required to suppress warnings because otherwise there is no listener that
// processes tick event_system
void onTick(const TickEvent& event) {
}
};
auto main() -> int {
try {
auto keyPressedEvent =
KeyPressedEvent { 0 }; // NOLINT(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
auto keyReleasedEvent =
KeyReleasedEvent { 1 }; // NOLINT(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
auto keyRepeatedEvent =
KeyRepeatedEvent { 42 }; // NOLINT(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
auto listener = Listener {};
auto* scheduler = &Scheduler<Dispatcher>::getInstance();
scheduler->registerListener(listener);
scheduler->scheduleOnce(WindowEvent { EventCause::WindowClosed }, 1s);
scheduler->scheduleOnce(keyPressedEvent);
scheduler->scheduleOnce(keyReleasedEvent);
scheduler->scheduleOnce(keyRepeatedEvent);
scheduler->startMainLoop();
} catch (const std::exception& e) {
logError("Exception caught: {}", e.what());
}
return EXIT_SUCCESS;
}
Mouse Event Definition
Mouse events are emitted whenever a mouse movement or mouse click is being done. The event definition for mouse events are implemented as follows:
#pragma once
#include <obscura/event_system/event_definitions/event_definition.hxx>
#include <obscura/event_system/events/mouse_events.hxx>
namespace obscura {
template<class Listener>
using MouseMovedCallback = CallbackWrapper<Listener, MouseMovedEvent, &Listener::onMouseMoved>;
using MouseMovedDefintion = EventDefinition<"MouseMovedDefintion", MouseMovedEvent, MouseMovedCallback>;
template<class Listener>
using MouseClickedCallback = CallbackWrapper<Listener, MouseClickedEvent, &Listener::onMouseClicked>;
using MouseClickedDefinition = EventDefinition<"MouseClickedDefinition", MouseClickedEvent, MouseClickedCallback>;
} // namespace obscura
Example
This example shows how key events can be used. The example implements a listener for mouse events, registers it with the dispatcher and then sends various events to the dispatcher and dispatches them.
#include <obscura/obscura.hxx>
using namespace obscura;
using namespace std::chrono_literals;
class Listener {
public:
static void onMouseClicked(const MouseClickedEvent& event) {
logInfo("Mouse key {} pressed", event.getKey());
logInfo("Mouse moved to x: {}, y: {}", event.getPosition().x, event.getPosition().y);
}
static void onMouseMoved(const MouseMovedEvent& event) {
logInfo("Mouse moved to x: {}, y: {}", event.getCurrentPosition().x, event.getCurrentPosition().y);
logInfo("Mouse moved from x: {}, y: {}", event.getPreviousPosition().x, event.getPreviousPosition().y);
logInfo("Mouse moved by {} horizontal pixels, vertical {} pixels", event.getOffset().x, event.getOffset().y);
}
static void onWindowClosed(const WindowEvent& /*unused*/) {
Scheduler<Dispatcher>::getInstance().stopMainLoop();
}
// Only required to suppress warnings because otherwise there is no listener that
// processes tick event_system
void onTick(const TickEvent& /*unused*/) {
}
};
auto main() -> int {
try {
auto mouseClickedEvent =
MouseClickedEvent { 2, 3, 42 }; // NOLINT(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
auto mouseMovedEvent =
MouseMovedEvent { 0,
5,
10,
25 }; // NOLINT(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
auto listener = Listener {};
auto* scheduler = &Scheduler<Dispatcher>::getInstance();
scheduler->registerListener(listener);
scheduler->scheduleOnce(WindowEvent { EventCause::WindowClosed }, 1s);
scheduler->scheduleOnce(mouseClickedEvent);
scheduler->scheduleOnce(mouseMovedEvent);
scheduler->startMainLoop();
} catch (const std::exception& e) {
logError("Exception caught: {}", e.what());
}
return EXIT_SUCCESS;
}
Loop Event Definition
Loop events are triggered by the game loop(s). The events that are emitted are ``TickEvent``s that are either emitted for each tick or emitted at the end of the loop. Here is how loop events are implemented
#pragma once
#include <obscura/event_system/event_definitions/event_definition.hxx>
#include <obscura/event_system/events/loop_events.hxx>
namespace obscura {
template<class Listener>
using TickCallback = CallbackWrapper<Listener, TickEvent, &Listener::onTick>;
using TickDefinition = EventDefinition<"TickDefinition", TickEvent, TickCallback>;
template<class Listener>
using LoopEndCallback = CallbackWrapper<Listener, LoopStoppedEvent, &Listener::onLoopStop>;
using LoopEndDefinition = EventDefinition<"LoopEndDefinition", LoopStoppedEvent, LoopEndCallback>;
} // namespace obscura
Example
#include <obscura/obscura.hxx>
using namespace obscura;
using namespace std::chrono_literals;
class Listener {
public:
void onTick(const TickEvent& event) {
logInfo("The Frame took {} microseconds", event.getDurationSinceLastTick().count());
++tickCounter;
logInfo("This is tick #{}", tickCounter);
}
static void onLoopStop(const TickEvent& /*unused*/) {
logInfo("The loop stopped and I can clean stuff up!");
}
static void onWindowClosed(const WindowEvent& /*unused*/) {
Scheduler<Dispatcher>::getInstance().stopMainLoop();
}
private:
std::size_t tickCounter = 0;
};
auto main() -> int {
try {
auto listener = Listener {};
auto* scheduler = &Scheduler<Dispatcher>::getInstance();
scheduler->registerListener(listener);
scheduler->scheduleOnce(WindowEvent { EventCause::WindowClosed }, 1s);
scheduler->startMainLoop();
} catch (const std::exception& e) {
logError("Exception caught: {}", e.what());
}
return EXIT_SUCCESS;
}
Window Event Definition
Window events are emitted whenever there is a change to the window. The events are defined as follows:
#pragma once
#include <obscura/event_system/event_definitions/event_definition.hxx>
#include <obscura/event_system/events/window_events.hxx>
namespace obscura {
template<class Listener>
using WindowClosedCallback = CallbackWrapper<Listener, WindowEvent, &Listener::onWindowClosed>;
using WindowClosedDefintion = EventDefinition<"WindowClosedDefintion", WindowEvent, WindowClosedCallback>;
} // namespace obscura
Example
#include <obscura/obscura.hxx>
using namespace obscura;
using namespace std::chrono_literals;
class Listener {
public:
explicit Listener(Window<HeadlessWindow>& window)
: window(&window) {
}
void onWindowClosed(const WindowEvent& /*unused*/) {
logInfo("Close window!");
window->close();
logInfo("Window was closed!");
Scheduler<Dispatcher>::getInstance().stopMainLoop();
}
// Only required to suppress warnings because otherwise there is no listener that
// processes tick event_system
void onTick(const TickEvent& /*unused*/) {
}
private:
Window<HeadlessWindow>* window;
};
auto main() -> int {
try {
auto& scheduler = Scheduler<Dispatcher>::getInstance();
auto window = Window<HeadlessWindow> { WindowProperties {} };
auto listener = Listener { window };
scheduler.registerListener(listener);
scheduler.scheduleOnce(WindowEvent { EventCause::WindowClosed }, 1s);
scheduler.startMainLoop();
} catch (const std::exception& e) {
logError("Exception caught: {}", e.what());
}
}
Custom Event Definition
For creating a custom event that can be used with Obscura you have to implement a respective event definition and pass the definition to the Dispatcher. The following example shows how to define a custom event definition and how to use it in combination with the dispatcher:
#include <obscura/obscura.hxx>
#include <utility>
class CustomEvent: public obscura::Event {
public:
explicit CustomEvent(std::string message)
: obscura::Event(obscura::EventCategory::None, obscura::EventCause::None),
message(std::move(message)) {
}
[[nodiscard]] auto getMessage() const -> const std::string& {
return message;
}
private:
std::string message;
};
template<class Listener>
using CustomEventCallback = CallbackWrapper<Listener, CustomEvent, &Listener::onCustomEvent>;
using CustomEventDefinition = EventDefinition<"CustomEventDefinition", CustomEvent, CustomEventCallback>;
class CustomListener {
public:
void onCustomEvent(const CustomEvent& event) { // NOLINT(readability-convert-member-functions-to-static)
// The function cannot be static because it needs to be registered as a callback.
obscura::logInfo("{}", event.getMessage());
}
};
auto main() -> int {
try {
auto event = CustomEvent("I traveled through the dispatcher!");
auto listener = CustomListener();
// All event definitions that are used in a project should be passed to the
// ConfigurableDispatcher template. Here, we only need one event definition.
auto dispatcher = obscura::ConfigurableDispatcher<CustomEventDefinition>();
dispatcher.registerListener(listener);
dispatcher.enqueue(event);
obscura::logInfo("Event is in queue but not yet dispatched!");
dispatcher.dispatch();
obscura::logInfo("Event was dispatched!");
} catch (const std::exception& e) {
obscura::logError("Exception caught: {}", e.what());
}
return EXIT_SUCCESS;
}