Loops

At the heart of many game engines lies the event loop, a continuous cycle that checks for and responds to events, such as user input or system triggers.

In Obscura, loops are extensions of dispatchers. This means every loop has its own dedicated dispatcher. When you want a loop to respond to specific events or inputs, you need to register the appropriate listeners with that particular loop.

We recommend to initialize and organize loops via the Scheduler.

There are three types of loops:

  • MainLoop: The main loop for the engine. There should only be one main loop.

  • SyncLoop: A loop that runs in sync with the main loop.

  • AsyncLoop: A loop that runs asynchronously to the main loop.

Available loops

template<Dispatchable Dispatcher>
class MainLoop : public obscura::LoopBase<Dispatcher>

Public Functions

inline void run(const FPS &fps)
inline void stop()
template<Dispatchable Dispatcher, Loop BaseLoop>
class SyncLoop : public obscura::LoopBase<Dispatcher>

Public Functions

inline void run(BaseLoop &newBaseLoop, const LoopConfig &config)
inline void stop()
template<Dispatchable Dispatcher>
class AsyncLoop : public obscura::LoopBase<Dispatcher>

Public Functions

inline void run(const FPS &fps)
inline void stop()

Loop base class

template<Dispatchable Dispatcher>
class LoopBase : public obscura::ConfigurableDispatcher<TickDefinition, KeyPressedDefinition, MouseMovedDefintion, KeyReleasedDefinition, KeyRepeatedDefinition, WindowClosedDefintion, MouseClickedDefinition, LoopEndDefinition>

Subclassed by obscura::MainLoop< obscura::ConfigurableDispatcher >, obscura::AsyncLoop< Dispatcher >, obscura::MainLoop< Dispatcher >, obscura::SyncLoop< Dispatcher, BaseLoop >

Public Functions

inline LoopBase()
~LoopBase() = default
LoopBase(LoopBase<Dispatcher>&&) noexcept = default
auto operator=(LoopBase<Dispatcher>&&) noexcept -> LoopBase<Dispatcher>& = default
LoopBase(const LoopBase<Dispatcher>&) = delete
auto operator=(const LoopBase<Dispatcher>&) -> LoopBase<Dispatcher>& = delete
inline void tick()
template<class Event>
inline void scheduleOnce(Event &&event)
template<class Event, class Duration>
inline void scheduleOnce(Event &&event, const Duration &delay)
template<class Event>
inline void scheduleOnce(Event &&event, std::size_t afterNFrames)
inline auto getFramePacer() const -> const FramePacer<>&
inline void waitForStop()

Loop config

class LoopConfig

Subclassed by obscura::LoopSynchronizer< BaseLoop, DependentLoop >

Public Static Functions

static auto targetFPS(const FPS &fps)
static auto runAtFractionOfBaseLoop(float fraction) -> FractionalLoopConfig
class FractionalLoopConfig : public obscura::BaseLoopConfig<FractionalLoopConfig>

Public Functions

auto build() const -> LoopConfig
class TargetFPSLoopConfig : public obscura::BaseLoopConfig<TargetFPSLoopConfig>

Public Functions

auto build() const
template<class Child>
class BaseLoopConfig

Public Functions

inline auto setMinFrameTime(float newMinFrameTime) -> Child&
inline auto setMaxFrameTime(float newMaxFrameTime) -> Child&

Examples

Create and use multiple synchronous loops

#include <obscura/obscura.hxx>

using namespace obscura;
using namespace std::chrono_literals;

class Listener {
  public:
    explicit Listener(std::string shoutOut)
      : shoutOut(std::move(shoutOut)) {
    }

    void onTick(const TickEvent& /*unused*/) {
        logInfo("{}", shoutOut);
    }

  private:
    std::string shoutOut;
};

// NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
auto main() -> int {
    try {
        auto application = obscura::Application<HeadlessWindow>();
        auto defaultApplicationListener = DefaultApplicationListener<Application<HeadlessWindow>>(application);

        auto mainListener = Listener("cha");
        auto syncListener1 = Listener("Dooo!");
        auto syncListener2 = Listener("MAA!");

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

        scheduler.registerListener(mainListener);
        scheduler.registerListener(defaultApplicationListener);

        auto loopConfig1 = LoopConfig::runAtFractionOfBaseLoop(0.5).build();
        scheduler.createSyncLoop("syncLoop1", loopConfig1);

        auto loopConfig2 = LoopConfig::runAtFractionOfBaseLoop(0.25).build();
        scheduler.createSyncLoop("syncLoop2", loopConfig2);

        scheduler.registerListener(syncListener1, "syncLoop1");
        scheduler.registerListener(syncListener2, "syncLoop2");

        scheduler.scheduleOnce(WindowEvent { EventCause::WindowClosed }, 2s);


        application.run();
    } catch (const std::exception& e) {
        logError("Exception caught: {}", e.what());
    }

    return EXIT_SUCCESS;
}

// NOLINTEND(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)