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 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:
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:
(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!
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.
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.
// 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:
Translation
(because less data needs to be accessed)Translation
won't block systems accessing Rotation
.However this approach also has some pretty serious downsides:
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.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.
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!");
}
}
}
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.
I implemented "read only" traits for queries that don't mutate anything. This allows us to guarantee that a query won't mutate anything.
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.
As a result of these optimizations, direct component lookup is much faster than it used to be:
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.
IOTaskPool
, ComputePool
, and AsyncComputePool
in bevy_tasks
crate.ParallelIterator
trait.
query.iter().par_iter(batch_size).for_each(/* ... */)
Or
in ECS queries.unload()
and unload_sync()
on SceneSpawner
for unloading scenes..AudioOuput
is now able to play anything Decodable
.Color::hex
for creating Color
from string hex values.
Color::rgb_u8
and Color::rgba_u8
.bevy_render::pass::ClearColor
to prelude.SpriteResizeMode
may choose how Sprite
resizing should be handled. Automatic
by default.Input<T>
for iterator access to keys.
get_pressed()
, get_just_pressed()
, get_just_released()
Copy
for MouseScrollUnit
.Clone
for UI component bundles.AppBuilder::add_startup_stage_|before/after
get_id_mut
Assets::get_id_mut
-> Assets::get_with_id_mut
DrawableText
render
feature, which makes the entire render pipeline optional.A huge thanks to the 87 contributors that made this release (and associated docs) possible!