Open-Source Wikis

/

Bevy

/

Bevy

/

Architecture

bevyengine/bevy

Architecture

Bevy is an opinionated composition of small crates around a single core idea: an App owns one or more Worlds, and a Schedule runs Systems that read and mutate those worlds. Every other feature — rendering, audio, UI, assets — is a Plugin that adds resources, components, and systems to the App.

This page is the map. Detailed treatment of each crate lives in Packages; foundational ECS types are in Primitives.

The four-layer mental model

graph TD
    A[App / Plugins<br/>bevy_app] --> B[ECS<br/>bevy_ecs]
    A --> C[Reflect / Math / Tasks<br/>bevy_reflect, bevy_math, bevy_tasks]
    B --> D[Scenes & Assets<br/>bevy_scene, bevy_asset, bevy_gltf]
    B --> E[Windowing & Input<br/>bevy_winit, bevy_window, bevy_input, bevy_gilrs]
    B --> F[Renderer<br/>bevy_render, bevy_core_pipeline, bevy_pbr, bevy_sprite_render, bevy_ui_render]
    B --> G[Audio / Animation / UI<br/>bevy_audio, bevy_animation, bevy_ui]
    B --> H[Tooling<br/>bevy_dev_tools, bevy_diagnostic, bevy_remote, bevy_log]

The arrows are roughly "depends on" relationships in the workspace. Crates near the top are foundational; crates near the bottom are end-user features.

Workspace layout

The repository is a Cargo workspace defined in the root Cargo.toml. Members fall into four buckets:

Where What's there
crates/ The 60 official bevy_* crates that make up the engine.
examples/ 400+ runnable examples grouped by topic (2d/, 3d/, ecs/, shader/, ui/, etc).
tools/ Build/CI helpers — tools/ci, tools/build-wasm-example, tools/example-showcase, tools/build-templated-pages.
benches/, tests/, tests-integration/ Benchmarks and integration tests.

The top-level bevy crate (src/lib.rs) is a thin re-export of bevy_internal. Most users depend on bevy directly with cargo features that toggle whole subsystems on or off (see reference/configuration).

The App lifecycle

bevy_app::App is the top-level container. Its lifecycle has three phases:

sequenceDiagram
    participant U as User code
    participant App as App
    participant Plugins as Plugin chain
    participant World as World(s)
    participant Runner as App runner

    U->>App: App::new()
    U->>App: add_plugins(DefaultPlugins)
    App->>Plugins: Plugin::build(&mut App)
    Plugins->>World: register components, resources, systems
    U->>App: run()
    App->>Plugins: Plugin::ready / finish / cleanup
    App->>Runner: invoke runner (winit event loop or ScheduleRunner)
    loop Each frame / tick
        Runner->>World: World::run_schedule(Main)
    end

The runner is pluggable. With windowing, bevy_winit::WinitPlugin installs a runner driven by the OS event loop. Without windowing, bevy_app::ScheduleRunnerPlugin provides a headless runner used for servers, tests, and CLI tools.

The full default plugin chain is defined in crates/bevy_internal/src/default_plugins.rs via the plugin_group! macro. DefaultPlugins includes ~40 plugins covering panic handling, logging, task pools, time, transforms, diagnostics, input, windowing, assets, scenes, rendering, sprites, text, UI, PBR, animation, gizmos, audio, gilrs, dev tools, and state. MinimalPlugins is the headless equivalent — task pool, time, frame count, schedule runner.

SubApps and the rendering boundary

A World and its Schedules live inside a SubApp. The main game logic runs in the main SubApp. The renderer runs in a separate RenderSubApp whose world is built by extracting data from the main world each frame.

graph LR
    subgraph Main SubApp
        M1[Main schedule] --> M2[PreUpdate]
        M2 --> M3[Update]
        M3 --> M4[PostUpdate]
        M4 --> M5[Last]
    end
    subgraph Render SubApp
        R1[ExtractSchedule] --> R2[Render]
        R2 --> R3[Cleanup]
    end
    M4 -.extract.-> R1

The ExtractSchedule is the only safe bridge: it runs while both worlds are paused and copies the data the renderer will read for this frame (camera positions, mesh instances, lights, UI nodes). Once extraction finishes, the main world is unblocked and the next frame begins computing while the GPU work for this frame is still being prepared. This is what makes Bevy's renderer pipelined.

Implementation: crates/bevy_render/src/lib.rs defines RenderSubApp and RenderApp. The render schedule is built around crates/bevy_render/src/render_graph (a DAG of render nodes that execute on the GPU command buffer).

The frame loop

A typical frame in a default Bevy app does this, in order:

  1. First / PreStartup / Startup / PostStartup — only on the first tick. Initial state setup.
  2. First — frame counters, time updates.
  3. PreUpdate — input gathering (bevy_input, bevy_gilrs), window event processing.
  4. StateTransitionbevy_state schedules transitions if a state was queued.
  5. RunFixedMainLoop — runs the fixed-timestep schedule (FixedUpdate) zero or more times based on accumulated time.
  6. Update — user game systems. Most of an app lives here.
  7. PostUpdate — transform propagation, visibility computation, animation, asset events, UI layout.
  8. Last — diagnostic logging, deferred despawns.
  9. Render — extract → prepare → queue → render. Runs in the render SubApp.

Schedule labels are defined in crates/bevy_app/src/main_schedule.rs. The fixed-timestep machinery is crates/bevy_app/src/main_schedule.rs (RunFixedMainLoopSystem) plus crates/bevy_time/src/fixed.rs.

The render pipeline

Inside the render SubApp, each frame walks four phases:

graph LR
    Extract -->|extracted entities| Prepare
    Prepare -->|GPU resources, bind groups| Queue
    Queue -->|draw commands per phase| Render
    Render --> GPU[Submit to wgpu]
  • Extract — copy the data the renderer needs from the main world.
  • Prepare — upload buffers, create bind groups, allocate per-frame GPU state.
  • Queue — sort draws into render phases (opaque, alpha-mask, transparent, shadow, etc.) per view.
  • Render — walk the render graph and record wgpu::CommandBuffers.

Built-in render nodes live in crates/bevy_core_pipeline/src (clear, tonemapping, bloom, tone-mapping LUT) and in crates/bevy_pbr/src/render (mesh draw, prepass, deferred, shadows). Sprite and UI rendering are in crates/bevy_sprite_render and crates/bevy_ui_render.

For the full picture see the Rendering pipeline feature page.

Plugin composition

Plugins are the unit of composition. A plugin is anything that implements bevy_app::Plugin:

use bevy::prelude::*;

pub struct MyPlugin;

impl Plugin for MyPlugin {
    fn build(&self, app: &mut App) {
        app.insert_resource(MyConfig::default())
           .add_systems(Update, my_system);
    }
}

Plugin groups (PluginGroup) bundle related plugins and let you disable individuals. DefaultPlugins.build().disable::<AudioPlugin>() is a common pattern when you want everything except a specific subsystem. The group machinery is in crates/bevy_app/src/plugin_group.rs.

Threading and parallelism

Bevy is multi-threaded by default. bevy_tasks provides three task pools:

  • ComputeTaskPool — the parallel system executor uses this. Most systems are pure CPU work and run here.
  • AsyncComputeTaskPool — long-running work spawned by users.
  • IoTaskPool — disk and network I/O for the asset server.

The schedule executor in crates/bevy_ecs/src/schedule/executor/multi_threaded.rs looks at the access patterns each system declares (which components/resources it reads and writes) and runs systems in parallel whenever there is no conflict. This is the central reason Bevy uses an archetypal ECS — the access graph is computable from query types.

When the multi_threaded feature is off (or on Wasm without threads), the single-threaded executor in crates/bevy_ecs/src/schedule/executor/single_threaded.rs runs systems in registration order on the calling thread.

What's not here

A few things you might expect from a "monolithic" engine that Bevy deliberately leaves out (or scopes narrowly):

  • No engine binary. There is no bevy_editor.exe shipped from this repo. The editor is a separate, work-in-progress project.
  • No physics. Use a third-party crate like avian or bevy_rapier.
  • No networking. Use bevy_replicon or similar.
  • No scripting language. Game logic is Rust. There is community tooling for scripting via plugins.

Bevy's modularity is a feature: every subsystem is a separate crate so you can replace or omit it. The bevy umbrella crate exists for convenience, but plenty of users depend on a curated subset of bevy_* crates instead.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Architecture – Bevy wiki | Factory