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:

};

#define DEFINE_EVENT(ClassName, EventType, EventCategory, EventCause, callback)                   \
    struct ClassName: EventDefinitionBase<EventType> {                                            \
        inline static constexpr auto getEventCode() {                                             \
            return eventCode(EventCategory, EventCause);                                          \
        };                                                                                        \
                                                                                                  \
        template<class Listener>                                                                  \
        static constexpr bool hasCallback = requires(Listener listener, const EventType& event) { \
            listener.callback(event);                                                             \
        };                                                                                        \
                                                                                                  \
        template<class Listener>                                                                  \
        static constexpr auto getCallback(Listener* listener) -> decltype(auto) {                 \
            if constexpr (hasCallback<Listener>) {                                                \
                return [listener](const Event& event) {                                           \
                    listener->callback(event);                                                    \
                };                                                                                \
            } else {                                                                              \
                return []([[maybe_unused]] const Event& event) {                                  \
                };                                                                                \
            }                                                                                     \
        }                                                                                         \
                                                                                                  \
        inline static auto getName() -> std::string {                                             \
            return #ClassName;                                                                    \

Key Event Definition

The event definition for key events are implemented as follows:

#pragma once

#include <obscura/events/event_definitions/event_definition.hxx>
#include <obscura/events/key_events.hxx>

namespace obscura {
DEFINE_EVENT(KeyPressedDefinition, KeyPressedEvent, EventCategory::KeyInput, EventCause::KeyPressed, onKeyPressed);
DEFINE_EVENT(KeyReleasedDefintion, KeyReleasedEvent, EventCategory::KeyInput, EventCause::KeyReleased, onKeyReleased);

DEFINE_EVENT(KeyRepeatedDefintion, KeyRepeatedEvent, EventCategory::KeyInput, EventCause::KeyRepeated, onKeyRepeated);
}  // 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) {
        OBS_LOG_INFO("Key {} pressed", event.getKey());
    }

    static void onKeyReleased(const KeyReleasedEvent& event) {
        OBS_LOG_INFO("Key {} released", event.getKey());
    }

    static void onKeyRepeated(const KeyRepeatedEvent& event) {
        OBS_LOG_INFO("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 events
    void onTick(const TickEvent& event) {
    }
};

auto main() -> int {
    try {
        Logging::init();

        auto keyPressedEvent = KeyPressedEvent { 0 };
        auto keyReleasedEvent = KeyReleasedEvent { 1 };
        auto keyRepeatedEvent = KeyRepeatedEvent { 42 };

        auto listener = Listener {};

        auto* scheduler = &Scheduler<Dispatcher>::getInstance();

        scheduler->appendListener(listener);

        scheduler->scheduleOnce(WindowEvent { EventCause::WindowClosed }, 1s);
        scheduler->scheduleOnce(keyPressedEvent);
        scheduler->scheduleOnce(keyReleasedEvent);
        scheduler->scheduleOnce(keyRepeatedEvent);

        scheduler->startMainLoop();
    } catch (const std::exception& e) {
        OBS_LOG_ERROR("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/events/event_definitions/event_definition.hxx>
#include <obscura/events/mouse_events.hxx>

namespace obscura {
DEFINE_EVENT(
    MouseMovedDefintion,
    MouseMovedEvent,
    EventCategory::CoordinateInput,
    EventCause::MouseMoved,
    onMouseMoved);

DEFINE_EVENT(
    MouseClickedDefinition,
    MouseClickedEvent,
    EventCategory::KeyInput,
    EventCause::MouseClicked,
    onMouseClicked);
}  // 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) {
        OBS_LOG_INFO("Mouse key {} pressed", event.getKey());
        OBS_LOG_INFO("Mouse moved to x: {}, y: {}", event.getPosition().x, event.getPosition().y);
    }

    static void onMouseMoved(const MouseMovedEvent& event) {
        OBS_LOG_INFO("Mouse moved to x: {}, y: {}", event.getCurrentPosition().x, event.getCurrentPosition().y);
        OBS_LOG_INFO("Mouse moved from x: {}, y: {}", event.getPreviousPosition().x, event.getPreviousPosition().y);
        OBS_LOG_INFO(
            "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 events
    void onTick(const TickEvent& /*unused*/) {
    }
};

auto main() -> int {
    try {
        Logging::init();

        auto mouseClickedEvent = MouseClickedEvent { 2, 3, 42 };
        auto mouseMovedEvent = MouseMovedEvent { 0, 5, 10, 25 };

        auto listener = Listener {};

        auto* scheduler = &Scheduler<Dispatcher>::getInstance();

        scheduler->appendListener(listener);

        scheduler->scheduleOnce(WindowEvent { EventCause::WindowClosed }, 1s);
        scheduler->scheduleOnce(mouseClickedEvent);
        scheduler->scheduleOnce(mouseMovedEvent);

        scheduler->startMainLoop();
    } catch (const std::exception& e) {
        OBS_LOG_ERROR("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/events/event_definitions/event_definition.hxx>
#include <obscura/events/loop_events.hxx>

namespace obscura {
DEFINE_EVENT(TickDefinition, TickEvent, EventCategory::Application, EventCause::LoopTicked, onTick);
DEFINE_EVENT(LoopEndDefinition, TickEvent, EventCategory::Application, EventCause::LoopStopped, onLoopStop);
}  // namespace obscura

Example

#include <obscura/obscura.hxx>

using namespace obscura;
using namespace std::chrono_literals;

class Listener {
  public:
    void onTick(const TickEvent& event) {
        OBS_LOG_INFO("The Frame took {} microseconds", event.getDurationSinceLastTick().count());
        ++tickCounter;
        OBS_LOG_INFO("This is tick #{}", tickCounter);
    }

    static void onLoopStop(const TickEvent& /*unused*/) {
        OBS_LOG_INFO("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 {
        Logging::init();

        auto listener = Listener {};

        auto* scheduler = &Scheduler<Dispatcher>::getInstance();

        scheduler->appendListener(listener);

        scheduler->scheduleOnce(WindowEvent { EventCause::WindowClosed }, 1s);

        scheduler->startMainLoop();
    } catch (const std::exception& e) {
        OBS_LOG_ERROR("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/events/event_definitions/event_definition.hxx>
#include <obscura/events/window_events.hxx>

namespace obscura {
DEFINE_EVENT(WindowClosedDefintion, WindowEvent, EventCategory::Window, EventCause::WindowClosed, onWindowClosed);
}  // namespace obscura

Example

#include <obscura/obscura.hxx>

using namespace obscura;
using namespace std::chrono_literals;

class Listener {
  public:
    explicit Listener(Window<Dispatcher, HeadlessWindow>& window)
      : window(&window) {
    }

    void onWindowClosed(const WindowEvent& /*unused*/) {
        OBS_LOG_INFO("Close window!");
        window->close();
        OBS_LOG_INFO("Window was closed!");
        Scheduler<Dispatcher>::getInstance().stopMainLoop();
    }

    // Only required to suppress warnings because otherwise there is no listener that
    // processes tick events
    void onTick(const TickEvent& /*unused*/) {
    }

  private:
    Window<Dispatcher, HeadlessWindow>* window;
};

auto main() -> int {
    try {
        Logging::init();

        auto* scheduler = &Scheduler<Dispatcher>::getInstance();

        auto window = Window<Dispatcher, HeadlessWindow> { scheduler->getMainLoop(), WindowProperties {} };
        auto listener = Listener { window };
        scheduler->appendListener(listener);

        scheduler->scheduleOnce(WindowEvent { EventCause::WindowClosed }, 1s);
        scheduler->startMainLoop();
    } catch (const std::exception& e) {
        OBS_LOG_ERROR("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;
};

DEFINE_EVENT(  // NOLINT(modernize-use-trailing-return-type)
    CustomEventDefinition,
    CustomEvent,
    obscura::EventCategory::None,
    obscura::EventCause::None,
    onCustomEvent);

class CustomListener {
  public:
    static void onCustomEvent(const CustomEvent& event) {
        OBS_LOG_INFO("{}", event.getMessage());
    }
};

auto main() -> int {
    try {
        obscura::Logging::init();

        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.appendListener(listener);
        dispatcher.enqueue(event);

        OBS_LOG_INFO("Event is in queue but not yet dispatched!");

        dispatcher.dispatch();

        OBS_LOG_INFO("Event was dispatched!");
    } catch (const std::exception& e) {
        OBS_LOG_ERROR("Exception caught: {}", e.what());
    }

    return EXIT_SUCCESS;
}