Consuming the engine
Start with the first-game walkthrough for a maintained application covering startup, controls, asset loading, and sprite/text submission. This page describes the source and SDK consumption contracts.
Applications can compose source targets from a checkout or link a prebuilt
development SDK. projects/engine/ owns the neutral
Cheryl::Engine target, backed by the build
target cengine and archive output name cheryl-engine. Applications link selected
integration targets explicitly; the module guide describes ownership,
standalone composition and tests.
set(CHERYL_BUILD_TESTS OFF)
set(CHERYL_BUILD_DEMO OFF)
set(CHERYL_BUILD_NATIVE_GLFW OFF)
set(CHERYL_BUILD_DEBUG_TERMINAL_LINUX OFF)
set(CHERYL_BUILD_OPENGL OFF)
set(CHERYL_BUILD_UI_TGUI OFF)
set(CHERYL_BUILD_UI_RMLUI OFF)
set(CHERYL_BUILD_AUDIO_MINIAUDIO OFF)
add_subdirectory(path/to/cheryl-engine cheryl)
add_executable(application main.cpp)
target_link_libraries(application PRIVATE Cheryl::Engine)
For native graphics, enable Native GLFW, OpenGL and native input, then link
Cheryl::Engine, Cheryl::NativeGLFW and Cheryl::OpenGL. Select and link
Cheryl::UI::TGUI, Cheryl::UI::RmlUi or Cheryl::Audio::Miniaudio when needed.
For command-line startup, link Cheryl::OpenGL::Startup; it supplies the common
startup target and the native/graphics targets transitively.
Consumers obtain C++23, includes, logging policy and SDK requirements through
targets rather than compiling Engine sources or repeating dependency lists.
Prebuilt development SDKs
Extract an SDK and select its root through CMAKE_PREFIX_PATH or
Cheryl_DIR=<sdk>/lib/cmake/Cheryl. No Cheryl implementation checkout is needed
to consume it. Link the same public aliases as source consumers:
cmake_minimum_required(VERSION 3.28)
project(my_game LANGUAGES CXX)
find_package(Cheryl 4 CONFIG REQUIRED)
add_executable(my_game main.cpp)
target_link_libraries(my_game PRIVATE Cheryl::Engine)
# Add Cheryl::OpenGL::Startup or the selected module/support targets as needed.
For a Linux Release SDK, configure the application’s directory as follows:
(
set -e
cd /path/to/my_game
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH=/absolute/path/to/extracted-sdk
cmake --build build --parallel
)
The SDK contains compiled Engine, Startup and selected module/topology libraries,
their dependency libraries or headers, the crash-bootstrap object, public headers,
CMake import/version files and licenses. .h/.hpp and required .inl/.ipp/.tpp
files remain included. C/C++ implementation translation units, tests, artwork,
applications and the demo are excluded. There is no separate demo package.
Header-based dependencies such as miniaudio still require their public header.
The build guide describes production;
SDK-MANIFEST.json records selected features, source/submodule revisions, OS,
architecture, compiler/version, configuration, logging profile and native versions.
Use a package compatible with the target OS/distribution, architecture, compiler, standard-library ABI and configuration. Windows CI SDKs use MSVC x64 RelWithDebInfo and the DLL CRT; Linux CI SDKs use Ubuntu 24.04 x64 GCC 14 Release. A locally built SDK’s identity describes that local toolchain, not those hosted configurations. Imported logging definitions are fixed to the producer’s compiled profile/mask; the application must not select contradictory definitions. Match MSVC runtime and configuration rather than mixing incompatible Debug/Release C++ binaries.
Native dependencies |
Consumer requirements |
|---|---|
Linux |
Install compatible FreeType, HarfBuzz and ICU development/runtime packages; hwloc when selected, the producer’s Backward resolver libraries, and X11/OpenGL when selected. Pinned C++ and selected integration dependencies are already supplied. |
Windows |
The |
The package config discovers native libraries, checks the producer’s ICU major
version and requires the hwloc 2.5+ (2.x) API when that provider is included.
Windows config prepends the bundled dependency prefix and exposes
Cheryl_RUNTIME_DIRECTORIES for runtime-path setup; it does not deploy DLLs beside
an application automatically. OpenGL still needs the host’s graphics runtime.
SDK relocation does not establish compatibility with another ABI or distribution.
Installed-target consumers and first-include probes cover the selected SDK composition, including both Startup support headers, on the selected hosted Linux and Windows toolchains. The CI guide retains hosted skipped-case limits and the pending main-push artifact collection. Desktop, GPU and physical-device coverage remain separate. The SDK does not export the Linux Debug terminal; that module retains its source-composition path.
Command-line startup
CE::Application::Startup
owns CLI11 parsing and validates execution, input polling and simulation timing
before invoking a selected backend factory. Link Cheryl::Startup for a custom
backend or Cheryl::OpenGL::Startup for the
GLFW/OpenGL factory and options.
The support targets expose CLI11 in their public headers; linking Engine or OpenGL
alone retains their direct context/runtime construction APIs without that public
dependency.
Create startup on the platform owner, register application options with
add_application_options, then call initialize(argc, argv) once. Option-definition
callbacks run immediately; any values bound to the parser must remain at a stable
address through initialization. Startup itself cannot be copied or moved because
its callbacks bind its configuration.
Help and parse/configuration errors produce a CE::Application::StartupResult with no context.
Return its exit_code when should_start() is false; help succeeds and invalid
arguments fail before native context construction. A successful result owns the
CE::Runtime::Context and the parsed CE::Application::RuntimeOptions. Construct the game from that
context, then call result.make_runtime(game) to apply its run mode, polling and
timing together. Keep the result and game alive through runtime destruction;
make_runtime requires an lvalue result. Backend construction failures propagate,
and CE::Runtime::GameRuntime::run() retains the runtime’s existing initialization/cleanup contract.
CE::Application::StartupOptions supplies application defaults through execution and
runtime. The common Engine options override those defaults:
Argument |
Effect |
|---|---|
|
Selects a separate simulation owner thread. |
|
Selects fixed-step simulation. |
|
Selects fixed simulation and sets its positive step in milliseconds. |
|
Sets variable-update spacing; zero permits unpaced updates. |
|
Sets a positive limit on ordinary fixed updates per scheduler turn. |
|
Selects fixed simulation with bounded variable catch-up recovery. |
|
Sets the fixed-update prefix before recovery, at most |
|
Caps the recovery duration; zero removes the cap. |
|
Selects unlimited completed polls between simulation updates. |
|
Selects finite input polling with a positive completed-poll capacity. |
|
Sets minimum spacing between completed polls; zero removes the delay. |
|
Sets capacity for the lazily created Engine CPU pool, separate from the simulation thread. Owned capacity must be positive; an injected shared pool retains its own capacity. |
Numeric values are nonnegative and must satisfy the linked
timing and
polling contracts.
Option callbacks apply in parse order: --input-unlimited --input-capacity=4
selects finite polling; reversing them selects unlimited polling. Without overrides,
the default configuration is sequential variable simulation, lockstep polling and
one lazy CPU worker.
A custom CE::Application::StartupBackend provides a required create(const CE::Runtime::ExecutionOptions&) factory,
optional Backend argument definitions and optional validation. The factory must
return an owned context; a null result fails startup. Backend selection is currently
programmatic. CLI backend selection, application-preference/build-default resolution
and runtime-loaded discovery remain unimplemented at the selection point after
parsing. The demo option helper
shows application-specific registration without duplicating the engine parser.
Game hooks and ownership
Implement CE::Game::AbstractGame and lend it, with an CE::Runtime::Context, to
CE::Runtime::GameRuntime. Keep both alive through CE::Runtime::GameRuntime::run() returning or throwing. The selected
factory assembles display, window, input, presentation, renderer and resources;
CE::Runtime::GameRuntime::run() initializes those adapters before calling the game.
Hook |
Owner |
Responsibility |
|---|---|---|
Platform/graphics |
Bind actions and create initial resources after adapter startup. |
|
|
Simulation |
Advance game state using |
|
Simulation |
Resolve complete ordered passes/packets; publish no borrowed game state. |
Platform after simulation joins |
Stop external producers and invalidate borrowed listeners; keep their dependencies alive. |
|
Platform/graphics |
Release application resources after accepted work settles and frames recycle. |
Sequential mode runs all hooks on the calling thread. Concurrent mode gives update/frame preparation to one simulation worker; platform polling, uploads and presentation remain on the caller. Cleanup pairs with attempted initialization, so hooks must tolerate partial startup. The frame boundary defines shutdown and failure ordering.
Minimal native application
With Native GLFW, OpenGL and native input enabled, this opens a window and stops when Escape is pressed. It submits no draws yet:
#include <cheryl/backends/opengl/startup.h>
#include <cheryl/core/controls/input-interface.h>
#include <cheryl/core/game-framework/abstract-game.h>
#include <cheryl/core/game-framework/game-runtime.h>
#include <gainput/gainput.h>
class Game final : public CE::Game::AbstractGame {
CE::Runtime::Context& engine_;
static constexpr CE::Input::Actions::Id quit_{1};
public:
explicit Game(CE::Runtime::Context& engine) : engine_(engine) {}
void init() override {
auto& input = engine_.input();
static_cast<void>(input.bindings().bind_button({input.keyboard_id(), gainput::KeyEscape}, quit_));
}
void update(const CE::Game::TickContext& tick) override {
if (tick.input.button(quit_).pressed())
tick.request_stop();
}
void prepare_render_frame(CE::Rendering::FrameWriter&) const override {}
void deinit() override {}
};
int main(int argc, char** argv) {
auto startup = CE::Application::GLFWOpenGL::make_startup("My game", {.title = "My game"});
auto result = startup.initialize(argc, argv);
if (!result.should_start())
return result.exit_code;
Game game(*result.context);
auto runtime = result.make_runtime(game);
runtime.run();
return result.exit_code;
}
Link this example to Cheryl::OpenGL::Startup. Pass --concurrent to select the
simulation worker, or use the common timing/polling options above. Applications
that construct CE::Runtime::GameRuntime directly can still pass RunMode::Concurrent as its
third argument.
Keep native binding IDs inside bootstrap; gameplay reads semantic CE::Input::Actions::Ids.
Never change bindings or query a live window from concurrent update().
To add presentation, follow asset loading and
pipeline/material construction
in init(), then use CPU submission helpers in frame preparation. For later
replacements, prepare owned data on workers,
submit upload to platform and adopt only a ready
complete result during update. Keep the prior generation while work is pending.
The demo implementation shows that flow.
Use input actions and focus for controls, simulation timing for fixed/variable policies, and asset playback for per-entity cursors. The Unicode service prepares retained text; UI adapters keep their toolkit authoring APIs. Audio is separately application-owned and needs its producers settled before closure. World storage, entities, collision and mechanics belong to the game or its selected modules.
Dependencies and headers
Requirement |
Target scope and reason |
|---|---|
C++23, Threads, logging configuration |
Engine public requirements used by templates/runtime consumers. |
spdlog, CTTI, GLM |
Engine public requirements; legacy |
Backward::Interface |
Engine public trace/resolver requirements; its own global signal-handler object is not linked. |
STB and JSON |
Engine private implementation include directories. |
FreeType, HarfBuzz, ICU uc/i18n |
Engine private font/layout implementation and link requirements; no library-native types appear in public headers. |
CLI11 |
Startup support’s public parsing API; reuse |
GLFW |
Native GLFW private implementation requirement, also used privately by the OpenGL context binding. |
Gainput |
Native GLFW public requirement when |
X11 |
Native input’s Linux dependency; OpenGL acceptance also uses it for explicitly selected X11 scenarios. |
GLAD and OpenGL::GL |
OpenGL public generated types and private system link requirement. |
GoogleTest |
Test-only; disabled owners do not discover their integration SDKs. |
miniaudio |
Optional audio module’s private implementation/link requirement; public Engine and module audio headers expose no SDK types. |
hwloc 2.5+ (2.x) |
Optional WorkerTopology::Hwloc private SDK/final link requirement; Engine alone does not discover or link it. |
The Engine umbrellas and granular contracts are neutral. Concrete window/input
headers belong to Native GLFW; backends/opengl headers belong to OpenGL. Existing
cheryl/... and legacy core/.../assets/... include spellings are preserved through
each target’s public roots, including in the installed SDK.
<assets.h> and <core.h> expose no Glad types; concrete OpenGL resources are
opt-in through <backends/opengl.h>. Portable asset values and primitives live in
assets/types/, with shared 2D bases under assets/types/2d/base/. CPU geometry
builders live in assets/geometry/, parsed grids/clips/manifests in
assets/definitions/, and neutral upload/resource interfaces in assets/resources/.
core/rendering/ owns renderer contracts, frame submissions and resolved packets.
Common submission uses public parameter keys; the
backend mapping
owns native uniform names and graphics types.
Crash and exception traces
Cheryl::Engine delivers the automatic crash bootstrap.
Final consumers receive its object directly, so a static linker cannot omit an
unreferenced archive initializer. Installation follows the bootstrap object’s
NDEBUG scope in the engine build: Debug-style builds install
backward::SignalHandling; NDEBUG builds do not.
Exception and explicit stack capture remain available through the existing bounded
capture/fallback implementation in all builds. The legacy global trace resolver and
Cheryl::SignalHandlers target remain available; the demo needs only the selected
engine/module targets. NDEBUG controls signal bootstrap installation, not explicit
trace capture. The legacy ST_ON_SIGNALS definition exposes the sh declaration
through core/logging.h; it does not change installation scope. See the
signal procedure.
Independent consumers and header probes
Engine consumer links only Engine and exercises multiple translation units, events, workers and CPU resources. Its first-include probes include the neutral umbrellas and clipping contract and reject GL/GLFW header leakage. Standalone bootstrapping selects Engine only in a local scope.
Native GLFW consumer
and OpenGL consumer
link their actual module with first-include probes. They
reference real implementation symbols without requiring a display at execution.
CHERYL_BUILD_CONSUMER_TESTS=ON adds consumers for the selected root assembly.
The Engine-only consumer entry point is:
(
set -e
cd "$(git rev-parse --show-toplevel)"
cmake -S projects/engine/tests/consumer -B build-consumer-engine -G Ninja \
-DCMAKE_BUILD_TYPE=Release -DCHERYL_REPOSITORY_ROOT="$PWD"
cmake --build build-consumer-engine --target consumer-cengine --parallel
./build-consumer-engine/cheryl-consumer
)
Standalone consumer entry points require
CHERYL_REPOSITORY_ROOT=/path/to/cheryl-engine for shared helpers and bootstrapping.
Engine, Native GLFW and OpenGL consumer bootstraps suppress both UI selections in
a local variable scope, including inherited ON choices, without changing the
enclosing configuration’s cache.
Normal Native GLFW consumption selects no Cheryl OpenGL/GLAD dependency; the
OpenGL consumer selects both integration owners. CMake 4 hosts may need
CMAKE_POLICY_VERSION_MINIMUM=3.5 for pinned legacy dependency projects. Standalone
module entry points and explicit null-platform selection are documented separately
in the module guide. Architecture validation describes
independent graph, consumer and runner checks and their coverage limits.
Optional output terminal
For Linux GNU/Clang consumers, explicitly link Cheryl::DebugTerminal::Linux (or
the root’s selected Cheryl::DebugTerminal) to choose the process entry attachment.
Plain Engine and command-line Startup do not select it. AUTO includes actual
Debug only; ON permits a non-Debug consumer to display it with --debug-terminal.
See the module guide
for argument consumption, fallback and independent viewer lifetime.
The small Cheryl::DebugTerminal::Startup support target accepts an implementation
callback; with no callback it calls the application unchanged. Native capture,
viewer launch and process-wide shutdown live entirely in the selected module.
Standard headers in an existing build
An existing CMake/CLion profile can retain failed C++ compiler ABI discovery. Check
whether generated CMakeCXXCompiler.cmake records
CMAKE_CXX_ABI_COMPILED FALSE and empty implicit include directories. This can turn
the X11 dependency’s /usr/include into an explicit -isystem argument;
GCC can then fail to resolve CE::Math.h or stdlib.h through #include_next in
C++ standard headers.
Refresh compiler discovery and regenerate the build files while preserving the profile/toolchain settings. CMake normally filters its detected implicit include directories from explicit compiler arguments. Reload CMake in CLion after repairing an existing build externally. Preserve intended profile/toolchain/cache settings when resetting generated discovery metadata or creating a fresh build directory.