Bevy 0.2
Posted on September 19, 2020 by Carter Anderson ( @cart @cart_cart 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 #
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) #
Total Combined Percent CPU Usage - 32 Core Machine (smaller is better) #
Initial Web Platform Support #
(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) #
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 #
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 #
// 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 accessingRotation
.
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 #
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 #
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:
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 #
- Task System for Bevy
- Replaces rayon with a custom designed task system that consists of several "TaskPools".
- Exports
IOTaskPool
,ComputePool
, andAsyncComputePool
inbevy_tasks
crate.
- Parallel queries for distributing work over with the
ParallelIterator
trait.- e.g.
query.iter().par_iter(batch_size).for_each(/* ... */)
- e.g.
- Added gamepad support using Gilrs
- Implement WASM support for bevy_winit
- Create winit canvas under WebAssembly
- Implement single threaded task scheduler for WebAssembly
- Support for binary glTF (.glb).
- Support for
Or
in ECS queries. - Added methods
unload()
andunload_sync()
onSceneSpawner
for unloading scenes.. - Custom rodio source for audio.
AudioOuput
is now able to play anythingDecodable
.
Color::hex
for creatingColor
from string hex values.- Accepts the forms RGB, RGBA, RRGGBB, and RRGGBBAA.
Color::rgb_u8
andColor::rgba_u8
.- Added
bevy_render::pass::ClearColor
to prelude. SpriteResizeMode
may choose howSprite
resizing should be handled.Automatic
by default.- Added methods on
Input<T>
for iterator access to keys.get_pressed()
,get_just_pressed()
,get_just_released()
- Derived
Copy
forMouseScrollUnit
. - Derived
Clone
for UI component bundles. - Some examples of documentation
- Update docs for Updated, Changed and Mutated
- Tips for faster builds on macOS: #312, #314, #433
- Added and documented cargo features
- Added more instructions for Linux dependencies
- Arch / Manjaro, NixOS, Ubuntu and Solus
- Provide shell.nix for easier compiling with nix-shell
- Add
AppBuilder::add_startup_stage_|before/after
Changed #
- Transform rewrite
- Use generational entity ids and other optimizations
- Optimize transform systems to only run on changes.
- Send an AssetEvent when modifying using
get_id_mut
- Rename
Assets::get_id_mut
->Assets::get_with_id_mut
- Support multiline text in
DrawableText
- iOS: use shaderc-rs for glsl to spirv compilation
- Changed the default node size to Auto instead of Undefined to match the Stretch implementation.
- Load assets from root path when loading directly
- Add
render
feature, which makes the entire render pipeline optional.
Fixed #
- Properly track added and removed RenderResources in RenderResourcesNode.
- Fixes issues where entities vanished or changed color when new entities were spawned/despawned.
- Fixed sprite clipping at same depth
- Transparent sprites should no longer clip.
- Check asset path existence
- Fixed deadlock in hot asset reloading
- Fixed hot asset reloading on Windows
- Allow glTFs to be loaded that don't have uvs and normals
- Fixed archetypes_generation being incorrectly updated for systems
- Remove child from parent when it is despawned
- Initialize App.schedule systems when running the app
- Fix missing asset info path for synchronous loading
- fix font atlas overflow
- do not assume font handle is present in assets
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