Skip to content
CXO2 edited this page Jul 17, 2026 · 17 revisions

Overview

The Genode System module contains core components that serve as your application bootstrappers and framework-wide classes such as Gx::Exception. The System module in Genode has a different concept from the SFML System module, which encapsulates the low-level operating system features.

At the time of writing, the System module has two core components: Gx::Application and Gx::Context. The event dispatching mechanism that used to be part of this module now lives in the dedicated Events module as Gx::Events::EventDispatcher.

Application

This class represents your application (or game) in a high-level perspective. It is an abstract class that encapsulates the traditional game loop and presents it as application lifecycles and delegates functions. This class also hosts the application modules, including the Gx::Context and a Gx::SceneDirector that is installed by default.

Lifecycle and Delegates

Here are the following lifecycle methods that Gx::Application provides that you can override:

  • Boot(): Called when the application is about to start. Use this method to initialize global dependencies, states and resources.
  • Update(const sf::Time& delta): Called every frame. Use this method to perform your game logic.
  • Render(RenderSurface&, RenderStates) const: Called every frame. Use this method to render your objects.
  • Shutdown(): Called when the application is about to exit. Use this method to stop operations and destroy external or manually managed resources. You also need to return the application exit code. You can return 0 to indicate the game was run successfully.

In addition to the lifecycle methods, there are several event delegates that you can implement too:

  • OnWindowCreated(sf::RenderWindow&): Called when the main application window was created or re-created.
  • OnFocusChanged(bool): Called when the main application window focus state was changed.
  • OnResized(const sf::Vector2u&): Called when the main application window was resized.
  • OnInputReceived(sf::Event&): Called when foreign events from the main application window have been polled. The event is mutable: the base implementation re-maps mouse coordinates from window pixels into view coordinates before forwarding the event.
  • OnClose(): Called when the main application window is about to close, return true to proceed with the close event; otherwise, return false to cancel it.

Boot and Shutdown are pure virtual functions, so your application must implement them. On top of that, Gx::Application implements Gx::RenderSurface that interconnects with the main sf::RenderWindow directly. See the Render Surface concept for more details.

Important

The base implementations of Update, Render, and OnInputReceived are responsible for driving the installed modules, including the default Gx::SceneDirector. When overriding these functions, call the base implementation; otherwise, the modules (and thus the active scene) will not be updated, rendered, or receive input.

Note

Only a single application instance can exist.
Starting another application instance via Start() will throw Gx::Exception.

Important

Single-threaded application

The application class is designed to run on a single thread with the following execution order in each frame: Input Polling -> Update -> Render. Without calling Start, the application will not be bootstrapped correctly, and manually calling these operations will lead to unexpected behavior.

One window at a time

The application class is designed to handle only one window at a time. Furthermore, SFML currently has a limitation that prevents you from changing the window state once a render window is created. The framework will re-create the window at any point should you change the window state via SetWindowState() method.

Currently, you cannot use the Gx::Application class if you wish to write a multi-threaded application or work with multiple windows simultaneously.

Example

Here's what a minimum example of Gx::Application implementation would look like:

class MyAwesomeGame : public Gx::Application
{
public:
    // Application constructor
    MyAwesomeGame(std::string title, const sf::VideoMode& mode, const sf::View& view, const bool fullScreen, const sf::ContextSettings& settings) :
        Gx::Application(std::move(title), mode, view, fullScreen, settings)
    {
    }

protected:
    void Boot() override
    {
        // Do some initialization here!

        // Load the texture and sprite
        m_texture = sf::Texture("/some/path/to/the/texture.png");
        m_sprite = Gx::Sprite(m_texture);
    }

    void Update(const sf::Time& delta) override
    {
        // Call the base implementation to keep the installed modules updated
        Gx::Application::Update(delta);

        // Move the sprite by 10px to the right every 5 seconds
        m_elapsed += delta;
        if (m_elapsed >= sf::seconds(5))
        {
            m_sprite.Move({10, 0});
            m_elapsed -= sf::seconds(5);

            // Reset position when it reaches a certain point
            if (m_sprite.GetPosition().x >= 500)
            {
                m_sprite.SetPosition({0, 0});
            }
        }
    }

    RenderStates Render(RenderSurface& surface, RenderStates states) const override
    {
        // Call the base implementation to keep the installed modules rendered
        states = Gx::Application::Render(surface, states);

        surface.Render(m_sprite, states);
        return states;
    }

private:
    sf::Time m_elapsed;
    sf::Texture m_texture;
    Gx::Sprite m_sprite;
};

To start the application, simply instantiate your application class and call the Start() function.

#include "MyAwesomeGame.hpp"

int main(int argc , char** argv)
{
    auto myGame = MyAwesomeGame(
        "My Awesome Game",            // Title bar string
        sf::VideoMode({1920, 1080}),  // sf::VideoMode to initialize the window
        sf::View({0, 0, 1920, 1080}), // Default view of the application
        false                         // Fullscreen mode
    );

    // Start is a thread-blocking function, and it returns the exit code of the application propagated from `Shutdown()`
    return myGame.Start();
}

Modules

A module is a long-lived component that is installed into the application and participates in the application lifecycle. Any class that implements the Gx::Module interface (an empty marker interface with a virtual destructor) can be installed as a module.

Use Install<TModule>() to install a module. The module is provided into the application context with the Singleton scope and instantiated immediately, with its constructor dependencies resolved from the context. Similar to Context, an interface module can be bound with a concrete type via Install<TInterface, TConcrete>(), and a builder overload is available to control how the module is constructed.

class MyGame : public Gx::Application
{
protected:
    void Boot() override
    {
        // Install a module with automatic dependency resolution
        Install<MessageService>();

        // ..Or bind an interface module with its concrete type
        Install<AudioService, StreamingAudioService>();

        // ..Or use a builder to control the module construction
        Install<TelemetryService>([] (const Gx::Context& ctx)
        {
            return std::make_unique<TelemetryService>("https://telemetry.example.com");
        });
    }

    // The rest of the application implementation..
};

Once installed, a module participates in the application game loop based on the interfaces it implements:

  • Modules that implement Gx::Updatable are updated every frame.
  • Modules that implement Gx::Renderable are rendered every frame.
  • Modules that implement Gx::Inputable receive the polled window input.

Modules are driven by the base implementations of Update, Render, and OnInputReceived, in the order they were installed.

Use GetModule<TModule>() to retrieve an installed module, and Uninstall<TModule>() to remove it from the lifecycle:

auto& messages = GetModule<MessageService>();

Important

Installing a module type that is already installed throws Gx::InvalidOperationException, and so does retrieving a module that is not installed. GetModule<Gx::Context>() is an exception: it always returns the application context without requiring an installation.

By default, Gx::Application installs a Gx::SceneDirector module that manages the scenes of your game. Retrieve it via GetModule<Gx::SceneDirector>().

Context

The Genode system context is a lightweight yet powerful IoC Container class for bootstrapping, managing class dependencies, and performing dependency injection. While several great IoC solutions that are much more powerful and flexible are available, Gx::Context aims to be a lightweight/small-footprint container with a simple implementation, non-intrusive, and intuitive interface to use.

Dependency injection is a fancy phrase that essentially means this: class dependencies are "injected" into the class via the constructor or, in some cases, "setter" methods. But what does "bootstrapping" mean? It generally means registering whatever components your game needs to run. This could be a resource manager that is globally shared across your game components, a fresh instance of the physic engine, or an instance of loaded game configuration—you name it.

Here's an overview of Gx::Context in action looks like:

struct GameConfig
{
    unsigned int FrameLimit;
    bool AccessibilityEnabled;

    // Some other configuration that your game needs..
};

class MyGameEntity : public Gx::Renderable, public Gx::Updatable
{
public:
    // This is where the dependencies are injected to your class. Hence, dependency injection
    MyGameEntity(GameConfig& config, Gx::AudioMixer& mixer) : 
        m_config(config),
        m_mixer(mixer)
    {
    }

protected:
    RenderStates Render(RenderSurface& surface, RenderStates states) const 
    {
        // Render your game objects here..
        
        // Render additional things related to accessibility
        if (m_config.AccessibilityEnabled)
        {
            // Render stuff that relates to accessibility here..
        }
    }

    void Update(const sf::Time& delta)
    {
        // Update your game objects here
        
        // Perform additional logic when accessibility is enabled
        if (m_config.AccessibilityEnabled)
        {
            // Accessibility logic here..
        }
    }

private:
    GameConfig& m_config;
    Gx::AudioMixer& m_mixer;
};

// Here's where the magic happens:
int main(int argc, char** argv)
{
    // Create the context
    auto context = Gx::Context();

    // Define how we can provide Game Config instance when requested
    context.Provide<GameConfig>([] (auto& ctx)
    {
        auto config = std::make_unique<GameConfig>();
        // Add logic to load your game config here..
        // Use Context in `ctx` parameter if the GameConfig requires another dependency

        return config;
    }, Gx::Context::Scope::Singleton); // GameConfig instance will be shared across different scopes

    // Create our entity based on the context object
    // Notice that we don't have to configure Gx::AudioMixer. It is automatically bootstrapped when needed!
    auto entity = context.Instantiate<MyGameEntity>();
}

Gx::Context can auto-resolve dependency with no configuration. The Gx::Context class can also resolve dependencies that have nested dependencies declared on their constructors, enabling you to build a complex dependency hierarchy that can be configured in any order with minimum configuration.

Caution

Do not inject Gx::Context to your class directly. Avoid resolving dependencies directly from the context instance in your class as much as possible. This will obscure the dependencies of your class and is often considered an anti-pattern.

Note

Gx::Application instance comes with a context that can be retrieved via GetModule<Gx::Context>().
Use this context as the root application bootstrapper context in your game.

Providing dependencies

As mentioned, the context class does not require configuration, particularly for concrete classes. It will automatically create the object and resolve the dependencies for you recursively. However, the dependency can be bootstrapped manually by explicitly calling the Provide function.

Consider the following structures:

struct InputSystem {};
struct MovementSystem {
    MovementSystem(InputSystem& input) : m_input(&input) {}
    InputSystem* m_input;
};

The following code illustrates how to bootstrap the above structures:

auto context = Gx::Context();
context.Provide<MovementSystem>();
context.Provide<InputSystem>();

Tip

You can register your type out-of-order.
The object will be lazily created when you call Require<T>() or Create<T>(), and the container will resolve its dependency recursively auto-magically.

Important

The registrant type needs at least one public constructor. The context will use the constructor with the least number of parameters.

If the public constructor with the least number of parameters has one or more overloads, the container will fail to resolve that type, and a runtime error will be thrown when retrieving the object by reference. You must manually bootstrap the dependency by binding the type with builder before resolving such type.

Additionally, the type must never accept smart pointers in the constructor, as the context handles the lifetime of dependencies. For bootstrapping an interface type or a type with a protected constructor, see Binding interface below.

Binding interface

If your class constructor requires an interface type, you must bind the interface before retrieving or using the type as a dependency from the context. Use the two-type Provide<TInterface, TConcrete>() overload to bind your interface type with a concrete type.

Consider the following structures:

class IInputSystem
{
public:
    virtual void Input(sf::Event& ev) = 0;
};

struct InputSystem : IInputSystem 
{
protected:
    virtual void Input(sf::Event& ev) {}; // do something
};
struct MovementSystem {
    MovementSystem(InputSystem& input) : m_input(&input) {}
    InputSystem* m_input;
};

The following code illustrates how to register the interface type above:

auto context = Gx::Context();
context.Provide<IInputSystem, InputSystem>();
context.Provide<MovementSystem>(); // Optional

Important

A runtime error will be thrown if you try to resolve a type that depends on an unregistered interface type.
Providing interface type with default Provide<T>() function will yield a compile-time error.

Bootstrap with builder

Regardless of whether the specified type is concrete or interface type, you can specify how the context should construct the object by defining a custom builder when you call Provide<T>().

auto context = Gx::Context();
context.Provide<IInputSystem>([] (auto& ctx)
{
    // You can simply call `ctx.Instantiate<InputSystem>()` here if the dependencies are concrete types
    return std::make_unique<InputSystem>( // return as a std::unique_ptr
        ctx.Require<KeyboardSystem>(), 
        ctx.Require<MouseSystem>()
    );
});

Resolving object

Use Require<T>() to resolve the object inside the container. The method will try to call the Provide<T>() function internally to attempt to bootstrap the specified type if it is not registered within the container.

auto context = Gx::Context();

// Create or retrieve MovementSystem from the container
// If the type is not registered, the container will automatically register the type for you.
auto& movementSystem = context.Require<MovementSystem>();

// Use the pointer overload if you don't want the container to register the type automatically.
// If the type is not registered, it will return `nullptr` instead.
auto lifeSystem = context.Require<LifeSystem*>();
assert(lifeSystem == nullptr, "Life System is not registered within the context!");

Use Instantiate<T>() to create a new fresh instance of the specified type. If the type requires one or more dependencies, they will be automatically resolved from the context or bootstrapped as needed, and those newly bootstrapped dependencies will be inserted and bound to the current Gx::Context instance. The instance itself is not stored inside the context; the caller owns it.

// Instantiate function will return a std::unique_ptr. Ensure you have provided the type previously if the type is an interface type!
auto lifeSystem = context.Instantiate<LifeSystem>();

Lifetime and Scopes

Each Provide<T>() method overload has an optional Scope parameter, allowing you to choose between Scope::Local and Scope::Singleton to control how you want to bind the object's lifetime with the Gx::Context instances. By default, the Provide<T>() function uses Scope::Local, which makes each initial Require<T>() call create a new instance of T in every Gx::Context scope.

Using scopes allows finer-grained lifetime control, where all types bootstrapped with the local scope are unique within a given scope. This allows singleton-like behavior within a scope, but multiple object instances can be created across scopes.

A new scope can be created by calling CreateScope() on a context instance. The scope takes a snapshot of every registration of its parent context: types provided with Scope::Singleton continue to resolve to the same instance as the parent, while types provided with Scope::Local start fresh, and their instances are created within (and owned by) the new scope. Local instances are destroyed when their scope is destroyed.

Note

Registrations made on the parent after the scope was created are not part of the snapshot. Singleton registrations are still resolvable through the scope (shared with the parent as usual), but local registrations are not visible to previously created scopes: resolving such a type in the scope bootstraps the scope's own registration instead, and the parent's local instance is never shared with the scope. This also means a late local registration with a custom builder or an interface binding is not reflected to previously created scopes.

Registration is intended to happen once, up front. Provide your dependencies before creating the scopes that should use them.

auto context = Gx::Context();
context.Provide<FooBar>(Gx::Context::Scope::Local);              // Specifying `Scope::Local` is optional
context.Provide<SingletonFooBar>(Gx::Context::Scope::Singleton); // Bootstrap a singleton instance

auto& singletonInstance1 = context.Require<SingletonFooBar>();
auto& instance1 = context.Require<FooBar>();
auto& instance2 = context.Require<FooBar>();

// The context itself is the scope
assert(&instance1 == &instance2);

{
    // Create a new scope
    auto scope = context.CreateScope();
    auto& singletonInstance2 = scope.Require<SingletonFooBar>();
    auto& instance3 = scope.Require<FooBar>();
    auto& instance4 = scope.Require<FooBar>();
    
    // Instances should be equal inside a scope
    assert(&instance3 == &instance4);
    
    // Instances should not be equal across scopes
    assert(&instance1 != &instance3);

    // Singleton instances should be equal across scopes
    assert(&singletonInstance1 == &singletonInstance2);
}

// The scope is destroyed at this point, along with the local instances it owned

Scopes are used throughout the framework: for example, each Gx::Scene receives its own scope created from the root context, giving local dependencies a per-scene lifetime. See the Scene Context documentation for more details.

Clone this wiki locally