Bevy 0.2

Posted on September 19, 2020 by Carter Anderson ( A silhouette of a figure with cat ears waving a tentacle, or Octocat: GitHub's mascot and logo @cart A vector art of a grey bird flying; former logo of X (formerly Twitter) @cart_cart A triangle pointing right in a rounded rectangle; Youtube's logo cartdev )

A month after the initial Bevy release, and thanks to 87 contributors, 174 pull requests, and our generous sponsors, I'm happy to announce the Bevy 0.2 release on crates.io!

For those who don't know, Bevy is a refreshingly simple data-driven game engine built in Rust. You can check out The Quick Start Guide to get started. Bevy is also free and open source forever! You can grab the full source code on GitHub.

Here are some of the highlights from this release:

Async Task System #

authors: @lachlansneff and @aclysma

Bevy uses multi-threading throughout the engine: ECS scheduling, asset loading, rendering, etc. Before this release it used Rayon for almost all of these tasks. Rayon is nice because it is generally as simple as calling some_list.par_iter().for_each(|x| do_something(x)). Rayon then automatically breaks the for_each into tasks and runs them on as many cores as it can. Rayon is a great choice if you want to easily parallelize code, but it has the downside of being pretty cpu-hungry.

Bevy (and a number of other rust game engines and ecs frameworks using rayon) have received feedback that they were overly cpu hungry / usage was not proportional to "real" work done.

We decided to resolve this problem by building a custom async-friendly task system, which enables the creation of context-specific task pools. For example, you might have separate pools for compute, IO, networking, etc. This also gives us the flexibility to load balance work appropriately according to work type and/or priority. The cpu usage wins have been huge:

Total Combined Percent CPU Usage - 8 Core Machine (smaller is better) #

threading cpu usage 8 core

Total Combined Percent CPU Usage - 32 Core Machine (smaller is better) #

threading cpu usage 32 core

Initial Web Platform Support #

authors: @smokku

(A subset of) Bevy now runs on the web using WebAssembly/WASM! Specifically, Bevy apps can run Bevy ECS schedules, react to input events, create an empty canvas (using winit), and a few other things. This is a huge first step, but it is important to call out that there are still a number of missing pieces, such as 2D/3D rendering, multi-threading, and sound.

Those limitations haven't stopped @mrk-its from building the first WASM Bevy game!

bevy-robbo (playable here) #

bevy-robbo

They use Bevy for game logic and cleverly work around the render limitations by passing ASCII art game state from this Bevy system to this JavaScript function.

You can play around with some Bevy WASM examples by following the instructions here.

Parallel Queries #

authors: @GrantMoyer

Bevy ECS Queries are a flexible way to retrieve data from the Entity Component System. Systems that use queries already run in parallel, but before this change the queries themselves could not be iterated in parallel. Bevy 0.2 adds the ability to easily iterate queries in parallel:

fn system(pool: Res<ComputeTaskPool>, mut query: Query<&mut Transform>) {
    query.iter().par_iter(32).for_each(&pool, |mut transform| {
      transform.translate(Vec3::new(1.0, 0.0, 0.0));
    });
}

This provides a nice functional API (similar to Rayon) that runs on top of the new bevy_tasks system. It breaks the query up into 32 "batches" and runs each batch as a different task in the bevy task system.

Transform System Rewrite #

authors: @MarekLg
// old
fn system(translation: &Translation, rotation: &Rotation, scale: &Scale) {
  println!("{} {} {}", translation.0, rotation.0, scale.0);
}

// new
fn system(transform: &Transform) {
  println!("{} {} {}", transform.translation(), transform.rotation(), transform.scale());
}

Bevy's old transform system used separate Translation, Rotation, and Scale components as the "source of truth". Users modified with these components in their systems, after which they were synced to a LocalTransform component, which was in turn synced to a global Transform component, taking hierarchy into account. This was nice for a couple of reasons:

  • Slightly more cache efficient to retrieve individual components like Translation (because less data needs to be accessed)
  • Theoretically more parallel-friendly. Systems that only access Translation won't block systems accessing Rotation.

However this approach also has some pretty serious downsides:

  • The "individual components" are the source of truth, so LocalTransform is out of date when user systems are running. If an up to date "full transform" is needed, it must be manually constructed by accessing all three components.
  • Very hard to reason about. There are 5 components users need to think about and they all interact with each other differently.
  • Setting a Transform to a specific matrix value (ex: Mat4::look_at()) was extremely cumbersome, and the value would be immediately overwritten unless the user explicitly disabled component syncing.

Given these issues, we decided to move to a single unified local-to-parent Transform component as the source of truth, and a computed GlobalTransform component for world-space transforms. We think this API will be much easier to use and to reason about. Unity is also considering a similar Transform rework for their ECS and a lot of discussion on this topic happened in this Amethyst Forum Thread.

Joystick/Gamepad Input #

authors: @simpuid

The Bevy Input plugin now has cross-platform support for most controllers thanks to the gilrs library!

fn button_system(gamepads: Res<Vec<Gamepad>>, button_input: Res<Input<GamepadButton>>) {
    for gamepad in gamepads.iter() {
        if button_input.just_pressed(GamepadButton(*gamepad, GamepadButtonType::RightTrigger)) {
            println!("Pressed right trigger!");
        }
    }
}

Bevy ECS Performance Improvements #

authors: @cart

Generational Entity IDs #

We changed Entity IDs from being random UUIDs to incrementing generational indices. Random UUIDs were nice because they could be created anywhere, were unique across game runs, and could be safely persisted to files or reused across networks. I was really hoping we could make them work, but they ended up being too slow relative to the alternatives. The randomness had a measurable cost and entity locations had to be looked up using a hash map.

By moving to generational indices (we use the hecs implementation), we can directly use entity ids as array indices, which makes entity location lookups lightning fast.

Read Only Queries #

I implemented "read only" traits for queries that don't mutate anything. This allows us to guarantee that a query won't mutate anything.

Removed locking from World APIs #

This gives us a really nice speed boost. We can do this safely due to a combination of the new "read only queries" and changing World mutation APIs to be a mutable World borrow.

This is not yet enabled for Queries in systems because a system could have multiple Queries, which could be simultaneously accessed in a way that doesn't make mutable access unique. I think thats a solve-able problem, but it will take a bit more work. Fortunately "for-each" systems don't have any collision risk, so we now use lock-less queries there.

Direct component lookup (in nanoseconds, smaller is better) #

As a result of these optimizations, direct component lookup is much faster than it used to be:

get_component graph

Note that this benchmark used world.get::<T>(entity). query.get::<T>(entity) should have results similar to the hecs results because it still uses a lock. Eventually I'm hoping that we can remove locks from system queries too.

Change Log #

Added #

Changed #

Fixed #

Internal Improvements #

Many improvements to Bevy's CI: #325, #349, #357, #373, #423.

Contributors #

A huge thanks to the 87 contributors that made this release (and associated docs) possible!

  • 0x22fe
  • 8bit-pudding
  • aarongeorge
  • ablakey
  • aclysma
  • adekau
  • aevyrie
  • AmionSky
  • andreheringer
  • AngelOnFira
  • ashneverdawn
  • BafDyce
  • BimDav
  • bitshifter
  • Bobox214
  • Boiethios
  • caelunshun
  • cart
  • CleanCut
  • dallenng
  • DGriffin91
  • Dispersia
  • DJMcNab
  • eliaspekkala
  • EllenNyan
  • eXodiquas
  • figsoda
  • Fishrock123
  • FSMaxB
  • GabLotus
  • GrantMoyer
  • guimcaballero
  • Halfwhit
  • hannobraun
  • IceSentry
  • ifletsomeclaire
  • Incipium
  • io12
  • jakerr
  • jamadazi
  • joejoepie
  • JohnDoneth
  • julhe
  • kaflu
  • karroffel
  • lachlansneff
  • lberrymage
  • logannc
  • Lowentwickler
  • MarekLg
  • MatteoGgl
  • memoryruins
  • mfrancis107
  • MGlolenstine
  • MichaelHills
  • MilanVasko
  • Moxinilian
  • mrk-its
  • mtsr
  • multun
  • naithar
  • ncallaway
  • ndarilek
  • OptimisticPeach
  • PrototypeNM1
  • reidbhuntley
  • RobDavenport
  • saicu
  • simpuid
  • SmiteWindows
  • smokku
  • StarArawn
  • stefee
  • tarkah
  • TehPers
  • Telzhaak
  • TheNeikos
  • thirdsgames
  • TomBebb
  • tristanpemble
  • verzuz
  • VitalyAnkh
  • w1th0utnam3
  • Waridley
  • wyhaya
  • Xavientois
  • zicklag