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!
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:
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 // new
Bevy's old transform system used separate
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)
Translationwon't block systems accessing
However this approach also has some pretty serious downsides:
LocalTransformis 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!
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
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.
query.iter().par_iter(batch_size).for_each(/* ... */)
Orin ECS queries.
SceneSpawnerfor unloading scenes..
AudioOuputis now able to play anything
Colorfrom string hex values.
SpriteResizeModemay choose how
Spriteresizing should be handled.
Input<T>for iterator access to keys.
Clonefor UI component bundles.
renderfeature, which makes the entire render pipeline optional.
A huge thanks to the 87 contributors that made this release (and associated docs) possible!