Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Design

This section documents the design of the REPL and its components. I include it here as a reference for myself and for anyone who wants to understand how the REPL works.

Headless Bevy

The REPL is designed to be an interactive console for the Bevy app at runtime. It runs in the terminal while your Bevy app is running, even in headless mode.

"Headless" mode is when a Bevy app runs in the terminal without a window. All systems run as normal, such as input detection and asset loading, but the app exits after one loop iteration unless it is configured to run indefinitely. The app runs headless if the bevy_window feature is disabled or the WindowPlugin is disabled.

Bevy headless examples:

  • https://github.com/bevyengine/bevy/blob/main/examples/app/headless.rs
  • https://github.com/bevyengine/bevy/blob/main/examples/app/headless_renderer.rs

Headless app with default features except windowing

The preferred way to run a Bevy app headless is to disable default bevy features and explicitly add the desire features, leaving out bevy_winit and bevy_window. (Note that Bevy Repl requires bevy_log and trace features.)

[dependencies]

bevy = { 
  version = "*", # replace "*" with the most recent version of bevy
  default-features = false,
  features = [
    "bevy_log", "trace", # Bevy REPL needs `bevy_log` and `trace`.
    # include all the other feature flags you need here.
    # see: https://docs.rs/bevy/latest/bevy/#features
  ]
}
use bevy::prelude::*;

fn main() {
    let mut app = App::new();

    app.add_plugins((
        // with bevy_window and bevy_winit disabled, those plugins aren't in
        // DefaultPlugins. All we have to do is tell the runner to run at 60 fps
        // so it doesn't consume the whole CPU core.
        DefaultPlugins,
        bevy::app::ScheduleRunnerPlugin::run_loop(
            std::time::Duration::from_secs_f64(1.0 / 60.0),
        )
    ));

    // Exit with Ctrl+C
    app.run();
}

Minimal headless app (no default features)

Even with all the default features, Bevy ships MinimalPlugins with the minimum set of plugins required to run a Bevy app. Be sure to also enable InputPlugin so the app can handle keyboard inputs, like for the REPL or Ctrl+C interrupts.

use bevy::prelude::*;

fn main() {
    let mut app = App::new();

    // Run in headless mode at 60 fps
    app.add_plugins((
        MinimalPlugins,
        bevy::input::InputPlugin::default(),
        // The ScheduleRunnerPlugin handles the app run loop. In a headless Bevy
        // app (no window) using the schedule runner with no frame wait
        // configured, the loop runs as fast as possible (busy-loop on native),
        // consuming a core. Run at 60 fps so it doesn't melt your CPU.
        bevy::app::ScheduleRunnerPlugin::run_loop(
            std::time::Duration::from_secs_f64(1.0 / 60.0),
        )
    ));

    // Exit with Ctrl+C
    app.run();
}

Headless app with default features and windowing disabled

If you need to keep the windowing features, you can disable the WindowPlugin and WinitPlugin to run the app in headless mode.

[!TIP] Bevy REPL still runs in the terminal even if you spawn windows, so this is probably only useful if you are running the app in CI or some other headless environment.

use bevy::{
    prelude::*, // WindowPlugin is in the prelude
    window::ExitCondition,
    winit::WinitPlugin,
};

fn main() {
    let mut app = App::new();

    app.add_plugins((
        DefaultPlugins
            .set(WindowPlugin {
                // Don't make a new window at startup
                primary_window: None,
                // Don’t automatically exit due to having no windows.
                // Instead, run until an `AppExit` event is produced.
                exit_condition: ExitCondition::DontExit,
                ..default()
            })
            // WinitPlugin will panic in environments without a display server.
            .disable::<WinitPlugin>(),
        // We still want to set the FPS so the app doesn't melt the CPU.
        // ScheduleRunnerPlugin replaces the bevy_winit app runner, though, so
        // disabling the windowing plugins is redundant.
        bevy::app::ScheduleRunnerPlugin::run_loop(
            std::time::Duration::from_secs_f64(1.0 / 60.0),
        ),
    ));

    // Exit with Ctrl+C
    app.run();
}