Skip to content

Architecture overview

Aegis2D is split into small libraries with one job each. A game links only what it needs: the server never loads rendering, audio or windowing code, and game rules never live inside the engine.

Status

This page describes the target architecture. The module boundaries, build targets and dependencies already exist; the module contents are being implemented ticket by ticket (see the changelog).

Layers

flowchart TB
    subgraph game["Game (project-rtype)"]
        server["r-type_server<br/>authoritative simulation"]
        client["r-type_client<br/>input, prediction, display"]
    end

    subgraph engine["Aegis2D engine"]
        direction TB
        render["render<br/>OpenGL renderer"]
        audio["audio<br/>sounds, music"]
        platform["platform<br/>window, GL context, input"]
        ecs["ecs<br/>entities, components, systems"]
        net["net<br/>serialization, UDP/TCP, net thread"]
        core["core<br/>log, time, loop, events, config"]
    end

    subgraph deps["Third-party (CPM)"]
        sdl["SDL3"]
        gl["glad · GLM · stb_image"]
        asio["Asio"]
        json["nlohmann-json"]
    end

    server --> ecs & net & core
    client --> render & audio & platform & ecs & net & core
    render --> platform
    render --> gl
    platform --> core
    platform --> sdl
    audio --> core
    audio --> sdl
    ecs --> core
    net --> core
    net --> asio
    core --> json
Module Library Depends on Built for
core aegis2d::core nlohmann-json server, client
ecs aegis2d::ecs core server, client
net aegis2d::net core, Asio server, client
platform aegis2d::platform core, SDL3 client
render aegis2d::render platform, glad, GLM, stb_image, SDL3 client
audio aegis2d::audio core, SDL3 client

Rules that keep the layers apart:

  • Dependencies point down. core depends on no other module; nothing in the engine depends on the game.
  • Third-party libraries stay private. Modules link SDL3, glad, Asio and the others privately, so their headers never leak into game code: the game sees engine types only.
  • Server builds stop at net. With AEGIS2D_BUILD_CLIENT=OFF, platform, render and audio are not even configured, and SDL3 and glad are not downloaded.

How the subsystems talk

flowchart LR
    subgraph clientproc["Client process"]
        input["platform<br/>input actions"] --> cgame["game systems"]
        cgame --> cecs[("ECS world")]
        cecs --> renderer["render"]
        cgame -. events .-> sound["audio"]
        cnet["net thread"] <-->|SPSC queues| cgame
    end

    subgraph serverproc["Server process"]
        snet["net thread"] <-->|SPSC queues| sim["simulation<br/>60 Hz fixed step"]
        sim --> secs[("ECS world")]
    end

    cnet <-->|"UDP: inputs, snapshots, events<br/>TCP: login"| snet
  • ECS as the shared world model. Gameplay, networking and rendering read and write components (Transform, Velocity, Sprite, NetworkId...). Rendering only needs a position and a sprite; it never sees health or damage.
  • Event bus for decoupling. Subsystems publish typed events (EntityDestroyed, PlayerFired...) on the core event bus; audio plays sounds by listening to them, so gameplay code never calls audio.
  • Network thread and queues. Sockets run on their own thread (Asio). Messages cross to the game thread through lock-free single-producer/single-consumer queues, so the simulation never blocks on the network.
  • Fixed time step. core drives the simulation at a fixed 60 Hz with an accumulator, independent of the frame rate; rendering interpolates between steps. Nothing depends on CPU speed.
  • Authoritative server. Clients send inputs; the server simulates, then broadcasts snapshots and reliable events. Clients display the server's world, interpolating remote entities between snapshots.

Build structure

  • Every module is declared with aegis2d_module(<name> <sources>) (cmake/Modules.cmake): a shared library with hidden symbol visibility, a generated aegis2d/<name>/export.hpp, public headers in public/aegis2d/<name>/ and the aegis2d::<name> alias.
  • aegis2d::warnings (strict warnings, optionally as errors) and aegis2d::sanitizers (ASan and UBSan when AEGIS2D_SANITIZE=ON) are attached to every module.
  • aegis2d::engine links every built module, for games that want all of them.
  • Dependencies are declared once in cmake/Dependencies.cmake, pinned, and fetched by CPM.

Repositories and delivery

flowchart LR
    engine["Aegis2D-Dev/aegis2d<br/>engine"] -->|FetchContent| game["Aegis2D-Dev/project-rtype<br/>game"]
    engine -->|"push to master"| mirror["Epitech mirror workflow"]
    game -->|"push to master<br/>(dispatch)"| mirror
    mirror -->|"build + validated sources"| epitech["Epitech delivery repository"]

The game fetches the engine with CMake FetchContent. On delivery, the mirror workflow assembles both repositories, builds them, and publishes the validated sources to the Epitech repository.