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:
You can try out the Bevy Android example by following the instructions here. While many things work, please note that this is very hot off the presses. Some features will work and others probably won't. Now is a great time to dive in and help us close the gaps!
This was a massive group effort that spanned multiple projects:
bevy_assetbackend using Android Asset Manager (@enfipy)
Bevy can now run on iOS!
This was another large group effort that spanned multiple projects:
@mrk-its has been hard at work on expanding Bevy's WASM support. In this release we landed WASM asset loading. You can now load assets when you publish to WASM just like you would on any other platform:
If the asset hasn't already been loaded, this will make a
fetch() request to retrieve the asset over HTTP.
@mrk-its has also been building a custom WebGL2
bevy_render backend. It is already pretty usable, but its not quite ready yet. Expect more news on this soon!
Bevy now has support for touches:
You can also consume raw touch events using the
Assets are now automatically freed when their "handle reference count" reaches zero. This means you no longer need to think about freeing assets manually:
// Calling load() now returns a strong handle: let handle = asset_server.load; // Note that you no longer need to unwrap() loaded handles. Ergonomics for the win! // Cloning a handle increases the reference count by one let second_handle = handle.clone; // Spawn a sprite and give it our handle commands.spawn; // Later in some other system: commands.despawn; // There are no more active handles to "sprite.png", so it will be freed before the next update
In past releases,
AssetLoaders could only produce a single asset of a single type. In Bevy 0.3, they can now produce any number of assets for any type. The old behavior was extremely limiting when loading assets like GLTF files, which might produce many meshes, textures, and scenes.
Sometimes you only want to load a specific asset from an asset source. You can now load sub assets like this:
// Mesh0/Primitive0 references the first mesh primitive in "my_scene.gltf" let mesh = asset_server.load;
AssetServer is now backed by the
AssetIo trait. This allows us to load assets from whatever storage we want. This means on desktop we now load from the filesystem, on Android we use the Android Asset Manager, and on the web we make HTTP requests using the
Assets can now depend on other assets, which will automatically be loaded when the original asset is loaded. This is useful when loading something like a "scene" which might reference other asset sources. We utilize this in our new GLTF loader.
This might rustle some feathers, but
AssetServer::load_sync() had to go! This api wasn't WASM friendly, encouraged users to block game execution for the sake of convenience (which causes "hitching"), and was incompatible with the new AssetLoader api. Asset loading is now always asynchronous. Users of
load_sync() should instead
load() their assets, check load status in their systems, and change game state accordingly.
Up until this point, the GLTF loader was painfully limited. It could only load the first mesh with a single texture in a GLTF file. For Bevy 0.3, we took advantage of the asset system improvements to write a new
GltfLoader that loads GLTF files as Bevy
Scenes, along with all meshes and textures in the files.
Here's Bevy loading the Khronos Flight Helmet example, which consists of multiple meshes and textures!
Here is the complete code for a system that loads a GLTF file and spawns it as a scene:
In this release I finally was able to remove the one thing I truly despised in Bevy ECS. In previous versions of Bevy, iterating over the components in a
Query looked like this:
for in &mut query.iter // Or if you preferred you could do this for in query.iter.iter
Similarly, retrieving a specific entity's component's looked like this:
if let Ok = query.entity
In Bevy 0.3 you can just do this:
// iteration for in query.iter // entity lookup if let Ok = query.get
You might naturally be thinking something like:
Why did this take so long? Why would removing a single
&mut be hard?
It's a long story! In summary:
query.iter()didn't actually return an iterator. It returned a wrapper that held an atomic lock on the component storages. The same was true for the type returned by
Fortunately we finally found ways to solve all of these problems. The newly added
QuerySets allow us to completely remove the locks (and wrapper types). And by completely rewriting
QueryIter we were able to avoid the performance hit that removing the wrapper incurred. Read on for the details!
Bevy ECS is now completely lock free. In Bevy 0.2, we made direct
World access and "for-each" systems lock free. This is possible because the Bevy ECS scheduler ensures that systems only run in parallel in ways that respect Rust's mutability rules.
We couldn't remove locks from
Query systems because of systems like this:
The locks ensured that the second
q1.get_mut(some_entity) access panicked, keeping us nice and safe. In Bevy 0.3, a system like
conflicting_query_system will fail when the schedule is constructed. By default, systems cannot have conflicting queries.
However there are some cases where a system needs conflicting queries to do what it needs to do. For these cases, we added
By putting our conflicting
Queries in a
QuerySet, the Rust borrow checker protects us from unsafe query accesses.
Because of this, we were able to remove all safety checks from
query.get(entity), which means these methods are now exactly as fast as their
World counterparts (which we made lock-free in Bevy 0.2).
Bevy had a number of nice performance improvements this release:
QueryIterto be simpler (and therefore easier to control optimizations for), which allowed us to remove the iterator wrapper without tanking performance. This also resolved some performance inconsistencies where some system permutations performed optimally and others didn't. Now everything is on the "fast path"!
Note: these numbers are for getting a component 100,000 times, not for an individual component lookup
This is where the big wins were. By removing locks and safety checks from Query systems, we were able to significantly reduce the cost of retrieving a specific entity's component from within a system.
I included a comparison to Legion ECS (another great archetypal ECS with a parallel scheduler) to illustrate why Bevy's new approach is so cool. Legion exposes a direct "world like" api (called a SubWorld) in its systems. The SubWorld's entry api cannot know ahead of time what types will be passed into it, which means it must do (relatively) expensive safety checks to ensure the user doesn't request access to something they shouldn't.
Bevy's scheduler pre-checks
Queries once ahead of time, which allows systems to access their results without any additional checks.
The test was to lookup (and modify) a specific entity's component 100,000 times on each system iteration. Here is a quick rundown of how these tests were performed in each case:
Query<&mut A>that accesses the component using
let entry = world.entry(entity); entry.get_component_mut::<A>()
let entry = world.entry(entity); entry.get_component_mut::<A>()
It's worth noting that using
query.get_component::<T>(entity) instead of
query.get(entity) does require safety checks, for the same reason the legion entry api does. We cannot know ahead of time what component type a caller will pass into the method, which means we must check it to make sure it matches the
Additionally, here are some relevant ecs_bench_suite results (omitted benchmarks had no significant change):
Some resource types cannot (or should not) be passed between threads. This is often true for low level apis like windowing, input, and audio. It is now possible to add "thread local resources" to the
Resources collection, which can only be accessed from the main thread using "thread local systems":
// in your app setup app.add_thread_local_resource; // a thread local system
First, to improve clarity we renamed
query.get_component::<Component>(entity). We now return the "full" query result for a specific entity using
To allow multiple concurrent reads of Queries (where it is safe), we added separate
query.iter_mut() apis, as well as
query.get_mut(entity). Queries that are "read only" can now retrieve their results via an immutable borrow.
Bevy meshes used to require exactly three "vertex attributes":
uv. This worked for most things, but there are a number of cases that require other attributes, such as "vertex colors" or "bone weights for animation". Bevy 0.3 adds support for custom vertex attributes. Meshes can define whatever attributes they want and shaders can consume whatever attributes they want!
Here is an example that illustrates how to define a custom shader that consumes a mesh with an added "vertex color" attribute.
Rendering meshes often involves using vertex "indices" to cut down on duplicate vertex information. Bevy used to hard code the precision of these indices to
u16, which was too small for some cases. Now render pipelines can "specialize" based on a configured index buffer, which now defaults to
u32 to cover most use cases.
Transforms are important to get right. They are used in many slices of the engine, user code touches them constantly, and they are relatively expensive to compute: especially transform hierarchies.
In the last release, we vastly simplified Bevy's transform system to use a consolidated
GlobalTransform instead of multiple separate
Scale components (which were synced to
GlobalTransform). This made the user-facing api/dataflow simpler, as well as the underlying implementation. The
Transform component was backed by a 4x4 matrix. I pressed the big green "merge" button ... happy that we had solved the Transform problem once and for all!
It turns out there was still more work to be done! @AThilenius pointed out that using a 4x4 matrix as the source of truth for an affine transform accumulates error over time. Additionally, the Transform api was still a little cumbersome to use. At the suggestion of @termhn we decided to investigate using a "similarity" as the source of truth. This had the following benefits:
We collectively decided this was a good path forward and now we have a re-rewrite that is even better. Yes this is another breaking change, but thats why we label Bevy as being in the "experimentation phase". Now is the time to break things as often as possible to ensure that we find good apis that will stand the test of time.
This is what the new
Transform api looks like in a Bevy ECS system:
Compared to the last version this is easier to use, more correct, and should also be slightly faster.
The newly added
GamepadSettings resource gives developers the ability to customize gamepad settings on a per-controller, per-axis/button basis:
If you've used Bevy, you're probably familiar with this part of
This adds the plugins for all of the "core" engine functionality (rendering, input, audio, windowing, etc). It was straightforward, but also very static. What if you don't want to add all of the default plugins? What if you want to create your own custom set of plugins?
To resolve this, we added
PluginGroups, which are ordered collections of plugins that can be individually enabled or disabled:
// This: app.add_default_plugins // Has been replaced by this: app.add_plugins // You can disable specific plugins in a PluginGroup: app.add_plugins_with; // And you can create your own PluginGroups: ; app.add_plugins;
Bevy provides a backend-agnostic windowing api. Up until this point, window settings could only be set once at app startup. If you wanted to set window settings dynamically, you had to directly interact with window backends (ex: winit).
In this release we added the ability to dynamically set window properties at runtime using the Bevy window abstraction:
// This system dynamically sets the window title to the number of seconds since startup. Because why not?
bevy crate documentation search function now returns results for all sub-crates (like bevy_sprite). Due to how documentation is generated for re-exported crates, by default the
bevy search index only covered the "prelude". @memoryruins found a way to fix this problem by creating new modules and exporting the contents of each crate within those modules (as opposed to aliasing the crates).
bevy_input::touch: implement touch input
PluginGroupis a collection of plugins where each plugin can be enabled or disabled.
Commands::write_world_boxedtakes a pre-boxed world writer to the ECS's command queue
FrameTimeDiagnosticsPluginnow shows "frame count" in addition to "frame time" and "fps"
WgpuPowerOptionsfor choosing between low power, high performance, and adaptive power
Debugfor more types: #597, #632
-Zrun-dsymutil-nofor faster compilation on MacOS
query.iter()is now a real iterator!
QuerySetallows working with conflicting queries and is checked at compile-time.
WindowDescriptoron wasm32 targets to optionally provide an existing canvas element as winit window
ArchetypeAccesstracks mutable & immutable deps
Color::rgbawill be converted to linear sRGB.
Color::rgba_linearwill accept colors already in linear sRGB (the old behavior)
Meshoverhaul with custom vertex attributes
Local<T>system resources #745
IndexFormatas a property
A huge thanks to the 59 contributors that made this release (and associated docs) possible!