bevy/crates/bevy_ecs/src/query/fetch.rs

1824 lines
56 KiB
Rust
Raw Normal View History

Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
use crate::{
Dynamic queries and builder API (#9774) # Objective Expand the existing `Query` API to support more dynamic use cases i.e. scripting. ## Prior Art - #6390 - #8308 - #10037 ## Solution - Create a `QueryBuilder` with runtime methods to define the set of component accesses for a built query. - Create new `WorldQueryData` implementations `FilteredEntityMut` and `FilteredEntityRef` as variants of `EntityMut` and `EntityRef` that provide run time checked access to the components included in a given query. - Add new methods to `Query` to create "query lens" with a subset of the access of the initial query. ### Query Builder The `QueryBuilder` API allows you to define a query at runtime. At it's most basic use it will simply create a query with the corresponding type signature: ```rust let query = QueryBuilder::<Entity, With<A>>::new(&mut world).build(); // is equivalent to let query = QueryState::<Entity, With<A>>::new(&mut world); ``` Before calling `.build()` you also have the opportunity to add additional accesses and filters. Here is a simple example where we add additional filter terms: ```rust let entity_a = world.spawn((A(0), B(0))).id(); let entity_b = world.spawn((A(0), C(0))).id(); let mut query_a = QueryBuilder::<Entity>::new(&mut world) .with::<A>() .without::<C>() .build(); assert_eq!(entity_a, query_a.single(&world)); ``` This alone is useful in that allows you to decide which archetypes your query will match at runtime. However it is also very limited, consider a case like the following: ```rust let query_a = QueryBuilder::<&A>::new(&mut world) // Add an additional access .data::<&B>() .build(); ``` This will grant the query an additional read access to component B however we have no way of accessing the data while iterating as the type signature still only includes &A. For an even more concrete example of this consider dynamic components: ```rust let query_a = QueryBuilder::<Entity>::new(&mut world) // Adding a filter is easy since it doesn't need be read later .with_id(component_id_a) // How do I access the data of this component? .ref_id(component_id_b) .build(); ``` With this in mind the `QueryBuilder` API seems somewhat incomplete by itself, we need some way method of accessing the components dynamically. So here's one: ### Query Transmutation If the problem is not having the component in the type signature why not just add it? This PR also adds transmute methods to `QueryBuilder` and `QueryState`. Here's a simple example: ```rust world.spawn(A(0)); world.spawn((A(1), B(0))); let mut query = QueryBuilder::<()>::new(&mut world) .with::<B>() .transmute::<&A>() .build(); query.iter(&world).for_each(|a| assert_eq!(a.0, 1)); ``` The `QueryState` and `QueryBuilder` transmute methods look quite similar but are different in one respect. Transmuting a builder will always succeed as it will just add the additional accesses needed for the new terms if they weren't already included. Transmuting a `QueryState` will panic in the case that the new type signature would give it access it didn't already have, for example: ```rust let query = QueryState::<&A, Option<&B>>::new(&mut world); /// This is fine, the access for Option<&A> is less restrictive than &A query.transmute::<Option<&A>>(&world); /// Oh no, this would allow access to &B on entities that might not have it, so it panics query.transmute::<&B>(&world); /// This is right out query.transmute::<&C>(&world); ``` This is quite an appealing API to also have available on `Query` however it does pose one additional wrinkle: In order to to change the iterator we need to create a new `QueryState` to back it. `Query` doesn't own it's own state though, it just borrows it, so we need a place to borrow it from. This is why `QueryLens` exists, it is a place to store the new state so it can be borrowed when you call `.query()` leaving you with an API like this: ```rust fn function_that_takes_a_query(query: &Query<&A>) { // ... } fn system(query: Query<(&A, &B)>) { let lens = query.transmute_lens::<&A>(); let q = lens.query(); function_that_takes_a_query(&q); } ``` Now you may be thinking: Hey, wait a second, you introduced the problem with dynamic components and then described a solution that only works for static components! Ok, you got me, I guess we need a bit more: ### Filtered Entity References Currently the only way you can access dynamic components on entities through a query is with either `EntityMut` or `EntityRef`, however these can access all components and so conflict with all other accesses. This PR introduces `FilteredEntityMut` and `FilteredEntityRef` as alternatives that have additional runtime checking to prevent accessing components that you shouldn't. This way you can build a query with a `QueryBuilder` and actually access the components you asked for: ```rust let mut query = QueryBuilder::<FilteredEntityRef>::new(&mut world) .ref_id(component_id_a) .with(component_id_b) .build(); let entity_ref = query.single(&world); // Returns Some(Ptr) as we have that component and are allowed to read it let a = entity_ref.get_by_id(component_id_a); // Will return None even though the entity does have the component, as we are not allowed to read it let b = entity_ref.get_by_id(component_id_b); ``` For the most part these new structs have the exact same methods as their non-filtered equivalents. Putting all of this together we can do some truly dynamic ECS queries, check out the `dynamic` example to see it in action: ``` Commands: comp, c Create new components spawn, s Spawn entities query, q Query for entities Enter a command with no parameters for usage. > c A, B, C, Data 4 Component A created with id: 0 Component B created with id: 1 Component C created with id: 2 Component Data created with id: 3 > s A, B, Data 1 Entity spawned with id: 0v0 > s A, C, Data 0 Entity spawned with id: 1v0 > q &Data 0v0: Data: [1, 0, 0, 0] 1v0: Data: [0, 0, 0, 0] > q B, &mut Data 0v0: Data: [2, 1, 1, 1] > q B || C, &Data 0v0: Data: [2, 1, 1, 1] 1v0: Data: [0, 0, 0, 0] ``` ## Changelog - Add new `transmute_lens` methods to `Query`. - Add new types `QueryBuilder`, `FilteredEntityMut`, `FilteredEntityRef` and `QueryLens` - `update_archetype_component_access` has been removed, archetype component accesses are now determined by the accesses set in `update_component_access` - Added method `set_access` to `WorldQuery`, this is called before `update_component_access` for queries that have a restricted set of accesses, such as those built by `QueryBuilder` or `QueryLens`. This is primarily used by the `FilteredEntity*` variants and has an empty trait implementation. - Added method `get_state` to `WorldQuery` as a fallible version of `init_state` when you don't have `&mut World` access. ## Future Work Improve performance of `FilteredEntityMut` and `FilteredEntityRef`, currently they have to determine the accesses a query has in a given archetype during iteration which is far from ideal, especially since we already did the work when matching the archetype in the first place. To avoid making more internal API changes I have left it out of this PR. --------- Co-authored-by: Mike Hsu <mike.hsu@gmail.com>
2024-01-16 19:16:49 +00:00
archetype::Archetype,
change_detection::{Ticks, TicksMut},
2023-03-09 20:02:56 +00:00
component::{Component, ComponentId, ComponentStorage, StorageType, Tick},
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
entity::Entity,
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
query::{Access, DebugCheckedUnwrap, FilteredAccess, WorldQuery},
storage::{ComponentSparseSet, Table, TableRow},
Dynamic queries and builder API (#9774) # Objective Expand the existing `Query` API to support more dynamic use cases i.e. scripting. ## Prior Art - #6390 - #8308 - #10037 ## Solution - Create a `QueryBuilder` with runtime methods to define the set of component accesses for a built query. - Create new `WorldQueryData` implementations `FilteredEntityMut` and `FilteredEntityRef` as variants of `EntityMut` and `EntityRef` that provide run time checked access to the components included in a given query. - Add new methods to `Query` to create "query lens" with a subset of the access of the initial query. ### Query Builder The `QueryBuilder` API allows you to define a query at runtime. At it's most basic use it will simply create a query with the corresponding type signature: ```rust let query = QueryBuilder::<Entity, With<A>>::new(&mut world).build(); // is equivalent to let query = QueryState::<Entity, With<A>>::new(&mut world); ``` Before calling `.build()` you also have the opportunity to add additional accesses and filters. Here is a simple example where we add additional filter terms: ```rust let entity_a = world.spawn((A(0), B(0))).id(); let entity_b = world.spawn((A(0), C(0))).id(); let mut query_a = QueryBuilder::<Entity>::new(&mut world) .with::<A>() .without::<C>() .build(); assert_eq!(entity_a, query_a.single(&world)); ``` This alone is useful in that allows you to decide which archetypes your query will match at runtime. However it is also very limited, consider a case like the following: ```rust let query_a = QueryBuilder::<&A>::new(&mut world) // Add an additional access .data::<&B>() .build(); ``` This will grant the query an additional read access to component B however we have no way of accessing the data while iterating as the type signature still only includes &A. For an even more concrete example of this consider dynamic components: ```rust let query_a = QueryBuilder::<Entity>::new(&mut world) // Adding a filter is easy since it doesn't need be read later .with_id(component_id_a) // How do I access the data of this component? .ref_id(component_id_b) .build(); ``` With this in mind the `QueryBuilder` API seems somewhat incomplete by itself, we need some way method of accessing the components dynamically. So here's one: ### Query Transmutation If the problem is not having the component in the type signature why not just add it? This PR also adds transmute methods to `QueryBuilder` and `QueryState`. Here's a simple example: ```rust world.spawn(A(0)); world.spawn((A(1), B(0))); let mut query = QueryBuilder::<()>::new(&mut world) .with::<B>() .transmute::<&A>() .build(); query.iter(&world).for_each(|a| assert_eq!(a.0, 1)); ``` The `QueryState` and `QueryBuilder` transmute methods look quite similar but are different in one respect. Transmuting a builder will always succeed as it will just add the additional accesses needed for the new terms if they weren't already included. Transmuting a `QueryState` will panic in the case that the new type signature would give it access it didn't already have, for example: ```rust let query = QueryState::<&A, Option<&B>>::new(&mut world); /// This is fine, the access for Option<&A> is less restrictive than &A query.transmute::<Option<&A>>(&world); /// Oh no, this would allow access to &B on entities that might not have it, so it panics query.transmute::<&B>(&world); /// This is right out query.transmute::<&C>(&world); ``` This is quite an appealing API to also have available on `Query` however it does pose one additional wrinkle: In order to to change the iterator we need to create a new `QueryState` to back it. `Query` doesn't own it's own state though, it just borrows it, so we need a place to borrow it from. This is why `QueryLens` exists, it is a place to store the new state so it can be borrowed when you call `.query()` leaving you with an API like this: ```rust fn function_that_takes_a_query(query: &Query<&A>) { // ... } fn system(query: Query<(&A, &B)>) { let lens = query.transmute_lens::<&A>(); let q = lens.query(); function_that_takes_a_query(&q); } ``` Now you may be thinking: Hey, wait a second, you introduced the problem with dynamic components and then described a solution that only works for static components! Ok, you got me, I guess we need a bit more: ### Filtered Entity References Currently the only way you can access dynamic components on entities through a query is with either `EntityMut` or `EntityRef`, however these can access all components and so conflict with all other accesses. This PR introduces `FilteredEntityMut` and `FilteredEntityRef` as alternatives that have additional runtime checking to prevent accessing components that you shouldn't. This way you can build a query with a `QueryBuilder` and actually access the components you asked for: ```rust let mut query = QueryBuilder::<FilteredEntityRef>::new(&mut world) .ref_id(component_id_a) .with(component_id_b) .build(); let entity_ref = query.single(&world); // Returns Some(Ptr) as we have that component and are allowed to read it let a = entity_ref.get_by_id(component_id_a); // Will return None even though the entity does have the component, as we are not allowed to read it let b = entity_ref.get_by_id(component_id_b); ``` For the most part these new structs have the exact same methods as their non-filtered equivalents. Putting all of this together we can do some truly dynamic ECS queries, check out the `dynamic` example to see it in action: ``` Commands: comp, c Create new components spawn, s Spawn entities query, q Query for entities Enter a command with no parameters for usage. > c A, B, C, Data 4 Component A created with id: 0 Component B created with id: 1 Component C created with id: 2 Component Data created with id: 3 > s A, B, Data 1 Entity spawned with id: 0v0 > s A, C, Data 0 Entity spawned with id: 1v0 > q &Data 0v0: Data: [1, 0, 0, 0] 1v0: Data: [0, 0, 0, 0] > q B, &mut Data 0v0: Data: [2, 1, 1, 1] > q B || C, &Data 0v0: Data: [2, 1, 1, 1] 1v0: Data: [0, 0, 0, 0] ``` ## Changelog - Add new `transmute_lens` methods to `Query`. - Add new types `QueryBuilder`, `FilteredEntityMut`, `FilteredEntityRef` and `QueryLens` - `update_archetype_component_access` has been removed, archetype component accesses are now determined by the accesses set in `update_component_access` - Added method `set_access` to `WorldQuery`, this is called before `update_component_access` for queries that have a restricted set of accesses, such as those built by `QueryBuilder` or `QueryLens`. This is primarily used by the `FilteredEntity*` variants and has an empty trait implementation. - Added method `get_state` to `WorldQuery` as a fallible version of `init_state` when you don't have `&mut World` access. ## Future Work Improve performance of `FilteredEntityMut` and `FilteredEntityRef`, currently they have to determine the accesses a query has in a given archetype during iteration which is far from ideal, especially since we already did the work when matching the archetype in the first place. To avoid making more internal API changes I have left it out of this PR. --------- Co-authored-by: Mike Hsu <mike.hsu@gmail.com>
2024-01-16 19:16:49 +00:00
world::{
unsafe_world_cell::UnsafeWorldCell, EntityMut, EntityRef, FilteredEntityMut,
FilteredEntityRef, Mut, Ref, World,
},
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
};
use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref};
use bevy_utils::all_tuples;
Use lifetimed, type erased pointers in bevy_ecs (#3001) # Objective `bevy_ecs` has large amounts of unsafe code which is hard to get right and makes it difficult to audit for soundness. ## Solution Introduce lifetimed, type-erased pointers: `Ptr<'a>` `PtrMut<'a>` `OwningPtr<'a>'` and `ThinSlicePtr<'a, T>` which are newtypes around a raw pointer with a lifetime and conceptually representing strong invariants about the pointee and validity of the pointer. The process of converting bevy_ecs to use these has already caught multiple cases of unsound behavior. ## Changelog TL;DR for release notes: `bevy_ecs` now uses lifetimed, type-erased pointers internally, significantly improving safety and legibility without sacrificing performance. This should have approximately no end user impact, unless you were meddling with the (unfortunately public) internals of `bevy_ecs`. - `Fetch`, `FilterFetch` and `ReadOnlyFetch` trait no longer have a `'state` lifetime - this was unneeded - `ReadOnly/Fetch` associated types on `WorldQuery` are now on a new `WorldQueryGats<'world>` trait - was required to work around lack of Generic Associated Types (we wish to express `type Fetch<'a>: Fetch<'a>`) - `derive(WorldQuery)` no longer requires `'w` lifetime on struct - this was unneeded, and improves the end user experience - `EntityMut::get_unchecked_mut` returns `&'_ mut T` not `&'w mut T` - allows easier use of unsafe API with less footguns, and can be worked around via lifetime transmutery as a user - `Bundle::from_components` now takes a `ctx` parameter to pass to the `FnMut` closure - required because closure return types can't borrow from captures - `Fetch::init` takes `&'world World`, `Fetch::set_archetype` takes `&'world Archetype` and `&'world Tables`, `Fetch::set_table` takes `&'world Table` - allows types implementing `Fetch` to store borrows into world - `WorldQuery` trait now has a `shrink` fn to shorten the lifetime in `Fetch::<'a>::Item` - this works around lack of subtyping of assoc types, rust doesnt allow you to turn `<T as Fetch<'static>>::Item'` into `<T as Fetch<'a>>::Item'` - `QueryCombinationsIter` requires this - Most types implementing `Fetch` now have a lifetime `'w` - allows the fetches to store borrows of world data instead of using raw pointers ## Migration guide - `EntityMut::get_unchecked_mut` returns a more restricted lifetime, there is no general way to migrate this as it depends on your code - `Bundle::from_components` implementations must pass the `ctx` arg to `func` - `Bundle::from_components` callers have to use a fn arg instead of closure captures for borrowing from world - Remove lifetime args on `derive(WorldQuery)` structs as it is nonsensical - `<Q as WorldQuery>::ReadOnly/Fetch` should be changed to either `RO/QueryFetch<'world>` or `<Q as WorldQueryGats<'world>>::ReadOnly/Fetch` - `<F as Fetch<'w, 's>>` should be changed to `<F as Fetch<'w>>` - Change the fn sigs of `Fetch::init/set_archetype/set_table` to match respective trait fn sigs - Implement the required `fn shrink` on any `WorldQuery` implementations - Move assoc types `Fetch` and `ReadOnlyFetch` on `WorldQuery` impls to `WorldQueryGats` impls - Pass an appropriate `'world` lifetime to whatever fetch struct you are for some reason using ### Type inference regression in some cases rustc may give spurrious errors when attempting to infer the `F` parameter on a query/querystate this can be fixed by manually specifying the type, i.e. `QueryState::new::<_, ()>(world)`. The error is rather confusing: ```rust= error[E0271]: type mismatch resolving `<() as Fetch<'_>>::Item == bool` --> crates/bevy_pbr/src/render/light.rs:1413:30 | 1413 | main_view_query: QueryState::new(world), | ^^^^^^^^^^^^^^^ expected `bool`, found `()` | = note: required because of the requirements on the impl of `for<'x> FilterFetch<'x>` for `<() as WorldQueryGats<'x>>::Fetch` note: required by a bound in `bevy_ecs::query::QueryState::<Q, F>::new` --> crates/bevy_ecs/src/query/state.rs:49:32 | 49 | for<'x> QueryFetch<'x, F>: FilterFetch<'x>, | ^^^^^^^^^^^^^^^ required by this bound in `bevy_ecs::query::QueryState::<Q, F>::new` ``` --- Made with help from @BoxyUwU and @alice-i-cecile Co-authored-by: Boxy <supbscripter@gmail.com>
2022-04-27 23:44:06 +00:00
use std::{cell::UnsafeCell, marker::PhantomData};
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
/// Types that can be fetched from a [`World`] using a [`Query`].
///
/// There are many types that natively implement this trait:
///
/// - **Component references.**
/// Fetches a component by reference (immutably or mutably).
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
/// - **`QueryData` tuples.**
/// If every element of a tuple implements `QueryData`, then the tuple itself also implements the same trait.
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
/// This enables a single `Query` to access multiple components.
/// Due to the current lack of variadic generics in Rust, the trait has been implemented for tuples from 0 to 15 elements,
/// but nesting of tuples allows infinite `WorldQuery`s.
/// - **[`Entity`].**
/// Gets the identifier of the queried entity.
/// - **[`Option`].**
/// By default, a world query only tests entities that have the matching component types.
/// Wrapping it into an `Option` will increase the query search space, and it will return `None` if an entity doesn't satisfy the `WorldQuery`.
/// - **[`AnyOf`].**
/// Equivalent to wrapping each world query inside it into an `Option`.
2023-03-09 20:02:56 +00:00
/// - **[`Ref`].**
/// Similar to change detection filters but it is used as a query fetch parameter.
/// It exposes methods to check for changes to the wrapped component.
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
/// - **[`Has`].**
/// Returns a bool indicating whether the entity has the specified component.
Implement `WorldQuery` derive macro (#2713) # Objective - Closes #786 - Closes #2252 - Closes #2588 This PR implements a derive macro that allows users to define their queries as structs with named fields. ## Example ```rust #[derive(WorldQuery)] #[world_query(derive(Debug))] struct NumQuery<'w, T: Component, P: Component> { entity: Entity, u: UNumQuery<'w>, generic: GenericQuery<'w, T, P>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct UNumQuery<'w> { u_16: &'w u16, u_32_opt: Option<&'w u32>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct GenericQuery<'w, T: Component, P: Component> { generic: (&'w T, &'w P), } #[derive(WorldQuery)] #[world_query(filter)] struct NumQueryFilter<T: Component, P: Component> { _u_16: With<u16>, _u_32: With<u32>, _or: Or<(With<i16>, Changed<u16>, Added<u32>)>, _generic_tuple: (With<T>, With<P>), _without: Without<Option<u16>>, _tp: PhantomData<(T, P)>, } fn print_nums_readonly(query: Query<NumQuery<u64, i64>, NumQueryFilter<u64, i64>>) { for num in query.iter() { println!("{:#?}", num); } } #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct MutNumQuery<'w, T: Component, P: Component> { i_16: &'w mut i16, i_32_opt: Option<&'w mut i32>, } fn print_nums(mut query: Query<MutNumQuery, NumQueryFilter<u64, i64>>) { for num in query.iter_mut() { println!("{:#?}", num); } } ``` ## TODOs: - [x] Add support for `&T` and `&mut T` - [x] Test - [x] Add support for optional types - [x] Test - [x] Add support for `Entity` - [x] Test - [x] Add support for nested `WorldQuery` - [x] Test - [x] Add support for tuples - [x] Test - [x] Add support for generics - [x] Test - [x] Add support for query filters - [x] Test - [x] Add support for `PhantomData` - [x] Test - [x] Refactor `read_world_query_field_type_info` - [x] Properly document `readonly` attribute for nested queries and the static assertions that guarantee safety - [x] Test that we never implement `ReadOnlyFetch` for types that need mutable access - [x] Test that we insert static assertions for nested `WorldQuery` that a user marked as readonly
2022-02-24 00:19:49 +00:00
///
/// Implementing the trait manually can allow for a fundamentally new type of behavior.
Implement `WorldQuery` derive macro (#2713) # Objective - Closes #786 - Closes #2252 - Closes #2588 This PR implements a derive macro that allows users to define their queries as structs with named fields. ## Example ```rust #[derive(WorldQuery)] #[world_query(derive(Debug))] struct NumQuery<'w, T: Component, P: Component> { entity: Entity, u: UNumQuery<'w>, generic: GenericQuery<'w, T, P>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct UNumQuery<'w> { u_16: &'w u16, u_32_opt: Option<&'w u32>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct GenericQuery<'w, T: Component, P: Component> { generic: (&'w T, &'w P), } #[derive(WorldQuery)] #[world_query(filter)] struct NumQueryFilter<T: Component, P: Component> { _u_16: With<u16>, _u_32: With<u32>, _or: Or<(With<i16>, Changed<u16>, Added<u32>)>, _generic_tuple: (With<T>, With<P>), _without: Without<Option<u16>>, _tp: PhantomData<(T, P)>, } fn print_nums_readonly(query: Query<NumQuery<u64, i64>, NumQueryFilter<u64, i64>>) { for num in query.iter() { println!("{:#?}", num); } } #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct MutNumQuery<'w, T: Component, P: Component> { i_16: &'w mut i16, i_32_opt: Option<&'w mut i32>, } fn print_nums(mut query: Query<MutNumQuery, NumQueryFilter<u64, i64>>) { for num in query.iter_mut() { println!("{:#?}", num); } } ``` ## TODOs: - [x] Add support for `&T` and `&mut T` - [x] Test - [x] Add support for optional types - [x] Test - [x] Add support for `Entity` - [x] Test - [x] Add support for nested `WorldQuery` - [x] Test - [x] Add support for tuples - [x] Test - [x] Add support for generics - [x] Test - [x] Add support for query filters - [x] Test - [x] Add support for `PhantomData` - [x] Test - [x] Refactor `read_world_query_field_type_info` - [x] Properly document `readonly` attribute for nested queries and the static assertions that guarantee safety - [x] Test that we never implement `ReadOnlyFetch` for types that need mutable access - [x] Test that we insert static assertions for nested `WorldQuery` that a user marked as readonly
2022-02-24 00:19:49 +00:00
///
/// # Trait derivation
///
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
/// Query design can be easily structured by deriving `QueryData` for custom types.
/// Despite the added complexity, this approach has several advantages over using `QueryData` tuples.
/// The most relevant improvements are:
Implement `WorldQuery` derive macro (#2713) # Objective - Closes #786 - Closes #2252 - Closes #2588 This PR implements a derive macro that allows users to define their queries as structs with named fields. ## Example ```rust #[derive(WorldQuery)] #[world_query(derive(Debug))] struct NumQuery<'w, T: Component, P: Component> { entity: Entity, u: UNumQuery<'w>, generic: GenericQuery<'w, T, P>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct UNumQuery<'w> { u_16: &'w u16, u_32_opt: Option<&'w u32>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct GenericQuery<'w, T: Component, P: Component> { generic: (&'w T, &'w P), } #[derive(WorldQuery)] #[world_query(filter)] struct NumQueryFilter<T: Component, P: Component> { _u_16: With<u16>, _u_32: With<u32>, _or: Or<(With<i16>, Changed<u16>, Added<u32>)>, _generic_tuple: (With<T>, With<P>), _without: Without<Option<u16>>, _tp: PhantomData<(T, P)>, } fn print_nums_readonly(query: Query<NumQuery<u64, i64>, NumQueryFilter<u64, i64>>) { for num in query.iter() { println!("{:#?}", num); } } #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct MutNumQuery<'w, T: Component, P: Component> { i_16: &'w mut i16, i_32_opt: Option<&'w mut i32>, } fn print_nums(mut query: Query<MutNumQuery, NumQueryFilter<u64, i64>>) { for num in query.iter_mut() { println!("{:#?}", num); } } ``` ## TODOs: - [x] Add support for `&T` and `&mut T` - [x] Test - [x] Add support for optional types - [x] Test - [x] Add support for `Entity` - [x] Test - [x] Add support for nested `WorldQuery` - [x] Test - [x] Add support for tuples - [x] Test - [x] Add support for generics - [x] Test - [x] Add support for query filters - [x] Test - [x] Add support for `PhantomData` - [x] Test - [x] Refactor `read_world_query_field_type_info` - [x] Properly document `readonly` attribute for nested queries and the static assertions that guarantee safety - [x] Test that we never implement `ReadOnlyFetch` for types that need mutable access - [x] Test that we insert static assertions for nested `WorldQuery` that a user marked as readonly
2022-02-24 00:19:49 +00:00
///
/// - Reusability across multiple systems.
/// - There is no need to destructure a tuple since all fields are named.
/// - Subqueries can be composed together to create a more complex query.
/// - Methods can be implemented for the query items.
/// - There is no hardcoded limit on the number of elements.
Implement `WorldQuery` derive macro (#2713) # Objective - Closes #786 - Closes #2252 - Closes #2588 This PR implements a derive macro that allows users to define their queries as structs with named fields. ## Example ```rust #[derive(WorldQuery)] #[world_query(derive(Debug))] struct NumQuery<'w, T: Component, P: Component> { entity: Entity, u: UNumQuery<'w>, generic: GenericQuery<'w, T, P>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct UNumQuery<'w> { u_16: &'w u16, u_32_opt: Option<&'w u32>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct GenericQuery<'w, T: Component, P: Component> { generic: (&'w T, &'w P), } #[derive(WorldQuery)] #[world_query(filter)] struct NumQueryFilter<T: Component, P: Component> { _u_16: With<u16>, _u_32: With<u32>, _or: Or<(With<i16>, Changed<u16>, Added<u32>)>, _generic_tuple: (With<T>, With<P>), _without: Without<Option<u16>>, _tp: PhantomData<(T, P)>, } fn print_nums_readonly(query: Query<NumQuery<u64, i64>, NumQueryFilter<u64, i64>>) { for num in query.iter() { println!("{:#?}", num); } } #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct MutNumQuery<'w, T: Component, P: Component> { i_16: &'w mut i16, i_32_opt: Option<&'w mut i32>, } fn print_nums(mut query: Query<MutNumQuery, NumQueryFilter<u64, i64>>) { for num in query.iter_mut() { println!("{:#?}", num); } } ``` ## TODOs: - [x] Add support for `&T` and `&mut T` - [x] Test - [x] Add support for optional types - [x] Test - [x] Add support for `Entity` - [x] Test - [x] Add support for nested `WorldQuery` - [x] Test - [x] Add support for tuples - [x] Test - [x] Add support for generics - [x] Test - [x] Add support for query filters - [x] Test - [x] Add support for `PhantomData` - [x] Test - [x] Refactor `read_world_query_field_type_info` - [x] Properly document `readonly` attribute for nested queries and the static assertions that guarantee safety - [x] Test that we never implement `ReadOnlyFetch` for types that need mutable access - [x] Test that we insert static assertions for nested `WorldQuery` that a user marked as readonly
2022-02-24 00:19:49 +00:00
///
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
/// This trait can only be derived for structs, if each field also implements `QueryData`.
Implement `WorldQuery` derive macro (#2713) # Objective - Closes #786 - Closes #2252 - Closes #2588 This PR implements a derive macro that allows users to define their queries as structs with named fields. ## Example ```rust #[derive(WorldQuery)] #[world_query(derive(Debug))] struct NumQuery<'w, T: Component, P: Component> { entity: Entity, u: UNumQuery<'w>, generic: GenericQuery<'w, T, P>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct UNumQuery<'w> { u_16: &'w u16, u_32_opt: Option<&'w u32>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct GenericQuery<'w, T: Component, P: Component> { generic: (&'w T, &'w P), } #[derive(WorldQuery)] #[world_query(filter)] struct NumQueryFilter<T: Component, P: Component> { _u_16: With<u16>, _u_32: With<u32>, _or: Or<(With<i16>, Changed<u16>, Added<u32>)>, _generic_tuple: (With<T>, With<P>), _without: Without<Option<u16>>, _tp: PhantomData<(T, P)>, } fn print_nums_readonly(query: Query<NumQuery<u64, i64>, NumQueryFilter<u64, i64>>) { for num in query.iter() { println!("{:#?}", num); } } #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct MutNumQuery<'w, T: Component, P: Component> { i_16: &'w mut i16, i_32_opt: Option<&'w mut i32>, } fn print_nums(mut query: Query<MutNumQuery, NumQueryFilter<u64, i64>>) { for num in query.iter_mut() { println!("{:#?}", num); } } ``` ## TODOs: - [x] Add support for `&T` and `&mut T` - [x] Test - [x] Add support for optional types - [x] Test - [x] Add support for `Entity` - [x] Test - [x] Add support for nested `WorldQuery` - [x] Test - [x] Add support for tuples - [x] Test - [x] Add support for generics - [x] Test - [x] Add support for query filters - [x] Test - [x] Add support for `PhantomData` - [x] Test - [x] Refactor `read_world_query_field_type_info` - [x] Properly document `readonly` attribute for nested queries and the static assertions that guarantee safety - [x] Test that we never implement `ReadOnlyFetch` for types that need mutable access - [x] Test that we insert static assertions for nested `WorldQuery` that a user marked as readonly
2022-02-24 00:19:49 +00:00
///
/// ```
/// # use bevy_ecs::prelude::*;
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
/// use bevy_ecs::query::QueryData;
/// #
/// # #[derive(Component)]
/// # struct ComponentA;
/// # #[derive(Component)]
/// # struct ComponentB;
Implement `WorldQuery` derive macro (#2713) # Objective - Closes #786 - Closes #2252 - Closes #2588 This PR implements a derive macro that allows users to define their queries as structs with named fields. ## Example ```rust #[derive(WorldQuery)] #[world_query(derive(Debug))] struct NumQuery<'w, T: Component, P: Component> { entity: Entity, u: UNumQuery<'w>, generic: GenericQuery<'w, T, P>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct UNumQuery<'w> { u_16: &'w u16, u_32_opt: Option<&'w u32>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct GenericQuery<'w, T: Component, P: Component> { generic: (&'w T, &'w P), } #[derive(WorldQuery)] #[world_query(filter)] struct NumQueryFilter<T: Component, P: Component> { _u_16: With<u16>, _u_32: With<u32>, _or: Or<(With<i16>, Changed<u16>, Added<u32>)>, _generic_tuple: (With<T>, With<P>), _without: Without<Option<u16>>, _tp: PhantomData<(T, P)>, } fn print_nums_readonly(query: Query<NumQuery<u64, i64>, NumQueryFilter<u64, i64>>) { for num in query.iter() { println!("{:#?}", num); } } #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct MutNumQuery<'w, T: Component, P: Component> { i_16: &'w mut i16, i_32_opt: Option<&'w mut i32>, } fn print_nums(mut query: Query<MutNumQuery, NumQueryFilter<u64, i64>>) { for num in query.iter_mut() { println!("{:#?}", num); } } ``` ## TODOs: - [x] Add support for `&T` and `&mut T` - [x] Test - [x] Add support for optional types - [x] Test - [x] Add support for `Entity` - [x] Test - [x] Add support for nested `WorldQuery` - [x] Test - [x] Add support for tuples - [x] Test - [x] Add support for generics - [x] Test - [x] Add support for query filters - [x] Test - [x] Add support for `PhantomData` - [x] Test - [x] Refactor `read_world_query_field_type_info` - [x] Properly document `readonly` attribute for nested queries and the static assertions that guarantee safety - [x] Test that we never implement `ReadOnlyFetch` for types that need mutable access - [x] Test that we insert static assertions for nested `WorldQuery` that a user marked as readonly
2022-02-24 00:19:49 +00:00
///
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
/// #[derive(QueryData)]
Use lifetimed, type erased pointers in bevy_ecs (#3001) # Objective `bevy_ecs` has large amounts of unsafe code which is hard to get right and makes it difficult to audit for soundness. ## Solution Introduce lifetimed, type-erased pointers: `Ptr<'a>` `PtrMut<'a>` `OwningPtr<'a>'` and `ThinSlicePtr<'a, T>` which are newtypes around a raw pointer with a lifetime and conceptually representing strong invariants about the pointee and validity of the pointer. The process of converting bevy_ecs to use these has already caught multiple cases of unsound behavior. ## Changelog TL;DR for release notes: `bevy_ecs` now uses lifetimed, type-erased pointers internally, significantly improving safety and legibility without sacrificing performance. This should have approximately no end user impact, unless you were meddling with the (unfortunately public) internals of `bevy_ecs`. - `Fetch`, `FilterFetch` and `ReadOnlyFetch` trait no longer have a `'state` lifetime - this was unneeded - `ReadOnly/Fetch` associated types on `WorldQuery` are now on a new `WorldQueryGats<'world>` trait - was required to work around lack of Generic Associated Types (we wish to express `type Fetch<'a>: Fetch<'a>`) - `derive(WorldQuery)` no longer requires `'w` lifetime on struct - this was unneeded, and improves the end user experience - `EntityMut::get_unchecked_mut` returns `&'_ mut T` not `&'w mut T` - allows easier use of unsafe API with less footguns, and can be worked around via lifetime transmutery as a user - `Bundle::from_components` now takes a `ctx` parameter to pass to the `FnMut` closure - required because closure return types can't borrow from captures - `Fetch::init` takes `&'world World`, `Fetch::set_archetype` takes `&'world Archetype` and `&'world Tables`, `Fetch::set_table` takes `&'world Table` - allows types implementing `Fetch` to store borrows into world - `WorldQuery` trait now has a `shrink` fn to shorten the lifetime in `Fetch::<'a>::Item` - this works around lack of subtyping of assoc types, rust doesnt allow you to turn `<T as Fetch<'static>>::Item'` into `<T as Fetch<'a>>::Item'` - `QueryCombinationsIter` requires this - Most types implementing `Fetch` now have a lifetime `'w` - allows the fetches to store borrows of world data instead of using raw pointers ## Migration guide - `EntityMut::get_unchecked_mut` returns a more restricted lifetime, there is no general way to migrate this as it depends on your code - `Bundle::from_components` implementations must pass the `ctx` arg to `func` - `Bundle::from_components` callers have to use a fn arg instead of closure captures for borrowing from world - Remove lifetime args on `derive(WorldQuery)` structs as it is nonsensical - `<Q as WorldQuery>::ReadOnly/Fetch` should be changed to either `RO/QueryFetch<'world>` or `<Q as WorldQueryGats<'world>>::ReadOnly/Fetch` - `<F as Fetch<'w, 's>>` should be changed to `<F as Fetch<'w>>` - Change the fn sigs of `Fetch::init/set_archetype/set_table` to match respective trait fn sigs - Implement the required `fn shrink` on any `WorldQuery` implementations - Move assoc types `Fetch` and `ReadOnlyFetch` on `WorldQuery` impls to `WorldQueryGats` impls - Pass an appropriate `'world` lifetime to whatever fetch struct you are for some reason using ### Type inference regression in some cases rustc may give spurrious errors when attempting to infer the `F` parameter on a query/querystate this can be fixed by manually specifying the type, i.e. `QueryState::new::<_, ()>(world)`. The error is rather confusing: ```rust= error[E0271]: type mismatch resolving `<() as Fetch<'_>>::Item == bool` --> crates/bevy_pbr/src/render/light.rs:1413:30 | 1413 | main_view_query: QueryState::new(world), | ^^^^^^^^^^^^^^^ expected `bool`, found `()` | = note: required because of the requirements on the impl of `for<'x> FilterFetch<'x>` for `<() as WorldQueryGats<'x>>::Fetch` note: required by a bound in `bevy_ecs::query::QueryState::<Q, F>::new` --> crates/bevy_ecs/src/query/state.rs:49:32 | 49 | for<'x> QueryFetch<'x, F>: FilterFetch<'x>, | ^^^^^^^^^^^^^^^ required by this bound in `bevy_ecs::query::QueryState::<Q, F>::new` ``` --- Made with help from @BoxyUwU and @alice-i-cecile Co-authored-by: Boxy <supbscripter@gmail.com>
2022-04-27 23:44:06 +00:00
/// struct MyQuery {
Implement `WorldQuery` derive macro (#2713) # Objective - Closes #786 - Closes #2252 - Closes #2588 This PR implements a derive macro that allows users to define their queries as structs with named fields. ## Example ```rust #[derive(WorldQuery)] #[world_query(derive(Debug))] struct NumQuery<'w, T: Component, P: Component> { entity: Entity, u: UNumQuery<'w>, generic: GenericQuery<'w, T, P>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct UNumQuery<'w> { u_16: &'w u16, u_32_opt: Option<&'w u32>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct GenericQuery<'w, T: Component, P: Component> { generic: (&'w T, &'w P), } #[derive(WorldQuery)] #[world_query(filter)] struct NumQueryFilter<T: Component, P: Component> { _u_16: With<u16>, _u_32: With<u32>, _or: Or<(With<i16>, Changed<u16>, Added<u32>)>, _generic_tuple: (With<T>, With<P>), _without: Without<Option<u16>>, _tp: PhantomData<(T, P)>, } fn print_nums_readonly(query: Query<NumQuery<u64, i64>, NumQueryFilter<u64, i64>>) { for num in query.iter() { println!("{:#?}", num); } } #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct MutNumQuery<'w, T: Component, P: Component> { i_16: &'w mut i16, i_32_opt: Option<&'w mut i32>, } fn print_nums(mut query: Query<MutNumQuery, NumQueryFilter<u64, i64>>) { for num in query.iter_mut() { println!("{:#?}", num); } } ``` ## TODOs: - [x] Add support for `&T` and `&mut T` - [x] Test - [x] Add support for optional types - [x] Test - [x] Add support for `Entity` - [x] Test - [x] Add support for nested `WorldQuery` - [x] Test - [x] Add support for tuples - [x] Test - [x] Add support for generics - [x] Test - [x] Add support for query filters - [x] Test - [x] Add support for `PhantomData` - [x] Test - [x] Refactor `read_world_query_field_type_info` - [x] Properly document `readonly` attribute for nested queries and the static assertions that guarantee safety - [x] Test that we never implement `ReadOnlyFetch` for types that need mutable access - [x] Test that we insert static assertions for nested `WorldQuery` that a user marked as readonly
2022-02-24 00:19:49 +00:00
/// entity: Entity,
/// // It is required that all reference lifetimes are explicitly annotated, just like in any
/// // struct. Each lifetime should be 'static.
/// component_a: &'static ComponentA,
/// component_b: &'static ComponentB,
Implement `WorldQuery` derive macro (#2713) # Objective - Closes #786 - Closes #2252 - Closes #2588 This PR implements a derive macro that allows users to define their queries as structs with named fields. ## Example ```rust #[derive(WorldQuery)] #[world_query(derive(Debug))] struct NumQuery<'w, T: Component, P: Component> { entity: Entity, u: UNumQuery<'w>, generic: GenericQuery<'w, T, P>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct UNumQuery<'w> { u_16: &'w u16, u_32_opt: Option<&'w u32>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct GenericQuery<'w, T: Component, P: Component> { generic: (&'w T, &'w P), } #[derive(WorldQuery)] #[world_query(filter)] struct NumQueryFilter<T: Component, P: Component> { _u_16: With<u16>, _u_32: With<u32>, _or: Or<(With<i16>, Changed<u16>, Added<u32>)>, _generic_tuple: (With<T>, With<P>), _without: Without<Option<u16>>, _tp: PhantomData<(T, P)>, } fn print_nums_readonly(query: Query<NumQuery<u64, i64>, NumQueryFilter<u64, i64>>) { for num in query.iter() { println!("{:#?}", num); } } #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct MutNumQuery<'w, T: Component, P: Component> { i_16: &'w mut i16, i_32_opt: Option<&'w mut i32>, } fn print_nums(mut query: Query<MutNumQuery, NumQueryFilter<u64, i64>>) { for num in query.iter_mut() { println!("{:#?}", num); } } ``` ## TODOs: - [x] Add support for `&T` and `&mut T` - [x] Test - [x] Add support for optional types - [x] Test - [x] Add support for `Entity` - [x] Test - [x] Add support for nested `WorldQuery` - [x] Test - [x] Add support for tuples - [x] Test - [x] Add support for generics - [x] Test - [x] Add support for query filters - [x] Test - [x] Add support for `PhantomData` - [x] Test - [x] Refactor `read_world_query_field_type_info` - [x] Properly document `readonly` attribute for nested queries and the static assertions that guarantee safety - [x] Test that we never implement `ReadOnlyFetch` for types that need mutable access - [x] Test that we insert static assertions for nested `WorldQuery` that a user marked as readonly
2022-02-24 00:19:49 +00:00
/// }
///
/// fn my_system(query: Query<MyQuery>) {
/// for q in &query {
/// q.component_a;
Implement `WorldQuery` derive macro (#2713) # Objective - Closes #786 - Closes #2252 - Closes #2588 This PR implements a derive macro that allows users to define their queries as structs with named fields. ## Example ```rust #[derive(WorldQuery)] #[world_query(derive(Debug))] struct NumQuery<'w, T: Component, P: Component> { entity: Entity, u: UNumQuery<'w>, generic: GenericQuery<'w, T, P>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct UNumQuery<'w> { u_16: &'w u16, u_32_opt: Option<&'w u32>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct GenericQuery<'w, T: Component, P: Component> { generic: (&'w T, &'w P), } #[derive(WorldQuery)] #[world_query(filter)] struct NumQueryFilter<T: Component, P: Component> { _u_16: With<u16>, _u_32: With<u32>, _or: Or<(With<i16>, Changed<u16>, Added<u32>)>, _generic_tuple: (With<T>, With<P>), _without: Without<Option<u16>>, _tp: PhantomData<(T, P)>, } fn print_nums_readonly(query: Query<NumQuery<u64, i64>, NumQueryFilter<u64, i64>>) { for num in query.iter() { println!("{:#?}", num); } } #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct MutNumQuery<'w, T: Component, P: Component> { i_16: &'w mut i16, i_32_opt: Option<&'w mut i32>, } fn print_nums(mut query: Query<MutNumQuery, NumQueryFilter<u64, i64>>) { for num in query.iter_mut() { println!("{:#?}", num); } } ``` ## TODOs: - [x] Add support for `&T` and `&mut T` - [x] Test - [x] Add support for optional types - [x] Test - [x] Add support for `Entity` - [x] Test - [x] Add support for nested `WorldQuery` - [x] Test - [x] Add support for tuples - [x] Test - [x] Add support for generics - [x] Test - [x] Add support for query filters - [x] Test - [x] Add support for `PhantomData` - [x] Test - [x] Refactor `read_world_query_field_type_info` - [x] Properly document `readonly` attribute for nested queries and the static assertions that guarantee safety - [x] Test that we never implement `ReadOnlyFetch` for types that need mutable access - [x] Test that we insert static assertions for nested `WorldQuery` that a user marked as readonly
2022-02-24 00:19:49 +00:00
/// }
/// }
/// # bevy_ecs::system::assert_is_system(my_system);
/// ```
///
/// ## Macro expansion
///
/// Expanding the macro will declare one or three additional structs, depending on whether or not the struct is marked as mutable.
/// For a struct named `X`, the additional structs will be:
///
/// |Struct name|`mutable` only|Description|
/// |:---:|:---:|---|
/// |`XItem`|---|The type of the query item for `X`|
/// |`XReadOnlyItem`|✓|The type of the query item for `XReadOnly`|
/// |`XReadOnly`|✓|[`ReadOnly`] variant of `X`|
///
/// ## Adding mutable references
///
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
/// Simply adding mutable references to a derived `QueryData` will result in a compilation error:
///
/// ```compile_fail
/// # use bevy_ecs::prelude::*;
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
/// # use bevy_ecs::query::QueryData;
/// #
/// # #[derive(Component)]
/// # struct ComponentA;
/// #
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
/// #[derive(QueryData)]
/// struct CustomQuery {
/// component_a: &'static mut ComponentA,
/// }
/// ```
Implement `WorldQuery` derive macro (#2713) # Objective - Closes #786 - Closes #2252 - Closes #2588 This PR implements a derive macro that allows users to define their queries as structs with named fields. ## Example ```rust #[derive(WorldQuery)] #[world_query(derive(Debug))] struct NumQuery<'w, T: Component, P: Component> { entity: Entity, u: UNumQuery<'w>, generic: GenericQuery<'w, T, P>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct UNumQuery<'w> { u_16: &'w u16, u_32_opt: Option<&'w u32>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct GenericQuery<'w, T: Component, P: Component> { generic: (&'w T, &'w P), } #[derive(WorldQuery)] #[world_query(filter)] struct NumQueryFilter<T: Component, P: Component> { _u_16: With<u16>, _u_32: With<u32>, _or: Or<(With<i16>, Changed<u16>, Added<u32>)>, _generic_tuple: (With<T>, With<P>), _without: Without<Option<u16>>, _tp: PhantomData<(T, P)>, } fn print_nums_readonly(query: Query<NumQuery<u64, i64>, NumQueryFilter<u64, i64>>) { for num in query.iter() { println!("{:#?}", num); } } #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct MutNumQuery<'w, T: Component, P: Component> { i_16: &'w mut i16, i_32_opt: Option<&'w mut i32>, } fn print_nums(mut query: Query<MutNumQuery, NumQueryFilter<u64, i64>>) { for num in query.iter_mut() { println!("{:#?}", num); } } ``` ## TODOs: - [x] Add support for `&T` and `&mut T` - [x] Test - [x] Add support for optional types - [x] Test - [x] Add support for `Entity` - [x] Test - [x] Add support for nested `WorldQuery` - [x] Test - [x] Add support for tuples - [x] Test - [x] Add support for generics - [x] Test - [x] Add support for query filters - [x] Test - [x] Add support for `PhantomData` - [x] Test - [x] Refactor `read_world_query_field_type_info` - [x] Properly document `readonly` attribute for nested queries and the static assertions that guarantee safety - [x] Test that we never implement `ReadOnlyFetch` for types that need mutable access - [x] Test that we insert static assertions for nested `WorldQuery` that a user marked as readonly
2022-02-24 00:19:49 +00:00
///
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
/// To grant mutable access to components, the struct must be marked with the `#[query_data(mutable)]` attribute.
/// This will also create three more structs that will be used for accessing the query immutably (see table above).
Implement `WorldQuery` derive macro (#2713) # Objective - Closes #786 - Closes #2252 - Closes #2588 This PR implements a derive macro that allows users to define their queries as structs with named fields. ## Example ```rust #[derive(WorldQuery)] #[world_query(derive(Debug))] struct NumQuery<'w, T: Component, P: Component> { entity: Entity, u: UNumQuery<'w>, generic: GenericQuery<'w, T, P>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct UNumQuery<'w> { u_16: &'w u16, u_32_opt: Option<&'w u32>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct GenericQuery<'w, T: Component, P: Component> { generic: (&'w T, &'w P), } #[derive(WorldQuery)] #[world_query(filter)] struct NumQueryFilter<T: Component, P: Component> { _u_16: With<u16>, _u_32: With<u32>, _or: Or<(With<i16>, Changed<u16>, Added<u32>)>, _generic_tuple: (With<T>, With<P>), _without: Without<Option<u16>>, _tp: PhantomData<(T, P)>, } fn print_nums_readonly(query: Query<NumQuery<u64, i64>, NumQueryFilter<u64, i64>>) { for num in query.iter() { println!("{:#?}", num); } } #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct MutNumQuery<'w, T: Component, P: Component> { i_16: &'w mut i16, i_32_opt: Option<&'w mut i32>, } fn print_nums(mut query: Query<MutNumQuery, NumQueryFilter<u64, i64>>) { for num in query.iter_mut() { println!("{:#?}", num); } } ``` ## TODOs: - [x] Add support for `&T` and `&mut T` - [x] Test - [x] Add support for optional types - [x] Test - [x] Add support for `Entity` - [x] Test - [x] Add support for nested `WorldQuery` - [x] Test - [x] Add support for tuples - [x] Test - [x] Add support for generics - [x] Test - [x] Add support for query filters - [x] Test - [x] Add support for `PhantomData` - [x] Test - [x] Refactor `read_world_query_field_type_info` - [x] Properly document `readonly` attribute for nested queries and the static assertions that guarantee safety - [x] Test that we never implement `ReadOnlyFetch` for types that need mutable access - [x] Test that we insert static assertions for nested `WorldQuery` that a user marked as readonly
2022-02-24 00:19:49 +00:00
///
/// ```
/// # use bevy_ecs::prelude::*;
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
/// # use bevy_ecs::query::QueryData;
/// #
/// # #[derive(Component)]
/// # struct ComponentA;
/// #
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
/// #[derive(QueryData)]
/// #[query_data(mutable)]
/// struct CustomQuery {
/// component_a: &'static mut ComponentA,
/// }
/// ```
///
/// ## Adding methods to query items
///
/// It is possible to add methods to query items in order to write reusable logic about related components.
/// This will often make systems more readable because low level logic is moved out from them.
/// It is done by adding `impl` blocks with methods for the `-Item` or `-ReadOnlyItem` generated structs.
Implement `WorldQuery` derive macro (#2713) # Objective - Closes #786 - Closes #2252 - Closes #2588 This PR implements a derive macro that allows users to define their queries as structs with named fields. ## Example ```rust #[derive(WorldQuery)] #[world_query(derive(Debug))] struct NumQuery<'w, T: Component, P: Component> { entity: Entity, u: UNumQuery<'w>, generic: GenericQuery<'w, T, P>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct UNumQuery<'w> { u_16: &'w u16, u_32_opt: Option<&'w u32>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct GenericQuery<'w, T: Component, P: Component> { generic: (&'w T, &'w P), } #[derive(WorldQuery)] #[world_query(filter)] struct NumQueryFilter<T: Component, P: Component> { _u_16: With<u16>, _u_32: With<u32>, _or: Or<(With<i16>, Changed<u16>, Added<u32>)>, _generic_tuple: (With<T>, With<P>), _without: Without<Option<u16>>, _tp: PhantomData<(T, P)>, } fn print_nums_readonly(query: Query<NumQuery<u64, i64>, NumQueryFilter<u64, i64>>) { for num in query.iter() { println!("{:#?}", num); } } #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct MutNumQuery<'w, T: Component, P: Component> { i_16: &'w mut i16, i_32_opt: Option<&'w mut i32>, } fn print_nums(mut query: Query<MutNumQuery, NumQueryFilter<u64, i64>>) { for num in query.iter_mut() { println!("{:#?}", num); } } ``` ## TODOs: - [x] Add support for `&T` and `&mut T` - [x] Test - [x] Add support for optional types - [x] Test - [x] Add support for `Entity` - [x] Test - [x] Add support for nested `WorldQuery` - [x] Test - [x] Add support for tuples - [x] Test - [x] Add support for generics - [x] Test - [x] Add support for query filters - [x] Test - [x] Add support for `PhantomData` - [x] Test - [x] Refactor `read_world_query_field_type_info` - [x] Properly document `readonly` attribute for nested queries and the static assertions that guarantee safety - [x] Test that we never implement `ReadOnlyFetch` for types that need mutable access - [x] Test that we insert static assertions for nested `WorldQuery` that a user marked as readonly
2022-02-24 00:19:49 +00:00
///
/// ```
/// # use bevy_ecs::prelude::*;
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
/// # use bevy_ecs::query::QueryData;
/// #
Implement `WorldQuery` derive macro (#2713) # Objective - Closes #786 - Closes #2252 - Closes #2588 This PR implements a derive macro that allows users to define their queries as structs with named fields. ## Example ```rust #[derive(WorldQuery)] #[world_query(derive(Debug))] struct NumQuery<'w, T: Component, P: Component> { entity: Entity, u: UNumQuery<'w>, generic: GenericQuery<'w, T, P>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct UNumQuery<'w> { u_16: &'w u16, u_32_opt: Option<&'w u32>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct GenericQuery<'w, T: Component, P: Component> { generic: (&'w T, &'w P), } #[derive(WorldQuery)] #[world_query(filter)] struct NumQueryFilter<T: Component, P: Component> { _u_16: With<u16>, _u_32: With<u32>, _or: Or<(With<i16>, Changed<u16>, Added<u32>)>, _generic_tuple: (With<T>, With<P>), _without: Without<Option<u16>>, _tp: PhantomData<(T, P)>, } fn print_nums_readonly(query: Query<NumQuery<u64, i64>, NumQueryFilter<u64, i64>>) { for num in query.iter() { println!("{:#?}", num); } } #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct MutNumQuery<'w, T: Component, P: Component> { i_16: &'w mut i16, i_32_opt: Option<&'w mut i32>, } fn print_nums(mut query: Query<MutNumQuery, NumQueryFilter<u64, i64>>) { for num in query.iter_mut() { println!("{:#?}", num); } } ``` ## TODOs: - [x] Add support for `&T` and `&mut T` - [x] Test - [x] Add support for optional types - [x] Test - [x] Add support for `Entity` - [x] Test - [x] Add support for nested `WorldQuery` - [x] Test - [x] Add support for tuples - [x] Test - [x] Add support for generics - [x] Test - [x] Add support for query filters - [x] Test - [x] Add support for `PhantomData` - [x] Test - [x] Refactor `read_world_query_field_type_info` - [x] Properly document `readonly` attribute for nested queries and the static assertions that guarantee safety - [x] Test that we never implement `ReadOnlyFetch` for types that need mutable access - [x] Test that we insert static assertions for nested `WorldQuery` that a user marked as readonly
2022-02-24 00:19:49 +00:00
/// #[derive(Component)]
/// struct Health(f32);
///
Implement `WorldQuery` derive macro (#2713) # Objective - Closes #786 - Closes #2252 - Closes #2588 This PR implements a derive macro that allows users to define their queries as structs with named fields. ## Example ```rust #[derive(WorldQuery)] #[world_query(derive(Debug))] struct NumQuery<'w, T: Component, P: Component> { entity: Entity, u: UNumQuery<'w>, generic: GenericQuery<'w, T, P>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct UNumQuery<'w> { u_16: &'w u16, u_32_opt: Option<&'w u32>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct GenericQuery<'w, T: Component, P: Component> { generic: (&'w T, &'w P), } #[derive(WorldQuery)] #[world_query(filter)] struct NumQueryFilter<T: Component, P: Component> { _u_16: With<u16>, _u_32: With<u32>, _or: Or<(With<i16>, Changed<u16>, Added<u32>)>, _generic_tuple: (With<T>, With<P>), _without: Without<Option<u16>>, _tp: PhantomData<(T, P)>, } fn print_nums_readonly(query: Query<NumQuery<u64, i64>, NumQueryFilter<u64, i64>>) { for num in query.iter() { println!("{:#?}", num); } } #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct MutNumQuery<'w, T: Component, P: Component> { i_16: &'w mut i16, i_32_opt: Option<&'w mut i32>, } fn print_nums(mut query: Query<MutNumQuery, NumQueryFilter<u64, i64>>) { for num in query.iter_mut() { println!("{:#?}", num); } } ``` ## TODOs: - [x] Add support for `&T` and `&mut T` - [x] Test - [x] Add support for optional types - [x] Test - [x] Add support for `Entity` - [x] Test - [x] Add support for nested `WorldQuery` - [x] Test - [x] Add support for tuples - [x] Test - [x] Add support for generics - [x] Test - [x] Add support for query filters - [x] Test - [x] Add support for `PhantomData` - [x] Test - [x] Refactor `read_world_query_field_type_info` - [x] Properly document `readonly` attribute for nested queries and the static assertions that guarantee safety - [x] Test that we never implement `ReadOnlyFetch` for types that need mutable access - [x] Test that we insert static assertions for nested `WorldQuery` that a user marked as readonly
2022-02-24 00:19:49 +00:00
/// #[derive(Component)]
/// struct Buff(f32);
///
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
/// #[derive(QueryData)]
/// #[query_data(mutable)]
Use lifetimed, type erased pointers in bevy_ecs (#3001) # Objective `bevy_ecs` has large amounts of unsafe code which is hard to get right and makes it difficult to audit for soundness. ## Solution Introduce lifetimed, type-erased pointers: `Ptr<'a>` `PtrMut<'a>` `OwningPtr<'a>'` and `ThinSlicePtr<'a, T>` which are newtypes around a raw pointer with a lifetime and conceptually representing strong invariants about the pointee and validity of the pointer. The process of converting bevy_ecs to use these has already caught multiple cases of unsound behavior. ## Changelog TL;DR for release notes: `bevy_ecs` now uses lifetimed, type-erased pointers internally, significantly improving safety and legibility without sacrificing performance. This should have approximately no end user impact, unless you were meddling with the (unfortunately public) internals of `bevy_ecs`. - `Fetch`, `FilterFetch` and `ReadOnlyFetch` trait no longer have a `'state` lifetime - this was unneeded - `ReadOnly/Fetch` associated types on `WorldQuery` are now on a new `WorldQueryGats<'world>` trait - was required to work around lack of Generic Associated Types (we wish to express `type Fetch<'a>: Fetch<'a>`) - `derive(WorldQuery)` no longer requires `'w` lifetime on struct - this was unneeded, and improves the end user experience - `EntityMut::get_unchecked_mut` returns `&'_ mut T` not `&'w mut T` - allows easier use of unsafe API with less footguns, and can be worked around via lifetime transmutery as a user - `Bundle::from_components` now takes a `ctx` parameter to pass to the `FnMut` closure - required because closure return types can't borrow from captures - `Fetch::init` takes `&'world World`, `Fetch::set_archetype` takes `&'world Archetype` and `&'world Tables`, `Fetch::set_table` takes `&'world Table` - allows types implementing `Fetch` to store borrows into world - `WorldQuery` trait now has a `shrink` fn to shorten the lifetime in `Fetch::<'a>::Item` - this works around lack of subtyping of assoc types, rust doesnt allow you to turn `<T as Fetch<'static>>::Item'` into `<T as Fetch<'a>>::Item'` - `QueryCombinationsIter` requires this - Most types implementing `Fetch` now have a lifetime `'w` - allows the fetches to store borrows of world data instead of using raw pointers ## Migration guide - `EntityMut::get_unchecked_mut` returns a more restricted lifetime, there is no general way to migrate this as it depends on your code - `Bundle::from_components` implementations must pass the `ctx` arg to `func` - `Bundle::from_components` callers have to use a fn arg instead of closure captures for borrowing from world - Remove lifetime args on `derive(WorldQuery)` structs as it is nonsensical - `<Q as WorldQuery>::ReadOnly/Fetch` should be changed to either `RO/QueryFetch<'world>` or `<Q as WorldQueryGats<'world>>::ReadOnly/Fetch` - `<F as Fetch<'w, 's>>` should be changed to `<F as Fetch<'w>>` - Change the fn sigs of `Fetch::init/set_archetype/set_table` to match respective trait fn sigs - Implement the required `fn shrink` on any `WorldQuery` implementations - Move assoc types `Fetch` and `ReadOnlyFetch` on `WorldQuery` impls to `WorldQueryGats` impls - Pass an appropriate `'world` lifetime to whatever fetch struct you are for some reason using ### Type inference regression in some cases rustc may give spurrious errors when attempting to infer the `F` parameter on a query/querystate this can be fixed by manually specifying the type, i.e. `QueryState::new::<_, ()>(world)`. The error is rather confusing: ```rust= error[E0271]: type mismatch resolving `<() as Fetch<'_>>::Item == bool` --> crates/bevy_pbr/src/render/light.rs:1413:30 | 1413 | main_view_query: QueryState::new(world), | ^^^^^^^^^^^^^^^ expected `bool`, found `()` | = note: required because of the requirements on the impl of `for<'x> FilterFetch<'x>` for `<() as WorldQueryGats<'x>>::Fetch` note: required by a bound in `bevy_ecs::query::QueryState::<Q, F>::new` --> crates/bevy_ecs/src/query/state.rs:49:32 | 49 | for<'x> QueryFetch<'x, F>: FilterFetch<'x>, | ^^^^^^^^^^^^^^^ required by this bound in `bevy_ecs::query::QueryState::<Q, F>::new` ``` --- Made with help from @BoxyUwU and @alice-i-cecile Co-authored-by: Boxy <supbscripter@gmail.com>
2022-04-27 23:44:06 +00:00
/// struct HealthQuery {
/// health: &'static mut Health,
/// buff: Option<&'static mut Buff>,
Implement `WorldQuery` derive macro (#2713) # Objective - Closes #786 - Closes #2252 - Closes #2588 This PR implements a derive macro that allows users to define their queries as structs with named fields. ## Example ```rust #[derive(WorldQuery)] #[world_query(derive(Debug))] struct NumQuery<'w, T: Component, P: Component> { entity: Entity, u: UNumQuery<'w>, generic: GenericQuery<'w, T, P>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct UNumQuery<'w> { u_16: &'w u16, u_32_opt: Option<&'w u32>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct GenericQuery<'w, T: Component, P: Component> { generic: (&'w T, &'w P), } #[derive(WorldQuery)] #[world_query(filter)] struct NumQueryFilter<T: Component, P: Component> { _u_16: With<u16>, _u_32: With<u32>, _or: Or<(With<i16>, Changed<u16>, Added<u32>)>, _generic_tuple: (With<T>, With<P>), _without: Without<Option<u16>>, _tp: PhantomData<(T, P)>, } fn print_nums_readonly(query: Query<NumQuery<u64, i64>, NumQueryFilter<u64, i64>>) { for num in query.iter() { println!("{:#?}", num); } } #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct MutNumQuery<'w, T: Component, P: Component> { i_16: &'w mut i16, i_32_opt: Option<&'w mut i32>, } fn print_nums(mut query: Query<MutNumQuery, NumQueryFilter<u64, i64>>) { for num in query.iter_mut() { println!("{:#?}", num); } } ``` ## TODOs: - [x] Add support for `&T` and `&mut T` - [x] Test - [x] Add support for optional types - [x] Test - [x] Add support for `Entity` - [x] Test - [x] Add support for nested `WorldQuery` - [x] Test - [x] Add support for tuples - [x] Test - [x] Add support for generics - [x] Test - [x] Add support for query filters - [x] Test - [x] Add support for `PhantomData` - [x] Test - [x] Refactor `read_world_query_field_type_info` - [x] Properly document `readonly` attribute for nested queries and the static assertions that guarantee safety - [x] Test that we never implement `ReadOnlyFetch` for types that need mutable access - [x] Test that we insert static assertions for nested `WorldQuery` that a user marked as readonly
2022-02-24 00:19:49 +00:00
/// }
///
/// // `HealthQueryItem` is only available when accessing the query with mutable methods.
Implement `WorldQuery` derive macro (#2713) # Objective - Closes #786 - Closes #2252 - Closes #2588 This PR implements a derive macro that allows users to define their queries as structs with named fields. ## Example ```rust #[derive(WorldQuery)] #[world_query(derive(Debug))] struct NumQuery<'w, T: Component, P: Component> { entity: Entity, u: UNumQuery<'w>, generic: GenericQuery<'w, T, P>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct UNumQuery<'w> { u_16: &'w u16, u_32_opt: Option<&'w u32>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct GenericQuery<'w, T: Component, P: Component> { generic: (&'w T, &'w P), } #[derive(WorldQuery)] #[world_query(filter)] struct NumQueryFilter<T: Component, P: Component> { _u_16: With<u16>, _u_32: With<u32>, _or: Or<(With<i16>, Changed<u16>, Added<u32>)>, _generic_tuple: (With<T>, With<P>), _without: Without<Option<u16>>, _tp: PhantomData<(T, P)>, } fn print_nums_readonly(query: Query<NumQuery<u64, i64>, NumQueryFilter<u64, i64>>) { for num in query.iter() { println!("{:#?}", num); } } #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct MutNumQuery<'w, T: Component, P: Component> { i_16: &'w mut i16, i_32_opt: Option<&'w mut i32>, } fn print_nums(mut query: Query<MutNumQuery, NumQueryFilter<u64, i64>>) { for num in query.iter_mut() { println!("{:#?}", num); } } ``` ## TODOs: - [x] Add support for `&T` and `&mut T` - [x] Test - [x] Add support for optional types - [x] Test - [x] Add support for `Entity` - [x] Test - [x] Add support for nested `WorldQuery` - [x] Test - [x] Add support for tuples - [x] Test - [x] Add support for generics - [x] Test - [x] Add support for query filters - [x] Test - [x] Add support for `PhantomData` - [x] Test - [x] Refactor `read_world_query_field_type_info` - [x] Properly document `readonly` attribute for nested queries and the static assertions that guarantee safety - [x] Test that we never implement `ReadOnlyFetch` for types that need mutable access - [x] Test that we insert static assertions for nested `WorldQuery` that a user marked as readonly
2022-02-24 00:19:49 +00:00
/// impl<'w> HealthQueryItem<'w> {
/// fn damage(&mut self, value: f32) {
/// self.health.0 -= value;
/// }
///
/// fn total(&self) -> f32 {
/// self.health.0 + self.buff.as_deref().map_or(0.0, |Buff(buff)| *buff)
/// }
/// }
///
/// // `HealthQueryReadOnlyItem` is only available when accessing the query with immutable methods.
Implement `WorldQuery` derive macro (#2713) # Objective - Closes #786 - Closes #2252 - Closes #2588 This PR implements a derive macro that allows users to define their queries as structs with named fields. ## Example ```rust #[derive(WorldQuery)] #[world_query(derive(Debug))] struct NumQuery<'w, T: Component, P: Component> { entity: Entity, u: UNumQuery<'w>, generic: GenericQuery<'w, T, P>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct UNumQuery<'w> { u_16: &'w u16, u_32_opt: Option<&'w u32>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct GenericQuery<'w, T: Component, P: Component> { generic: (&'w T, &'w P), } #[derive(WorldQuery)] #[world_query(filter)] struct NumQueryFilter<T: Component, P: Component> { _u_16: With<u16>, _u_32: With<u32>, _or: Or<(With<i16>, Changed<u16>, Added<u32>)>, _generic_tuple: (With<T>, With<P>), _without: Without<Option<u16>>, _tp: PhantomData<(T, P)>, } fn print_nums_readonly(query: Query<NumQuery<u64, i64>, NumQueryFilter<u64, i64>>) { for num in query.iter() { println!("{:#?}", num); } } #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct MutNumQuery<'w, T: Component, P: Component> { i_16: &'w mut i16, i_32_opt: Option<&'w mut i32>, } fn print_nums(mut query: Query<MutNumQuery, NumQueryFilter<u64, i64>>) { for num in query.iter_mut() { println!("{:#?}", num); } } ``` ## TODOs: - [x] Add support for `&T` and `&mut T` - [x] Test - [x] Add support for optional types - [x] Test - [x] Add support for `Entity` - [x] Test - [x] Add support for nested `WorldQuery` - [x] Test - [x] Add support for tuples - [x] Test - [x] Add support for generics - [x] Test - [x] Add support for query filters - [x] Test - [x] Add support for `PhantomData` - [x] Test - [x] Refactor `read_world_query_field_type_info` - [x] Properly document `readonly` attribute for nested queries and the static assertions that guarantee safety - [x] Test that we never implement `ReadOnlyFetch` for types that need mutable access - [x] Test that we insert static assertions for nested `WorldQuery` that a user marked as readonly
2022-02-24 00:19:49 +00:00
/// impl<'w> HealthQueryReadOnlyItem<'w> {
/// fn total(&self) -> f32 {
/// self.health.0 + self.buff.map_or(0.0, |Buff(buff)| *buff)
/// }
/// }
///
/// fn my_system(mut health_query: Query<HealthQuery>) {
/// // The item returned by the iterator is of type `HealthQueryReadOnlyItem`.
/// for health in health_query.iter() {
Implement `WorldQuery` derive macro (#2713) # Objective - Closes #786 - Closes #2252 - Closes #2588 This PR implements a derive macro that allows users to define their queries as structs with named fields. ## Example ```rust #[derive(WorldQuery)] #[world_query(derive(Debug))] struct NumQuery<'w, T: Component, P: Component> { entity: Entity, u: UNumQuery<'w>, generic: GenericQuery<'w, T, P>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct UNumQuery<'w> { u_16: &'w u16, u_32_opt: Option<&'w u32>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct GenericQuery<'w, T: Component, P: Component> { generic: (&'w T, &'w P), } #[derive(WorldQuery)] #[world_query(filter)] struct NumQueryFilter<T: Component, P: Component> { _u_16: With<u16>, _u_32: With<u32>, _or: Or<(With<i16>, Changed<u16>, Added<u32>)>, _generic_tuple: (With<T>, With<P>), _without: Without<Option<u16>>, _tp: PhantomData<(T, P)>, } fn print_nums_readonly(query: Query<NumQuery<u64, i64>, NumQueryFilter<u64, i64>>) { for num in query.iter() { println!("{:#?}", num); } } #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct MutNumQuery<'w, T: Component, P: Component> { i_16: &'w mut i16, i_32_opt: Option<&'w mut i32>, } fn print_nums(mut query: Query<MutNumQuery, NumQueryFilter<u64, i64>>) { for num in query.iter_mut() { println!("{:#?}", num); } } ``` ## TODOs: - [x] Add support for `&T` and `&mut T` - [x] Test - [x] Add support for optional types - [x] Test - [x] Add support for `Entity` - [x] Test - [x] Add support for nested `WorldQuery` - [x] Test - [x] Add support for tuples - [x] Test - [x] Add support for generics - [x] Test - [x] Add support for query filters - [x] Test - [x] Add support for `PhantomData` - [x] Test - [x] Refactor `read_world_query_field_type_info` - [x] Properly document `readonly` attribute for nested queries and the static assertions that guarantee safety - [x] Test that we never implement `ReadOnlyFetch` for types that need mutable access - [x] Test that we insert static assertions for nested `WorldQuery` that a user marked as readonly
2022-02-24 00:19:49 +00:00
/// println!("Total: {}", health.total());
/// }
/// // The item returned by the iterator is of type `HealthQueryItem`.
/// for mut health in &mut health_query {
Implement `WorldQuery` derive macro (#2713) # Objective - Closes #786 - Closes #2252 - Closes #2588 This PR implements a derive macro that allows users to define their queries as structs with named fields. ## Example ```rust #[derive(WorldQuery)] #[world_query(derive(Debug))] struct NumQuery<'w, T: Component, P: Component> { entity: Entity, u: UNumQuery<'w>, generic: GenericQuery<'w, T, P>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct UNumQuery<'w> { u_16: &'w u16, u_32_opt: Option<&'w u32>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct GenericQuery<'w, T: Component, P: Component> { generic: (&'w T, &'w P), } #[derive(WorldQuery)] #[world_query(filter)] struct NumQueryFilter<T: Component, P: Component> { _u_16: With<u16>, _u_32: With<u32>, _or: Or<(With<i16>, Changed<u16>, Added<u32>)>, _generic_tuple: (With<T>, With<P>), _without: Without<Option<u16>>, _tp: PhantomData<(T, P)>, } fn print_nums_readonly(query: Query<NumQuery<u64, i64>, NumQueryFilter<u64, i64>>) { for num in query.iter() { println!("{:#?}", num); } } #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct MutNumQuery<'w, T: Component, P: Component> { i_16: &'w mut i16, i_32_opt: Option<&'w mut i32>, } fn print_nums(mut query: Query<MutNumQuery, NumQueryFilter<u64, i64>>) { for num in query.iter_mut() { println!("{:#?}", num); } } ``` ## TODOs: - [x] Add support for `&T` and `&mut T` - [x] Test - [x] Add support for optional types - [x] Test - [x] Add support for `Entity` - [x] Test - [x] Add support for nested `WorldQuery` - [x] Test - [x] Add support for tuples - [x] Test - [x] Add support for generics - [x] Test - [x] Add support for query filters - [x] Test - [x] Add support for `PhantomData` - [x] Test - [x] Refactor `read_world_query_field_type_info` - [x] Properly document `readonly` attribute for nested queries and the static assertions that guarantee safety - [x] Test that we never implement `ReadOnlyFetch` for types that need mutable access - [x] Test that we insert static assertions for nested `WorldQuery` that a user marked as readonly
2022-02-24 00:19:49 +00:00
/// health.damage(1.0);
/// println!("Total (mut): {}", health.total());
/// }
/// }
/// # bevy_ecs::system::assert_is_system(my_system);
/// ```
///
/// ## Deriving traits for query items
Replace `ReadOnlyFetch` with `ReadOnlyWorldQuery` (#4626) # Objective - Fix a type inference regression introduced by #3001 - Make read only bounds on world queries more user friendly ptrification required you to write `Q::Fetch: ReadOnlyFetch` as `for<'w> QueryFetch<'w, Q>: ReadOnlyFetch` which has the same type inference problem as `for<'w> QueryFetch<'w, Q>: FilterFetch<'w>` had, i.e. the following code would error: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: WorldQuery>(a: Query<Q, ()>) where for<'w> QueryFetch<'w, Q>: ReadOnlyFetch, { } ``` `for<..>` bounds are also rather user unfriendly.. ## Solution Remove the `ReadOnlyFetch` trait in favour of a `ReadOnlyWorldQuery` trait, and remove `WorldQueryGats::ReadOnlyFetch` in favor of `WorldQuery::ReadOnly` allowing the previous code snippet to be written as: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: ReadOnlyWorldQuery>(a: Query<Q, ()>) {} ``` This avoids the `for<...>` bound which makes the code simpler and also fixes the type inference issue. The reason for moving the two functions out of `FetchState` and into `WorldQuery` is to allow the world query `&mut T` to share a `State` with the `&T` world query so that it can have `type ReadOnly = &T`. Presumably it would be possible to instead have a `ReadOnlyRefMut<T>` world query and then do `type ReadOnly = ReadOnlyRefMut<T>` much like how (before this PR) we had a `ReadOnlyWriteFetch<T>`. A side benefit of the current solution in this PR is that it will likely make it easier in the future to support an API such as `Query<&mut T> -> Query<&T>`. The primary benefit IMO is just that `ReadOnlyRefMut<T>` and its associated fetch would have to reimplement all of the logic that the `&T` world query impl does but this solution avoids that :) --- ## Changelog/Migration Guide The trait `ReadOnlyFetch` has been replaced with `ReadOnlyWorldQuery` along with the `WorldQueryGats::ReadOnlyFetch` assoc type which has been replaced with `<WorldQuery::ReadOnly as WorldQueryGats>::Fetch` - Any where clauses such as `QueryFetch<Q>: ReadOnlyFetch` should be replaced with `Q: ReadOnlyWorldQuery`. - Any custom world query impls should implement `ReadOnlyWorldQuery` insead of `ReadOnlyFetch` Functions `update_component_access` and `update_archetype_component_access` have been moved from the `FetchState` trait to `WorldQuery` - Any callers should now call `Q::update_component_access(state` instead of `state.update_component_access` (and `update_archetype_component_access` respectively) - Any custom world query impls should move the functions from the `FetchState` impl to `WorldQuery` impl `WorldQuery` has been made an `unsafe trait`, `FetchState` has been made a safe `trait`. (I think this is how it should have always been, but regardless this is _definitely_ necessary now that the two functions have been moved to `WorldQuery`) - If you have a custom `FetchState` impl make it a normal `impl` instead of `unsafe impl` - If you have a custom `WorldQuery` impl make it an `unsafe impl`, if your code was sound before it is going to still be sound
2022-06-13 23:35:54 +00:00
///
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
/// The `QueryData` derive macro does not automatically implement the traits of the struct to the query item types.
/// Something similar can be done by using the `#[query_data(derive(...))]` attribute.
/// This will apply the listed derivable traits to the query item structs.
Replace `ReadOnlyFetch` with `ReadOnlyWorldQuery` (#4626) # Objective - Fix a type inference regression introduced by #3001 - Make read only bounds on world queries more user friendly ptrification required you to write `Q::Fetch: ReadOnlyFetch` as `for<'w> QueryFetch<'w, Q>: ReadOnlyFetch` which has the same type inference problem as `for<'w> QueryFetch<'w, Q>: FilterFetch<'w>` had, i.e. the following code would error: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: WorldQuery>(a: Query<Q, ()>) where for<'w> QueryFetch<'w, Q>: ReadOnlyFetch, { } ``` `for<..>` bounds are also rather user unfriendly.. ## Solution Remove the `ReadOnlyFetch` trait in favour of a `ReadOnlyWorldQuery` trait, and remove `WorldQueryGats::ReadOnlyFetch` in favor of `WorldQuery::ReadOnly` allowing the previous code snippet to be written as: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: ReadOnlyWorldQuery>(a: Query<Q, ()>) {} ``` This avoids the `for<...>` bound which makes the code simpler and also fixes the type inference issue. The reason for moving the two functions out of `FetchState` and into `WorldQuery` is to allow the world query `&mut T` to share a `State` with the `&T` world query so that it can have `type ReadOnly = &T`. Presumably it would be possible to instead have a `ReadOnlyRefMut<T>` world query and then do `type ReadOnly = ReadOnlyRefMut<T>` much like how (before this PR) we had a `ReadOnlyWriteFetch<T>`. A side benefit of the current solution in this PR is that it will likely make it easier in the future to support an API such as `Query<&mut T> -> Query<&T>`. The primary benefit IMO is just that `ReadOnlyRefMut<T>` and its associated fetch would have to reimplement all of the logic that the `&T` world query impl does but this solution avoids that :) --- ## Changelog/Migration Guide The trait `ReadOnlyFetch` has been replaced with `ReadOnlyWorldQuery` along with the `WorldQueryGats::ReadOnlyFetch` assoc type which has been replaced with `<WorldQuery::ReadOnly as WorldQueryGats>::Fetch` - Any where clauses such as `QueryFetch<Q>: ReadOnlyFetch` should be replaced with `Q: ReadOnlyWorldQuery`. - Any custom world query impls should implement `ReadOnlyWorldQuery` insead of `ReadOnlyFetch` Functions `update_component_access` and `update_archetype_component_access` have been moved from the `FetchState` trait to `WorldQuery` - Any callers should now call `Q::update_component_access(state` instead of `state.update_component_access` (and `update_archetype_component_access` respectively) - Any custom world query impls should move the functions from the `FetchState` impl to `WorldQuery` impl `WorldQuery` has been made an `unsafe trait`, `FetchState` has been made a safe `trait`. (I think this is how it should have always been, but regardless this is _definitely_ necessary now that the two functions have been moved to `WorldQuery`) - If you have a custom `FetchState` impl make it a normal `impl` instead of `unsafe impl` - If you have a custom `WorldQuery` impl make it an `unsafe impl`, if your code was sound before it is going to still be sound
2022-06-13 23:35:54 +00:00
///
/// ```
Implement `WorldQuery` derive macro (#2713) # Objective - Closes #786 - Closes #2252 - Closes #2588 This PR implements a derive macro that allows users to define their queries as structs with named fields. ## Example ```rust #[derive(WorldQuery)] #[world_query(derive(Debug))] struct NumQuery<'w, T: Component, P: Component> { entity: Entity, u: UNumQuery<'w>, generic: GenericQuery<'w, T, P>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct UNumQuery<'w> { u_16: &'w u16, u_32_opt: Option<&'w u32>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct GenericQuery<'w, T: Component, P: Component> { generic: (&'w T, &'w P), } #[derive(WorldQuery)] #[world_query(filter)] struct NumQueryFilter<T: Component, P: Component> { _u_16: With<u16>, _u_32: With<u32>, _or: Or<(With<i16>, Changed<u16>, Added<u32>)>, _generic_tuple: (With<T>, With<P>), _without: Without<Option<u16>>, _tp: PhantomData<(T, P)>, } fn print_nums_readonly(query: Query<NumQuery<u64, i64>, NumQueryFilter<u64, i64>>) { for num in query.iter() { println!("{:#?}", num); } } #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct MutNumQuery<'w, T: Component, P: Component> { i_16: &'w mut i16, i_32_opt: Option<&'w mut i32>, } fn print_nums(mut query: Query<MutNumQuery, NumQueryFilter<u64, i64>>) { for num in query.iter_mut() { println!("{:#?}", num); } } ``` ## TODOs: - [x] Add support for `&T` and `&mut T` - [x] Test - [x] Add support for optional types - [x] Test - [x] Add support for `Entity` - [x] Test - [x] Add support for nested `WorldQuery` - [x] Test - [x] Add support for tuples - [x] Test - [x] Add support for generics - [x] Test - [x] Add support for query filters - [x] Test - [x] Add support for `PhantomData` - [x] Test - [x] Refactor `read_world_query_field_type_info` - [x] Properly document `readonly` attribute for nested queries and the static assertions that guarantee safety - [x] Test that we never implement `ReadOnlyFetch` for types that need mutable access - [x] Test that we insert static assertions for nested `WorldQuery` that a user marked as readonly
2022-02-24 00:19:49 +00:00
/// # use bevy_ecs::prelude::*;
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
/// # use bevy_ecs::query::QueryData;
/// #
/// # #[derive(Component, Debug)]
/// # struct ComponentA;
/// #
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
/// #[derive(QueryData)]
/// #[query_data(mutable, derive(Debug))]
/// struct CustomQuery {
/// component_a: &'static ComponentA,
Implement `WorldQuery` derive macro (#2713) # Objective - Closes #786 - Closes #2252 - Closes #2588 This PR implements a derive macro that allows users to define their queries as structs with named fields. ## Example ```rust #[derive(WorldQuery)] #[world_query(derive(Debug))] struct NumQuery<'w, T: Component, P: Component> { entity: Entity, u: UNumQuery<'w>, generic: GenericQuery<'w, T, P>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct UNumQuery<'w> { u_16: &'w u16, u_32_opt: Option<&'w u32>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct GenericQuery<'w, T: Component, P: Component> { generic: (&'w T, &'w P), } #[derive(WorldQuery)] #[world_query(filter)] struct NumQueryFilter<T: Component, P: Component> { _u_16: With<u16>, _u_32: With<u32>, _or: Or<(With<i16>, Changed<u16>, Added<u32>)>, _generic_tuple: (With<T>, With<P>), _without: Without<Option<u16>>, _tp: PhantomData<(T, P)>, } fn print_nums_readonly(query: Query<NumQuery<u64, i64>, NumQueryFilter<u64, i64>>) { for num in query.iter() { println!("{:#?}", num); } } #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct MutNumQuery<'w, T: Component, P: Component> { i_16: &'w mut i16, i_32_opt: Option<&'w mut i32>, } fn print_nums(mut query: Query<MutNumQuery, NumQueryFilter<u64, i64>>) { for num in query.iter_mut() { println!("{:#?}", num); } } ``` ## TODOs: - [x] Add support for `&T` and `&mut T` - [x] Test - [x] Add support for optional types - [x] Test - [x] Add support for `Entity` - [x] Test - [x] Add support for nested `WorldQuery` - [x] Test - [x] Add support for tuples - [x] Test - [x] Add support for generics - [x] Test - [x] Add support for query filters - [x] Test - [x] Add support for `PhantomData` - [x] Test - [x] Refactor `read_world_query_field_type_info` - [x] Properly document `readonly` attribute for nested queries and the static assertions that guarantee safety - [x] Test that we never implement `ReadOnlyFetch` for types that need mutable access - [x] Test that we insert static assertions for nested `WorldQuery` that a user marked as readonly
2022-02-24 00:19:49 +00:00
/// }
///
/// // This function statically checks that `T` implements `Debug`.
Implement `WorldQuery` derive macro (#2713) # Objective - Closes #786 - Closes #2252 - Closes #2588 This PR implements a derive macro that allows users to define their queries as structs with named fields. ## Example ```rust #[derive(WorldQuery)] #[world_query(derive(Debug))] struct NumQuery<'w, T: Component, P: Component> { entity: Entity, u: UNumQuery<'w>, generic: GenericQuery<'w, T, P>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct UNumQuery<'w> { u_16: &'w u16, u_32_opt: Option<&'w u32>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct GenericQuery<'w, T: Component, P: Component> { generic: (&'w T, &'w P), } #[derive(WorldQuery)] #[world_query(filter)] struct NumQueryFilter<T: Component, P: Component> { _u_16: With<u16>, _u_32: With<u32>, _or: Or<(With<i16>, Changed<u16>, Added<u32>)>, _generic_tuple: (With<T>, With<P>), _without: Without<Option<u16>>, _tp: PhantomData<(T, P)>, } fn print_nums_readonly(query: Query<NumQuery<u64, i64>, NumQueryFilter<u64, i64>>) { for num in query.iter() { println!("{:#?}", num); } } #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct MutNumQuery<'w, T: Component, P: Component> { i_16: &'w mut i16, i_32_opt: Option<&'w mut i32>, } fn print_nums(mut query: Query<MutNumQuery, NumQueryFilter<u64, i64>>) { for num in query.iter_mut() { println!("{:#?}", num); } } ``` ## TODOs: - [x] Add support for `&T` and `&mut T` - [x] Test - [x] Add support for optional types - [x] Test - [x] Add support for `Entity` - [x] Test - [x] Add support for nested `WorldQuery` - [x] Test - [x] Add support for tuples - [x] Test - [x] Add support for generics - [x] Test - [x] Add support for query filters - [x] Test - [x] Add support for `PhantomData` - [x] Test - [x] Refactor `read_world_query_field_type_info` - [x] Properly document `readonly` attribute for nested queries and the static assertions that guarantee safety - [x] Test that we never implement `ReadOnlyFetch` for types that need mutable access - [x] Test that we insert static assertions for nested `WorldQuery` that a user marked as readonly
2022-02-24 00:19:49 +00:00
/// fn assert_debug<T: std::fmt::Debug>() {}
///
/// assert_debug::<CustomQueryItem>();
/// assert_debug::<CustomQueryReadOnlyItem>();
Implement `WorldQuery` derive macro (#2713) # Objective - Closes #786 - Closes #2252 - Closes #2588 This PR implements a derive macro that allows users to define their queries as structs with named fields. ## Example ```rust #[derive(WorldQuery)] #[world_query(derive(Debug))] struct NumQuery<'w, T: Component, P: Component> { entity: Entity, u: UNumQuery<'w>, generic: GenericQuery<'w, T, P>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct UNumQuery<'w> { u_16: &'w u16, u_32_opt: Option<&'w u32>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct GenericQuery<'w, T: Component, P: Component> { generic: (&'w T, &'w P), } #[derive(WorldQuery)] #[world_query(filter)] struct NumQueryFilter<T: Component, P: Component> { _u_16: With<u16>, _u_32: With<u32>, _or: Or<(With<i16>, Changed<u16>, Added<u32>)>, _generic_tuple: (With<T>, With<P>), _without: Without<Option<u16>>, _tp: PhantomData<(T, P)>, } fn print_nums_readonly(query: Query<NumQuery<u64, i64>, NumQueryFilter<u64, i64>>) { for num in query.iter() { println!("{:#?}", num); } } #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct MutNumQuery<'w, T: Component, P: Component> { i_16: &'w mut i16, i_32_opt: Option<&'w mut i32>, } fn print_nums(mut query: Query<MutNumQuery, NumQueryFilter<u64, i64>>) { for num in query.iter_mut() { println!("{:#?}", num); } } ``` ## TODOs: - [x] Add support for `&T` and `&mut T` - [x] Test - [x] Add support for optional types - [x] Test - [x] Add support for `Entity` - [x] Test - [x] Add support for nested `WorldQuery` - [x] Test - [x] Add support for tuples - [x] Test - [x] Add support for generics - [x] Test - [x] Add support for query filters - [x] Test - [x] Add support for `PhantomData` - [x] Test - [x] Refactor `read_world_query_field_type_info` - [x] Properly document `readonly` attribute for nested queries and the static assertions that guarantee safety - [x] Test that we never implement `ReadOnlyFetch` for types that need mutable access - [x] Test that we insert static assertions for nested `WorldQuery` that a user marked as readonly
2022-02-24 00:19:49 +00:00
/// ```
///
/// ## Query composition
Implement `WorldQuery` derive macro (#2713) # Objective - Closes #786 - Closes #2252 - Closes #2588 This PR implements a derive macro that allows users to define their queries as structs with named fields. ## Example ```rust #[derive(WorldQuery)] #[world_query(derive(Debug))] struct NumQuery<'w, T: Component, P: Component> { entity: Entity, u: UNumQuery<'w>, generic: GenericQuery<'w, T, P>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct UNumQuery<'w> { u_16: &'w u16, u_32_opt: Option<&'w u32>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct GenericQuery<'w, T: Component, P: Component> { generic: (&'w T, &'w P), } #[derive(WorldQuery)] #[world_query(filter)] struct NumQueryFilter<T: Component, P: Component> { _u_16: With<u16>, _u_32: With<u32>, _or: Or<(With<i16>, Changed<u16>, Added<u32>)>, _generic_tuple: (With<T>, With<P>), _without: Without<Option<u16>>, _tp: PhantomData<(T, P)>, } fn print_nums_readonly(query: Query<NumQuery<u64, i64>, NumQueryFilter<u64, i64>>) { for num in query.iter() { println!("{:#?}", num); } } #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct MutNumQuery<'w, T: Component, P: Component> { i_16: &'w mut i16, i_32_opt: Option<&'w mut i32>, } fn print_nums(mut query: Query<MutNumQuery, NumQueryFilter<u64, i64>>) { for num in query.iter_mut() { println!("{:#?}", num); } } ``` ## TODOs: - [x] Add support for `&T` and `&mut T` - [x] Test - [x] Add support for optional types - [x] Test - [x] Add support for `Entity` - [x] Test - [x] Add support for nested `WorldQuery` - [x] Test - [x] Add support for tuples - [x] Test - [x] Add support for generics - [x] Test - [x] Add support for query filters - [x] Test - [x] Add support for `PhantomData` - [x] Test - [x] Refactor `read_world_query_field_type_info` - [x] Properly document `readonly` attribute for nested queries and the static assertions that guarantee safety - [x] Test that we never implement `ReadOnlyFetch` for types that need mutable access - [x] Test that we insert static assertions for nested `WorldQuery` that a user marked as readonly
2022-02-24 00:19:49 +00:00
///
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
/// It is possible to use any `QueryData` as a field of another one.
/// This means that a `QueryData` can also be used as a subquery, potentially in multiple places.
Implement `WorldQuery` derive macro (#2713) # Objective - Closes #786 - Closes #2252 - Closes #2588 This PR implements a derive macro that allows users to define their queries as structs with named fields. ## Example ```rust #[derive(WorldQuery)] #[world_query(derive(Debug))] struct NumQuery<'w, T: Component, P: Component> { entity: Entity, u: UNumQuery<'w>, generic: GenericQuery<'w, T, P>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct UNumQuery<'w> { u_16: &'w u16, u_32_opt: Option<&'w u32>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct GenericQuery<'w, T: Component, P: Component> { generic: (&'w T, &'w P), } #[derive(WorldQuery)] #[world_query(filter)] struct NumQueryFilter<T: Component, P: Component> { _u_16: With<u16>, _u_32: With<u32>, _or: Or<(With<i16>, Changed<u16>, Added<u32>)>, _generic_tuple: (With<T>, With<P>), _without: Without<Option<u16>>, _tp: PhantomData<(T, P)>, } fn print_nums_readonly(query: Query<NumQuery<u64, i64>, NumQueryFilter<u64, i64>>) { for num in query.iter() { println!("{:#?}", num); } } #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct MutNumQuery<'w, T: Component, P: Component> { i_16: &'w mut i16, i_32_opt: Option<&'w mut i32>, } fn print_nums(mut query: Query<MutNumQuery, NumQueryFilter<u64, i64>>) { for num in query.iter_mut() { println!("{:#?}", num); } } ``` ## TODOs: - [x] Add support for `&T` and `&mut T` - [x] Test - [x] Add support for optional types - [x] Test - [x] Add support for `Entity` - [x] Test - [x] Add support for nested `WorldQuery` - [x] Test - [x] Add support for tuples - [x] Test - [x] Add support for generics - [x] Test - [x] Add support for query filters - [x] Test - [x] Add support for `PhantomData` - [x] Test - [x] Refactor `read_world_query_field_type_info` - [x] Properly document `readonly` attribute for nested queries and the static assertions that guarantee safety - [x] Test that we never implement `ReadOnlyFetch` for types that need mutable access - [x] Test that we insert static assertions for nested `WorldQuery` that a user marked as readonly
2022-02-24 00:19:49 +00:00
///
/// ```
/// # use bevy_ecs::prelude::*;
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
/// # use bevy_ecs::query::QueryData;
/// #
/// # #[derive(Component)]
/// # struct ComponentA;
/// # #[derive(Component)]
/// # struct ComponentB;
/// # #[derive(Component)]
/// # struct ComponentC;
/// #
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
/// #[derive(QueryData)]
/// struct SubQuery {
/// component_a: &'static ComponentA,
/// component_b: &'static ComponentB,
Implement `WorldQuery` derive macro (#2713) # Objective - Closes #786 - Closes #2252 - Closes #2588 This PR implements a derive macro that allows users to define their queries as structs with named fields. ## Example ```rust #[derive(WorldQuery)] #[world_query(derive(Debug))] struct NumQuery<'w, T: Component, P: Component> { entity: Entity, u: UNumQuery<'w>, generic: GenericQuery<'w, T, P>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct UNumQuery<'w> { u_16: &'w u16, u_32_opt: Option<&'w u32>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct GenericQuery<'w, T: Component, P: Component> { generic: (&'w T, &'w P), } #[derive(WorldQuery)] #[world_query(filter)] struct NumQueryFilter<T: Component, P: Component> { _u_16: With<u16>, _u_32: With<u32>, _or: Or<(With<i16>, Changed<u16>, Added<u32>)>, _generic_tuple: (With<T>, With<P>), _without: Without<Option<u16>>, _tp: PhantomData<(T, P)>, } fn print_nums_readonly(query: Query<NumQuery<u64, i64>, NumQueryFilter<u64, i64>>) { for num in query.iter() { println!("{:#?}", num); } } #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct MutNumQuery<'w, T: Component, P: Component> { i_16: &'w mut i16, i_32_opt: Option<&'w mut i32>, } fn print_nums(mut query: Query<MutNumQuery, NumQueryFilter<u64, i64>>) { for num in query.iter_mut() { println!("{:#?}", num); } } ``` ## TODOs: - [x] Add support for `&T` and `&mut T` - [x] Test - [x] Add support for optional types - [x] Test - [x] Add support for `Entity` - [x] Test - [x] Add support for nested `WorldQuery` - [x] Test - [x] Add support for tuples - [x] Test - [x] Add support for generics - [x] Test - [x] Add support for query filters - [x] Test - [x] Add support for `PhantomData` - [x] Test - [x] Refactor `read_world_query_field_type_info` - [x] Properly document `readonly` attribute for nested queries and the static assertions that guarantee safety - [x] Test that we never implement `ReadOnlyFetch` for types that need mutable access - [x] Test that we insert static assertions for nested `WorldQuery` that a user marked as readonly
2022-02-24 00:19:49 +00:00
/// }
///
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
/// #[derive(QueryData)]
/// struct MyQuery {
/// subquery: SubQuery,
/// component_c: &'static ComponentC,
Implement `WorldQuery` derive macro (#2713) # Objective - Closes #786 - Closes #2252 - Closes #2588 This PR implements a derive macro that allows users to define their queries as structs with named fields. ## Example ```rust #[derive(WorldQuery)] #[world_query(derive(Debug))] struct NumQuery<'w, T: Component, P: Component> { entity: Entity, u: UNumQuery<'w>, generic: GenericQuery<'w, T, P>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct UNumQuery<'w> { u_16: &'w u16, u_32_opt: Option<&'w u32>, } #[derive(WorldQuery)] #[world_query(derive(Debug))] struct GenericQuery<'w, T: Component, P: Component> { generic: (&'w T, &'w P), } #[derive(WorldQuery)] #[world_query(filter)] struct NumQueryFilter<T: Component, P: Component> { _u_16: With<u16>, _u_32: With<u32>, _or: Or<(With<i16>, Changed<u16>, Added<u32>)>, _generic_tuple: (With<T>, With<P>), _without: Without<Option<u16>>, _tp: PhantomData<(T, P)>, } fn print_nums_readonly(query: Query<NumQuery<u64, i64>, NumQueryFilter<u64, i64>>) { for num in query.iter() { println!("{:#?}", num); } } #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct MutNumQuery<'w, T: Component, P: Component> { i_16: &'w mut i16, i_32_opt: Option<&'w mut i32>, } fn print_nums(mut query: Query<MutNumQuery, NumQueryFilter<u64, i64>>) { for num in query.iter_mut() { println!("{:#?}", num); } } ``` ## TODOs: - [x] Add support for `&T` and `&mut T` - [x] Test - [x] Add support for optional types - [x] Test - [x] Add support for `Entity` - [x] Test - [x] Add support for nested `WorldQuery` - [x] Test - [x] Add support for tuples - [x] Test - [x] Add support for generics - [x] Test - [x] Add support for query filters - [x] Test - [x] Add support for `PhantomData` - [x] Test - [x] Refactor `read_world_query_field_type_info` - [x] Properly document `readonly` attribute for nested queries and the static assertions that guarantee safety - [x] Test that we never implement `ReadOnlyFetch` for types that need mutable access - [x] Test that we insert static assertions for nested `WorldQuery` that a user marked as readonly
2022-02-24 00:19:49 +00:00
/// }
/// ```
///
/// # Generic Queries
///
/// When writing generic code, it is often necessary to use [`PhantomData`]
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
/// to constrain type parameters. Since `QueryData` is implemented for all
/// `PhantomData<T>` types, this pattern can be used with this macro.
///
/// ```
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
/// # use bevy_ecs::{prelude::*, query::QueryData};
/// # use std::marker::PhantomData;
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
/// #[derive(QueryData)]
/// pub struct GenericQuery<T> {
/// id: Entity,
/// marker: PhantomData<T>,
/// }
/// # fn my_system(q: Query<GenericQuery<()>>) {}
/// # bevy_ecs::system::assert_is_system(my_system);
/// ```
///
Replace `ReadOnlyFetch` with `ReadOnlyWorldQuery` (#4626) # Objective - Fix a type inference regression introduced by #3001 - Make read only bounds on world queries more user friendly ptrification required you to write `Q::Fetch: ReadOnlyFetch` as `for<'w> QueryFetch<'w, Q>: ReadOnlyFetch` which has the same type inference problem as `for<'w> QueryFetch<'w, Q>: FilterFetch<'w>` had, i.e. the following code would error: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: WorldQuery>(a: Query<Q, ()>) where for<'w> QueryFetch<'w, Q>: ReadOnlyFetch, { } ``` `for<..>` bounds are also rather user unfriendly.. ## Solution Remove the `ReadOnlyFetch` trait in favour of a `ReadOnlyWorldQuery` trait, and remove `WorldQueryGats::ReadOnlyFetch` in favor of `WorldQuery::ReadOnly` allowing the previous code snippet to be written as: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: ReadOnlyWorldQuery>(a: Query<Q, ()>) {} ``` This avoids the `for<...>` bound which makes the code simpler and also fixes the type inference issue. The reason for moving the two functions out of `FetchState` and into `WorldQuery` is to allow the world query `&mut T` to share a `State` with the `&T` world query so that it can have `type ReadOnly = &T`. Presumably it would be possible to instead have a `ReadOnlyRefMut<T>` world query and then do `type ReadOnly = ReadOnlyRefMut<T>` much like how (before this PR) we had a `ReadOnlyWriteFetch<T>`. A side benefit of the current solution in this PR is that it will likely make it easier in the future to support an API such as `Query<&mut T> -> Query<&T>`. The primary benefit IMO is just that `ReadOnlyRefMut<T>` and its associated fetch would have to reimplement all of the logic that the `&T` world query impl does but this solution avoids that :) --- ## Changelog/Migration Guide The trait `ReadOnlyFetch` has been replaced with `ReadOnlyWorldQuery` along with the `WorldQueryGats::ReadOnlyFetch` assoc type which has been replaced with `<WorldQuery::ReadOnly as WorldQueryGats>::Fetch` - Any where clauses such as `QueryFetch<Q>: ReadOnlyFetch` should be replaced with `Q: ReadOnlyWorldQuery`. - Any custom world query impls should implement `ReadOnlyWorldQuery` insead of `ReadOnlyFetch` Functions `update_component_access` and `update_archetype_component_access` have been moved from the `FetchState` trait to `WorldQuery` - Any callers should now call `Q::update_component_access(state` instead of `state.update_component_access` (and `update_archetype_component_access` respectively) - Any custom world query impls should move the functions from the `FetchState` impl to `WorldQuery` impl `WorldQuery` has been made an `unsafe trait`, `FetchState` has been made a safe `trait`. (I think this is how it should have always been, but regardless this is _definitely_ necessary now that the two functions have been moved to `WorldQuery`) - If you have a custom `FetchState` impl make it a normal `impl` instead of `unsafe impl` - If you have a custom `WorldQuery` impl make it an `unsafe impl`, if your code was sound before it is going to still be sound
2022-06-13 23:35:54 +00:00
/// # Safety
///
/// Component access of `Self::ReadOnly` must be a subset of `Self`
/// and `Self::ReadOnly` must match exactly the same archetypes/tables as `Self`
Replace `ReadOnlyFetch` with `ReadOnlyWorldQuery` (#4626) # Objective - Fix a type inference regression introduced by #3001 - Make read only bounds on world queries more user friendly ptrification required you to write `Q::Fetch: ReadOnlyFetch` as `for<'w> QueryFetch<'w, Q>: ReadOnlyFetch` which has the same type inference problem as `for<'w> QueryFetch<'w, Q>: FilterFetch<'w>` had, i.e. the following code would error: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: WorldQuery>(a: Query<Q, ()>) where for<'w> QueryFetch<'w, Q>: ReadOnlyFetch, { } ``` `for<..>` bounds are also rather user unfriendly.. ## Solution Remove the `ReadOnlyFetch` trait in favour of a `ReadOnlyWorldQuery` trait, and remove `WorldQueryGats::ReadOnlyFetch` in favor of `WorldQuery::ReadOnly` allowing the previous code snippet to be written as: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: ReadOnlyWorldQuery>(a: Query<Q, ()>) {} ``` This avoids the `for<...>` bound which makes the code simpler and also fixes the type inference issue. The reason for moving the two functions out of `FetchState` and into `WorldQuery` is to allow the world query `&mut T` to share a `State` with the `&T` world query so that it can have `type ReadOnly = &T`. Presumably it would be possible to instead have a `ReadOnlyRefMut<T>` world query and then do `type ReadOnly = ReadOnlyRefMut<T>` much like how (before this PR) we had a `ReadOnlyWriteFetch<T>`. A side benefit of the current solution in this PR is that it will likely make it easier in the future to support an API such as `Query<&mut T> -> Query<&T>`. The primary benefit IMO is just that `ReadOnlyRefMut<T>` and its associated fetch would have to reimplement all of the logic that the `&T` world query impl does but this solution avoids that :) --- ## Changelog/Migration Guide The trait `ReadOnlyFetch` has been replaced with `ReadOnlyWorldQuery` along with the `WorldQueryGats::ReadOnlyFetch` assoc type which has been replaced with `<WorldQuery::ReadOnly as WorldQueryGats>::Fetch` - Any where clauses such as `QueryFetch<Q>: ReadOnlyFetch` should be replaced with `Q: ReadOnlyWorldQuery`. - Any custom world query impls should implement `ReadOnlyWorldQuery` insead of `ReadOnlyFetch` Functions `update_component_access` and `update_archetype_component_access` have been moved from the `FetchState` trait to `WorldQuery` - Any callers should now call `Q::update_component_access(state` instead of `state.update_component_access` (and `update_archetype_component_access` respectively) - Any custom world query impls should move the functions from the `FetchState` impl to `WorldQuery` impl `WorldQuery` has been made an `unsafe trait`, `FetchState` has been made a safe `trait`. (I think this is how it should have always been, but regardless this is _definitely_ necessary now that the two functions have been moved to `WorldQuery`) - If you have a custom `FetchState` impl make it a normal `impl` instead of `unsafe impl` - If you have a custom `WorldQuery` impl make it an `unsafe impl`, if your code was sound before it is going to still be sound
2022-06-13 23:35:54 +00:00
///
/// [`Query`]: crate::system::Query
/// [`ReadOnly`]: Self::ReadOnly
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
pub unsafe trait QueryData: WorldQuery {
/// The read-only variant of this [`QueryData`], which satisfies the [`ReadOnlyQueryData`] trait.
type ReadOnly: ReadOnlyQueryData<State = <Self as WorldQuery>::State>;
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
}
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
/// A [`QueryData`] that is read only.
///
/// # Safety
///
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
/// This must only be implemented for read-only [`QueryData`]'s.
pub unsafe trait ReadOnlyQueryData: QueryData<ReadOnly = Self> {}
Use lifetimed, type erased pointers in bevy_ecs (#3001) # Objective `bevy_ecs` has large amounts of unsafe code which is hard to get right and makes it difficult to audit for soundness. ## Solution Introduce lifetimed, type-erased pointers: `Ptr<'a>` `PtrMut<'a>` `OwningPtr<'a>'` and `ThinSlicePtr<'a, T>` which are newtypes around a raw pointer with a lifetime and conceptually representing strong invariants about the pointee and validity of the pointer. The process of converting bevy_ecs to use these has already caught multiple cases of unsound behavior. ## Changelog TL;DR for release notes: `bevy_ecs` now uses lifetimed, type-erased pointers internally, significantly improving safety and legibility without sacrificing performance. This should have approximately no end user impact, unless you were meddling with the (unfortunately public) internals of `bevy_ecs`. - `Fetch`, `FilterFetch` and `ReadOnlyFetch` trait no longer have a `'state` lifetime - this was unneeded - `ReadOnly/Fetch` associated types on `WorldQuery` are now on a new `WorldQueryGats<'world>` trait - was required to work around lack of Generic Associated Types (we wish to express `type Fetch<'a>: Fetch<'a>`) - `derive(WorldQuery)` no longer requires `'w` lifetime on struct - this was unneeded, and improves the end user experience - `EntityMut::get_unchecked_mut` returns `&'_ mut T` not `&'w mut T` - allows easier use of unsafe API with less footguns, and can be worked around via lifetime transmutery as a user - `Bundle::from_components` now takes a `ctx` parameter to pass to the `FnMut` closure - required because closure return types can't borrow from captures - `Fetch::init` takes `&'world World`, `Fetch::set_archetype` takes `&'world Archetype` and `&'world Tables`, `Fetch::set_table` takes `&'world Table` - allows types implementing `Fetch` to store borrows into world - `WorldQuery` trait now has a `shrink` fn to shorten the lifetime in `Fetch::<'a>::Item` - this works around lack of subtyping of assoc types, rust doesnt allow you to turn `<T as Fetch<'static>>::Item'` into `<T as Fetch<'a>>::Item'` - `QueryCombinationsIter` requires this - Most types implementing `Fetch` now have a lifetime `'w` - allows the fetches to store borrows of world data instead of using raw pointers ## Migration guide - `EntityMut::get_unchecked_mut` returns a more restricted lifetime, there is no general way to migrate this as it depends on your code - `Bundle::from_components` implementations must pass the `ctx` arg to `func` - `Bundle::from_components` callers have to use a fn arg instead of closure captures for borrowing from world - Remove lifetime args on `derive(WorldQuery)` structs as it is nonsensical - `<Q as WorldQuery>::ReadOnly/Fetch` should be changed to either `RO/QueryFetch<'world>` or `<Q as WorldQueryGats<'world>>::ReadOnly/Fetch` - `<F as Fetch<'w, 's>>` should be changed to `<F as Fetch<'w>>` - Change the fn sigs of `Fetch::init/set_archetype/set_table` to match respective trait fn sigs - Implement the required `fn shrink` on any `WorldQuery` implementations - Move assoc types `Fetch` and `ReadOnlyFetch` on `WorldQuery` impls to `WorldQueryGats` impls - Pass an appropriate `'world` lifetime to whatever fetch struct you are for some reason using ### Type inference regression in some cases rustc may give spurrious errors when attempting to infer the `F` parameter on a query/querystate this can be fixed by manually specifying the type, i.e. `QueryState::new::<_, ()>(world)`. The error is rather confusing: ```rust= error[E0271]: type mismatch resolving `<() as Fetch<'_>>::Item == bool` --> crates/bevy_pbr/src/render/light.rs:1413:30 | 1413 | main_view_query: QueryState::new(world), | ^^^^^^^^^^^^^^^ expected `bool`, found `()` | = note: required because of the requirements on the impl of `for<'x> FilterFetch<'x>` for `<() as WorldQueryGats<'x>>::Fetch` note: required by a bound in `bevy_ecs::query::QueryState::<Q, F>::new` --> crates/bevy_ecs/src/query/state.rs:49:32 | 49 | for<'x> QueryFetch<'x, F>: FilterFetch<'x>, | ^^^^^^^^^^^^^^^ required by this bound in `bevy_ecs::query::QueryState::<Q, F>::new` ``` --- Made with help from @BoxyUwU and @alice-i-cecile Co-authored-by: Boxy <supbscripter@gmail.com>
2022-04-27 23:44:06 +00:00
/// The item type returned when a [`WorldQuery`] is iterated over
pub type QueryItem<'w, Q> = <Q as WorldQuery>::Item<'w>;
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
/// The read-only variant of the item type returned when a [`QueryData`] is iterated over immutably
pub type ROQueryItem<'w, D> = QueryItem<'w, <D as QueryData>::ReadOnly>;
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
/// SAFETY:
/// `update_component_access` and `update_archetype_component_access` do nothing.
/// This is sound because `fetch` does not access components.
unsafe impl WorldQuery for Entity {
type Item<'w> = Entity;
type Fetch<'w> = ();
type State = ();
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> {
item
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
}
unsafe fn init_fetch<'w>(
Migrate the rest of the engine to `UnsafeWorldCell` (#8833) # Objective Follow-up to #6404 and #8292. Mutating the world through a shared reference is surprising, and it makes the meaning of `&World` unclear: sometimes it gives read-only access to the entire world, and sometimes it gives interior mutable access to only part of it. This is an up-to-date version of #6972. ## Solution Use `UnsafeWorldCell` for all interior mutability. Now, `&World` *always* gives you read-only access to the entire world. --- ## Changelog TODO - do we still care about changelogs? ## Migration Guide Mutating any world data using `&World` is now considered unsound -- the type `UnsafeWorldCell` must be used to achieve interior mutability. The following methods now accept `UnsafeWorldCell` instead of `&World`: - `QueryState`: `get_unchecked`, `iter_unchecked`, `iter_combinations_unchecked`, `for_each_unchecked`, `get_single_unchecked`, `get_single_unchecked_manual`. - `SystemState`: `get_unchecked_manual` ```rust let mut world = World::new(); let mut query = world.query::<&mut T>(); // Before: let t1 = query.get_unchecked(&world, entity_1); let t2 = query.get_unchecked(&world, entity_2); // After: let world_cell = world.as_unsafe_world_cell(); let t1 = query.get_unchecked(world_cell, entity_1); let t2 = query.get_unchecked(world_cell, entity_2); ``` The methods `QueryState::validate_world` and `SystemState::matches_world` now take a `WorldId` instead of `&World`: ```rust // Before: query_state.validate_world(&world); // After: query_state.validate_world(world.id()); ``` The methods `QueryState::update_archetypes` and `SystemState::update_archetypes` now take `UnsafeWorldCell` instead of `&World`: ```rust // Before: query_state.update_archetypes(&world); // After: query_state.update_archetypes(world.as_unsafe_world_cell_readonly()); ```
2023-06-15 01:31:56 +00:00
_world: UnsafeWorldCell<'w>,
_state: &Self::State,
_last_run: Tick,
_this_run: Tick,
) -> Self::Fetch<'w> {
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
}
const IS_DENSE: bool = true;
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
#[inline]
unsafe fn set_archetype<'w>(
_fetch: &mut Self::Fetch<'w>,
_state: &Self::State,
_archetype: &'w Archetype,
_table: &Table,
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
) {
}
#[inline]
unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) {
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
}
#[inline(always)]
unsafe fn fetch<'w>(
_fetch: &mut Self::Fetch<'w>,
entity: Entity,
_table_row: TableRow,
) -> Self::Item<'w> {
entity
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
}
Replace `ReadOnlyFetch` with `ReadOnlyWorldQuery` (#4626) # Objective - Fix a type inference regression introduced by #3001 - Make read only bounds on world queries more user friendly ptrification required you to write `Q::Fetch: ReadOnlyFetch` as `for<'w> QueryFetch<'w, Q>: ReadOnlyFetch` which has the same type inference problem as `for<'w> QueryFetch<'w, Q>: FilterFetch<'w>` had, i.e. the following code would error: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: WorldQuery>(a: Query<Q, ()>) where for<'w> QueryFetch<'w, Q>: ReadOnlyFetch, { } ``` `for<..>` bounds are also rather user unfriendly.. ## Solution Remove the `ReadOnlyFetch` trait in favour of a `ReadOnlyWorldQuery` trait, and remove `WorldQueryGats::ReadOnlyFetch` in favor of `WorldQuery::ReadOnly` allowing the previous code snippet to be written as: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: ReadOnlyWorldQuery>(a: Query<Q, ()>) {} ``` This avoids the `for<...>` bound which makes the code simpler and also fixes the type inference issue. The reason for moving the two functions out of `FetchState` and into `WorldQuery` is to allow the world query `&mut T` to share a `State` with the `&T` world query so that it can have `type ReadOnly = &T`. Presumably it would be possible to instead have a `ReadOnlyRefMut<T>` world query and then do `type ReadOnly = ReadOnlyRefMut<T>` much like how (before this PR) we had a `ReadOnlyWriteFetch<T>`. A side benefit of the current solution in this PR is that it will likely make it easier in the future to support an API such as `Query<&mut T> -> Query<&T>`. The primary benefit IMO is just that `ReadOnlyRefMut<T>` and its associated fetch would have to reimplement all of the logic that the `&T` world query impl does but this solution avoids that :) --- ## Changelog/Migration Guide The trait `ReadOnlyFetch` has been replaced with `ReadOnlyWorldQuery` along with the `WorldQueryGats::ReadOnlyFetch` assoc type which has been replaced with `<WorldQuery::ReadOnly as WorldQueryGats>::Fetch` - Any where clauses such as `QueryFetch<Q>: ReadOnlyFetch` should be replaced with `Q: ReadOnlyWorldQuery`. - Any custom world query impls should implement `ReadOnlyWorldQuery` insead of `ReadOnlyFetch` Functions `update_component_access` and `update_archetype_component_access` have been moved from the `FetchState` trait to `WorldQuery` - Any callers should now call `Q::update_component_access(state` instead of `state.update_component_access` (and `update_archetype_component_access` respectively) - Any custom world query impls should move the functions from the `FetchState` impl to `WorldQuery` impl `WorldQuery` has been made an `unsafe trait`, `FetchState` has been made a safe `trait`. (I think this is how it should have always been, but regardless this is _definitely_ necessary now that the two functions have been moved to `WorldQuery`) - If you have a custom `FetchState` impl make it a normal `impl` instead of `unsafe impl` - If you have a custom `WorldQuery` impl make it an `unsafe impl`, if your code was sound before it is going to still be sound
2022-06-13 23:35:54 +00:00
fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess<ComponentId>) {}
fn init_state(_world: &mut World) {}
Use lifetimed, type erased pointers in bevy_ecs (#3001) # Objective `bevy_ecs` has large amounts of unsafe code which is hard to get right and makes it difficult to audit for soundness. ## Solution Introduce lifetimed, type-erased pointers: `Ptr<'a>` `PtrMut<'a>` `OwningPtr<'a>'` and `ThinSlicePtr<'a, T>` which are newtypes around a raw pointer with a lifetime and conceptually representing strong invariants about the pointee and validity of the pointer. The process of converting bevy_ecs to use these has already caught multiple cases of unsound behavior. ## Changelog TL;DR for release notes: `bevy_ecs` now uses lifetimed, type-erased pointers internally, significantly improving safety and legibility without sacrificing performance. This should have approximately no end user impact, unless you were meddling with the (unfortunately public) internals of `bevy_ecs`. - `Fetch`, `FilterFetch` and `ReadOnlyFetch` trait no longer have a `'state` lifetime - this was unneeded - `ReadOnly/Fetch` associated types on `WorldQuery` are now on a new `WorldQueryGats<'world>` trait - was required to work around lack of Generic Associated Types (we wish to express `type Fetch<'a>: Fetch<'a>`) - `derive(WorldQuery)` no longer requires `'w` lifetime on struct - this was unneeded, and improves the end user experience - `EntityMut::get_unchecked_mut` returns `&'_ mut T` not `&'w mut T` - allows easier use of unsafe API with less footguns, and can be worked around via lifetime transmutery as a user - `Bundle::from_components` now takes a `ctx` parameter to pass to the `FnMut` closure - required because closure return types can't borrow from captures - `Fetch::init` takes `&'world World`, `Fetch::set_archetype` takes `&'world Archetype` and `&'world Tables`, `Fetch::set_table` takes `&'world Table` - allows types implementing `Fetch` to store borrows into world - `WorldQuery` trait now has a `shrink` fn to shorten the lifetime in `Fetch::<'a>::Item` - this works around lack of subtyping of assoc types, rust doesnt allow you to turn `<T as Fetch<'static>>::Item'` into `<T as Fetch<'a>>::Item'` - `QueryCombinationsIter` requires this - Most types implementing `Fetch` now have a lifetime `'w` - allows the fetches to store borrows of world data instead of using raw pointers ## Migration guide - `EntityMut::get_unchecked_mut` returns a more restricted lifetime, there is no general way to migrate this as it depends on your code - `Bundle::from_components` implementations must pass the `ctx` arg to `func` - `Bundle::from_components` callers have to use a fn arg instead of closure captures for borrowing from world - Remove lifetime args on `derive(WorldQuery)` structs as it is nonsensical - `<Q as WorldQuery>::ReadOnly/Fetch` should be changed to either `RO/QueryFetch<'world>` or `<Q as WorldQueryGats<'world>>::ReadOnly/Fetch` - `<F as Fetch<'w, 's>>` should be changed to `<F as Fetch<'w>>` - Change the fn sigs of `Fetch::init/set_archetype/set_table` to match respective trait fn sigs - Implement the required `fn shrink` on any `WorldQuery` implementations - Move assoc types `Fetch` and `ReadOnlyFetch` on `WorldQuery` impls to `WorldQueryGats` impls - Pass an appropriate `'world` lifetime to whatever fetch struct you are for some reason using ### Type inference regression in some cases rustc may give spurrious errors when attempting to infer the `F` parameter on a query/querystate this can be fixed by manually specifying the type, i.e. `QueryState::new::<_, ()>(world)`. The error is rather confusing: ```rust= error[E0271]: type mismatch resolving `<() as Fetch<'_>>::Item == bool` --> crates/bevy_pbr/src/render/light.rs:1413:30 | 1413 | main_view_query: QueryState::new(world), | ^^^^^^^^^^^^^^^ expected `bool`, found `()` | = note: required because of the requirements on the impl of `for<'x> FilterFetch<'x>` for `<() as WorldQueryGats<'x>>::Fetch` note: required by a bound in `bevy_ecs::query::QueryState::<Q, F>::new` --> crates/bevy_ecs/src/query/state.rs:49:32 | 49 | for<'x> QueryFetch<'x, F>: FilterFetch<'x>, | ^^^^^^^^^^^^^^^ required by this bound in `bevy_ecs::query::QueryState::<Q, F>::new` ``` --- Made with help from @BoxyUwU and @alice-i-cecile Co-authored-by: Boxy <supbscripter@gmail.com>
2022-04-27 23:44:06 +00:00
Dynamic queries and builder API (#9774) # Objective Expand the existing `Query` API to support more dynamic use cases i.e. scripting. ## Prior Art - #6390 - #8308 - #10037 ## Solution - Create a `QueryBuilder` with runtime methods to define the set of component accesses for a built query. - Create new `WorldQueryData` implementations `FilteredEntityMut` and `FilteredEntityRef` as variants of `EntityMut` and `EntityRef` that provide run time checked access to the components included in a given query. - Add new methods to `Query` to create "query lens" with a subset of the access of the initial query. ### Query Builder The `QueryBuilder` API allows you to define a query at runtime. At it's most basic use it will simply create a query with the corresponding type signature: ```rust let query = QueryBuilder::<Entity, With<A>>::new(&mut world).build(); // is equivalent to let query = QueryState::<Entity, With<A>>::new(&mut world); ``` Before calling `.build()` you also have the opportunity to add additional accesses and filters. Here is a simple example where we add additional filter terms: ```rust let entity_a = world.spawn((A(0), B(0))).id(); let entity_b = world.spawn((A(0), C(0))).id(); let mut query_a = QueryBuilder::<Entity>::new(&mut world) .with::<A>() .without::<C>() .build(); assert_eq!(entity_a, query_a.single(&world)); ``` This alone is useful in that allows you to decide which archetypes your query will match at runtime. However it is also very limited, consider a case like the following: ```rust let query_a = QueryBuilder::<&A>::new(&mut world) // Add an additional access .data::<&B>() .build(); ``` This will grant the query an additional read access to component B however we have no way of accessing the data while iterating as the type signature still only includes &A. For an even more concrete example of this consider dynamic components: ```rust let query_a = QueryBuilder::<Entity>::new(&mut world) // Adding a filter is easy since it doesn't need be read later .with_id(component_id_a) // How do I access the data of this component? .ref_id(component_id_b) .build(); ``` With this in mind the `QueryBuilder` API seems somewhat incomplete by itself, we need some way method of accessing the components dynamically. So here's one: ### Query Transmutation If the problem is not having the component in the type signature why not just add it? This PR also adds transmute methods to `QueryBuilder` and `QueryState`. Here's a simple example: ```rust world.spawn(A(0)); world.spawn((A(1), B(0))); let mut query = QueryBuilder::<()>::new(&mut world) .with::<B>() .transmute::<&A>() .build(); query.iter(&world).for_each(|a| assert_eq!(a.0, 1)); ``` The `QueryState` and `QueryBuilder` transmute methods look quite similar but are different in one respect. Transmuting a builder will always succeed as it will just add the additional accesses needed for the new terms if they weren't already included. Transmuting a `QueryState` will panic in the case that the new type signature would give it access it didn't already have, for example: ```rust let query = QueryState::<&A, Option<&B>>::new(&mut world); /// This is fine, the access for Option<&A> is less restrictive than &A query.transmute::<Option<&A>>(&world); /// Oh no, this would allow access to &B on entities that might not have it, so it panics query.transmute::<&B>(&world); /// This is right out query.transmute::<&C>(&world); ``` This is quite an appealing API to also have available on `Query` however it does pose one additional wrinkle: In order to to change the iterator we need to create a new `QueryState` to back it. `Query` doesn't own it's own state though, it just borrows it, so we need a place to borrow it from. This is why `QueryLens` exists, it is a place to store the new state so it can be borrowed when you call `.query()` leaving you with an API like this: ```rust fn function_that_takes_a_query(query: &Query<&A>) { // ... } fn system(query: Query<(&A, &B)>) { let lens = query.transmute_lens::<&A>(); let q = lens.query(); function_that_takes_a_query(&q); } ``` Now you may be thinking: Hey, wait a second, you introduced the problem with dynamic components and then described a solution that only works for static components! Ok, you got me, I guess we need a bit more: ### Filtered Entity References Currently the only way you can access dynamic components on entities through a query is with either `EntityMut` or `EntityRef`, however these can access all components and so conflict with all other accesses. This PR introduces `FilteredEntityMut` and `FilteredEntityRef` as alternatives that have additional runtime checking to prevent accessing components that you shouldn't. This way you can build a query with a `QueryBuilder` and actually access the components you asked for: ```rust let mut query = QueryBuilder::<FilteredEntityRef>::new(&mut world) .ref_id(component_id_a) .with(component_id_b) .build(); let entity_ref = query.single(&world); // Returns Some(Ptr) as we have that component and are allowed to read it let a = entity_ref.get_by_id(component_id_a); // Will return None even though the entity does have the component, as we are not allowed to read it let b = entity_ref.get_by_id(component_id_b); ``` For the most part these new structs have the exact same methods as their non-filtered equivalents. Putting all of this together we can do some truly dynamic ECS queries, check out the `dynamic` example to see it in action: ``` Commands: comp, c Create new components spawn, s Spawn entities query, q Query for entities Enter a command with no parameters for usage. > c A, B, C, Data 4 Component A created with id: 0 Component B created with id: 1 Component C created with id: 2 Component Data created with id: 3 > s A, B, Data 1 Entity spawned with id: 0v0 > s A, C, Data 0 Entity spawned with id: 1v0 > q &Data 0v0: Data: [1, 0, 0, 0] 1v0: Data: [0, 0, 0, 0] > q B, &mut Data 0v0: Data: [2, 1, 1, 1] > q B || C, &Data 0v0: Data: [2, 1, 1, 1] 1v0: Data: [0, 0, 0, 0] ``` ## Changelog - Add new `transmute_lens` methods to `Query`. - Add new types `QueryBuilder`, `FilteredEntityMut`, `FilteredEntityRef` and `QueryLens` - `update_archetype_component_access` has been removed, archetype component accesses are now determined by the accesses set in `update_component_access` - Added method `set_access` to `WorldQuery`, this is called before `update_component_access` for queries that have a restricted set of accesses, such as those built by `QueryBuilder` or `QueryLens`. This is primarily used by the `FilteredEntity*` variants and has an empty trait implementation. - Added method `get_state` to `WorldQuery` as a fallible version of `init_state` when you don't have `&mut World` access. ## Future Work Improve performance of `FilteredEntityMut` and `FilteredEntityRef`, currently they have to determine the accesses a query has in a given archetype during iteration which is far from ideal, especially since we already did the work when matching the archetype in the first place. To avoid making more internal API changes I have left it out of this PR. --------- Co-authored-by: Mike Hsu <mike.hsu@gmail.com>
2024-01-16 19:16:49 +00:00
fn get_state(_world: &World) -> Option<()> {
Some(())
}
fn matches_component_set(
_state: &Self::State,
_set_contains_id: &impl Fn(ComponentId) -> bool,
) -> bool {
true
Use lifetimed, type erased pointers in bevy_ecs (#3001) # Objective `bevy_ecs` has large amounts of unsafe code which is hard to get right and makes it difficult to audit for soundness. ## Solution Introduce lifetimed, type-erased pointers: `Ptr<'a>` `PtrMut<'a>` `OwningPtr<'a>'` and `ThinSlicePtr<'a, T>` which are newtypes around a raw pointer with a lifetime and conceptually representing strong invariants about the pointee and validity of the pointer. The process of converting bevy_ecs to use these has already caught multiple cases of unsound behavior. ## Changelog TL;DR for release notes: `bevy_ecs` now uses lifetimed, type-erased pointers internally, significantly improving safety and legibility without sacrificing performance. This should have approximately no end user impact, unless you were meddling with the (unfortunately public) internals of `bevy_ecs`. - `Fetch`, `FilterFetch` and `ReadOnlyFetch` trait no longer have a `'state` lifetime - this was unneeded - `ReadOnly/Fetch` associated types on `WorldQuery` are now on a new `WorldQueryGats<'world>` trait - was required to work around lack of Generic Associated Types (we wish to express `type Fetch<'a>: Fetch<'a>`) - `derive(WorldQuery)` no longer requires `'w` lifetime on struct - this was unneeded, and improves the end user experience - `EntityMut::get_unchecked_mut` returns `&'_ mut T` not `&'w mut T` - allows easier use of unsafe API with less footguns, and can be worked around via lifetime transmutery as a user - `Bundle::from_components` now takes a `ctx` parameter to pass to the `FnMut` closure - required because closure return types can't borrow from captures - `Fetch::init` takes `&'world World`, `Fetch::set_archetype` takes `&'world Archetype` and `&'world Tables`, `Fetch::set_table` takes `&'world Table` - allows types implementing `Fetch` to store borrows into world - `WorldQuery` trait now has a `shrink` fn to shorten the lifetime in `Fetch::<'a>::Item` - this works around lack of subtyping of assoc types, rust doesnt allow you to turn `<T as Fetch<'static>>::Item'` into `<T as Fetch<'a>>::Item'` - `QueryCombinationsIter` requires this - Most types implementing `Fetch` now have a lifetime `'w` - allows the fetches to store borrows of world data instead of using raw pointers ## Migration guide - `EntityMut::get_unchecked_mut` returns a more restricted lifetime, there is no general way to migrate this as it depends on your code - `Bundle::from_components` implementations must pass the `ctx` arg to `func` - `Bundle::from_components` callers have to use a fn arg instead of closure captures for borrowing from world - Remove lifetime args on `derive(WorldQuery)` structs as it is nonsensical - `<Q as WorldQuery>::ReadOnly/Fetch` should be changed to either `RO/QueryFetch<'world>` or `<Q as WorldQueryGats<'world>>::ReadOnly/Fetch` - `<F as Fetch<'w, 's>>` should be changed to `<F as Fetch<'w>>` - Change the fn sigs of `Fetch::init/set_archetype/set_table` to match respective trait fn sigs - Implement the required `fn shrink` on any `WorldQuery` implementations - Move assoc types `Fetch` and `ReadOnlyFetch` on `WorldQuery` impls to `WorldQueryGats` impls - Pass an appropriate `'world` lifetime to whatever fetch struct you are for some reason using ### Type inference regression in some cases rustc may give spurrious errors when attempting to infer the `F` parameter on a query/querystate this can be fixed by manually specifying the type, i.e. `QueryState::new::<_, ()>(world)`. The error is rather confusing: ```rust= error[E0271]: type mismatch resolving `<() as Fetch<'_>>::Item == bool` --> crates/bevy_pbr/src/render/light.rs:1413:30 | 1413 | main_view_query: QueryState::new(world), | ^^^^^^^^^^^^^^^ expected `bool`, found `()` | = note: required because of the requirements on the impl of `for<'x> FilterFetch<'x>` for `<() as WorldQueryGats<'x>>::Fetch` note: required by a bound in `bevy_ecs::query::QueryState::<Q, F>::new` --> crates/bevy_ecs/src/query/state.rs:49:32 | 49 | for<'x> QueryFetch<'x, F>: FilterFetch<'x>, | ^^^^^^^^^^^^^^^ required by this bound in `bevy_ecs::query::QueryState::<Q, F>::new` ``` --- Made with help from @BoxyUwU and @alice-i-cecile Co-authored-by: Boxy <supbscripter@gmail.com>
2022-04-27 23:44:06 +00:00
}
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
}
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
/// SAFETY: `Self` is the same as `Self::ReadOnly`
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
unsafe impl QueryData for Entity {
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
type ReadOnly = Self;
}
/// SAFETY: access is read only
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
unsafe impl ReadOnlyQueryData for Entity {}
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
/// SAFETY:
/// `fetch` accesses all components in a readonly way.
/// This is sound because `update_component_access` and `update_archetype_component_access` set read access for all components and panic when appropriate.
/// Filters are unchanged.
unsafe impl<'a> WorldQuery for EntityRef<'a> {
Implement WorldQuery for EntityRef (#6960) # Objective Partially address #5504. Fix #4278. Provide "whole entity" access in queries. This can be useful when you don't know at compile time what you're accessing (i.e. reflection via `ReflectComponent`). ## Solution Implement `WorldQuery` for `EntityRef`. - This provides read-only access to the entire entity, and supports anything that `EntityRef` can normally do. - It matches all archetypes and tables and will densely iterate when possible. - It marks all of the ArchetypeComponentIds of a matched archetype as read. - Adding it to a query will cause it to panic if used in conjunction with any other mutable access. - Expanded the docs on Query to advertise this feature. - Added tests to ensure the panics were working as intended. - Added `EntityRef` to the ECS prelude. To make this safe, `EntityRef::world` was removed as it gave potential `UnsafeCell`-like access to other parts of the `World` including aliased mutable access to the components it would otherwise read safely. ## Performance Not great beyond the additional parallelization opportunity over exclusive systems. The `EntityRef` is fetched from `Entities` like any other call to `World::entity`, which can be very random access heavy. This could be simplified if `ArchetypeRow` is available in `WorldQuery::fetch`'s arguments, but that's likely not something we should optimize for. ## Future work An equivalent API where it gives mutable access to all components on a entity can be done with a scoped version of `EntityMut` where it does not provide `&mut World` access nor allow for structural changes to the entity is feasible as well. This could be done as a safe alternative to exclusive system when structural mutation isn't required or the target set of entities is scoped. --- ## Changelog Added: `Access::has_any_write` Added: `EntityRef` now implements `WorldQuery`. Allows read-only access to the entire entity, incompatible with any other mutable access, can be mixed with `With`/`Without` filters for more targeted use. Added: `EntityRef` to `bevy::ecs::prelude`. Removed: `EntityRef::world` ## Migration Guide TODO --------- Co-authored-by: Carter Weinberg <weinbergcarter@gmail.com> Co-authored-by: Jakob Hellermann <jakob.hellermann@protonmail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2023-06-22 21:20:00 +00:00
type Item<'w> = EntityRef<'w>;
type Fetch<'w> = UnsafeWorldCell<'w>;
Implement WorldQuery for EntityRef (#6960) # Objective Partially address #5504. Fix #4278. Provide "whole entity" access in queries. This can be useful when you don't know at compile time what you're accessing (i.e. reflection via `ReflectComponent`). ## Solution Implement `WorldQuery` for `EntityRef`. - This provides read-only access to the entire entity, and supports anything that `EntityRef` can normally do. - It matches all archetypes and tables and will densely iterate when possible. - It marks all of the ArchetypeComponentIds of a matched archetype as read. - Adding it to a query will cause it to panic if used in conjunction with any other mutable access. - Expanded the docs on Query to advertise this feature. - Added tests to ensure the panics were working as intended. - Added `EntityRef` to the ECS prelude. To make this safe, `EntityRef::world` was removed as it gave potential `UnsafeCell`-like access to other parts of the `World` including aliased mutable access to the components it would otherwise read safely. ## Performance Not great beyond the additional parallelization opportunity over exclusive systems. The `EntityRef` is fetched from `Entities` like any other call to `World::entity`, which can be very random access heavy. This could be simplified if `ArchetypeRow` is available in `WorldQuery::fetch`'s arguments, but that's likely not something we should optimize for. ## Future work An equivalent API where it gives mutable access to all components on a entity can be done with a scoped version of `EntityMut` where it does not provide `&mut World` access nor allow for structural changes to the entity is feasible as well. This could be done as a safe alternative to exclusive system when structural mutation isn't required or the target set of entities is scoped. --- ## Changelog Added: `Access::has_any_write` Added: `EntityRef` now implements `WorldQuery`. Allows read-only access to the entire entity, incompatible with any other mutable access, can be mixed with `With`/`Without` filters for more targeted use. Added: `EntityRef` to `bevy::ecs::prelude`. Removed: `EntityRef::world` ## Migration Guide TODO --------- Co-authored-by: Carter Weinberg <weinbergcarter@gmail.com> Co-authored-by: Jakob Hellermann <jakob.hellermann@protonmail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2023-06-22 21:20:00 +00:00
type State = ();
fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> {
item
}
unsafe fn init_fetch<'w>(
world: UnsafeWorldCell<'w>,
_state: &Self::State,
_last_run: Tick,
_this_run: Tick,
) -> Self::Fetch<'w> {
Allow disjoint mutable world access via `EntityMut` (#9419) # Objective Fix #4278 Fix #5504 Fix #9422 Provide safe ways to borrow an entire entity, while allowing disjoint mutable access. `EntityRef` and `EntityMut` are not suitable for this, since they provide access to the entire world -- they are just helper types for working with `&World`/`&mut World`. This has potential uses for reflection and serialization ## Solution Remove `EntityRef::world`, which allows it to soundly be used within queries. `EntityMut` no longer supports structural world mutations, which allows multiple instances of it to exist for different entities at once. Structural world mutations are performed using the new type `EntityWorldMut`. ```rust fn disjoint_system( q2: Query<&mut A>, q1: Query<EntityMut, Without<A>>, ) { ... } let [entity1, entity2] = world.many_entities_mut([id1, id2]); *entity1.get_mut::<T>().unwrap() = *entity2.get().unwrap(); for entity in world.iter_entities_mut() { ... } ``` --- ## Changelog - Removed `EntityRef::world`, to fix a soundness issue with queries. + Removed the ability to structurally mutate the world using `EntityMut`, which allows it to be used in queries. + Added `EntityWorldMut`, which is used to perform structural mutations that are no longer allowed using `EntityMut`. ## Migration Guide **Note for maintainers: ensure that the guide for #9604 is updated accordingly.** Removed the method `EntityRef::world`, to fix a soundness issue with queries. If you need access to `&World` while using an `EntityRef`, consider passing the world as a separate parameter. `EntityMut` can no longer perform 'structural' world mutations, such as adding or removing components, or despawning the entity. Additionally, `EntityMut::world`, `EntityMut::world_mut` , and `EntityMut::world_scope` have been removed. Instead, use the newly-added type `EntityWorldMut`, which is a helper type for working with `&mut World`. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-08-28 23:30:59 +00:00
world
Implement WorldQuery for EntityRef (#6960) # Objective Partially address #5504. Fix #4278. Provide "whole entity" access in queries. This can be useful when you don't know at compile time what you're accessing (i.e. reflection via `ReflectComponent`). ## Solution Implement `WorldQuery` for `EntityRef`. - This provides read-only access to the entire entity, and supports anything that `EntityRef` can normally do. - It matches all archetypes and tables and will densely iterate when possible. - It marks all of the ArchetypeComponentIds of a matched archetype as read. - Adding it to a query will cause it to panic if used in conjunction with any other mutable access. - Expanded the docs on Query to advertise this feature. - Added tests to ensure the panics were working as intended. - Added `EntityRef` to the ECS prelude. To make this safe, `EntityRef::world` was removed as it gave potential `UnsafeCell`-like access to other parts of the `World` including aliased mutable access to the components it would otherwise read safely. ## Performance Not great beyond the additional parallelization opportunity over exclusive systems. The `EntityRef` is fetched from `Entities` like any other call to `World::entity`, which can be very random access heavy. This could be simplified if `ArchetypeRow` is available in `WorldQuery::fetch`'s arguments, but that's likely not something we should optimize for. ## Future work An equivalent API where it gives mutable access to all components on a entity can be done with a scoped version of `EntityMut` where it does not provide `&mut World` access nor allow for structural changes to the entity is feasible as well. This could be done as a safe alternative to exclusive system when structural mutation isn't required or the target set of entities is scoped. --- ## Changelog Added: `Access::has_any_write` Added: `EntityRef` now implements `WorldQuery`. Allows read-only access to the entire entity, incompatible with any other mutable access, can be mixed with `With`/`Without` filters for more targeted use. Added: `EntityRef` to `bevy::ecs::prelude`. Removed: `EntityRef::world` ## Migration Guide TODO --------- Co-authored-by: Carter Weinberg <weinbergcarter@gmail.com> Co-authored-by: Jakob Hellermann <jakob.hellermann@protonmail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2023-06-22 21:20:00 +00:00
}
const IS_DENSE: bool = true;
Implement WorldQuery for EntityRef (#6960) # Objective Partially address #5504. Fix #4278. Provide "whole entity" access in queries. This can be useful when you don't know at compile time what you're accessing (i.e. reflection via `ReflectComponent`). ## Solution Implement `WorldQuery` for `EntityRef`. - This provides read-only access to the entire entity, and supports anything that `EntityRef` can normally do. - It matches all archetypes and tables and will densely iterate when possible. - It marks all of the ArchetypeComponentIds of a matched archetype as read. - Adding it to a query will cause it to panic if used in conjunction with any other mutable access. - Expanded the docs on Query to advertise this feature. - Added tests to ensure the panics were working as intended. - Added `EntityRef` to the ECS prelude. To make this safe, `EntityRef::world` was removed as it gave potential `UnsafeCell`-like access to other parts of the `World` including aliased mutable access to the components it would otherwise read safely. ## Performance Not great beyond the additional parallelization opportunity over exclusive systems. The `EntityRef` is fetched from `Entities` like any other call to `World::entity`, which can be very random access heavy. This could be simplified if `ArchetypeRow` is available in `WorldQuery::fetch`'s arguments, but that's likely not something we should optimize for. ## Future work An equivalent API where it gives mutable access to all components on a entity can be done with a scoped version of `EntityMut` where it does not provide `&mut World` access nor allow for structural changes to the entity is feasible as well. This could be done as a safe alternative to exclusive system when structural mutation isn't required or the target set of entities is scoped. --- ## Changelog Added: `Access::has_any_write` Added: `EntityRef` now implements `WorldQuery`. Allows read-only access to the entire entity, incompatible with any other mutable access, can be mixed with `With`/`Without` filters for more targeted use. Added: `EntityRef` to `bevy::ecs::prelude`. Removed: `EntityRef::world` ## Migration Guide TODO --------- Co-authored-by: Carter Weinberg <weinbergcarter@gmail.com> Co-authored-by: Jakob Hellermann <jakob.hellermann@protonmail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2023-06-22 21:20:00 +00:00
#[inline]
unsafe fn set_archetype<'w>(
_fetch: &mut Self::Fetch<'w>,
_state: &Self::State,
_archetype: &'w Archetype,
_table: &Table,
) {
}
#[inline]
unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) {
}
#[inline(always)]
unsafe fn fetch<'w>(
world: &mut Self::Fetch<'w>,
entity: Entity,
_table_row: TableRow,
) -> Self::Item<'w> {
// SAFETY: `fetch` must be called with an entity that exists in the world
Allow disjoint mutable world access via `EntityMut` (#9419) # Objective Fix #4278 Fix #5504 Fix #9422 Provide safe ways to borrow an entire entity, while allowing disjoint mutable access. `EntityRef` and `EntityMut` are not suitable for this, since they provide access to the entire world -- they are just helper types for working with `&World`/`&mut World`. This has potential uses for reflection and serialization ## Solution Remove `EntityRef::world`, which allows it to soundly be used within queries. `EntityMut` no longer supports structural world mutations, which allows multiple instances of it to exist for different entities at once. Structural world mutations are performed using the new type `EntityWorldMut`. ```rust fn disjoint_system( q2: Query<&mut A>, q1: Query<EntityMut, Without<A>>, ) { ... } let [entity1, entity2] = world.many_entities_mut([id1, id2]); *entity1.get_mut::<T>().unwrap() = *entity2.get().unwrap(); for entity in world.iter_entities_mut() { ... } ``` --- ## Changelog - Removed `EntityRef::world`, to fix a soundness issue with queries. + Removed the ability to structurally mutate the world using `EntityMut`, which allows it to be used in queries. + Added `EntityWorldMut`, which is used to perform structural mutations that are no longer allowed using `EntityMut`. ## Migration Guide **Note for maintainers: ensure that the guide for #9604 is updated accordingly.** Removed the method `EntityRef::world`, to fix a soundness issue with queries. If you need access to `&World` while using an `EntityRef`, consider passing the world as a separate parameter. `EntityMut` can no longer perform 'structural' world mutations, such as adding or removing components, or despawning the entity. Additionally, `EntityMut::world`, `EntityMut::world_mut` , and `EntityMut::world_scope` have been removed. Instead, use the newly-added type `EntityWorldMut`, which is a helper type for working with `&mut World`. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-08-28 23:30:59 +00:00
let cell = world.get_entity(entity).debug_checked_unwrap();
// SAFETY: Read-only access to every component has been registered.
EntityRef::new(cell)
Implement WorldQuery for EntityRef (#6960) # Objective Partially address #5504. Fix #4278. Provide "whole entity" access in queries. This can be useful when you don't know at compile time what you're accessing (i.e. reflection via `ReflectComponent`). ## Solution Implement `WorldQuery` for `EntityRef`. - This provides read-only access to the entire entity, and supports anything that `EntityRef` can normally do. - It matches all archetypes and tables and will densely iterate when possible. - It marks all of the ArchetypeComponentIds of a matched archetype as read. - Adding it to a query will cause it to panic if used in conjunction with any other mutable access. - Expanded the docs on Query to advertise this feature. - Added tests to ensure the panics were working as intended. - Added `EntityRef` to the ECS prelude. To make this safe, `EntityRef::world` was removed as it gave potential `UnsafeCell`-like access to other parts of the `World` including aliased mutable access to the components it would otherwise read safely. ## Performance Not great beyond the additional parallelization opportunity over exclusive systems. The `EntityRef` is fetched from `Entities` like any other call to `World::entity`, which can be very random access heavy. This could be simplified if `ArchetypeRow` is available in `WorldQuery::fetch`'s arguments, but that's likely not something we should optimize for. ## Future work An equivalent API where it gives mutable access to all components on a entity can be done with a scoped version of `EntityMut` where it does not provide `&mut World` access nor allow for structural changes to the entity is feasible as well. This could be done as a safe alternative to exclusive system when structural mutation isn't required or the target set of entities is scoped. --- ## Changelog Added: `Access::has_any_write` Added: `EntityRef` now implements `WorldQuery`. Allows read-only access to the entire entity, incompatible with any other mutable access, can be mixed with `With`/`Without` filters for more targeted use. Added: `EntityRef` to `bevy::ecs::prelude`. Removed: `EntityRef::world` ## Migration Guide TODO --------- Co-authored-by: Carter Weinberg <weinbergcarter@gmail.com> Co-authored-by: Jakob Hellermann <jakob.hellermann@protonmail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2023-06-22 21:20:00 +00:00
}
fn update_component_access(_state: &Self::State, access: &mut FilteredAccess<ComponentId>) {
assert!(
!access.access().has_any_write(),
"EntityRef conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.",
);
access.read_all();
}
fn init_state(_world: &mut World) {}
Dynamic queries and builder API (#9774) # Objective Expand the existing `Query` API to support more dynamic use cases i.e. scripting. ## Prior Art - #6390 - #8308 - #10037 ## Solution - Create a `QueryBuilder` with runtime methods to define the set of component accesses for a built query. - Create new `WorldQueryData` implementations `FilteredEntityMut` and `FilteredEntityRef` as variants of `EntityMut` and `EntityRef` that provide run time checked access to the components included in a given query. - Add new methods to `Query` to create "query lens" with a subset of the access of the initial query. ### Query Builder The `QueryBuilder` API allows you to define a query at runtime. At it's most basic use it will simply create a query with the corresponding type signature: ```rust let query = QueryBuilder::<Entity, With<A>>::new(&mut world).build(); // is equivalent to let query = QueryState::<Entity, With<A>>::new(&mut world); ``` Before calling `.build()` you also have the opportunity to add additional accesses and filters. Here is a simple example where we add additional filter terms: ```rust let entity_a = world.spawn((A(0), B(0))).id(); let entity_b = world.spawn((A(0), C(0))).id(); let mut query_a = QueryBuilder::<Entity>::new(&mut world) .with::<A>() .without::<C>() .build(); assert_eq!(entity_a, query_a.single(&world)); ``` This alone is useful in that allows you to decide which archetypes your query will match at runtime. However it is also very limited, consider a case like the following: ```rust let query_a = QueryBuilder::<&A>::new(&mut world) // Add an additional access .data::<&B>() .build(); ``` This will grant the query an additional read access to component B however we have no way of accessing the data while iterating as the type signature still only includes &A. For an even more concrete example of this consider dynamic components: ```rust let query_a = QueryBuilder::<Entity>::new(&mut world) // Adding a filter is easy since it doesn't need be read later .with_id(component_id_a) // How do I access the data of this component? .ref_id(component_id_b) .build(); ``` With this in mind the `QueryBuilder` API seems somewhat incomplete by itself, we need some way method of accessing the components dynamically. So here's one: ### Query Transmutation If the problem is not having the component in the type signature why not just add it? This PR also adds transmute methods to `QueryBuilder` and `QueryState`. Here's a simple example: ```rust world.spawn(A(0)); world.spawn((A(1), B(0))); let mut query = QueryBuilder::<()>::new(&mut world) .with::<B>() .transmute::<&A>() .build(); query.iter(&world).for_each(|a| assert_eq!(a.0, 1)); ``` The `QueryState` and `QueryBuilder` transmute methods look quite similar but are different in one respect. Transmuting a builder will always succeed as it will just add the additional accesses needed for the new terms if they weren't already included. Transmuting a `QueryState` will panic in the case that the new type signature would give it access it didn't already have, for example: ```rust let query = QueryState::<&A, Option<&B>>::new(&mut world); /// This is fine, the access for Option<&A> is less restrictive than &A query.transmute::<Option<&A>>(&world); /// Oh no, this would allow access to &B on entities that might not have it, so it panics query.transmute::<&B>(&world); /// This is right out query.transmute::<&C>(&world); ``` This is quite an appealing API to also have available on `Query` however it does pose one additional wrinkle: In order to to change the iterator we need to create a new `QueryState` to back it. `Query` doesn't own it's own state though, it just borrows it, so we need a place to borrow it from. This is why `QueryLens` exists, it is a place to store the new state so it can be borrowed when you call `.query()` leaving you with an API like this: ```rust fn function_that_takes_a_query(query: &Query<&A>) { // ... } fn system(query: Query<(&A, &B)>) { let lens = query.transmute_lens::<&A>(); let q = lens.query(); function_that_takes_a_query(&q); } ``` Now you may be thinking: Hey, wait a second, you introduced the problem with dynamic components and then described a solution that only works for static components! Ok, you got me, I guess we need a bit more: ### Filtered Entity References Currently the only way you can access dynamic components on entities through a query is with either `EntityMut` or `EntityRef`, however these can access all components and so conflict with all other accesses. This PR introduces `FilteredEntityMut` and `FilteredEntityRef` as alternatives that have additional runtime checking to prevent accessing components that you shouldn't. This way you can build a query with a `QueryBuilder` and actually access the components you asked for: ```rust let mut query = QueryBuilder::<FilteredEntityRef>::new(&mut world) .ref_id(component_id_a) .with(component_id_b) .build(); let entity_ref = query.single(&world); // Returns Some(Ptr) as we have that component and are allowed to read it let a = entity_ref.get_by_id(component_id_a); // Will return None even though the entity does have the component, as we are not allowed to read it let b = entity_ref.get_by_id(component_id_b); ``` For the most part these new structs have the exact same methods as their non-filtered equivalents. Putting all of this together we can do some truly dynamic ECS queries, check out the `dynamic` example to see it in action: ``` Commands: comp, c Create new components spawn, s Spawn entities query, q Query for entities Enter a command with no parameters for usage. > c A, B, C, Data 4 Component A created with id: 0 Component B created with id: 1 Component C created with id: 2 Component Data created with id: 3 > s A, B, Data 1 Entity spawned with id: 0v0 > s A, C, Data 0 Entity spawned with id: 1v0 > q &Data 0v0: Data: [1, 0, 0, 0] 1v0: Data: [0, 0, 0, 0] > q B, &mut Data 0v0: Data: [2, 1, 1, 1] > q B || C, &Data 0v0: Data: [2, 1, 1, 1] 1v0: Data: [0, 0, 0, 0] ``` ## Changelog - Add new `transmute_lens` methods to `Query`. - Add new types `QueryBuilder`, `FilteredEntityMut`, `FilteredEntityRef` and `QueryLens` - `update_archetype_component_access` has been removed, archetype component accesses are now determined by the accesses set in `update_component_access` - Added method `set_access` to `WorldQuery`, this is called before `update_component_access` for queries that have a restricted set of accesses, such as those built by `QueryBuilder` or `QueryLens`. This is primarily used by the `FilteredEntity*` variants and has an empty trait implementation. - Added method `get_state` to `WorldQuery` as a fallible version of `init_state` when you don't have `&mut World` access. ## Future Work Improve performance of `FilteredEntityMut` and `FilteredEntityRef`, currently they have to determine the accesses a query has in a given archetype during iteration which is far from ideal, especially since we already did the work when matching the archetype in the first place. To avoid making more internal API changes I have left it out of this PR. --------- Co-authored-by: Mike Hsu <mike.hsu@gmail.com>
2024-01-16 19:16:49 +00:00
fn get_state(_world: &World) -> Option<()> {
Some(())
}
Implement WorldQuery for EntityRef (#6960) # Objective Partially address #5504. Fix #4278. Provide "whole entity" access in queries. This can be useful when you don't know at compile time what you're accessing (i.e. reflection via `ReflectComponent`). ## Solution Implement `WorldQuery` for `EntityRef`. - This provides read-only access to the entire entity, and supports anything that `EntityRef` can normally do. - It matches all archetypes and tables and will densely iterate when possible. - It marks all of the ArchetypeComponentIds of a matched archetype as read. - Adding it to a query will cause it to panic if used in conjunction with any other mutable access. - Expanded the docs on Query to advertise this feature. - Added tests to ensure the panics were working as intended. - Added `EntityRef` to the ECS prelude. To make this safe, `EntityRef::world` was removed as it gave potential `UnsafeCell`-like access to other parts of the `World` including aliased mutable access to the components it would otherwise read safely. ## Performance Not great beyond the additional parallelization opportunity over exclusive systems. The `EntityRef` is fetched from `Entities` like any other call to `World::entity`, which can be very random access heavy. This could be simplified if `ArchetypeRow` is available in `WorldQuery::fetch`'s arguments, but that's likely not something we should optimize for. ## Future work An equivalent API where it gives mutable access to all components on a entity can be done with a scoped version of `EntityMut` where it does not provide `&mut World` access nor allow for structural changes to the entity is feasible as well. This could be done as a safe alternative to exclusive system when structural mutation isn't required or the target set of entities is scoped. --- ## Changelog Added: `Access::has_any_write` Added: `EntityRef` now implements `WorldQuery`. Allows read-only access to the entire entity, incompatible with any other mutable access, can be mixed with `With`/`Without` filters for more targeted use. Added: `EntityRef` to `bevy::ecs::prelude`. Removed: `EntityRef::world` ## Migration Guide TODO --------- Co-authored-by: Carter Weinberg <weinbergcarter@gmail.com> Co-authored-by: Jakob Hellermann <jakob.hellermann@protonmail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2023-06-22 21:20:00 +00:00
fn matches_component_set(
_state: &Self::State,
_set_contains_id: &impl Fn(ComponentId) -> bool,
) -> bool {
true
}
}
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
/// SAFETY: `Self` is the same as `Self::ReadOnly`
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
unsafe impl<'a> QueryData for EntityRef<'a> {
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
type ReadOnly = Self;
}
/// SAFETY: access is read only
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
unsafe impl ReadOnlyQueryData for EntityRef<'_> {}
Allow disjoint mutable world access via `EntityMut` (#9419) # Objective Fix #4278 Fix #5504 Fix #9422 Provide safe ways to borrow an entire entity, while allowing disjoint mutable access. `EntityRef` and `EntityMut` are not suitable for this, since they provide access to the entire world -- they are just helper types for working with `&World`/`&mut World`. This has potential uses for reflection and serialization ## Solution Remove `EntityRef::world`, which allows it to soundly be used within queries. `EntityMut` no longer supports structural world mutations, which allows multiple instances of it to exist for different entities at once. Structural world mutations are performed using the new type `EntityWorldMut`. ```rust fn disjoint_system( q2: Query<&mut A>, q1: Query<EntityMut, Without<A>>, ) { ... } let [entity1, entity2] = world.many_entities_mut([id1, id2]); *entity1.get_mut::<T>().unwrap() = *entity2.get().unwrap(); for entity in world.iter_entities_mut() { ... } ``` --- ## Changelog - Removed `EntityRef::world`, to fix a soundness issue with queries. + Removed the ability to structurally mutate the world using `EntityMut`, which allows it to be used in queries. + Added `EntityWorldMut`, which is used to perform structural mutations that are no longer allowed using `EntityMut`. ## Migration Guide **Note for maintainers: ensure that the guide for #9604 is updated accordingly.** Removed the method `EntityRef::world`, to fix a soundness issue with queries. If you need access to `&World` while using an `EntityRef`, consider passing the world as a separate parameter. `EntityMut` can no longer perform 'structural' world mutations, such as adding or removing components, or despawning the entity. Additionally, `EntityMut::world`, `EntityMut::world_mut` , and `EntityMut::world_scope` have been removed. Instead, use the newly-added type `EntityWorldMut`, which is a helper type for working with `&mut World`. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-08-28 23:30:59 +00:00
/// SAFETY: The accesses of `Self::ReadOnly` are a subset of the accesses of `Self`
unsafe impl<'a> WorldQuery for EntityMut<'a> {
type Item<'w> = EntityMut<'w>;
type Fetch<'w> = UnsafeWorldCell<'w>;
Allow disjoint mutable world access via `EntityMut` (#9419) # Objective Fix #4278 Fix #5504 Fix #9422 Provide safe ways to borrow an entire entity, while allowing disjoint mutable access. `EntityRef` and `EntityMut` are not suitable for this, since they provide access to the entire world -- they are just helper types for working with `&World`/`&mut World`. This has potential uses for reflection and serialization ## Solution Remove `EntityRef::world`, which allows it to soundly be used within queries. `EntityMut` no longer supports structural world mutations, which allows multiple instances of it to exist for different entities at once. Structural world mutations are performed using the new type `EntityWorldMut`. ```rust fn disjoint_system( q2: Query<&mut A>, q1: Query<EntityMut, Without<A>>, ) { ... } let [entity1, entity2] = world.many_entities_mut([id1, id2]); *entity1.get_mut::<T>().unwrap() = *entity2.get().unwrap(); for entity in world.iter_entities_mut() { ... } ``` --- ## Changelog - Removed `EntityRef::world`, to fix a soundness issue with queries. + Removed the ability to structurally mutate the world using `EntityMut`, which allows it to be used in queries. + Added `EntityWorldMut`, which is used to perform structural mutations that are no longer allowed using `EntityMut`. ## Migration Guide **Note for maintainers: ensure that the guide for #9604 is updated accordingly.** Removed the method `EntityRef::world`, to fix a soundness issue with queries. If you need access to `&World` while using an `EntityRef`, consider passing the world as a separate parameter. `EntityMut` can no longer perform 'structural' world mutations, such as adding or removing components, or despawning the entity. Additionally, `EntityMut::world`, `EntityMut::world_mut` , and `EntityMut::world_scope` have been removed. Instead, use the newly-added type `EntityWorldMut`, which is a helper type for working with `&mut World`. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-08-28 23:30:59 +00:00
type State = ();
fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> {
item
}
unsafe fn init_fetch<'w>(
world: UnsafeWorldCell<'w>,
_state: &Self::State,
_last_run: Tick,
_this_run: Tick,
) -> Self::Fetch<'w> {
world
}
const IS_DENSE: bool = true;
Allow disjoint mutable world access via `EntityMut` (#9419) # Objective Fix #4278 Fix #5504 Fix #9422 Provide safe ways to borrow an entire entity, while allowing disjoint mutable access. `EntityRef` and `EntityMut` are not suitable for this, since they provide access to the entire world -- they are just helper types for working with `&World`/`&mut World`. This has potential uses for reflection and serialization ## Solution Remove `EntityRef::world`, which allows it to soundly be used within queries. `EntityMut` no longer supports structural world mutations, which allows multiple instances of it to exist for different entities at once. Structural world mutations are performed using the new type `EntityWorldMut`. ```rust fn disjoint_system( q2: Query<&mut A>, q1: Query<EntityMut, Without<A>>, ) { ... } let [entity1, entity2] = world.many_entities_mut([id1, id2]); *entity1.get_mut::<T>().unwrap() = *entity2.get().unwrap(); for entity in world.iter_entities_mut() { ... } ``` --- ## Changelog - Removed `EntityRef::world`, to fix a soundness issue with queries. + Removed the ability to structurally mutate the world using `EntityMut`, which allows it to be used in queries. + Added `EntityWorldMut`, which is used to perform structural mutations that are no longer allowed using `EntityMut`. ## Migration Guide **Note for maintainers: ensure that the guide for #9604 is updated accordingly.** Removed the method `EntityRef::world`, to fix a soundness issue with queries. If you need access to `&World` while using an `EntityRef`, consider passing the world as a separate parameter. `EntityMut` can no longer perform 'structural' world mutations, such as adding or removing components, or despawning the entity. Additionally, `EntityMut::world`, `EntityMut::world_mut` , and `EntityMut::world_scope` have been removed. Instead, use the newly-added type `EntityWorldMut`, which is a helper type for working with `&mut World`. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-08-28 23:30:59 +00:00
#[inline]
unsafe fn set_archetype<'w>(
_fetch: &mut Self::Fetch<'w>,
_state: &Self::State,
_archetype: &'w Archetype,
_table: &Table,
) {
}
#[inline]
unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) {
}
#[inline(always)]
unsafe fn fetch<'w>(
world: &mut Self::Fetch<'w>,
entity: Entity,
_table_row: TableRow,
) -> Self::Item<'w> {
// SAFETY: `fetch` must be called with an entity that exists in the world
let cell = world.get_entity(entity).debug_checked_unwrap();
// SAFETY: mutable access to every component has been registered.
EntityMut::new(cell)
}
fn update_component_access(_state: &Self::State, access: &mut FilteredAccess<ComponentId>) {
assert!(
!access.access().has_any_read(),
"EntityMut conflicts with a previous access in this query. Exclusive access cannot coincide with any other accesses.",
);
access.write_all();
}
fn init_state(_world: &mut World) {}
Dynamic queries and builder API (#9774) # Objective Expand the existing `Query` API to support more dynamic use cases i.e. scripting. ## Prior Art - #6390 - #8308 - #10037 ## Solution - Create a `QueryBuilder` with runtime methods to define the set of component accesses for a built query. - Create new `WorldQueryData` implementations `FilteredEntityMut` and `FilteredEntityRef` as variants of `EntityMut` and `EntityRef` that provide run time checked access to the components included in a given query. - Add new methods to `Query` to create "query lens" with a subset of the access of the initial query. ### Query Builder The `QueryBuilder` API allows you to define a query at runtime. At it's most basic use it will simply create a query with the corresponding type signature: ```rust let query = QueryBuilder::<Entity, With<A>>::new(&mut world).build(); // is equivalent to let query = QueryState::<Entity, With<A>>::new(&mut world); ``` Before calling `.build()` you also have the opportunity to add additional accesses and filters. Here is a simple example where we add additional filter terms: ```rust let entity_a = world.spawn((A(0), B(0))).id(); let entity_b = world.spawn((A(0), C(0))).id(); let mut query_a = QueryBuilder::<Entity>::new(&mut world) .with::<A>() .without::<C>() .build(); assert_eq!(entity_a, query_a.single(&world)); ``` This alone is useful in that allows you to decide which archetypes your query will match at runtime. However it is also very limited, consider a case like the following: ```rust let query_a = QueryBuilder::<&A>::new(&mut world) // Add an additional access .data::<&B>() .build(); ``` This will grant the query an additional read access to component B however we have no way of accessing the data while iterating as the type signature still only includes &A. For an even more concrete example of this consider dynamic components: ```rust let query_a = QueryBuilder::<Entity>::new(&mut world) // Adding a filter is easy since it doesn't need be read later .with_id(component_id_a) // How do I access the data of this component? .ref_id(component_id_b) .build(); ``` With this in mind the `QueryBuilder` API seems somewhat incomplete by itself, we need some way method of accessing the components dynamically. So here's one: ### Query Transmutation If the problem is not having the component in the type signature why not just add it? This PR also adds transmute methods to `QueryBuilder` and `QueryState`. Here's a simple example: ```rust world.spawn(A(0)); world.spawn((A(1), B(0))); let mut query = QueryBuilder::<()>::new(&mut world) .with::<B>() .transmute::<&A>() .build(); query.iter(&world).for_each(|a| assert_eq!(a.0, 1)); ``` The `QueryState` and `QueryBuilder` transmute methods look quite similar but are different in one respect. Transmuting a builder will always succeed as it will just add the additional accesses needed for the new terms if they weren't already included. Transmuting a `QueryState` will panic in the case that the new type signature would give it access it didn't already have, for example: ```rust let query = QueryState::<&A, Option<&B>>::new(&mut world); /// This is fine, the access for Option<&A> is less restrictive than &A query.transmute::<Option<&A>>(&world); /// Oh no, this would allow access to &B on entities that might not have it, so it panics query.transmute::<&B>(&world); /// This is right out query.transmute::<&C>(&world); ``` This is quite an appealing API to also have available on `Query` however it does pose one additional wrinkle: In order to to change the iterator we need to create a new `QueryState` to back it. `Query` doesn't own it's own state though, it just borrows it, so we need a place to borrow it from. This is why `QueryLens` exists, it is a place to store the new state so it can be borrowed when you call `.query()` leaving you with an API like this: ```rust fn function_that_takes_a_query(query: &Query<&A>) { // ... } fn system(query: Query<(&A, &B)>) { let lens = query.transmute_lens::<&A>(); let q = lens.query(); function_that_takes_a_query(&q); } ``` Now you may be thinking: Hey, wait a second, you introduced the problem with dynamic components and then described a solution that only works for static components! Ok, you got me, I guess we need a bit more: ### Filtered Entity References Currently the only way you can access dynamic components on entities through a query is with either `EntityMut` or `EntityRef`, however these can access all components and so conflict with all other accesses. This PR introduces `FilteredEntityMut` and `FilteredEntityRef` as alternatives that have additional runtime checking to prevent accessing components that you shouldn't. This way you can build a query with a `QueryBuilder` and actually access the components you asked for: ```rust let mut query = QueryBuilder::<FilteredEntityRef>::new(&mut world) .ref_id(component_id_a) .with(component_id_b) .build(); let entity_ref = query.single(&world); // Returns Some(Ptr) as we have that component and are allowed to read it let a = entity_ref.get_by_id(component_id_a); // Will return None even though the entity does have the component, as we are not allowed to read it let b = entity_ref.get_by_id(component_id_b); ``` For the most part these new structs have the exact same methods as their non-filtered equivalents. Putting all of this together we can do some truly dynamic ECS queries, check out the `dynamic` example to see it in action: ``` Commands: comp, c Create new components spawn, s Spawn entities query, q Query for entities Enter a command with no parameters for usage. > c A, B, C, Data 4 Component A created with id: 0 Component B created with id: 1 Component C created with id: 2 Component Data created with id: 3 > s A, B, Data 1 Entity spawned with id: 0v0 > s A, C, Data 0 Entity spawned with id: 1v0 > q &Data 0v0: Data: [1, 0, 0, 0] 1v0: Data: [0, 0, 0, 0] > q B, &mut Data 0v0: Data: [2, 1, 1, 1] > q B || C, &Data 0v0: Data: [2, 1, 1, 1] 1v0: Data: [0, 0, 0, 0] ``` ## Changelog - Add new `transmute_lens` methods to `Query`. - Add new types `QueryBuilder`, `FilteredEntityMut`, `FilteredEntityRef` and `QueryLens` - `update_archetype_component_access` has been removed, archetype component accesses are now determined by the accesses set in `update_component_access` - Added method `set_access` to `WorldQuery`, this is called before `update_component_access` for queries that have a restricted set of accesses, such as those built by `QueryBuilder` or `QueryLens`. This is primarily used by the `FilteredEntity*` variants and has an empty trait implementation. - Added method `get_state` to `WorldQuery` as a fallible version of `init_state` when you don't have `&mut World` access. ## Future Work Improve performance of `FilteredEntityMut` and `FilteredEntityRef`, currently they have to determine the accesses a query has in a given archetype during iteration which is far from ideal, especially since we already did the work when matching the archetype in the first place. To avoid making more internal API changes I have left it out of this PR. --------- Co-authored-by: Mike Hsu <mike.hsu@gmail.com>
2024-01-16 19:16:49 +00:00
fn get_state(_world: &World) -> Option<()> {
Some(())
}
Allow disjoint mutable world access via `EntityMut` (#9419) # Objective Fix #4278 Fix #5504 Fix #9422 Provide safe ways to borrow an entire entity, while allowing disjoint mutable access. `EntityRef` and `EntityMut` are not suitable for this, since they provide access to the entire world -- they are just helper types for working with `&World`/`&mut World`. This has potential uses for reflection and serialization ## Solution Remove `EntityRef::world`, which allows it to soundly be used within queries. `EntityMut` no longer supports structural world mutations, which allows multiple instances of it to exist for different entities at once. Structural world mutations are performed using the new type `EntityWorldMut`. ```rust fn disjoint_system( q2: Query<&mut A>, q1: Query<EntityMut, Without<A>>, ) { ... } let [entity1, entity2] = world.many_entities_mut([id1, id2]); *entity1.get_mut::<T>().unwrap() = *entity2.get().unwrap(); for entity in world.iter_entities_mut() { ... } ``` --- ## Changelog - Removed `EntityRef::world`, to fix a soundness issue with queries. + Removed the ability to structurally mutate the world using `EntityMut`, which allows it to be used in queries. + Added `EntityWorldMut`, which is used to perform structural mutations that are no longer allowed using `EntityMut`. ## Migration Guide **Note for maintainers: ensure that the guide for #9604 is updated accordingly.** Removed the method `EntityRef::world`, to fix a soundness issue with queries. If you need access to `&World` while using an `EntityRef`, consider passing the world as a separate parameter. `EntityMut` can no longer perform 'structural' world mutations, such as adding or removing components, or despawning the entity. Additionally, `EntityMut::world`, `EntityMut::world_mut` , and `EntityMut::world_scope` have been removed. Instead, use the newly-added type `EntityWorldMut`, which is a helper type for working with `&mut World`. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-08-28 23:30:59 +00:00
fn matches_component_set(
_state: &Self::State,
_set_contains_id: &impl Fn(ComponentId) -> bool,
) -> bool {
true
}
}
Implement WorldQuery for EntityRef (#6960) # Objective Partially address #5504. Fix #4278. Provide "whole entity" access in queries. This can be useful when you don't know at compile time what you're accessing (i.e. reflection via `ReflectComponent`). ## Solution Implement `WorldQuery` for `EntityRef`. - This provides read-only access to the entire entity, and supports anything that `EntityRef` can normally do. - It matches all archetypes and tables and will densely iterate when possible. - It marks all of the ArchetypeComponentIds of a matched archetype as read. - Adding it to a query will cause it to panic if used in conjunction with any other mutable access. - Expanded the docs on Query to advertise this feature. - Added tests to ensure the panics were working as intended. - Added `EntityRef` to the ECS prelude. To make this safe, `EntityRef::world` was removed as it gave potential `UnsafeCell`-like access to other parts of the `World` including aliased mutable access to the components it would otherwise read safely. ## Performance Not great beyond the additional parallelization opportunity over exclusive systems. The `EntityRef` is fetched from `Entities` like any other call to `World::entity`, which can be very random access heavy. This could be simplified if `ArchetypeRow` is available in `WorldQuery::fetch`'s arguments, but that's likely not something we should optimize for. ## Future work An equivalent API where it gives mutable access to all components on a entity can be done with a scoped version of `EntityMut` where it does not provide `&mut World` access nor allow for structural changes to the entity is feasible as well. This could be done as a safe alternative to exclusive system when structural mutation isn't required or the target set of entities is scoped. --- ## Changelog Added: `Access::has_any_write` Added: `EntityRef` now implements `WorldQuery`. Allows read-only access to the entire entity, incompatible with any other mutable access, can be mixed with `With`/`Without` filters for more targeted use. Added: `EntityRef` to `bevy::ecs::prelude`. Removed: `EntityRef::world` ## Migration Guide TODO --------- Co-authored-by: Carter Weinberg <weinbergcarter@gmail.com> Co-authored-by: Jakob Hellermann <jakob.hellermann@protonmail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2023-06-22 21:20:00 +00:00
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
/// SAFETY: access of `EntityRef` is a subset of `EntityMut`
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
unsafe impl<'a> QueryData for EntityMut<'a> {
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
type ReadOnly = EntityRef<'a>;
}
Dynamic queries and builder API (#9774) # Objective Expand the existing `Query` API to support more dynamic use cases i.e. scripting. ## Prior Art - #6390 - #8308 - #10037 ## Solution - Create a `QueryBuilder` with runtime methods to define the set of component accesses for a built query. - Create new `WorldQueryData` implementations `FilteredEntityMut` and `FilteredEntityRef` as variants of `EntityMut` and `EntityRef` that provide run time checked access to the components included in a given query. - Add new methods to `Query` to create "query lens" with a subset of the access of the initial query. ### Query Builder The `QueryBuilder` API allows you to define a query at runtime. At it's most basic use it will simply create a query with the corresponding type signature: ```rust let query = QueryBuilder::<Entity, With<A>>::new(&mut world).build(); // is equivalent to let query = QueryState::<Entity, With<A>>::new(&mut world); ``` Before calling `.build()` you also have the opportunity to add additional accesses and filters. Here is a simple example where we add additional filter terms: ```rust let entity_a = world.spawn((A(0), B(0))).id(); let entity_b = world.spawn((A(0), C(0))).id(); let mut query_a = QueryBuilder::<Entity>::new(&mut world) .with::<A>() .without::<C>() .build(); assert_eq!(entity_a, query_a.single(&world)); ``` This alone is useful in that allows you to decide which archetypes your query will match at runtime. However it is also very limited, consider a case like the following: ```rust let query_a = QueryBuilder::<&A>::new(&mut world) // Add an additional access .data::<&B>() .build(); ``` This will grant the query an additional read access to component B however we have no way of accessing the data while iterating as the type signature still only includes &A. For an even more concrete example of this consider dynamic components: ```rust let query_a = QueryBuilder::<Entity>::new(&mut world) // Adding a filter is easy since it doesn't need be read later .with_id(component_id_a) // How do I access the data of this component? .ref_id(component_id_b) .build(); ``` With this in mind the `QueryBuilder` API seems somewhat incomplete by itself, we need some way method of accessing the components dynamically. So here's one: ### Query Transmutation If the problem is not having the component in the type signature why not just add it? This PR also adds transmute methods to `QueryBuilder` and `QueryState`. Here's a simple example: ```rust world.spawn(A(0)); world.spawn((A(1), B(0))); let mut query = QueryBuilder::<()>::new(&mut world) .with::<B>() .transmute::<&A>() .build(); query.iter(&world).for_each(|a| assert_eq!(a.0, 1)); ``` The `QueryState` and `QueryBuilder` transmute methods look quite similar but are different in one respect. Transmuting a builder will always succeed as it will just add the additional accesses needed for the new terms if they weren't already included. Transmuting a `QueryState` will panic in the case that the new type signature would give it access it didn't already have, for example: ```rust let query = QueryState::<&A, Option<&B>>::new(&mut world); /// This is fine, the access for Option<&A> is less restrictive than &A query.transmute::<Option<&A>>(&world); /// Oh no, this would allow access to &B on entities that might not have it, so it panics query.transmute::<&B>(&world); /// This is right out query.transmute::<&C>(&world); ``` This is quite an appealing API to also have available on `Query` however it does pose one additional wrinkle: In order to to change the iterator we need to create a new `QueryState` to back it. `Query` doesn't own it's own state though, it just borrows it, so we need a place to borrow it from. This is why `QueryLens` exists, it is a place to store the new state so it can be borrowed when you call `.query()` leaving you with an API like this: ```rust fn function_that_takes_a_query(query: &Query<&A>) { // ... } fn system(query: Query<(&A, &B)>) { let lens = query.transmute_lens::<&A>(); let q = lens.query(); function_that_takes_a_query(&q); } ``` Now you may be thinking: Hey, wait a second, you introduced the problem with dynamic components and then described a solution that only works for static components! Ok, you got me, I guess we need a bit more: ### Filtered Entity References Currently the only way you can access dynamic components on entities through a query is with either `EntityMut` or `EntityRef`, however these can access all components and so conflict with all other accesses. This PR introduces `FilteredEntityMut` and `FilteredEntityRef` as alternatives that have additional runtime checking to prevent accessing components that you shouldn't. This way you can build a query with a `QueryBuilder` and actually access the components you asked for: ```rust let mut query = QueryBuilder::<FilteredEntityRef>::new(&mut world) .ref_id(component_id_a) .with(component_id_b) .build(); let entity_ref = query.single(&world); // Returns Some(Ptr) as we have that component and are allowed to read it let a = entity_ref.get_by_id(component_id_a); // Will return None even though the entity does have the component, as we are not allowed to read it let b = entity_ref.get_by_id(component_id_b); ``` For the most part these new structs have the exact same methods as their non-filtered equivalents. Putting all of this together we can do some truly dynamic ECS queries, check out the `dynamic` example to see it in action: ``` Commands: comp, c Create new components spawn, s Spawn entities query, q Query for entities Enter a command with no parameters for usage. > c A, B, C, Data 4 Component A created with id: 0 Component B created with id: 1 Component C created with id: 2 Component Data created with id: 3 > s A, B, Data 1 Entity spawned with id: 0v0 > s A, C, Data 0 Entity spawned with id: 1v0 > q &Data 0v0: Data: [1, 0, 0, 0] 1v0: Data: [0, 0, 0, 0] > q B, &mut Data 0v0: Data: [2, 1, 1, 1] > q B || C, &Data 0v0: Data: [2, 1, 1, 1] 1v0: Data: [0, 0, 0, 0] ``` ## Changelog - Add new `transmute_lens` methods to `Query`. - Add new types `QueryBuilder`, `FilteredEntityMut`, `FilteredEntityRef` and `QueryLens` - `update_archetype_component_access` has been removed, archetype component accesses are now determined by the accesses set in `update_component_access` - Added method `set_access` to `WorldQuery`, this is called before `update_component_access` for queries that have a restricted set of accesses, such as those built by `QueryBuilder` or `QueryLens`. This is primarily used by the `FilteredEntity*` variants and has an empty trait implementation. - Added method `get_state` to `WorldQuery` as a fallible version of `init_state` when you don't have `&mut World` access. ## Future Work Improve performance of `FilteredEntityMut` and `FilteredEntityRef`, currently they have to determine the accesses a query has in a given archetype during iteration which is far from ideal, especially since we already did the work when matching the archetype in the first place. To avoid making more internal API changes I have left it out of this PR. --------- Co-authored-by: Mike Hsu <mike.hsu@gmail.com>
2024-01-16 19:16:49 +00:00
/// SAFETY: The accesses of `Self::ReadOnly` are a subset of the accesses of `Self`
unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> {
type Fetch<'w> = (UnsafeWorldCell<'w>, Access<ComponentId>);
type Item<'w> = FilteredEntityRef<'w>;
type State = FilteredAccess<ComponentId>;
fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> {
item
}
const IS_DENSE: bool = false;
unsafe fn init_fetch<'w>(
world: UnsafeWorldCell<'w>,
_state: &Self::State,
_last_run: Tick,
_this_run: Tick,
) -> Self::Fetch<'w> {
let mut access = Access::default();
access.read_all();
(world, access)
}
#[inline]
unsafe fn set_archetype<'w>(
fetch: &mut Self::Fetch<'w>,
state: &Self::State,
archetype: &'w Archetype,
_table: &Table,
) {
let mut access = Access::default();
state.access.reads().for_each(|id| {
if archetype.contains(id) {
access.add_read(id);
}
});
fetch.1 = access;
}
#[inline]
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) {
let mut access = Access::default();
state.access.reads().for_each(|id| {
if table.has_column(id) {
access.add_read(id);
}
});
fetch.1 = access;
}
#[inline]
fn set_access<'w>(state: &mut Self::State, access: &FilteredAccess<ComponentId>) {
*state = access.clone();
state.access_mut().clear_writes();
}
#[inline(always)]
unsafe fn fetch<'w>(
(world, access): &mut Self::Fetch<'w>,
entity: Entity,
_table_row: TableRow,
) -> Self::Item<'w> {
// SAFETY: `fetch` must be called with an entity that exists in the world
let cell = world.get_entity(entity).debug_checked_unwrap();
// SAFETY: mutable access to every component has been registered.
FilteredEntityRef::new(cell, access.clone())
}
fn update_component_access(
state: &Self::State,
filtered_access: &mut FilteredAccess<ComponentId>,
) {
assert!(
filtered_access.access().is_compatible(&state.access),
"FilteredEntityRef conflicts with a previous access in this query. Exclusive access cannot coincide with any other accesses.",
);
filtered_access.access.extend(&state.access);
}
fn init_state(_world: &mut World) -> Self::State {
FilteredAccess::default()
}
fn get_state(_world: &World) -> Option<Self::State> {
Some(FilteredAccess::default())
}
fn matches_component_set(
_state: &Self::State,
_set_contains_id: &impl Fn(ComponentId) -> bool,
) -> bool {
true
}
}
/// SAFETY: `Self` is the same as `Self::ReadOnly`
unsafe impl<'a> QueryData for FilteredEntityRef<'a> {
type ReadOnly = Self;
}
/// SAFETY: Access is read-only.
unsafe impl ReadOnlyQueryData for FilteredEntityRef<'_> {}
/// SAFETY: The accesses of `Self::ReadOnly` are a subset of the accesses of `Self`
unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> {
type Fetch<'w> = (UnsafeWorldCell<'w>, Access<ComponentId>);
type Item<'w> = FilteredEntityMut<'w>;
type State = FilteredAccess<ComponentId>;
fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> {
item
}
const IS_DENSE: bool = false;
unsafe fn init_fetch<'w>(
world: UnsafeWorldCell<'w>,
_state: &Self::State,
_last_run: Tick,
_this_run: Tick,
) -> Self::Fetch<'w> {
let mut access = Access::default();
access.write_all();
(world, access)
}
#[inline]
unsafe fn set_archetype<'w>(
fetch: &mut Self::Fetch<'w>,
state: &Self::State,
archetype: &'w Archetype,
_table: &Table,
) {
let mut access = Access::default();
state.access.reads().for_each(|id| {
if archetype.contains(id) {
access.add_read(id);
}
});
state.access.writes().for_each(|id| {
if archetype.contains(id) {
access.add_write(id);
}
});
fetch.1 = access;
}
#[inline]
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) {
let mut access = Access::default();
state.access.reads().for_each(|id| {
if table.has_column(id) {
access.add_read(id);
}
});
state.access.writes().for_each(|id| {
if table.has_column(id) {
access.add_write(id);
}
});
fetch.1 = access;
}
#[inline]
fn set_access<'w>(state: &mut Self::State, access: &FilteredAccess<ComponentId>) {
*state = access.clone();
}
#[inline(always)]
unsafe fn fetch<'w>(
(world, access): &mut Self::Fetch<'w>,
entity: Entity,
_table_row: TableRow,
) -> Self::Item<'w> {
// SAFETY: `fetch` must be called with an entity that exists in the world
let cell = world.get_entity(entity).debug_checked_unwrap();
// SAFETY: mutable access to every component has been registered.
FilteredEntityMut::new(cell, access.clone())
}
fn update_component_access(
state: &Self::State,
filtered_access: &mut FilteredAccess<ComponentId>,
) {
assert!(
filtered_access.access().is_compatible(&state.access),
"FilteredEntityMut conflicts with a previous access in this query. Exclusive access cannot coincide with any other accesses.",
);
filtered_access.access.extend(&state.access);
}
fn init_state(_world: &mut World) -> Self::State {
FilteredAccess::default()
}
fn get_state(_world: &World) -> Option<Self::State> {
Some(FilteredAccess::default())
}
fn matches_component_set(
_state: &Self::State,
_set_contains_id: &impl Fn(ComponentId) -> bool,
) -> bool {
true
}
}
/// SAFETY: access of `FilteredEntityRef` is a subset of `FilteredEntityMut`
unsafe impl<'a> QueryData for FilteredEntityMut<'a> {
type ReadOnly = FilteredEntityRef<'a>;
}
#[doc(hidden)]
Use lifetimed, type erased pointers in bevy_ecs (#3001) # Objective `bevy_ecs` has large amounts of unsafe code which is hard to get right and makes it difficult to audit for soundness. ## Solution Introduce lifetimed, type-erased pointers: `Ptr<'a>` `PtrMut<'a>` `OwningPtr<'a>'` and `ThinSlicePtr<'a, T>` which are newtypes around a raw pointer with a lifetime and conceptually representing strong invariants about the pointee and validity of the pointer. The process of converting bevy_ecs to use these has already caught multiple cases of unsound behavior. ## Changelog TL;DR for release notes: `bevy_ecs` now uses lifetimed, type-erased pointers internally, significantly improving safety and legibility without sacrificing performance. This should have approximately no end user impact, unless you were meddling with the (unfortunately public) internals of `bevy_ecs`. - `Fetch`, `FilterFetch` and `ReadOnlyFetch` trait no longer have a `'state` lifetime - this was unneeded - `ReadOnly/Fetch` associated types on `WorldQuery` are now on a new `WorldQueryGats<'world>` trait - was required to work around lack of Generic Associated Types (we wish to express `type Fetch<'a>: Fetch<'a>`) - `derive(WorldQuery)` no longer requires `'w` lifetime on struct - this was unneeded, and improves the end user experience - `EntityMut::get_unchecked_mut` returns `&'_ mut T` not `&'w mut T` - allows easier use of unsafe API with less footguns, and can be worked around via lifetime transmutery as a user - `Bundle::from_components` now takes a `ctx` parameter to pass to the `FnMut` closure - required because closure return types can't borrow from captures - `Fetch::init` takes `&'world World`, `Fetch::set_archetype` takes `&'world Archetype` and `&'world Tables`, `Fetch::set_table` takes `&'world Table` - allows types implementing `Fetch` to store borrows into world - `WorldQuery` trait now has a `shrink` fn to shorten the lifetime in `Fetch::<'a>::Item` - this works around lack of subtyping of assoc types, rust doesnt allow you to turn `<T as Fetch<'static>>::Item'` into `<T as Fetch<'a>>::Item'` - `QueryCombinationsIter` requires this - Most types implementing `Fetch` now have a lifetime `'w` - allows the fetches to store borrows of world data instead of using raw pointers ## Migration guide - `EntityMut::get_unchecked_mut` returns a more restricted lifetime, there is no general way to migrate this as it depends on your code - `Bundle::from_components` implementations must pass the `ctx` arg to `func` - `Bundle::from_components` callers have to use a fn arg instead of closure captures for borrowing from world - Remove lifetime args on `derive(WorldQuery)` structs as it is nonsensical - `<Q as WorldQuery>::ReadOnly/Fetch` should be changed to either `RO/QueryFetch<'world>` or `<Q as WorldQueryGats<'world>>::ReadOnly/Fetch` - `<F as Fetch<'w, 's>>` should be changed to `<F as Fetch<'w>>` - Change the fn sigs of `Fetch::init/set_archetype/set_table` to match respective trait fn sigs - Implement the required `fn shrink` on any `WorldQuery` implementations - Move assoc types `Fetch` and `ReadOnlyFetch` on `WorldQuery` impls to `WorldQueryGats` impls - Pass an appropriate `'world` lifetime to whatever fetch struct you are for some reason using ### Type inference regression in some cases rustc may give spurrious errors when attempting to infer the `F` parameter on a query/querystate this can be fixed by manually specifying the type, i.e. `QueryState::new::<_, ()>(world)`. The error is rather confusing: ```rust= error[E0271]: type mismatch resolving `<() as Fetch<'_>>::Item == bool` --> crates/bevy_pbr/src/render/light.rs:1413:30 | 1413 | main_view_query: QueryState::new(world), | ^^^^^^^^^^^^^^^ expected `bool`, found `()` | = note: required because of the requirements on the impl of `for<'x> FilterFetch<'x>` for `<() as WorldQueryGats<'x>>::Fetch` note: required by a bound in `bevy_ecs::query::QueryState::<Q, F>::new` --> crates/bevy_ecs/src/query/state.rs:49:32 | 49 | for<'x> QueryFetch<'x, F>: FilterFetch<'x>, | ^^^^^^^^^^^^^^^ required by this bound in `bevy_ecs::query::QueryState::<Q, F>::new` ``` --- Made with help from @BoxyUwU and @alice-i-cecile Co-authored-by: Boxy <supbscripter@gmail.com>
2022-04-27 23:44:06 +00:00
pub struct ReadFetch<'w, T> {
// T::Storage = TableStorage
table_components: Option<ThinSlicePtr<'w, UnsafeCell<T>>>,
// T::Storage = SparseStorage
sparse_set: Option<&'w ComponentSparseSet>,
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
}
Fix safety invariants for `WorldQuery::fetch` and simplify cloning (#8246) # Objective Cloning a `WorldQuery` type's "fetch" struct was made unsafe in #5593, by adding the `unsafe fn clone_fetch` to `WorldQuery`. However, as that method's documentation explains, it is not the right place to put the safety invariant: > While calling this method on its own cannot cause UB it is marked `unsafe` as the caller must ensure that the returned value is not used in any way that would cause two `QueryItem<Self>` for the same `archetype_index` or `table_row` to be alive at the same time. You can clone a fetch struct all you want and it will never cause undefined behavior -- in order for something to go wrong, you need to improperly call `WorldQuery::fetch` with it (which is marked unsafe). Additionally, making it unsafe to clone a fetch struct does not even prevent undefined behavior, since there are other ways to incorrectly use a fetch struct. For example, you could just call fetch more than once for the same entity, which is not currently forbidden by any documented invariants. ## Solution Document a safety invariant on `WorldQuery::fetch` that requires the caller to not create aliased `WorldQueryItem`s for mutable types. Remove the `clone_fetch` function, and add the bound `Fetch: Clone` instead. --- ## Changelog - Removed the associated function `WorldQuery::clone_fetch`, and added a `Clone` bound to `WorldQuery::Fetch`. ## Migration Guide ### `fetch` invariants The function `WorldQuery::fetch` has had the following safety invariant added: > If this type does not implement `ReadOnlyWorldQuery`, then the caller must ensure that it is impossible for more than one `Self::Item` to exist for the same entity at any given time. This invariant was always required for soundness, but was previously undocumented. If you called this function manually anywhere, you should check to make sure that this invariant is not violated. ### Removed `clone_fetch` The function `WorldQuery::clone_fetch` has been removed. The associated type `WorldQuery::Fetch` now has the bound `Clone`. Before: ```rust struct MyFetch<'w> { ... } unsafe impl WorldQuery for MyQuery { ... type Fetch<'w> = MyFetch<'w> unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> { MyFetch { field1: fetch.field1, field2: fetch.field2.clone(), ... } } } ``` After: ```rust #[derive(Clone)] struct MyFetch<'w> { ... } unsafe impl WorldQuery for MyQuery { ... type Fetch<'w> = MyFetch<'w>; } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-07-25 21:16:22 +00:00
impl<T> Clone for ReadFetch<'_, T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for ReadFetch<'_, T> {}
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
/// SAFETY:
/// `fetch` accesses a single component in a readonly way.
/// This is sound because `update_component_access` and `update_archetype_component_access` add read access for that component and panic when appropriate.
/// `update_component_access` adds a `With` filter for a component.
/// This is sound because `matches_component_set` returns whether the set contains that component.
unsafe impl<T: Component> WorldQuery for &T {
type Item<'w> = &'w T;
type Fetch<'w> = ReadFetch<'w, T>;
type State = ComponentId;
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
fn shrink<'wlong: 'wshort, 'wshort>(item: &'wlong T) -> &'wshort T {
item
}
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
#[inline]
unsafe fn init_fetch<'w>(
Migrate the rest of the engine to `UnsafeWorldCell` (#8833) # Objective Follow-up to #6404 and #8292. Mutating the world through a shared reference is surprising, and it makes the meaning of `&World` unclear: sometimes it gives read-only access to the entire world, and sometimes it gives interior mutable access to only part of it. This is an up-to-date version of #6972. ## Solution Use `UnsafeWorldCell` for all interior mutability. Now, `&World` *always* gives you read-only access to the entire world. --- ## Changelog TODO - do we still care about changelogs? ## Migration Guide Mutating any world data using `&World` is now considered unsound -- the type `UnsafeWorldCell` must be used to achieve interior mutability. The following methods now accept `UnsafeWorldCell` instead of `&World`: - `QueryState`: `get_unchecked`, `iter_unchecked`, `iter_combinations_unchecked`, `for_each_unchecked`, `get_single_unchecked`, `get_single_unchecked_manual`. - `SystemState`: `get_unchecked_manual` ```rust let mut world = World::new(); let mut query = world.query::<&mut T>(); // Before: let t1 = query.get_unchecked(&world, entity_1); let t2 = query.get_unchecked(&world, entity_2); // After: let world_cell = world.as_unsafe_world_cell(); let t1 = query.get_unchecked(world_cell, entity_1); let t2 = query.get_unchecked(world_cell, entity_2); ``` The methods `QueryState::validate_world` and `SystemState::matches_world` now take a `WorldId` instead of `&World`: ```rust // Before: query_state.validate_world(&world); // After: query_state.validate_world(world.id()); ``` The methods `QueryState::update_archetypes` and `SystemState::update_archetypes` now take `UnsafeWorldCell` instead of `&World`: ```rust // Before: query_state.update_archetypes(&world); // After: query_state.update_archetypes(world.as_unsafe_world_cell_readonly()); ```
2023-06-15 01:31:56 +00:00
world: UnsafeWorldCell<'w>,
&component_id: &ComponentId,
_last_run: Tick,
_this_run: Tick,
Use lifetimed, type erased pointers in bevy_ecs (#3001) # Objective `bevy_ecs` has large amounts of unsafe code which is hard to get right and makes it difficult to audit for soundness. ## Solution Introduce lifetimed, type-erased pointers: `Ptr<'a>` `PtrMut<'a>` `OwningPtr<'a>'` and `ThinSlicePtr<'a, T>` which are newtypes around a raw pointer with a lifetime and conceptually representing strong invariants about the pointee and validity of the pointer. The process of converting bevy_ecs to use these has already caught multiple cases of unsound behavior. ## Changelog TL;DR for release notes: `bevy_ecs` now uses lifetimed, type-erased pointers internally, significantly improving safety and legibility without sacrificing performance. This should have approximately no end user impact, unless you were meddling with the (unfortunately public) internals of `bevy_ecs`. - `Fetch`, `FilterFetch` and `ReadOnlyFetch` trait no longer have a `'state` lifetime - this was unneeded - `ReadOnly/Fetch` associated types on `WorldQuery` are now on a new `WorldQueryGats<'world>` trait - was required to work around lack of Generic Associated Types (we wish to express `type Fetch<'a>: Fetch<'a>`) - `derive(WorldQuery)` no longer requires `'w` lifetime on struct - this was unneeded, and improves the end user experience - `EntityMut::get_unchecked_mut` returns `&'_ mut T` not `&'w mut T` - allows easier use of unsafe API with less footguns, and can be worked around via lifetime transmutery as a user - `Bundle::from_components` now takes a `ctx` parameter to pass to the `FnMut` closure - required because closure return types can't borrow from captures - `Fetch::init` takes `&'world World`, `Fetch::set_archetype` takes `&'world Archetype` and `&'world Tables`, `Fetch::set_table` takes `&'world Table` - allows types implementing `Fetch` to store borrows into world - `WorldQuery` trait now has a `shrink` fn to shorten the lifetime in `Fetch::<'a>::Item` - this works around lack of subtyping of assoc types, rust doesnt allow you to turn `<T as Fetch<'static>>::Item'` into `<T as Fetch<'a>>::Item'` - `QueryCombinationsIter` requires this - Most types implementing `Fetch` now have a lifetime `'w` - allows the fetches to store borrows of world data instead of using raw pointers ## Migration guide - `EntityMut::get_unchecked_mut` returns a more restricted lifetime, there is no general way to migrate this as it depends on your code - `Bundle::from_components` implementations must pass the `ctx` arg to `func` - `Bundle::from_components` callers have to use a fn arg instead of closure captures for borrowing from world - Remove lifetime args on `derive(WorldQuery)` structs as it is nonsensical - `<Q as WorldQuery>::ReadOnly/Fetch` should be changed to either `RO/QueryFetch<'world>` or `<Q as WorldQueryGats<'world>>::ReadOnly/Fetch` - `<F as Fetch<'w, 's>>` should be changed to `<F as Fetch<'w>>` - Change the fn sigs of `Fetch::init/set_archetype/set_table` to match respective trait fn sigs - Implement the required `fn shrink` on any `WorldQuery` implementations - Move assoc types `Fetch` and `ReadOnlyFetch` on `WorldQuery` impls to `WorldQueryGats` impls - Pass an appropriate `'world` lifetime to whatever fetch struct you are for some reason using ### Type inference regression in some cases rustc may give spurrious errors when attempting to infer the `F` parameter on a query/querystate this can be fixed by manually specifying the type, i.e. `QueryState::new::<_, ()>(world)`. The error is rather confusing: ```rust= error[E0271]: type mismatch resolving `<() as Fetch<'_>>::Item == bool` --> crates/bevy_pbr/src/render/light.rs:1413:30 | 1413 | main_view_query: QueryState::new(world), | ^^^^^^^^^^^^^^^ expected `bool`, found `()` | = note: required because of the requirements on the impl of `for<'x> FilterFetch<'x>` for `<() as WorldQueryGats<'x>>::Fetch` note: required by a bound in `bevy_ecs::query::QueryState::<Q, F>::new` --> crates/bevy_ecs/src/query/state.rs:49:32 | 49 | for<'x> QueryFetch<'x, F>: FilterFetch<'x>, | ^^^^^^^^^^^^^^^ required by this bound in `bevy_ecs::query::QueryState::<Q, F>::new` ``` --- Made with help from @BoxyUwU and @alice-i-cecile Co-authored-by: Boxy <supbscripter@gmail.com>
2022-04-27 23:44:06 +00:00
) -> ReadFetch<'w, T> {
ReadFetch {
table_components: None,
sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet).then(|| {
world
Migrate the rest of the engine to `UnsafeWorldCell` (#8833) # Objective Follow-up to #6404 and #8292. Mutating the world through a shared reference is surprising, and it makes the meaning of `&World` unclear: sometimes it gives read-only access to the entire world, and sometimes it gives interior mutable access to only part of it. This is an up-to-date version of #6972. ## Solution Use `UnsafeWorldCell` for all interior mutability. Now, `&World` *always* gives you read-only access to the entire world. --- ## Changelog TODO - do we still care about changelogs? ## Migration Guide Mutating any world data using `&World` is now considered unsound -- the type `UnsafeWorldCell` must be used to achieve interior mutability. The following methods now accept `UnsafeWorldCell` instead of `&World`: - `QueryState`: `get_unchecked`, `iter_unchecked`, `iter_combinations_unchecked`, `for_each_unchecked`, `get_single_unchecked`, `get_single_unchecked_manual`. - `SystemState`: `get_unchecked_manual` ```rust let mut world = World::new(); let mut query = world.query::<&mut T>(); // Before: let t1 = query.get_unchecked(&world, entity_1); let t2 = query.get_unchecked(&world, entity_2); // After: let world_cell = world.as_unsafe_world_cell(); let t1 = query.get_unchecked(world_cell, entity_1); let t2 = query.get_unchecked(world_cell, entity_2); ``` The methods `QueryState::validate_world` and `SystemState::matches_world` now take a `WorldId` instead of `&World`: ```rust // Before: query_state.validate_world(&world); // After: query_state.validate_world(world.id()); ``` The methods `QueryState::update_archetypes` and `SystemState::update_archetypes` now take `UnsafeWorldCell` instead of `&World`: ```rust // Before: query_state.update_archetypes(&world); // After: query_state.update_archetypes(world.as_unsafe_world_cell_readonly()); ```
2023-06-15 01:31:56 +00:00
// SAFETY: The underlying type associated with `component_id` is `T`,
// which we are allowed to access since we registered it in `update_archetype_component_access`.
// Note that we do not actually access any components in this function, we just get a shared
// reference to the sparse set, which is used to access the components in `Self::fetch`.
.storages()
.sparse_sets
.get(component_id)
Remove unnecesary branches/panics from Query accesses (#6461) # Objective Supercedes #6452. Upon inspection of the [generated assembly](https://gist.github.com/james7132/c2740c6941b80d7912f1e8888e223cbb#file-original-s) of a [simple Bevy binary](https://gist.github.com/james7132/c2740c6941b80d7912f1e8888e223cbb#file-source-rs) compiled with `cargo rustc --release -- --emit asm`, it's apparent that there are multiple unnecessary branches in the generated assembly: ```assembly .LBB5_5: cmpq %r10, %r11 je .LBB5_15 movq (%r11), %rcx movq 328(%r15), %rdx cmpq %rdx, %rcx jae .LBB5_14 movq 312(%r15), %rdi leaq (%rcx,%rcx,2), %rcx shlq $5, %rcx movq 336(%r12), %rdx movq 64(%rdi,%rcx), %rax cmpq %rdx, %rax jbe .LBB5_4 leaq (%rdi,%rcx), %rsi movq 48(%rsi), %rbp shlq $4, %rdx cmpq $0, (%rbp,%rdx) je .LBB5_4 movq 344(%r12), %rbx cmpq %rbx, %rax jbe .LBB5_4 shlq $4, %rbx cmpq $0, (%rbp,%rbx) je .LBB5_4 addq $8, %r11 movq 88(%rdi,%rcx), %rcx testq %rcx, %rcx je .LBB5_5 movq (%rsi), %rax movq 8(%rbp,%rdx), %rdx leaq (%rdx,%rdx,4), %rdi shlq $4, %rdi movq 32(%rax,%rdi), %rdx movq 56(%rax,%rdi), %r8 movq 8(%rbp,%rbx), %rbp leaq (%rbp,%rbp,4), %rbp shlq $4, %rbp movq 32(%rax,%rbp), %r9 xorl %ebp, %ebp jmp .LBB5_13 .p2align 4, 0x90 ``` Almost every one of the instructions starting with `j` is a potential branch, which can significantly slow down accesses. Of these, two labels are both common and never used: ```asm .LBB5_14: leaq __unnamed_2(%rip), %r8 callq _ZN4core9panicking18panic_bounds_check17h70367088e72af65aE ud2 .LBB5_4: callq _ZN8bevy_ecs5query25debug_checked_unreachable17h0855ff520ceaea77E ud2 .seh_endproc ``` These correpsond to subprocedure calls to panicking due to out of bounds from indexing `Tables` and `debug_checked_unreadable`. Both of which should be inlined and optimized out, but are not. ## Solution Make `debug_checked_unreachable` a macro to forcibly inline either `unreachable!()` in debug builds, and `std::hint::unreachable_unchecked()` in release mode. Replace the `Tables` and `Archetype` index access with `get(id).unwrap_or_else(|| debug_checked_unreachable!())` to assume that the table or archetype provided exists. This has no external breaking change of any kind. The equivalent section of code with these changes removes most of the conditional jump instructions: ```asm .LBB5_5: movss (%rbx,%rbp,4), %xmm0 movl %r14d, 4(%r8,%rbp,8) addss (%rdi,%rbp,4), %xmm0 movss %xmm0, (%rdi,%rbp,4) incq %rbp .LBB5_1: cmpq %rdx, %rbp jne .LBB5_5 .p2align 4, 0x90 .LBB5_2: cmpq %rcx, %rax je .LBB5_6 movq (%rax), %rdx addq $8, %rax movq 312(%rsi), %rbp leaq (%rdx,%rdx,2), %rbx shlq $5, %rbx movq 88(%rbp,%rbx), %rdx testq %rdx, %rdx je .LBB5_2 leaq (%rbx,%rbp), %r8 movq 336(%r15), %rdi movq 344(%r15), %r9 movq 48(%rbp,%rbx), %r10 shlq $4, %rdi movq (%r8), %rbx movq 8(%r10,%rdi), %rdi leaq (%rdi,%rdi,4), %rbp shlq $4, %rbp movq 32(%rbx,%rbp), %rdi movq 56(%rbx,%rbp), %r8 shlq $4, %r9 movq 8(%r10,%r9), %rbp leaq (%rbp,%rbp,4), %rbp shlq $4, %rbp movq 32(%rbx,%rbp), %rbx xorl %ebp, %ebp jmp .LBB5_5 .LBB5_6: addq $40, %rsp popq %rbx popq %rbp popq %rdi popq %rsi popq %r14 popq %r15 retq .seh_endproc ``` ## Performance Microbenchmarks results: <details> ``` group main no-panic-query ----- ---- -------------- busy_systems/01x_entities_03_systems 1.20 42.4±2.66µs ? ?/sec 1.00 35.3±1.68µs ? ?/sec busy_systems/01x_entities_06_systems 1.32 83.8±3.50µs ? ?/sec 1.00 63.6±1.72µs ? ?/sec busy_systems/01x_entities_09_systems 1.15 113.3±8.90µs ? ?/sec 1.00 98.2±6.15µs ? ?/sec busy_systems/01x_entities_12_systems 1.27 160.8±32.44µs ? ?/sec 1.00 126.6±4.70µs ? ?/sec busy_systems/01x_entities_15_systems 1.12 179.6±3.71µs ? ?/sec 1.00 160.3±11.03µs ? ?/sec busy_systems/02x_entities_03_systems 1.18 76.8±3.14µs ? ?/sec 1.00 65.2±3.17µs ? ?/sec busy_systems/02x_entities_06_systems 1.16 144.6±6.10µs ? ?/sec 1.00 124.5±5.14µs ? ?/sec busy_systems/02x_entities_09_systems 1.19 215.3±9.18µs ? ?/sec 1.00 181.5±5.67µs ? ?/sec busy_systems/02x_entities_12_systems 1.20 266.7±8.33µs ? ?/sec 1.00 222.0±9.53µs ? ?/sec busy_systems/02x_entities_15_systems 1.23 338.8±10.53µs ? ?/sec 1.00 276.3±6.94µs ? ?/sec busy_systems/03x_entities_03_systems 1.43 113.5±5.06µs ? ?/sec 1.00 79.6±1.49µs ? ?/sec busy_systems/03x_entities_06_systems 1.38 217.3±12.67µs ? ?/sec 1.00 157.5±3.07µs ? ?/sec busy_systems/03x_entities_09_systems 1.23 308.8±24.75µs ? ?/sec 1.00 251.6±8.93µs ? ?/sec busy_systems/03x_entities_12_systems 1.05 347.7±12.43µs ? ?/sec 1.00 330.6±11.43µs ? ?/sec busy_systems/03x_entities_15_systems 1.13 455.5±13.88µs ? ?/sec 1.00 401.7±17.29µs ? ?/sec busy_systems/04x_entities_03_systems 1.24 144.7±5.89µs ? ?/sec 1.00 116.9±6.29µs ? ?/sec busy_systems/04x_entities_06_systems 1.24 282.8±21.40µs ? ?/sec 1.00 228.6±21.31µs ? ?/sec busy_systems/04x_entities_09_systems 1.35 431.8±14.10µs ? ?/sec 1.00 319.6±9.83µs ? ?/sec busy_systems/04x_entities_12_systems 1.16 493.8±22.87µs ? ?/sec 1.00 424.9±15.24µs ? ?/sec busy_systems/04x_entities_15_systems 1.10 587.5±23.25µs ? ?/sec 1.00 531.7±16.32µs ? ?/sec busy_systems/05x_entities_03_systems 1.14 148.2±9.61µs ? ?/sec 1.00 129.5±4.32µs ? ?/sec busy_systems/05x_entities_06_systems 1.31 359.7±17.46µs ? ?/sec 1.00 273.6±10.55µs ? ?/sec busy_systems/05x_entities_09_systems 1.22 473.5±23.11µs ? ?/sec 1.00 389.3±13.62µs ? ?/sec busy_systems/05x_entities_12_systems 1.05 562.9±20.76µs ? ?/sec 1.00 536.5±24.35µs ? ?/sec busy_systems/05x_entities_15_systems 1.23 818.5±28.70µs ? ?/sec 1.00 666.6±45.87µs ? ?/sec contrived/01x_entities_03_systems 1.27 27.5±0.49µs ? ?/sec 1.00 21.6±1.71µs ? ?/sec contrived/01x_entities_06_systems 1.22 49.9±1.18µs ? ?/sec 1.00 40.7±2.62µs ? ?/sec contrived/01x_entities_09_systems 1.30 72.3±2.39µs ? ?/sec 1.00 55.4±2.60µs ? ?/sec contrived/01x_entities_12_systems 1.28 94.3±9.44µs ? ?/sec 1.00 73.7±3.62µs ? ?/sec contrived/01x_entities_15_systems 1.25 118.0±2.43µs ? ?/sec 1.00 94.1±3.99µs ? ?/sec contrived/02x_entities_03_systems 1.23 41.6±1.71µs ? ?/sec 1.00 33.7±2.30µs ? ?/sec contrived/02x_entities_06_systems 1.19 78.6±2.63µs ? ?/sec 1.00 65.9±2.35µs ? ?/sec contrived/02x_entities_09_systems 1.28 113.6±3.60µs ? ?/sec 1.00 88.6±3.60µs ? ?/sec contrived/02x_entities_12_systems 1.20 146.4±5.75µs ? ?/sec 1.00 121.7±3.35µs ? ?/sec contrived/02x_entities_15_systems 1.23 178.5±4.86µs ? ?/sec 1.00 145.7±4.00µs ? ?/sec contrived/03x_entities_03_systems 1.42 58.3±2.77µs ? ?/sec 1.00 41.1±1.54µs ? ?/sec contrived/03x_entities_06_systems 1.32 108.5±7.30µs ? ?/sec 1.00 82.4±4.86µs ? ?/sec contrived/03x_entities_09_systems 1.23 153.7±4.61µs ? ?/sec 1.00 125.0±4.76µs ? ?/sec contrived/03x_entities_12_systems 1.18 197.5±5.12µs ? ?/sec 1.00 166.8±8.14µs ? ?/sec contrived/03x_entities_15_systems 1.23 238.8±6.38µs ? ?/sec 1.00 194.6±4.55µs ? ?/sec contrived/04x_entities_03_systems 1.34 66.4±3.42µs ? ?/sec 1.00 49.5±1.98µs ? ?/sec contrived/04x_entities_06_systems 1.27 134.3±4.86µs ? ?/sec 1.00 105.8±3.58µs ? ?/sec contrived/04x_entities_09_systems 1.26 193.2±3.83µs ? ?/sec 1.00 153.0±5.60µs ? ?/sec contrived/04x_entities_12_systems 1.16 237.1±5.78µs ? ?/sec 1.00 204.9±18.77µs ? ?/sec contrived/04x_entities_15_systems 1.17 289.2±4.76µs ? ?/sec 1.00 246.3±8.57µs ? ?/sec contrived/05x_entities_03_systems 1.26 80.4±2.90µs ? ?/sec 1.00 63.7±3.07µs ? ?/sec contrived/05x_entities_06_systems 1.27 161.6±13.47µs ? ?/sec 1.00 127.2±5.59µs ? ?/sec contrived/05x_entities_09_systems 1.22 228.0±7.76µs ? ?/sec 1.00 186.2±7.68µs ? ?/sec contrived/05x_entities_12_systems 1.20 289.5±6.21µs ? ?/sec 1.00 241.8±7.52µs ? ?/sec contrived/05x_entities_15_systems 1.18 357.3±11.24µs ? ?/sec 1.00 302.7±7.21µs ? ?/sec heavy_compute/base 1.01 302.4±3.52µs ? ?/sec 1.00 300.2±3.40µs ? ?/sec iter_fragmented/base 1.00 348.1±7.51ns ? ?/sec 1.01 351.9±8.32ns ? ?/sec iter_fragmented/foreach 1.03 239.8±23.78ns ? ?/sec 1.00 233.8±18.12ns ? ?/sec iter_fragmented/foreach_wide 1.00 3.9±0.13µs ? ?/sec 1.02 4.0±0.22µs ? ?/sec iter_fragmented/wide 1.18 4.6±0.15µs ? ?/sec 1.00 3.9±0.10µs ? ?/sec iter_fragmented_sparse/base 1.02 8.1±0.15ns ? ?/sec 1.00 7.9±0.56ns ? ?/sec iter_fragmented_sparse/foreach 1.00 7.8±0.22ns ? ?/sec 1.01 7.9±0.62ns ? ?/sec iter_fragmented_sparse/foreach_wide 1.00 37.2±1.17ns ? ?/sec 1.10 40.9±0.95ns ? ?/sec iter_fragmented_sparse/wide 1.09 48.4±2.13ns ? ?/sec 1.00 44.5±18.34ns ? ?/sec iter_simple/base 1.02 8.4±0.10µs ? ?/sec 1.00 8.2±0.14µs ? ?/sec iter_simple/foreach 1.01 8.3±0.07µs ? ?/sec 1.00 8.2±0.09µs ? ?/sec iter_simple/foreach_sparse_set 1.00 25.3±0.32µs ? ?/sec 1.02 25.7±0.42µs ? ?/sec iter_simple/foreach_wide 1.03 41.1±0.94µs ? ?/sec 1.00 39.9±0.41µs ? ?/sec iter_simple/foreach_wide_sparse_set 1.05 123.6±2.05µs ? ?/sec 1.00 118.1±2.78µs ? ?/sec iter_simple/sparse_set 1.14 30.5±1.40µs ? ?/sec 1.00 26.9±0.64µs ? ?/sec iter_simple/system 1.01 8.4±0.25µs ? ?/sec 1.00 8.4±0.11µs ? ?/sec iter_simple/wide 1.18 48.2±0.62µs ? ?/sec 1.00 40.7±0.38µs ? ?/sec iter_simple/wide_sparse_set 1.12 140.8±21.56µs ? ?/sec 1.00 126.0±2.30µs ? ?/sec query_get/50000_entities_sparse 1.17 378.6±7.60µs ? ?/sec 1.00 324.1±23.17µs ? ?/sec query_get/50000_entities_table 1.08 330.9±10.90µs ? ?/sec 1.00 306.8±4.98µs ? ?/sec query_get_component/50000_entities_sparse 1.00 976.7±19.55µs ? ?/sec 1.00 979.8±35.87µs ? ?/sec query_get_component/50000_entities_table 1.00 1029.0±15.11µs ? ?/sec 1.05 1080.0±59.18µs ? ?/sec query_get_component_simple/system 1.13 839.7±14.18µs ? ?/sec 1.00 742.8±10.72µs ? ?/sec query_get_component_simple/unchecked 1.01 909.0±15.17µs ? ?/sec 1.00 898.0±13.56µs ? ?/sec query_get_many_10/50000_calls_sparse 1.04 5.5±0.54ms ? ?/sec 1.00 5.3±0.67ms ? ?/sec query_get_many_10/50000_calls_table 1.01 4.9±0.49ms ? ?/sec 1.00 4.8±0.45ms ? ?/sec query_get_many_2/50000_calls_sparse 1.28 848.4±210.89µs ? ?/sec 1.00 664.8±47.69µs ? ?/sec query_get_many_2/50000_calls_table 1.05 779.0±73.85µs ? ?/sec 1.00 739.2±83.02µs ? ?/sec query_get_many_5/50000_calls_sparse 1.05 2.4±0.37ms ? ?/sec 1.00 2.3±0.33ms ? ?/sec query_get_many_5/50000_calls_table 1.00 1939.9±75.22µs ? ?/sec 1.04 2.0±0.19ms ? ?/sec run_criteria/yes_using_query/001_systems 1.00 3.7±0.38µs ? ?/sec 1.30 4.9±0.14µs ? ?/sec run_criteria/yes_using_query/006_systems 1.00 8.9±0.40µs ? ?/sec 1.17 10.3±0.57µs ? ?/sec run_criteria/yes_using_query/011_systems 1.00 13.9±0.49µs ? ?/sec 1.08 15.0±0.89µs ? ?/sec run_criteria/yes_using_query/016_systems 1.00 18.8±0.74µs ? ?/sec 1.00 18.8±1.43µs ? ?/sec run_criteria/yes_using_query/021_systems 1.07 24.1±0.87µs ? ?/sec 1.00 22.6±1.58µs ? ?/sec run_criteria/yes_using_query/026_systems 1.04 27.9±0.62µs ? ?/sec 1.00 26.8±1.71µs ? ?/sec run_criteria/yes_using_query/031_systems 1.09 33.3±1.03µs ? ?/sec 1.00 30.5±2.18µs ? ?/sec run_criteria/yes_using_query/036_systems 1.14 38.7±0.80µs ? ?/sec 1.00 33.9±1.75µs ? ?/sec run_criteria/yes_using_query/041_systems 1.18 43.7±1.07µs ? ?/sec 1.00 37.0±2.39µs ? ?/sec run_criteria/yes_using_query/046_systems 1.14 47.6±1.16µs ? ?/sec 1.00 41.9±2.09µs ? ?/sec run_criteria/yes_using_query/051_systems 1.17 52.9±2.04µs ? ?/sec 1.00 45.3±1.75µs ? ?/sec run_criteria/yes_using_query/056_systems 1.25 59.2±2.38µs ? ?/sec 1.00 47.2±2.01µs ? ?/sec run_criteria/yes_using_query/061_systems 1.28 66.1±15.84µs ? ?/sec 1.00 51.5±2.47µs ? ?/sec run_criteria/yes_using_query/066_systems 1.28 70.2±2.57µs ? ?/sec 1.00 54.7±2.58µs ? ?/sec run_criteria/yes_using_query/071_systems 1.30 75.5±2.27µs ? ?/sec 1.00 58.2±3.31µs ? ?/sec run_criteria/yes_using_query/076_systems 1.26 81.5±2.66µs ? ?/sec 1.00 64.5±3.13µs ? ?/sec run_criteria/yes_using_query/081_systems 1.29 89.7±2.58µs ? ?/sec 1.00 69.3±3.47µs ? ?/sec run_criteria/yes_using_query/086_systems 1.33 95.6±3.39µs ? ?/sec 1.00 71.8±3.48µs ? ?/sec run_criteria/yes_using_query/091_systems 1.25 102.0±3.67µs ? ?/sec 1.00 81.4±4.82µs ? ?/sec run_criteria/yes_using_query/096_systems 1.33 111.7±3.29µs ? ?/sec 1.00 83.8±4.15µs ? ?/sec run_criteria/yes_using_query/101_systems 1.29 113.2±12.04µs ? ?/sec 1.00 87.7±5.15µs ? ?/sec world_query_for_each/50000_entities_sparse 1.00 47.4±0.51µs ? ?/sec 1.00 47.3±0.33µs ? ?/sec world_query_for_each/50000_entities_table 1.00 27.2±0.50µs ? ?/sec 1.00 27.2±0.17µs ? ?/sec world_query_get/50000_entities_sparse_wide 1.09 210.5±1.78µs ? ?/sec 1.00 192.5±2.61µs ? ?/sec world_query_get/50000_entities_table 1.00 127.7±2.09µs ? ?/sec 1.07 136.2±5.95µs ? ?/sec world_query_get/50000_entities_table_wide 1.00 209.8±2.37µs ? ?/sec 1.15 240.6±2.04µs ? ?/sec world_query_iter/50000_entities_sparse 1.00 54.2±0.36µs ? ?/sec 1.01 54.7±0.61µs ? ?/sec world_query_iter/50000_entities_table 1.00 27.2±0.31µs ? ?/sec 1.00 27.3±0.64µs ? ?/sec ``` </details> NOTE: This PR includes a change to enable LTO on our benchmarks to get a "fully optimized" baseline for our benchmarks. Both the main and the current PR's results were with LTO enabled.
2022-11-04 06:04:55 +00:00
.debug_checked_unwrap()
}),
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
}
}
const IS_DENSE: bool = {
match T::Storage::STORAGE_TYPE {
StorageType::Table => true,
StorageType::SparseSet => false,
}
};
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
#[inline]
unsafe fn set_archetype<'w>(
fetch: &mut ReadFetch<'w, T>,
component_id: &ComponentId,
_archetype: &'w Archetype,
table: &'w Table,
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
) {
if Self::IS_DENSE {
Self::set_table(fetch, component_id, table);
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
}
}
#[inline]
unsafe fn set_table<'w>(
fetch: &mut ReadFetch<'w, T>,
&component_id: &ComponentId,
table: &'w Table,
) {
fetch.table_components = Some(
table
.get_column(component_id)
Remove unnecesary branches/panics from Query accesses (#6461) # Objective Supercedes #6452. Upon inspection of the [generated assembly](https://gist.github.com/james7132/c2740c6941b80d7912f1e8888e223cbb#file-original-s) of a [simple Bevy binary](https://gist.github.com/james7132/c2740c6941b80d7912f1e8888e223cbb#file-source-rs) compiled with `cargo rustc --release -- --emit asm`, it's apparent that there are multiple unnecessary branches in the generated assembly: ```assembly .LBB5_5: cmpq %r10, %r11 je .LBB5_15 movq (%r11), %rcx movq 328(%r15), %rdx cmpq %rdx, %rcx jae .LBB5_14 movq 312(%r15), %rdi leaq (%rcx,%rcx,2), %rcx shlq $5, %rcx movq 336(%r12), %rdx movq 64(%rdi,%rcx), %rax cmpq %rdx, %rax jbe .LBB5_4 leaq (%rdi,%rcx), %rsi movq 48(%rsi), %rbp shlq $4, %rdx cmpq $0, (%rbp,%rdx) je .LBB5_4 movq 344(%r12), %rbx cmpq %rbx, %rax jbe .LBB5_4 shlq $4, %rbx cmpq $0, (%rbp,%rbx) je .LBB5_4 addq $8, %r11 movq 88(%rdi,%rcx), %rcx testq %rcx, %rcx je .LBB5_5 movq (%rsi), %rax movq 8(%rbp,%rdx), %rdx leaq (%rdx,%rdx,4), %rdi shlq $4, %rdi movq 32(%rax,%rdi), %rdx movq 56(%rax,%rdi), %r8 movq 8(%rbp,%rbx), %rbp leaq (%rbp,%rbp,4), %rbp shlq $4, %rbp movq 32(%rax,%rbp), %r9 xorl %ebp, %ebp jmp .LBB5_13 .p2align 4, 0x90 ``` Almost every one of the instructions starting with `j` is a potential branch, which can significantly slow down accesses. Of these, two labels are both common and never used: ```asm .LBB5_14: leaq __unnamed_2(%rip), %r8 callq _ZN4core9panicking18panic_bounds_check17h70367088e72af65aE ud2 .LBB5_4: callq _ZN8bevy_ecs5query25debug_checked_unreachable17h0855ff520ceaea77E ud2 .seh_endproc ``` These correpsond to subprocedure calls to panicking due to out of bounds from indexing `Tables` and `debug_checked_unreadable`. Both of which should be inlined and optimized out, but are not. ## Solution Make `debug_checked_unreachable` a macro to forcibly inline either `unreachable!()` in debug builds, and `std::hint::unreachable_unchecked()` in release mode. Replace the `Tables` and `Archetype` index access with `get(id).unwrap_or_else(|| debug_checked_unreachable!())` to assume that the table or archetype provided exists. This has no external breaking change of any kind. The equivalent section of code with these changes removes most of the conditional jump instructions: ```asm .LBB5_5: movss (%rbx,%rbp,4), %xmm0 movl %r14d, 4(%r8,%rbp,8) addss (%rdi,%rbp,4), %xmm0 movss %xmm0, (%rdi,%rbp,4) incq %rbp .LBB5_1: cmpq %rdx, %rbp jne .LBB5_5 .p2align 4, 0x90 .LBB5_2: cmpq %rcx, %rax je .LBB5_6 movq (%rax), %rdx addq $8, %rax movq 312(%rsi), %rbp leaq (%rdx,%rdx,2), %rbx shlq $5, %rbx movq 88(%rbp,%rbx), %rdx testq %rdx, %rdx je .LBB5_2 leaq (%rbx,%rbp), %r8 movq 336(%r15), %rdi movq 344(%r15), %r9 movq 48(%rbp,%rbx), %r10 shlq $4, %rdi movq (%r8), %rbx movq 8(%r10,%rdi), %rdi leaq (%rdi,%rdi,4), %rbp shlq $4, %rbp movq 32(%rbx,%rbp), %rdi movq 56(%rbx,%rbp), %r8 shlq $4, %r9 movq 8(%r10,%r9), %rbp leaq (%rbp,%rbp,4), %rbp shlq $4, %rbp movq 32(%rbx,%rbp), %rbx xorl %ebp, %ebp jmp .LBB5_5 .LBB5_6: addq $40, %rsp popq %rbx popq %rbp popq %rdi popq %rsi popq %r14 popq %r15 retq .seh_endproc ``` ## Performance Microbenchmarks results: <details> ``` group main no-panic-query ----- ---- -------------- busy_systems/01x_entities_03_systems 1.20 42.4±2.66µs ? ?/sec 1.00 35.3±1.68µs ? ?/sec busy_systems/01x_entities_06_systems 1.32 83.8±3.50µs ? ?/sec 1.00 63.6±1.72µs ? ?/sec busy_systems/01x_entities_09_systems 1.15 113.3±8.90µs ? ?/sec 1.00 98.2±6.15µs ? ?/sec busy_systems/01x_entities_12_systems 1.27 160.8±32.44µs ? ?/sec 1.00 126.6±4.70µs ? ?/sec busy_systems/01x_entities_15_systems 1.12 179.6±3.71µs ? ?/sec 1.00 160.3±11.03µs ? ?/sec busy_systems/02x_entities_03_systems 1.18 76.8±3.14µs ? ?/sec 1.00 65.2±3.17µs ? ?/sec busy_systems/02x_entities_06_systems 1.16 144.6±6.10µs ? ?/sec 1.00 124.5±5.14µs ? ?/sec busy_systems/02x_entities_09_systems 1.19 215.3±9.18µs ? ?/sec 1.00 181.5±5.67µs ? ?/sec busy_systems/02x_entities_12_systems 1.20 266.7±8.33µs ? ?/sec 1.00 222.0±9.53µs ? ?/sec busy_systems/02x_entities_15_systems 1.23 338.8±10.53µs ? ?/sec 1.00 276.3±6.94µs ? ?/sec busy_systems/03x_entities_03_systems 1.43 113.5±5.06µs ? ?/sec 1.00 79.6±1.49µs ? ?/sec busy_systems/03x_entities_06_systems 1.38 217.3±12.67µs ? ?/sec 1.00 157.5±3.07µs ? ?/sec busy_systems/03x_entities_09_systems 1.23 308.8±24.75µs ? ?/sec 1.00 251.6±8.93µs ? ?/sec busy_systems/03x_entities_12_systems 1.05 347.7±12.43µs ? ?/sec 1.00 330.6±11.43µs ? ?/sec busy_systems/03x_entities_15_systems 1.13 455.5±13.88µs ? ?/sec 1.00 401.7±17.29µs ? ?/sec busy_systems/04x_entities_03_systems 1.24 144.7±5.89µs ? ?/sec 1.00 116.9±6.29µs ? ?/sec busy_systems/04x_entities_06_systems 1.24 282.8±21.40µs ? ?/sec 1.00 228.6±21.31µs ? ?/sec busy_systems/04x_entities_09_systems 1.35 431.8±14.10µs ? ?/sec 1.00 319.6±9.83µs ? ?/sec busy_systems/04x_entities_12_systems 1.16 493.8±22.87µs ? ?/sec 1.00 424.9±15.24µs ? ?/sec busy_systems/04x_entities_15_systems 1.10 587.5±23.25µs ? ?/sec 1.00 531.7±16.32µs ? ?/sec busy_systems/05x_entities_03_systems 1.14 148.2±9.61µs ? ?/sec 1.00 129.5±4.32µs ? ?/sec busy_systems/05x_entities_06_systems 1.31 359.7±17.46µs ? ?/sec 1.00 273.6±10.55µs ? ?/sec busy_systems/05x_entities_09_systems 1.22 473.5±23.11µs ? ?/sec 1.00 389.3±13.62µs ? ?/sec busy_systems/05x_entities_12_systems 1.05 562.9±20.76µs ? ?/sec 1.00 536.5±24.35µs ? ?/sec busy_systems/05x_entities_15_systems 1.23 818.5±28.70µs ? ?/sec 1.00 666.6±45.87µs ? ?/sec contrived/01x_entities_03_systems 1.27 27.5±0.49µs ? ?/sec 1.00 21.6±1.71µs ? ?/sec contrived/01x_entities_06_systems 1.22 49.9±1.18µs ? ?/sec 1.00 40.7±2.62µs ? ?/sec contrived/01x_entities_09_systems 1.30 72.3±2.39µs ? ?/sec 1.00 55.4±2.60µs ? ?/sec contrived/01x_entities_12_systems 1.28 94.3±9.44µs ? ?/sec 1.00 73.7±3.62µs ? ?/sec contrived/01x_entities_15_systems 1.25 118.0±2.43µs ? ?/sec 1.00 94.1±3.99µs ? ?/sec contrived/02x_entities_03_systems 1.23 41.6±1.71µs ? ?/sec 1.00 33.7±2.30µs ? ?/sec contrived/02x_entities_06_systems 1.19 78.6±2.63µs ? ?/sec 1.00 65.9±2.35µs ? ?/sec contrived/02x_entities_09_systems 1.28 113.6±3.60µs ? ?/sec 1.00 88.6±3.60µs ? ?/sec contrived/02x_entities_12_systems 1.20 146.4±5.75µs ? ?/sec 1.00 121.7±3.35µs ? ?/sec contrived/02x_entities_15_systems 1.23 178.5±4.86µs ? ?/sec 1.00 145.7±4.00µs ? ?/sec contrived/03x_entities_03_systems 1.42 58.3±2.77µs ? ?/sec 1.00 41.1±1.54µs ? ?/sec contrived/03x_entities_06_systems 1.32 108.5±7.30µs ? ?/sec 1.00 82.4±4.86µs ? ?/sec contrived/03x_entities_09_systems 1.23 153.7±4.61µs ? ?/sec 1.00 125.0±4.76µs ? ?/sec contrived/03x_entities_12_systems 1.18 197.5±5.12µs ? ?/sec 1.00 166.8±8.14µs ? ?/sec contrived/03x_entities_15_systems 1.23 238.8±6.38µs ? ?/sec 1.00 194.6±4.55µs ? ?/sec contrived/04x_entities_03_systems 1.34 66.4±3.42µs ? ?/sec 1.00 49.5±1.98µs ? ?/sec contrived/04x_entities_06_systems 1.27 134.3±4.86µs ? ?/sec 1.00 105.8±3.58µs ? ?/sec contrived/04x_entities_09_systems 1.26 193.2±3.83µs ? ?/sec 1.00 153.0±5.60µs ? ?/sec contrived/04x_entities_12_systems 1.16 237.1±5.78µs ? ?/sec 1.00 204.9±18.77µs ? ?/sec contrived/04x_entities_15_systems 1.17 289.2±4.76µs ? ?/sec 1.00 246.3±8.57µs ? ?/sec contrived/05x_entities_03_systems 1.26 80.4±2.90µs ? ?/sec 1.00 63.7±3.07µs ? ?/sec contrived/05x_entities_06_systems 1.27 161.6±13.47µs ? ?/sec 1.00 127.2±5.59µs ? ?/sec contrived/05x_entities_09_systems 1.22 228.0±7.76µs ? ?/sec 1.00 186.2±7.68µs ? ?/sec contrived/05x_entities_12_systems 1.20 289.5±6.21µs ? ?/sec 1.00 241.8±7.52µs ? ?/sec contrived/05x_entities_15_systems 1.18 357.3±11.24µs ? ?/sec 1.00 302.7±7.21µs ? ?/sec heavy_compute/base 1.01 302.4±3.52µs ? ?/sec 1.00 300.2±3.40µs ? ?/sec iter_fragmented/base 1.00 348.1±7.51ns ? ?/sec 1.01 351.9±8.32ns ? ?/sec iter_fragmented/foreach 1.03 239.8±23.78ns ? ?/sec 1.00 233.8±18.12ns ? ?/sec iter_fragmented/foreach_wide 1.00 3.9±0.13µs ? ?/sec 1.02 4.0±0.22µs ? ?/sec iter_fragmented/wide 1.18 4.6±0.15µs ? ?/sec 1.00 3.9±0.10µs ? ?/sec iter_fragmented_sparse/base 1.02 8.1±0.15ns ? ?/sec 1.00 7.9±0.56ns ? ?/sec iter_fragmented_sparse/foreach 1.00 7.8±0.22ns ? ?/sec 1.01 7.9±0.62ns ? ?/sec iter_fragmented_sparse/foreach_wide 1.00 37.2±1.17ns ? ?/sec 1.10 40.9±0.95ns ? ?/sec iter_fragmented_sparse/wide 1.09 48.4±2.13ns ? ?/sec 1.00 44.5±18.34ns ? ?/sec iter_simple/base 1.02 8.4±0.10µs ? ?/sec 1.00 8.2±0.14µs ? ?/sec iter_simple/foreach 1.01 8.3±0.07µs ? ?/sec 1.00 8.2±0.09µs ? ?/sec iter_simple/foreach_sparse_set 1.00 25.3±0.32µs ? ?/sec 1.02 25.7±0.42µs ? ?/sec iter_simple/foreach_wide 1.03 41.1±0.94µs ? ?/sec 1.00 39.9±0.41µs ? ?/sec iter_simple/foreach_wide_sparse_set 1.05 123.6±2.05µs ? ?/sec 1.00 118.1±2.78µs ? ?/sec iter_simple/sparse_set 1.14 30.5±1.40µs ? ?/sec 1.00 26.9±0.64µs ? ?/sec iter_simple/system 1.01 8.4±0.25µs ? ?/sec 1.00 8.4±0.11µs ? ?/sec iter_simple/wide 1.18 48.2±0.62µs ? ?/sec 1.00 40.7±0.38µs ? ?/sec iter_simple/wide_sparse_set 1.12 140.8±21.56µs ? ?/sec 1.00 126.0±2.30µs ? ?/sec query_get/50000_entities_sparse 1.17 378.6±7.60µs ? ?/sec 1.00 324.1±23.17µs ? ?/sec query_get/50000_entities_table 1.08 330.9±10.90µs ? ?/sec 1.00 306.8±4.98µs ? ?/sec query_get_component/50000_entities_sparse 1.00 976.7±19.55µs ? ?/sec 1.00 979.8±35.87µs ? ?/sec query_get_component/50000_entities_table 1.00 1029.0±15.11µs ? ?/sec 1.05 1080.0±59.18µs ? ?/sec query_get_component_simple/system 1.13 839.7±14.18µs ? ?/sec 1.00 742.8±10.72µs ? ?/sec query_get_component_simple/unchecked 1.01 909.0±15.17µs ? ?/sec 1.00 898.0±13.56µs ? ?/sec query_get_many_10/50000_calls_sparse 1.04 5.5±0.54ms ? ?/sec 1.00 5.3±0.67ms ? ?/sec query_get_many_10/50000_calls_table 1.01 4.9±0.49ms ? ?/sec 1.00 4.8±0.45ms ? ?/sec query_get_many_2/50000_calls_sparse 1.28 848.4±210.89µs ? ?/sec 1.00 664.8±47.69µs ? ?/sec query_get_many_2/50000_calls_table 1.05 779.0±73.85µs ? ?/sec 1.00 739.2±83.02µs ? ?/sec query_get_many_5/50000_calls_sparse 1.05 2.4±0.37ms ? ?/sec 1.00 2.3±0.33ms ? ?/sec query_get_many_5/50000_calls_table 1.00 1939.9±75.22µs ? ?/sec 1.04 2.0±0.19ms ? ?/sec run_criteria/yes_using_query/001_systems 1.00 3.7±0.38µs ? ?/sec 1.30 4.9±0.14µs ? ?/sec run_criteria/yes_using_query/006_systems 1.00 8.9±0.40µs ? ?/sec 1.17 10.3±0.57µs ? ?/sec run_criteria/yes_using_query/011_systems 1.00 13.9±0.49µs ? ?/sec 1.08 15.0±0.89µs ? ?/sec run_criteria/yes_using_query/016_systems 1.00 18.8±0.74µs ? ?/sec 1.00 18.8±1.43µs ? ?/sec run_criteria/yes_using_query/021_systems 1.07 24.1±0.87µs ? ?/sec 1.00 22.6±1.58µs ? ?/sec run_criteria/yes_using_query/026_systems 1.04 27.9±0.62µs ? ?/sec 1.00 26.8±1.71µs ? ?/sec run_criteria/yes_using_query/031_systems 1.09 33.3±1.03µs ? ?/sec 1.00 30.5±2.18µs ? ?/sec run_criteria/yes_using_query/036_systems 1.14 38.7±0.80µs ? ?/sec 1.00 33.9±1.75µs ? ?/sec run_criteria/yes_using_query/041_systems 1.18 43.7±1.07µs ? ?/sec 1.00 37.0±2.39µs ? ?/sec run_criteria/yes_using_query/046_systems 1.14 47.6±1.16µs ? ?/sec 1.00 41.9±2.09µs ? ?/sec run_criteria/yes_using_query/051_systems 1.17 52.9±2.04µs ? ?/sec 1.00 45.3±1.75µs ? ?/sec run_criteria/yes_using_query/056_systems 1.25 59.2±2.38µs ? ?/sec 1.00 47.2±2.01µs ? ?/sec run_criteria/yes_using_query/061_systems 1.28 66.1±15.84µs ? ?/sec 1.00 51.5±2.47µs ? ?/sec run_criteria/yes_using_query/066_systems 1.28 70.2±2.57µs ? ?/sec 1.00 54.7±2.58µs ? ?/sec run_criteria/yes_using_query/071_systems 1.30 75.5±2.27µs ? ?/sec 1.00 58.2±3.31µs ? ?/sec run_criteria/yes_using_query/076_systems 1.26 81.5±2.66µs ? ?/sec 1.00 64.5±3.13µs ? ?/sec run_criteria/yes_using_query/081_systems 1.29 89.7±2.58µs ? ?/sec 1.00 69.3±3.47µs ? ?/sec run_criteria/yes_using_query/086_systems 1.33 95.6±3.39µs ? ?/sec 1.00 71.8±3.48µs ? ?/sec run_criteria/yes_using_query/091_systems 1.25 102.0±3.67µs ? ?/sec 1.00 81.4±4.82µs ? ?/sec run_criteria/yes_using_query/096_systems 1.33 111.7±3.29µs ? ?/sec 1.00 83.8±4.15µs ? ?/sec run_criteria/yes_using_query/101_systems 1.29 113.2±12.04µs ? ?/sec 1.00 87.7±5.15µs ? ?/sec world_query_for_each/50000_entities_sparse 1.00 47.4±0.51µs ? ?/sec 1.00 47.3±0.33µs ? ?/sec world_query_for_each/50000_entities_table 1.00 27.2±0.50µs ? ?/sec 1.00 27.2±0.17µs ? ?/sec world_query_get/50000_entities_sparse_wide 1.09 210.5±1.78µs ? ?/sec 1.00 192.5±2.61µs ? ?/sec world_query_get/50000_entities_table 1.00 127.7±2.09µs ? ?/sec 1.07 136.2±5.95µs ? ?/sec world_query_get/50000_entities_table_wide 1.00 209.8±2.37µs ? ?/sec 1.15 240.6±2.04µs ? ?/sec world_query_iter/50000_entities_sparse 1.00 54.2±0.36µs ? ?/sec 1.01 54.7±0.61µs ? ?/sec world_query_iter/50000_entities_table 1.00 27.2±0.31µs ? ?/sec 1.00 27.3±0.64µs ? ?/sec ``` </details> NOTE: This PR includes a change to enable LTO on our benchmarks to get a "fully optimized" baseline for our benchmarks. Both the main and the current PR's results were with LTO enabled.
2022-11-04 06:04:55 +00:00
.debug_checked_unwrap()
.get_data_slice()
.into(),
);
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
}
#[inline(always)]
unsafe fn fetch<'w>(
fetch: &mut Self::Fetch<'w>,
entity: Entity,
table_row: TableRow,
) -> Self::Item<'w> {
match T::Storage::STORAGE_TYPE {
StorageType::Table => fetch
.table_components
Remove unnecesary branches/panics from Query accesses (#6461) # Objective Supercedes #6452. Upon inspection of the [generated assembly](https://gist.github.com/james7132/c2740c6941b80d7912f1e8888e223cbb#file-original-s) of a [simple Bevy binary](https://gist.github.com/james7132/c2740c6941b80d7912f1e8888e223cbb#file-source-rs) compiled with `cargo rustc --release -- --emit asm`, it's apparent that there are multiple unnecessary branches in the generated assembly: ```assembly .LBB5_5: cmpq %r10, %r11 je .LBB5_15 movq (%r11), %rcx movq 328(%r15), %rdx cmpq %rdx, %rcx jae .LBB5_14 movq 312(%r15), %rdi leaq (%rcx,%rcx,2), %rcx shlq $5, %rcx movq 336(%r12), %rdx movq 64(%rdi,%rcx), %rax cmpq %rdx, %rax jbe .LBB5_4 leaq (%rdi,%rcx), %rsi movq 48(%rsi), %rbp shlq $4, %rdx cmpq $0, (%rbp,%rdx) je .LBB5_4 movq 344(%r12), %rbx cmpq %rbx, %rax jbe .LBB5_4 shlq $4, %rbx cmpq $0, (%rbp,%rbx) je .LBB5_4 addq $8, %r11 movq 88(%rdi,%rcx), %rcx testq %rcx, %rcx je .LBB5_5 movq (%rsi), %rax movq 8(%rbp,%rdx), %rdx leaq (%rdx,%rdx,4), %rdi shlq $4, %rdi movq 32(%rax,%rdi), %rdx movq 56(%rax,%rdi), %r8 movq 8(%rbp,%rbx), %rbp leaq (%rbp,%rbp,4), %rbp shlq $4, %rbp movq 32(%rax,%rbp), %r9 xorl %ebp, %ebp jmp .LBB5_13 .p2align 4, 0x90 ``` Almost every one of the instructions starting with `j` is a potential branch, which can significantly slow down accesses. Of these, two labels are both common and never used: ```asm .LBB5_14: leaq __unnamed_2(%rip), %r8 callq _ZN4core9panicking18panic_bounds_check17h70367088e72af65aE ud2 .LBB5_4: callq _ZN8bevy_ecs5query25debug_checked_unreachable17h0855ff520ceaea77E ud2 .seh_endproc ``` These correpsond to subprocedure calls to panicking due to out of bounds from indexing `Tables` and `debug_checked_unreadable`. Both of which should be inlined and optimized out, but are not. ## Solution Make `debug_checked_unreachable` a macro to forcibly inline either `unreachable!()` in debug builds, and `std::hint::unreachable_unchecked()` in release mode. Replace the `Tables` and `Archetype` index access with `get(id).unwrap_or_else(|| debug_checked_unreachable!())` to assume that the table or archetype provided exists. This has no external breaking change of any kind. The equivalent section of code with these changes removes most of the conditional jump instructions: ```asm .LBB5_5: movss (%rbx,%rbp,4), %xmm0 movl %r14d, 4(%r8,%rbp,8) addss (%rdi,%rbp,4), %xmm0 movss %xmm0, (%rdi,%rbp,4) incq %rbp .LBB5_1: cmpq %rdx, %rbp jne .LBB5_5 .p2align 4, 0x90 .LBB5_2: cmpq %rcx, %rax je .LBB5_6 movq (%rax), %rdx addq $8, %rax movq 312(%rsi), %rbp leaq (%rdx,%rdx,2), %rbx shlq $5, %rbx movq 88(%rbp,%rbx), %rdx testq %rdx, %rdx je .LBB5_2 leaq (%rbx,%rbp), %r8 movq 336(%r15), %rdi movq 344(%r15), %r9 movq 48(%rbp,%rbx), %r10 shlq $4, %rdi movq (%r8), %rbx movq 8(%r10,%rdi), %rdi leaq (%rdi,%rdi,4), %rbp shlq $4, %rbp movq 32(%rbx,%rbp), %rdi movq 56(%rbx,%rbp), %r8 shlq $4, %r9 movq 8(%r10,%r9), %rbp leaq (%rbp,%rbp,4), %rbp shlq $4, %rbp movq 32(%rbx,%rbp), %rbx xorl %ebp, %ebp jmp .LBB5_5 .LBB5_6: addq $40, %rsp popq %rbx popq %rbp popq %rdi popq %rsi popq %r14 popq %r15 retq .seh_endproc ``` ## Performance Microbenchmarks results: <details> ``` group main no-panic-query ----- ---- -------------- busy_systems/01x_entities_03_systems 1.20 42.4±2.66µs ? ?/sec 1.00 35.3±1.68µs ? ?/sec busy_systems/01x_entities_06_systems 1.32 83.8±3.50µs ? ?/sec 1.00 63.6±1.72µs ? ?/sec busy_systems/01x_entities_09_systems 1.15 113.3±8.90µs ? ?/sec 1.00 98.2±6.15µs ? ?/sec busy_systems/01x_entities_12_systems 1.27 160.8±32.44µs ? ?/sec 1.00 126.6±4.70µs ? ?/sec busy_systems/01x_entities_15_systems 1.12 179.6±3.71µs ? ?/sec 1.00 160.3±11.03µs ? ?/sec busy_systems/02x_entities_03_systems 1.18 76.8±3.14µs ? ?/sec 1.00 65.2±3.17µs ? ?/sec busy_systems/02x_entities_06_systems 1.16 144.6±6.10µs ? ?/sec 1.00 124.5±5.14µs ? ?/sec busy_systems/02x_entities_09_systems 1.19 215.3±9.18µs ? ?/sec 1.00 181.5±5.67µs ? ?/sec busy_systems/02x_entities_12_systems 1.20 266.7±8.33µs ? ?/sec 1.00 222.0±9.53µs ? ?/sec busy_systems/02x_entities_15_systems 1.23 338.8±10.53µs ? ?/sec 1.00 276.3±6.94µs ? ?/sec busy_systems/03x_entities_03_systems 1.43 113.5±5.06µs ? ?/sec 1.00 79.6±1.49µs ? ?/sec busy_systems/03x_entities_06_systems 1.38 217.3±12.67µs ? ?/sec 1.00 157.5±3.07µs ? ?/sec busy_systems/03x_entities_09_systems 1.23 308.8±24.75µs ? ?/sec 1.00 251.6±8.93µs ? ?/sec busy_systems/03x_entities_12_systems 1.05 347.7±12.43µs ? ?/sec 1.00 330.6±11.43µs ? ?/sec busy_systems/03x_entities_15_systems 1.13 455.5±13.88µs ? ?/sec 1.00 401.7±17.29µs ? ?/sec busy_systems/04x_entities_03_systems 1.24 144.7±5.89µs ? ?/sec 1.00 116.9±6.29µs ? ?/sec busy_systems/04x_entities_06_systems 1.24 282.8±21.40µs ? ?/sec 1.00 228.6±21.31µs ? ?/sec busy_systems/04x_entities_09_systems 1.35 431.8±14.10µs ? ?/sec 1.00 319.6±9.83µs ? ?/sec busy_systems/04x_entities_12_systems 1.16 493.8±22.87µs ? ?/sec 1.00 424.9±15.24µs ? ?/sec busy_systems/04x_entities_15_systems 1.10 587.5±23.25µs ? ?/sec 1.00 531.7±16.32µs ? ?/sec busy_systems/05x_entities_03_systems 1.14 148.2±9.61µs ? ?/sec 1.00 129.5±4.32µs ? ?/sec busy_systems/05x_entities_06_systems 1.31 359.7±17.46µs ? ?/sec 1.00 273.6±10.55µs ? ?/sec busy_systems/05x_entities_09_systems 1.22 473.5±23.11µs ? ?/sec 1.00 389.3±13.62µs ? ?/sec busy_systems/05x_entities_12_systems 1.05 562.9±20.76µs ? ?/sec 1.00 536.5±24.35µs ? ?/sec busy_systems/05x_entities_15_systems 1.23 818.5±28.70µs ? ?/sec 1.00 666.6±45.87µs ? ?/sec contrived/01x_entities_03_systems 1.27 27.5±0.49µs ? ?/sec 1.00 21.6±1.71µs ? ?/sec contrived/01x_entities_06_systems 1.22 49.9±1.18µs ? ?/sec 1.00 40.7±2.62µs ? ?/sec contrived/01x_entities_09_systems 1.30 72.3±2.39µs ? ?/sec 1.00 55.4±2.60µs ? ?/sec contrived/01x_entities_12_systems 1.28 94.3±9.44µs ? ?/sec 1.00 73.7±3.62µs ? ?/sec contrived/01x_entities_15_systems 1.25 118.0±2.43µs ? ?/sec 1.00 94.1±3.99µs ? ?/sec contrived/02x_entities_03_systems 1.23 41.6±1.71µs ? ?/sec 1.00 33.7±2.30µs ? ?/sec contrived/02x_entities_06_systems 1.19 78.6±2.63µs ? ?/sec 1.00 65.9±2.35µs ? ?/sec contrived/02x_entities_09_systems 1.28 113.6±3.60µs ? ?/sec 1.00 88.6±3.60µs ? ?/sec contrived/02x_entities_12_systems 1.20 146.4±5.75µs ? ?/sec 1.00 121.7±3.35µs ? ?/sec contrived/02x_entities_15_systems 1.23 178.5±4.86µs ? ?/sec 1.00 145.7±4.00µs ? ?/sec contrived/03x_entities_03_systems 1.42 58.3±2.77µs ? ?/sec 1.00 41.1±1.54µs ? ?/sec contrived/03x_entities_06_systems 1.32 108.5±7.30µs ? ?/sec 1.00 82.4±4.86µs ? ?/sec contrived/03x_entities_09_systems 1.23 153.7±4.61µs ? ?/sec 1.00 125.0±4.76µs ? ?/sec contrived/03x_entities_12_systems 1.18 197.5±5.12µs ? ?/sec 1.00 166.8±8.14µs ? ?/sec contrived/03x_entities_15_systems 1.23 238.8±6.38µs ? ?/sec 1.00 194.6±4.55µs ? ?/sec contrived/04x_entities_03_systems 1.34 66.4±3.42µs ? ?/sec 1.00 49.5±1.98µs ? ?/sec contrived/04x_entities_06_systems 1.27 134.3±4.86µs ? ?/sec 1.00 105.8±3.58µs ? ?/sec contrived/04x_entities_09_systems 1.26 193.2±3.83µs ? ?/sec 1.00 153.0±5.60µs ? ?/sec contrived/04x_entities_12_systems 1.16 237.1±5.78µs ? ?/sec 1.00 204.9±18.77µs ? ?/sec contrived/04x_entities_15_systems 1.17 289.2±4.76µs ? ?/sec 1.00 246.3±8.57µs ? ?/sec contrived/05x_entities_03_systems 1.26 80.4±2.90µs ? ?/sec 1.00 63.7±3.07µs ? ?/sec contrived/05x_entities_06_systems 1.27 161.6±13.47µs ? ?/sec 1.00 127.2±5.59µs ? ?/sec contrived/05x_entities_09_systems 1.22 228.0±7.76µs ? ?/sec 1.00 186.2±7.68µs ? ?/sec contrived/05x_entities_12_systems 1.20 289.5±6.21µs ? ?/sec 1.00 241.8±7.52µs ? ?/sec contrived/05x_entities_15_systems 1.18 357.3±11.24µs ? ?/sec 1.00 302.7±7.21µs ? ?/sec heavy_compute/base 1.01 302.4±3.52µs ? ?/sec 1.00 300.2±3.40µs ? ?/sec iter_fragmented/base 1.00 348.1±7.51ns ? ?/sec 1.01 351.9±8.32ns ? ?/sec iter_fragmented/foreach 1.03 239.8±23.78ns ? ?/sec 1.00 233.8±18.12ns ? ?/sec iter_fragmented/foreach_wide 1.00 3.9±0.13µs ? ?/sec 1.02 4.0±0.22µs ? ?/sec iter_fragmented/wide 1.18 4.6±0.15µs ? ?/sec 1.00 3.9±0.10µs ? ?/sec iter_fragmented_sparse/base 1.02 8.1±0.15ns ? ?/sec 1.00 7.9±0.56ns ? ?/sec iter_fragmented_sparse/foreach 1.00 7.8±0.22ns ? ?/sec 1.01 7.9±0.62ns ? ?/sec iter_fragmented_sparse/foreach_wide 1.00 37.2±1.17ns ? ?/sec 1.10 40.9±0.95ns ? ?/sec iter_fragmented_sparse/wide 1.09 48.4±2.13ns ? ?/sec 1.00 44.5±18.34ns ? ?/sec iter_simple/base 1.02 8.4±0.10µs ? ?/sec 1.00 8.2±0.14µs ? ?/sec iter_simple/foreach 1.01 8.3±0.07µs ? ?/sec 1.00 8.2±0.09µs ? ?/sec iter_simple/foreach_sparse_set 1.00 25.3±0.32µs ? ?/sec 1.02 25.7±0.42µs ? ?/sec iter_simple/foreach_wide 1.03 41.1±0.94µs ? ?/sec 1.00 39.9±0.41µs ? ?/sec iter_simple/foreach_wide_sparse_set 1.05 123.6±2.05µs ? ?/sec 1.00 118.1±2.78µs ? ?/sec iter_simple/sparse_set 1.14 30.5±1.40µs ? ?/sec 1.00 26.9±0.64µs ? ?/sec iter_simple/system 1.01 8.4±0.25µs ? ?/sec 1.00 8.4±0.11µs ? ?/sec iter_simple/wide 1.18 48.2±0.62µs ? ?/sec 1.00 40.7±0.38µs ? ?/sec iter_simple/wide_sparse_set 1.12 140.8±21.56µs ? ?/sec 1.00 126.0±2.30µs ? ?/sec query_get/50000_entities_sparse 1.17 378.6±7.60µs ? ?/sec 1.00 324.1±23.17µs ? ?/sec query_get/50000_entities_table 1.08 330.9±10.90µs ? ?/sec 1.00 306.8±4.98µs ? ?/sec query_get_component/50000_entities_sparse 1.00 976.7±19.55µs ? ?/sec 1.00 979.8±35.87µs ? ?/sec query_get_component/50000_entities_table 1.00 1029.0±15.11µs ? ?/sec 1.05 1080.0±59.18µs ? ?/sec query_get_component_simple/system 1.13 839.7±14.18µs ? ?/sec 1.00 742.8±10.72µs ? ?/sec query_get_component_simple/unchecked 1.01 909.0±15.17µs ? ?/sec 1.00 898.0±13.56µs ? ?/sec query_get_many_10/50000_calls_sparse 1.04 5.5±0.54ms ? ?/sec 1.00 5.3±0.67ms ? ?/sec query_get_many_10/50000_calls_table 1.01 4.9±0.49ms ? ?/sec 1.00 4.8±0.45ms ? ?/sec query_get_many_2/50000_calls_sparse 1.28 848.4±210.89µs ? ?/sec 1.00 664.8±47.69µs ? ?/sec query_get_many_2/50000_calls_table 1.05 779.0±73.85µs ? ?/sec 1.00 739.2±83.02µs ? ?/sec query_get_many_5/50000_calls_sparse 1.05 2.4±0.37ms ? ?/sec 1.00 2.3±0.33ms ? ?/sec query_get_many_5/50000_calls_table 1.00 1939.9±75.22µs ? ?/sec 1.04 2.0±0.19ms ? ?/sec run_criteria/yes_using_query/001_systems 1.00 3.7±0.38µs ? ?/sec 1.30 4.9±0.14µs ? ?/sec run_criteria/yes_using_query/006_systems 1.00 8.9±0.40µs ? ?/sec 1.17 10.3±0.57µs ? ?/sec run_criteria/yes_using_query/011_systems 1.00 13.9±0.49µs ? ?/sec 1.08 15.0±0.89µs ? ?/sec run_criteria/yes_using_query/016_systems 1.00 18.8±0.74µs ? ?/sec 1.00 18.8±1.43µs ? ?/sec run_criteria/yes_using_query/021_systems 1.07 24.1±0.87µs ? ?/sec 1.00 22.6±1.58µs ? ?/sec run_criteria/yes_using_query/026_systems 1.04 27.9±0.62µs ? ?/sec 1.00 26.8±1.71µs ? ?/sec run_criteria/yes_using_query/031_systems 1.09 33.3±1.03µs ? ?/sec 1.00 30.5±2.18µs ? ?/sec run_criteria/yes_using_query/036_systems 1.14 38.7±0.80µs ? ?/sec 1.00 33.9±1.75µs ? ?/sec run_criteria/yes_using_query/041_systems 1.18 43.7±1.07µs ? ?/sec 1.00 37.0±2.39µs ? ?/sec run_criteria/yes_using_query/046_systems 1.14 47.6±1.16µs ? ?/sec 1.00 41.9±2.09µs ? ?/sec run_criteria/yes_using_query/051_systems 1.17 52.9±2.04µs ? ?/sec 1.00 45.3±1.75µs ? ?/sec run_criteria/yes_using_query/056_systems 1.25 59.2±2.38µs ? ?/sec 1.00 47.2±2.01µs ? ?/sec run_criteria/yes_using_query/061_systems 1.28 66.1±15.84µs ? ?/sec 1.00 51.5±2.47µs ? ?/sec run_criteria/yes_using_query/066_systems 1.28 70.2±2.57µs ? ?/sec 1.00 54.7±2.58µs ? ?/sec run_criteria/yes_using_query/071_systems 1.30 75.5±2.27µs ? ?/sec 1.00 58.2±3.31µs ? ?/sec run_criteria/yes_using_query/076_systems 1.26 81.5±2.66µs ? ?/sec 1.00 64.5±3.13µs ? ?/sec run_criteria/yes_using_query/081_systems 1.29 89.7±2.58µs ? ?/sec 1.00 69.3±3.47µs ? ?/sec run_criteria/yes_using_query/086_systems 1.33 95.6±3.39µs ? ?/sec 1.00 71.8±3.48µs ? ?/sec run_criteria/yes_using_query/091_systems 1.25 102.0±3.67µs ? ?/sec 1.00 81.4±4.82µs ? ?/sec run_criteria/yes_using_query/096_systems 1.33 111.7±3.29µs ? ?/sec 1.00 83.8±4.15µs ? ?/sec run_criteria/yes_using_query/101_systems 1.29 113.2±12.04µs ? ?/sec 1.00 87.7±5.15µs ? ?/sec world_query_for_each/50000_entities_sparse 1.00 47.4±0.51µs ? ?/sec 1.00 47.3±0.33µs ? ?/sec world_query_for_each/50000_entities_table 1.00 27.2±0.50µs ? ?/sec 1.00 27.2±0.17µs ? ?/sec world_query_get/50000_entities_sparse_wide 1.09 210.5±1.78µs ? ?/sec 1.00 192.5±2.61µs ? ?/sec world_query_get/50000_entities_table 1.00 127.7±2.09µs ? ?/sec 1.07 136.2±5.95µs ? ?/sec world_query_get/50000_entities_table_wide 1.00 209.8±2.37µs ? ?/sec 1.15 240.6±2.04µs ? ?/sec world_query_iter/50000_entities_sparse 1.00 54.2±0.36µs ? ?/sec 1.01 54.7±0.61µs ? ?/sec world_query_iter/50000_entities_table 1.00 27.2±0.31µs ? ?/sec 1.00 27.3±0.64µs ? ?/sec ``` </details> NOTE: This PR includes a change to enable LTO on our benchmarks to get a "fully optimized" baseline for our benchmarks. Both the main and the current PR's results were with LTO enabled.
2022-11-04 06:04:55 +00:00
.debug_checked_unwrap()
Reduced `TableRow` `as` Casting (#10811) # Objective - Fixes #10806 ## Solution Replaced `new` and `index` methods for both `TableRow` and `TableId` with `from_*` and `as_*` methods. These remove the need to perform casting at call sites, reducing the total number of casts in the Bevy codebase. Within these methods, an appropriate `debug_assertion` ensures the cast will behave in an expected manner (no wrapping, etc.). I am using a `debug_assertion` instead of an `assert` to reduce any possible runtime overhead, however minimal. This choice is something I am open to changing (or leaving up to another PR) if anyone has any strong arguments for it. --- ## Changelog - `ComponentSparseSet::sparse` stores a `TableRow` instead of a `u32` (private change) - Replaced `TableRow::new` and `TableRow::index` methods with `TableRow::from_*` and `TableRow::as_*`, with `debug_assertions` protecting any internal casting. - Replaced `TableId::new` and `TableId::index` methods with `TableId::from_*` and `TableId::as_*`, with `debug_assertions` protecting any internal casting. - All `TableId` methods are now `const` ## Migration Guide - `TableRow::new` -> `TableRow::from_usize` - `TableRow::index` -> `TableRow::as_usize` - `TableId::new` -> `TableId::from_usize` - `TableId::index` -> `TableId::as_usize` --- ## Notes I have chosen to remove the `index` and `new` methods for the following chain of reasoning: - Across the codebase, `new` was called with a mixture of `u32` and `usize` values. Likewise for `index`. - Choosing `new` to either be `usize` or `u32` would break half of these call-sites, requiring `as` casting at the site. - Adding a second method `new_u32` or `new_usize` avoids the above, bu looks visually inconsistent. - Therefore, they should be replaced with `from_*` and `as_*` methods instead. Worth noting is that by updating `ComponentSparseSet`, there are now zero instances of interacting with the inner value of `TableRow` as a `u32`, it is exclusively used as a `usize` value (due to interactions with methods like `len` and slice indexing). I have left the `as_u32` and `from_u32` methods as the "proper" constructors/getters.
2023-12-05 02:44:33 +00:00
.get(table_row.as_usize())
.deref(),
StorageType::SparseSet => fetch
.sparse_set
Remove unnecesary branches/panics from Query accesses (#6461) # Objective Supercedes #6452. Upon inspection of the [generated assembly](https://gist.github.com/james7132/c2740c6941b80d7912f1e8888e223cbb#file-original-s) of a [simple Bevy binary](https://gist.github.com/james7132/c2740c6941b80d7912f1e8888e223cbb#file-source-rs) compiled with `cargo rustc --release -- --emit asm`, it's apparent that there are multiple unnecessary branches in the generated assembly: ```assembly .LBB5_5: cmpq %r10, %r11 je .LBB5_15 movq (%r11), %rcx movq 328(%r15), %rdx cmpq %rdx, %rcx jae .LBB5_14 movq 312(%r15), %rdi leaq (%rcx,%rcx,2), %rcx shlq $5, %rcx movq 336(%r12), %rdx movq 64(%rdi,%rcx), %rax cmpq %rdx, %rax jbe .LBB5_4 leaq (%rdi,%rcx), %rsi movq 48(%rsi), %rbp shlq $4, %rdx cmpq $0, (%rbp,%rdx) je .LBB5_4 movq 344(%r12), %rbx cmpq %rbx, %rax jbe .LBB5_4 shlq $4, %rbx cmpq $0, (%rbp,%rbx) je .LBB5_4 addq $8, %r11 movq 88(%rdi,%rcx), %rcx testq %rcx, %rcx je .LBB5_5 movq (%rsi), %rax movq 8(%rbp,%rdx), %rdx leaq (%rdx,%rdx,4), %rdi shlq $4, %rdi movq 32(%rax,%rdi), %rdx movq 56(%rax,%rdi), %r8 movq 8(%rbp,%rbx), %rbp leaq (%rbp,%rbp,4), %rbp shlq $4, %rbp movq 32(%rax,%rbp), %r9 xorl %ebp, %ebp jmp .LBB5_13 .p2align 4, 0x90 ``` Almost every one of the instructions starting with `j` is a potential branch, which can significantly slow down accesses. Of these, two labels are both common and never used: ```asm .LBB5_14: leaq __unnamed_2(%rip), %r8 callq _ZN4core9panicking18panic_bounds_check17h70367088e72af65aE ud2 .LBB5_4: callq _ZN8bevy_ecs5query25debug_checked_unreachable17h0855ff520ceaea77E ud2 .seh_endproc ``` These correpsond to subprocedure calls to panicking due to out of bounds from indexing `Tables` and `debug_checked_unreadable`. Both of which should be inlined and optimized out, but are not. ## Solution Make `debug_checked_unreachable` a macro to forcibly inline either `unreachable!()` in debug builds, and `std::hint::unreachable_unchecked()` in release mode. Replace the `Tables` and `Archetype` index access with `get(id).unwrap_or_else(|| debug_checked_unreachable!())` to assume that the table or archetype provided exists. This has no external breaking change of any kind. The equivalent section of code with these changes removes most of the conditional jump instructions: ```asm .LBB5_5: movss (%rbx,%rbp,4), %xmm0 movl %r14d, 4(%r8,%rbp,8) addss (%rdi,%rbp,4), %xmm0 movss %xmm0, (%rdi,%rbp,4) incq %rbp .LBB5_1: cmpq %rdx, %rbp jne .LBB5_5 .p2align 4, 0x90 .LBB5_2: cmpq %rcx, %rax je .LBB5_6 movq (%rax), %rdx addq $8, %rax movq 312(%rsi), %rbp leaq (%rdx,%rdx,2), %rbx shlq $5, %rbx movq 88(%rbp,%rbx), %rdx testq %rdx, %rdx je .LBB5_2 leaq (%rbx,%rbp), %r8 movq 336(%r15), %rdi movq 344(%r15), %r9 movq 48(%rbp,%rbx), %r10 shlq $4, %rdi movq (%r8), %rbx movq 8(%r10,%rdi), %rdi leaq (%rdi,%rdi,4), %rbp shlq $4, %rbp movq 32(%rbx,%rbp), %rdi movq 56(%rbx,%rbp), %r8 shlq $4, %r9 movq 8(%r10,%r9), %rbp leaq (%rbp,%rbp,4), %rbp shlq $4, %rbp movq 32(%rbx,%rbp), %rbx xorl %ebp, %ebp jmp .LBB5_5 .LBB5_6: addq $40, %rsp popq %rbx popq %rbp popq %rdi popq %rsi popq %r14 popq %r15 retq .seh_endproc ``` ## Performance Microbenchmarks results: <details> ``` group main no-panic-query ----- ---- -------------- busy_systems/01x_entities_03_systems 1.20 42.4±2.66µs ? ?/sec 1.00 35.3±1.68µs ? ?/sec busy_systems/01x_entities_06_systems 1.32 83.8±3.50µs ? ?/sec 1.00 63.6±1.72µs ? ?/sec busy_systems/01x_entities_09_systems 1.15 113.3±8.90µs ? ?/sec 1.00 98.2±6.15µs ? ?/sec busy_systems/01x_entities_12_systems 1.27 160.8±32.44µs ? ?/sec 1.00 126.6±4.70µs ? ?/sec busy_systems/01x_entities_15_systems 1.12 179.6±3.71µs ? ?/sec 1.00 160.3±11.03µs ? ?/sec busy_systems/02x_entities_03_systems 1.18 76.8±3.14µs ? ?/sec 1.00 65.2±3.17µs ? ?/sec busy_systems/02x_entities_06_systems 1.16 144.6±6.10µs ? ?/sec 1.00 124.5±5.14µs ? ?/sec busy_systems/02x_entities_09_systems 1.19 215.3±9.18µs ? ?/sec 1.00 181.5±5.67µs ? ?/sec busy_systems/02x_entities_12_systems 1.20 266.7±8.33µs ? ?/sec 1.00 222.0±9.53µs ? ?/sec busy_systems/02x_entities_15_systems 1.23 338.8±10.53µs ? ?/sec 1.00 276.3±6.94µs ? ?/sec busy_systems/03x_entities_03_systems 1.43 113.5±5.06µs ? ?/sec 1.00 79.6±1.49µs ? ?/sec busy_systems/03x_entities_06_systems 1.38 217.3±12.67µs ? ?/sec 1.00 157.5±3.07µs ? ?/sec busy_systems/03x_entities_09_systems 1.23 308.8±24.75µs ? ?/sec 1.00 251.6±8.93µs ? ?/sec busy_systems/03x_entities_12_systems 1.05 347.7±12.43µs ? ?/sec 1.00 330.6±11.43µs ? ?/sec busy_systems/03x_entities_15_systems 1.13 455.5±13.88µs ? ?/sec 1.00 401.7±17.29µs ? ?/sec busy_systems/04x_entities_03_systems 1.24 144.7±5.89µs ? ?/sec 1.00 116.9±6.29µs ? ?/sec busy_systems/04x_entities_06_systems 1.24 282.8±21.40µs ? ?/sec 1.00 228.6±21.31µs ? ?/sec busy_systems/04x_entities_09_systems 1.35 431.8±14.10µs ? ?/sec 1.00 319.6±9.83µs ? ?/sec busy_systems/04x_entities_12_systems 1.16 493.8±22.87µs ? ?/sec 1.00 424.9±15.24µs ? ?/sec busy_systems/04x_entities_15_systems 1.10 587.5±23.25µs ? ?/sec 1.00 531.7±16.32µs ? ?/sec busy_systems/05x_entities_03_systems 1.14 148.2±9.61µs ? ?/sec 1.00 129.5±4.32µs ? ?/sec busy_systems/05x_entities_06_systems 1.31 359.7±17.46µs ? ?/sec 1.00 273.6±10.55µs ? ?/sec busy_systems/05x_entities_09_systems 1.22 473.5±23.11µs ? ?/sec 1.00 389.3±13.62µs ? ?/sec busy_systems/05x_entities_12_systems 1.05 562.9±20.76µs ? ?/sec 1.00 536.5±24.35µs ? ?/sec busy_systems/05x_entities_15_systems 1.23 818.5±28.70µs ? ?/sec 1.00 666.6±45.87µs ? ?/sec contrived/01x_entities_03_systems 1.27 27.5±0.49µs ? ?/sec 1.00 21.6±1.71µs ? ?/sec contrived/01x_entities_06_systems 1.22 49.9±1.18µs ? ?/sec 1.00 40.7±2.62µs ? ?/sec contrived/01x_entities_09_systems 1.30 72.3±2.39µs ? ?/sec 1.00 55.4±2.60µs ? ?/sec contrived/01x_entities_12_systems 1.28 94.3±9.44µs ? ?/sec 1.00 73.7±3.62µs ? ?/sec contrived/01x_entities_15_systems 1.25 118.0±2.43µs ? ?/sec 1.00 94.1±3.99µs ? ?/sec contrived/02x_entities_03_systems 1.23 41.6±1.71µs ? ?/sec 1.00 33.7±2.30µs ? ?/sec contrived/02x_entities_06_systems 1.19 78.6±2.63µs ? ?/sec 1.00 65.9±2.35µs ? ?/sec contrived/02x_entities_09_systems 1.28 113.6±3.60µs ? ?/sec 1.00 88.6±3.60µs ? ?/sec contrived/02x_entities_12_systems 1.20 146.4±5.75µs ? ?/sec 1.00 121.7±3.35µs ? ?/sec contrived/02x_entities_15_systems 1.23 178.5±4.86µs ? ?/sec 1.00 145.7±4.00µs ? ?/sec contrived/03x_entities_03_systems 1.42 58.3±2.77µs ? ?/sec 1.00 41.1±1.54µs ? ?/sec contrived/03x_entities_06_systems 1.32 108.5±7.30µs ? ?/sec 1.00 82.4±4.86µs ? ?/sec contrived/03x_entities_09_systems 1.23 153.7±4.61µs ? ?/sec 1.00 125.0±4.76µs ? ?/sec contrived/03x_entities_12_systems 1.18 197.5±5.12µs ? ?/sec 1.00 166.8±8.14µs ? ?/sec contrived/03x_entities_15_systems 1.23 238.8±6.38µs ? ?/sec 1.00 194.6±4.55µs ? ?/sec contrived/04x_entities_03_systems 1.34 66.4±3.42µs ? ?/sec 1.00 49.5±1.98µs ? ?/sec contrived/04x_entities_06_systems 1.27 134.3±4.86µs ? ?/sec 1.00 105.8±3.58µs ? ?/sec contrived/04x_entities_09_systems 1.26 193.2±3.83µs ? ?/sec 1.00 153.0±5.60µs ? ?/sec contrived/04x_entities_12_systems 1.16 237.1±5.78µs ? ?/sec 1.00 204.9±18.77µs ? ?/sec contrived/04x_entities_15_systems 1.17 289.2±4.76µs ? ?/sec 1.00 246.3±8.57µs ? ?/sec contrived/05x_entities_03_systems 1.26 80.4±2.90µs ? ?/sec 1.00 63.7±3.07µs ? ?/sec contrived/05x_entities_06_systems 1.27 161.6±13.47µs ? ?/sec 1.00 127.2±5.59µs ? ?/sec contrived/05x_entities_09_systems 1.22 228.0±7.76µs ? ?/sec 1.00 186.2±7.68µs ? ?/sec contrived/05x_entities_12_systems 1.20 289.5±6.21µs ? ?/sec 1.00 241.8±7.52µs ? ?/sec contrived/05x_entities_15_systems 1.18 357.3±11.24µs ? ?/sec 1.00 302.7±7.21µs ? ?/sec heavy_compute/base 1.01 302.4±3.52µs ? ?/sec 1.00 300.2±3.40µs ? ?/sec iter_fragmented/base 1.00 348.1±7.51ns ? ?/sec 1.01 351.9±8.32ns ? ?/sec iter_fragmented/foreach 1.03 239.8±23.78ns ? ?/sec 1.00 233.8±18.12ns ? ?/sec iter_fragmented/foreach_wide 1.00 3.9±0.13µs ? ?/sec 1.02 4.0±0.22µs ? ?/sec iter_fragmented/wide 1.18 4.6±0.15µs ? ?/sec 1.00 3.9±0.10µs ? ?/sec iter_fragmented_sparse/base 1.02 8.1±0.15ns ? ?/sec 1.00 7.9±0.56ns ? ?/sec iter_fragmented_sparse/foreach 1.00 7.8±0.22ns ? ?/sec 1.01 7.9±0.62ns ? ?/sec iter_fragmented_sparse/foreach_wide 1.00 37.2±1.17ns ? ?/sec 1.10 40.9±0.95ns ? ?/sec iter_fragmented_sparse/wide 1.09 48.4±2.13ns ? ?/sec 1.00 44.5±18.34ns ? ?/sec iter_simple/base 1.02 8.4±0.10µs ? ?/sec 1.00 8.2±0.14µs ? ?/sec iter_simple/foreach 1.01 8.3±0.07µs ? ?/sec 1.00 8.2±0.09µs ? ?/sec iter_simple/foreach_sparse_set 1.00 25.3±0.32µs ? ?/sec 1.02 25.7±0.42µs ? ?/sec iter_simple/foreach_wide 1.03 41.1±0.94µs ? ?/sec 1.00 39.9±0.41µs ? ?/sec iter_simple/foreach_wide_sparse_set 1.05 123.6±2.05µs ? ?/sec 1.00 118.1±2.78µs ? ?/sec iter_simple/sparse_set 1.14 30.5±1.40µs ? ?/sec 1.00 26.9±0.64µs ? ?/sec iter_simple/system 1.01 8.4±0.25µs ? ?/sec 1.00 8.4±0.11µs ? ?/sec iter_simple/wide 1.18 48.2±0.62µs ? ?/sec 1.00 40.7±0.38µs ? ?/sec iter_simple/wide_sparse_set 1.12 140.8±21.56µs ? ?/sec 1.00 126.0±2.30µs ? ?/sec query_get/50000_entities_sparse 1.17 378.6±7.60µs ? ?/sec 1.00 324.1±23.17µs ? ?/sec query_get/50000_entities_table 1.08 330.9±10.90µs ? ?/sec 1.00 306.8±4.98µs ? ?/sec query_get_component/50000_entities_sparse 1.00 976.7±19.55µs ? ?/sec 1.00 979.8±35.87µs ? ?/sec query_get_component/50000_entities_table 1.00 1029.0±15.11µs ? ?/sec 1.05 1080.0±59.18µs ? ?/sec query_get_component_simple/system 1.13 839.7±14.18µs ? ?/sec 1.00 742.8±10.72µs ? ?/sec query_get_component_simple/unchecked 1.01 909.0±15.17µs ? ?/sec 1.00 898.0±13.56µs ? ?/sec query_get_many_10/50000_calls_sparse 1.04 5.5±0.54ms ? ?/sec 1.00 5.3±0.67ms ? ?/sec query_get_many_10/50000_calls_table 1.01 4.9±0.49ms ? ?/sec 1.00 4.8±0.45ms ? ?/sec query_get_many_2/50000_calls_sparse 1.28 848.4±210.89µs ? ?/sec 1.00 664.8±47.69µs ? ?/sec query_get_many_2/50000_calls_table 1.05 779.0±73.85µs ? ?/sec 1.00 739.2±83.02µs ? ?/sec query_get_many_5/50000_calls_sparse 1.05 2.4±0.37ms ? ?/sec 1.00 2.3±0.33ms ? ?/sec query_get_many_5/50000_calls_table 1.00 1939.9±75.22µs ? ?/sec 1.04 2.0±0.19ms ? ?/sec run_criteria/yes_using_query/001_systems 1.00 3.7±0.38µs ? ?/sec 1.30 4.9±0.14µs ? ?/sec run_criteria/yes_using_query/006_systems 1.00 8.9±0.40µs ? ?/sec 1.17 10.3±0.57µs ? ?/sec run_criteria/yes_using_query/011_systems 1.00 13.9±0.49µs ? ?/sec 1.08 15.0±0.89µs ? ?/sec run_criteria/yes_using_query/016_systems 1.00 18.8±0.74µs ? ?/sec 1.00 18.8±1.43µs ? ?/sec run_criteria/yes_using_query/021_systems 1.07 24.1±0.87µs ? ?/sec 1.00 22.6±1.58µs ? ?/sec run_criteria/yes_using_query/026_systems 1.04 27.9±0.62µs ? ?/sec 1.00 26.8±1.71µs ? ?/sec run_criteria/yes_using_query/031_systems 1.09 33.3±1.03µs ? ?/sec 1.00 30.5±2.18µs ? ?/sec run_criteria/yes_using_query/036_systems 1.14 38.7±0.80µs ? ?/sec 1.00 33.9±1.75µs ? ?/sec run_criteria/yes_using_query/041_systems 1.18 43.7±1.07µs ? ?/sec 1.00 37.0±2.39µs ? ?/sec run_criteria/yes_using_query/046_systems 1.14 47.6±1.16µs ? ?/sec 1.00 41.9±2.09µs ? ?/sec run_criteria/yes_using_query/051_systems 1.17 52.9±2.04µs ? ?/sec 1.00 45.3±1.75µs ? ?/sec run_criteria/yes_using_query/056_systems 1.25 59.2±2.38µs ? ?/sec 1.00 47.2±2.01µs ? ?/sec run_criteria/yes_using_query/061_systems 1.28 66.1±15.84µs ? ?/sec 1.00 51.5±2.47µs ? ?/sec run_criteria/yes_using_query/066_systems 1.28 70.2±2.57µs ? ?/sec 1.00 54.7±2.58µs ? ?/sec run_criteria/yes_using_query/071_systems 1.30 75.5±2.27µs ? ?/sec 1.00 58.2±3.31µs ? ?/sec run_criteria/yes_using_query/076_systems 1.26 81.5±2.66µs ? ?/sec 1.00 64.5±3.13µs ? ?/sec run_criteria/yes_using_query/081_systems 1.29 89.7±2.58µs ? ?/sec 1.00 69.3±3.47µs ? ?/sec run_criteria/yes_using_query/086_systems 1.33 95.6±3.39µs ? ?/sec 1.00 71.8±3.48µs ? ?/sec run_criteria/yes_using_query/091_systems 1.25 102.0±3.67µs ? ?/sec 1.00 81.4±4.82µs ? ?/sec run_criteria/yes_using_query/096_systems 1.33 111.7±3.29µs ? ?/sec 1.00 83.8±4.15µs ? ?/sec run_criteria/yes_using_query/101_systems 1.29 113.2±12.04µs ? ?/sec 1.00 87.7±5.15µs ? ?/sec world_query_for_each/50000_entities_sparse 1.00 47.4±0.51µs ? ?/sec 1.00 47.3±0.33µs ? ?/sec world_query_for_each/50000_entities_table 1.00 27.2±0.50µs ? ?/sec 1.00 27.2±0.17µs ? ?/sec world_query_get/50000_entities_sparse_wide 1.09 210.5±1.78µs ? ?/sec 1.00 192.5±2.61µs ? ?/sec world_query_get/50000_entities_table 1.00 127.7±2.09µs ? ?/sec 1.07 136.2±5.95µs ? ?/sec world_query_get/50000_entities_table_wide 1.00 209.8±2.37µs ? ?/sec 1.15 240.6±2.04µs ? ?/sec world_query_iter/50000_entities_sparse 1.00 54.2±0.36µs ? ?/sec 1.01 54.7±0.61µs ? ?/sec world_query_iter/50000_entities_table 1.00 27.2±0.31µs ? ?/sec 1.00 27.3±0.64µs ? ?/sec ``` </details> NOTE: This PR includes a change to enable LTO on our benchmarks to get a "fully optimized" baseline for our benchmarks. Both the main and the current PR's results were with LTO enabled.
2022-11-04 06:04:55 +00:00
.debug_checked_unwrap()
.get(entity)
Remove unnecesary branches/panics from Query accesses (#6461) # Objective Supercedes #6452. Upon inspection of the [generated assembly](https://gist.github.com/james7132/c2740c6941b80d7912f1e8888e223cbb#file-original-s) of a [simple Bevy binary](https://gist.github.com/james7132/c2740c6941b80d7912f1e8888e223cbb#file-source-rs) compiled with `cargo rustc --release -- --emit asm`, it's apparent that there are multiple unnecessary branches in the generated assembly: ```assembly .LBB5_5: cmpq %r10, %r11 je .LBB5_15 movq (%r11), %rcx movq 328(%r15), %rdx cmpq %rdx, %rcx jae .LBB5_14 movq 312(%r15), %rdi leaq (%rcx,%rcx,2), %rcx shlq $5, %rcx movq 336(%r12), %rdx movq 64(%rdi,%rcx), %rax cmpq %rdx, %rax jbe .LBB5_4 leaq (%rdi,%rcx), %rsi movq 48(%rsi), %rbp shlq $4, %rdx cmpq $0, (%rbp,%rdx) je .LBB5_4 movq 344(%r12), %rbx cmpq %rbx, %rax jbe .LBB5_4 shlq $4, %rbx cmpq $0, (%rbp,%rbx) je .LBB5_4 addq $8, %r11 movq 88(%rdi,%rcx), %rcx testq %rcx, %rcx je .LBB5_5 movq (%rsi), %rax movq 8(%rbp,%rdx), %rdx leaq (%rdx,%rdx,4), %rdi shlq $4, %rdi movq 32(%rax,%rdi), %rdx movq 56(%rax,%rdi), %r8 movq 8(%rbp,%rbx), %rbp leaq (%rbp,%rbp,4), %rbp shlq $4, %rbp movq 32(%rax,%rbp), %r9 xorl %ebp, %ebp jmp .LBB5_13 .p2align 4, 0x90 ``` Almost every one of the instructions starting with `j` is a potential branch, which can significantly slow down accesses. Of these, two labels are both common and never used: ```asm .LBB5_14: leaq __unnamed_2(%rip), %r8 callq _ZN4core9panicking18panic_bounds_check17h70367088e72af65aE ud2 .LBB5_4: callq _ZN8bevy_ecs5query25debug_checked_unreachable17h0855ff520ceaea77E ud2 .seh_endproc ``` These correpsond to subprocedure calls to panicking due to out of bounds from indexing `Tables` and `debug_checked_unreadable`. Both of which should be inlined and optimized out, but are not. ## Solution Make `debug_checked_unreachable` a macro to forcibly inline either `unreachable!()` in debug builds, and `std::hint::unreachable_unchecked()` in release mode. Replace the `Tables` and `Archetype` index access with `get(id).unwrap_or_else(|| debug_checked_unreachable!())` to assume that the table or archetype provided exists. This has no external breaking change of any kind. The equivalent section of code with these changes removes most of the conditional jump instructions: ```asm .LBB5_5: movss (%rbx,%rbp,4), %xmm0 movl %r14d, 4(%r8,%rbp,8) addss (%rdi,%rbp,4), %xmm0 movss %xmm0, (%rdi,%rbp,4) incq %rbp .LBB5_1: cmpq %rdx, %rbp jne .LBB5_5 .p2align 4, 0x90 .LBB5_2: cmpq %rcx, %rax je .LBB5_6 movq (%rax), %rdx addq $8, %rax movq 312(%rsi), %rbp leaq (%rdx,%rdx,2), %rbx shlq $5, %rbx movq 88(%rbp,%rbx), %rdx testq %rdx, %rdx je .LBB5_2 leaq (%rbx,%rbp), %r8 movq 336(%r15), %rdi movq 344(%r15), %r9 movq 48(%rbp,%rbx), %r10 shlq $4, %rdi movq (%r8), %rbx movq 8(%r10,%rdi), %rdi leaq (%rdi,%rdi,4), %rbp shlq $4, %rbp movq 32(%rbx,%rbp), %rdi movq 56(%rbx,%rbp), %r8 shlq $4, %r9 movq 8(%r10,%r9), %rbp leaq (%rbp,%rbp,4), %rbp shlq $4, %rbp movq 32(%rbx,%rbp), %rbx xorl %ebp, %ebp jmp .LBB5_5 .LBB5_6: addq $40, %rsp popq %rbx popq %rbp popq %rdi popq %rsi popq %r14 popq %r15 retq .seh_endproc ``` ## Performance Microbenchmarks results: <details> ``` group main no-panic-query ----- ---- -------------- busy_systems/01x_entities_03_systems 1.20 42.4±2.66µs ? ?/sec 1.00 35.3±1.68µs ? ?/sec busy_systems/01x_entities_06_systems 1.32 83.8±3.50µs ? ?/sec 1.00 63.6±1.72µs ? ?/sec busy_systems/01x_entities_09_systems 1.15 113.3±8.90µs ? ?/sec 1.00 98.2±6.15µs ? ?/sec busy_systems/01x_entities_12_systems 1.27 160.8±32.44µs ? ?/sec 1.00 126.6±4.70µs ? ?/sec busy_systems/01x_entities_15_systems 1.12 179.6±3.71µs ? ?/sec 1.00 160.3±11.03µs ? ?/sec busy_systems/02x_entities_03_systems 1.18 76.8±3.14µs ? ?/sec 1.00 65.2±3.17µs ? ?/sec busy_systems/02x_entities_06_systems 1.16 144.6±6.10µs ? ?/sec 1.00 124.5±5.14µs ? ?/sec busy_systems/02x_entities_09_systems 1.19 215.3±9.18µs ? ?/sec 1.00 181.5±5.67µs ? ?/sec busy_systems/02x_entities_12_systems 1.20 266.7±8.33µs ? ?/sec 1.00 222.0±9.53µs ? ?/sec busy_systems/02x_entities_15_systems 1.23 338.8±10.53µs ? ?/sec 1.00 276.3±6.94µs ? ?/sec busy_systems/03x_entities_03_systems 1.43 113.5±5.06µs ? ?/sec 1.00 79.6±1.49µs ? ?/sec busy_systems/03x_entities_06_systems 1.38 217.3±12.67µs ? ?/sec 1.00 157.5±3.07µs ? ?/sec busy_systems/03x_entities_09_systems 1.23 308.8±24.75µs ? ?/sec 1.00 251.6±8.93µs ? ?/sec busy_systems/03x_entities_12_systems 1.05 347.7±12.43µs ? ?/sec 1.00 330.6±11.43µs ? ?/sec busy_systems/03x_entities_15_systems 1.13 455.5±13.88µs ? ?/sec 1.00 401.7±17.29µs ? ?/sec busy_systems/04x_entities_03_systems 1.24 144.7±5.89µs ? ?/sec 1.00 116.9±6.29µs ? ?/sec busy_systems/04x_entities_06_systems 1.24 282.8±21.40µs ? ?/sec 1.00 228.6±21.31µs ? ?/sec busy_systems/04x_entities_09_systems 1.35 431.8±14.10µs ? ?/sec 1.00 319.6±9.83µs ? ?/sec busy_systems/04x_entities_12_systems 1.16 493.8±22.87µs ? ?/sec 1.00 424.9±15.24µs ? ?/sec busy_systems/04x_entities_15_systems 1.10 587.5±23.25µs ? ?/sec 1.00 531.7±16.32µs ? ?/sec busy_systems/05x_entities_03_systems 1.14 148.2±9.61µs ? ?/sec 1.00 129.5±4.32µs ? ?/sec busy_systems/05x_entities_06_systems 1.31 359.7±17.46µs ? ?/sec 1.00 273.6±10.55µs ? ?/sec busy_systems/05x_entities_09_systems 1.22 473.5±23.11µs ? ?/sec 1.00 389.3±13.62µs ? ?/sec busy_systems/05x_entities_12_systems 1.05 562.9±20.76µs ? ?/sec 1.00 536.5±24.35µs ? ?/sec busy_systems/05x_entities_15_systems 1.23 818.5±28.70µs ? ?/sec 1.00 666.6±45.87µs ? ?/sec contrived/01x_entities_03_systems 1.27 27.5±0.49µs ? ?/sec 1.00 21.6±1.71µs ? ?/sec contrived/01x_entities_06_systems 1.22 49.9±1.18µs ? ?/sec 1.00 40.7±2.62µs ? ?/sec contrived/01x_entities_09_systems 1.30 72.3±2.39µs ? ?/sec 1.00 55.4±2.60µs ? ?/sec contrived/01x_entities_12_systems 1.28 94.3±9.44µs ? ?/sec 1.00 73.7±3.62µs ? ?/sec contrived/01x_entities_15_systems 1.25 118.0±2.43µs ? ?/sec 1.00 94.1±3.99µs ? ?/sec contrived/02x_entities_03_systems 1.23 41.6±1.71µs ? ?/sec 1.00 33.7±2.30µs ? ?/sec contrived/02x_entities_06_systems 1.19 78.6±2.63µs ? ?/sec 1.00 65.9±2.35µs ? ?/sec contrived/02x_entities_09_systems 1.28 113.6±3.60µs ? ?/sec 1.00 88.6±3.60µs ? ?/sec contrived/02x_entities_12_systems 1.20 146.4±5.75µs ? ?/sec 1.00 121.7±3.35µs ? ?/sec contrived/02x_entities_15_systems 1.23 178.5±4.86µs ? ?/sec 1.00 145.7±4.00µs ? ?/sec contrived/03x_entities_03_systems 1.42 58.3±2.77µs ? ?/sec 1.00 41.1±1.54µs ? ?/sec contrived/03x_entities_06_systems 1.32 108.5±7.30µs ? ?/sec 1.00 82.4±4.86µs ? ?/sec contrived/03x_entities_09_systems 1.23 153.7±4.61µs ? ?/sec 1.00 125.0±4.76µs ? ?/sec contrived/03x_entities_12_systems 1.18 197.5±5.12µs ? ?/sec 1.00 166.8±8.14µs ? ?/sec contrived/03x_entities_15_systems 1.23 238.8±6.38µs ? ?/sec 1.00 194.6±4.55µs ? ?/sec contrived/04x_entities_03_systems 1.34 66.4±3.42µs ? ?/sec 1.00 49.5±1.98µs ? ?/sec contrived/04x_entities_06_systems 1.27 134.3±4.86µs ? ?/sec 1.00 105.8±3.58µs ? ?/sec contrived/04x_entities_09_systems 1.26 193.2±3.83µs ? ?/sec 1.00 153.0±5.60µs ? ?/sec contrived/04x_entities_12_systems 1.16 237.1±5.78µs ? ?/sec 1.00 204.9±18.77µs ? ?/sec contrived/04x_entities_15_systems 1.17 289.2±4.76µs ? ?/sec 1.00 246.3±8.57µs ? ?/sec contrived/05x_entities_03_systems 1.26 80.4±2.90µs ? ?/sec 1.00 63.7±3.07µs ? ?/sec contrived/05x_entities_06_systems 1.27 161.6±13.47µs ? ?/sec 1.00 127.2±5.59µs ? ?/sec contrived/05x_entities_09_systems 1.22 228.0±7.76µs ? ?/sec 1.00 186.2±7.68µs ? ?/sec contrived/05x_entities_12_systems 1.20 289.5±6.21µs ? ?/sec 1.00 241.8±7.52µs ? ?/sec contrived/05x_entities_15_systems 1.18 357.3±11.24µs ? ?/sec 1.00 302.7±7.21µs ? ?/sec heavy_compute/base 1.01 302.4±3.52µs ? ?/sec 1.00 300.2±3.40µs ? ?/sec iter_fragmented/base 1.00 348.1±7.51ns ? ?/sec 1.01 351.9±8.32ns ? ?/sec iter_fragmented/foreach 1.03 239.8±23.78ns ? ?/sec 1.00 233.8±18.12ns ? ?/sec iter_fragmented/foreach_wide 1.00 3.9±0.13µs ? ?/sec 1.02 4.0±0.22µs ? ?/sec iter_fragmented/wide 1.18 4.6±0.15µs ? ?/sec 1.00 3.9±0.10µs ? ?/sec iter_fragmented_sparse/base 1.02 8.1±0.15ns ? ?/sec 1.00 7.9±0.56ns ? ?/sec iter_fragmented_sparse/foreach 1.00 7.8±0.22ns ? ?/sec 1.01 7.9±0.62ns ? ?/sec iter_fragmented_sparse/foreach_wide 1.00 37.2±1.17ns ? ?/sec 1.10 40.9±0.95ns ? ?/sec iter_fragmented_sparse/wide 1.09 48.4±2.13ns ? ?/sec 1.00 44.5±18.34ns ? ?/sec iter_simple/base 1.02 8.4±0.10µs ? ?/sec 1.00 8.2±0.14µs ? ?/sec iter_simple/foreach 1.01 8.3±0.07µs ? ?/sec 1.00 8.2±0.09µs ? ?/sec iter_simple/foreach_sparse_set 1.00 25.3±0.32µs ? ?/sec 1.02 25.7±0.42µs ? ?/sec iter_simple/foreach_wide 1.03 41.1±0.94µs ? ?/sec 1.00 39.9±0.41µs ? ?/sec iter_simple/foreach_wide_sparse_set 1.05 123.6±2.05µs ? ?/sec 1.00 118.1±2.78µs ? ?/sec iter_simple/sparse_set 1.14 30.5±1.40µs ? ?/sec 1.00 26.9±0.64µs ? ?/sec iter_simple/system 1.01 8.4±0.25µs ? ?/sec 1.00 8.4±0.11µs ? ?/sec iter_simple/wide 1.18 48.2±0.62µs ? ?/sec 1.00 40.7±0.38µs ? ?/sec iter_simple/wide_sparse_set 1.12 140.8±21.56µs ? ?/sec 1.00 126.0±2.30µs ? ?/sec query_get/50000_entities_sparse 1.17 378.6±7.60µs ? ?/sec 1.00 324.1±23.17µs ? ?/sec query_get/50000_entities_table 1.08 330.9±10.90µs ? ?/sec 1.00 306.8±4.98µs ? ?/sec query_get_component/50000_entities_sparse 1.00 976.7±19.55µs ? ?/sec 1.00 979.8±35.87µs ? ?/sec query_get_component/50000_entities_table 1.00 1029.0±15.11µs ? ?/sec 1.05 1080.0±59.18µs ? ?/sec query_get_component_simple/system 1.13 839.7±14.18µs ? ?/sec 1.00 742.8±10.72µs ? ?/sec query_get_component_simple/unchecked 1.01 909.0±15.17µs ? ?/sec 1.00 898.0±13.56µs ? ?/sec query_get_many_10/50000_calls_sparse 1.04 5.5±0.54ms ? ?/sec 1.00 5.3±0.67ms ? ?/sec query_get_many_10/50000_calls_table 1.01 4.9±0.49ms ? ?/sec 1.00 4.8±0.45ms ? ?/sec query_get_many_2/50000_calls_sparse 1.28 848.4±210.89µs ? ?/sec 1.00 664.8±47.69µs ? ?/sec query_get_many_2/50000_calls_table 1.05 779.0±73.85µs ? ?/sec 1.00 739.2±83.02µs ? ?/sec query_get_many_5/50000_calls_sparse 1.05 2.4±0.37ms ? ?/sec 1.00 2.3±0.33ms ? ?/sec query_get_many_5/50000_calls_table 1.00 1939.9±75.22µs ? ?/sec 1.04 2.0±0.19ms ? ?/sec run_criteria/yes_using_query/001_systems 1.00 3.7±0.38µs ? ?/sec 1.30 4.9±0.14µs ? ?/sec run_criteria/yes_using_query/006_systems 1.00 8.9±0.40µs ? ?/sec 1.17 10.3±0.57µs ? ?/sec run_criteria/yes_using_query/011_systems 1.00 13.9±0.49µs ? ?/sec 1.08 15.0±0.89µs ? ?/sec run_criteria/yes_using_query/016_systems 1.00 18.8±0.74µs ? ?/sec 1.00 18.8±1.43µs ? ?/sec run_criteria/yes_using_query/021_systems 1.07 24.1±0.87µs ? ?/sec 1.00 22.6±1.58µs ? ?/sec run_criteria/yes_using_query/026_systems 1.04 27.9±0.62µs ? ?/sec 1.00 26.8±1.71µs ? ?/sec run_criteria/yes_using_query/031_systems 1.09 33.3±1.03µs ? ?/sec 1.00 30.5±2.18µs ? ?/sec run_criteria/yes_using_query/036_systems 1.14 38.7±0.80µs ? ?/sec 1.00 33.9±1.75µs ? ?/sec run_criteria/yes_using_query/041_systems 1.18 43.7±1.07µs ? ?/sec 1.00 37.0±2.39µs ? ?/sec run_criteria/yes_using_query/046_systems 1.14 47.6±1.16µs ? ?/sec 1.00 41.9±2.09µs ? ?/sec run_criteria/yes_using_query/051_systems 1.17 52.9±2.04µs ? ?/sec 1.00 45.3±1.75µs ? ?/sec run_criteria/yes_using_query/056_systems 1.25 59.2±2.38µs ? ?/sec 1.00 47.2±2.01µs ? ?/sec run_criteria/yes_using_query/061_systems 1.28 66.1±15.84µs ? ?/sec 1.00 51.5±2.47µs ? ?/sec run_criteria/yes_using_query/066_systems 1.28 70.2±2.57µs ? ?/sec 1.00 54.7±2.58µs ? ?/sec run_criteria/yes_using_query/071_systems 1.30 75.5±2.27µs ? ?/sec 1.00 58.2±3.31µs ? ?/sec run_criteria/yes_using_query/076_systems 1.26 81.5±2.66µs ? ?/sec 1.00 64.5±3.13µs ? ?/sec run_criteria/yes_using_query/081_systems 1.29 89.7±2.58µs ? ?/sec 1.00 69.3±3.47µs ? ?/sec run_criteria/yes_using_query/086_systems 1.33 95.6±3.39µs ? ?/sec 1.00 71.8±3.48µs ? ?/sec run_criteria/yes_using_query/091_systems 1.25 102.0±3.67µs ? ?/sec 1.00 81.4±4.82µs ? ?/sec run_criteria/yes_using_query/096_systems 1.33 111.7±3.29µs ? ?/sec 1.00 83.8±4.15µs ? ?/sec run_criteria/yes_using_query/101_systems 1.29 113.2±12.04µs ? ?/sec 1.00 87.7±5.15µs ? ?/sec world_query_for_each/50000_entities_sparse 1.00 47.4±0.51µs ? ?/sec 1.00 47.3±0.33µs ? ?/sec world_query_for_each/50000_entities_table 1.00 27.2±0.50µs ? ?/sec 1.00 27.2±0.17µs ? ?/sec world_query_get/50000_entities_sparse_wide 1.09 210.5±1.78µs ? ?/sec 1.00 192.5±2.61µs ? ?/sec world_query_get/50000_entities_table 1.00 127.7±2.09µs ? ?/sec 1.07 136.2±5.95µs ? ?/sec world_query_get/50000_entities_table_wide 1.00 209.8±2.37µs ? ?/sec 1.15 240.6±2.04µs ? ?/sec world_query_iter/50000_entities_sparse 1.00 54.2±0.36µs ? ?/sec 1.01 54.7±0.61µs ? ?/sec world_query_iter/50000_entities_table 1.00 27.2±0.31µs ? ?/sec 1.00 27.3±0.64µs ? ?/sec ``` </details> NOTE: This PR includes a change to enable LTO on our benchmarks to get a "fully optimized" baseline for our benchmarks. Both the main and the current PR's results were with LTO enabled.
2022-11-04 06:04:55 +00:00
.debug_checked_unwrap()
.deref(),
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
}
}
fn update_component_access(
&component_id: &ComponentId,
access: &mut FilteredAccess<ComponentId>,
) {
Replace `ReadOnlyFetch` with `ReadOnlyWorldQuery` (#4626) # Objective - Fix a type inference regression introduced by #3001 - Make read only bounds on world queries more user friendly ptrification required you to write `Q::Fetch: ReadOnlyFetch` as `for<'w> QueryFetch<'w, Q>: ReadOnlyFetch` which has the same type inference problem as `for<'w> QueryFetch<'w, Q>: FilterFetch<'w>` had, i.e. the following code would error: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: WorldQuery>(a: Query<Q, ()>) where for<'w> QueryFetch<'w, Q>: ReadOnlyFetch, { } ``` `for<..>` bounds are also rather user unfriendly.. ## Solution Remove the `ReadOnlyFetch` trait in favour of a `ReadOnlyWorldQuery` trait, and remove `WorldQueryGats::ReadOnlyFetch` in favor of `WorldQuery::ReadOnly` allowing the previous code snippet to be written as: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: ReadOnlyWorldQuery>(a: Query<Q, ()>) {} ``` This avoids the `for<...>` bound which makes the code simpler and also fixes the type inference issue. The reason for moving the two functions out of `FetchState` and into `WorldQuery` is to allow the world query `&mut T` to share a `State` with the `&T` world query so that it can have `type ReadOnly = &T`. Presumably it would be possible to instead have a `ReadOnlyRefMut<T>` world query and then do `type ReadOnly = ReadOnlyRefMut<T>` much like how (before this PR) we had a `ReadOnlyWriteFetch<T>`. A side benefit of the current solution in this PR is that it will likely make it easier in the future to support an API such as `Query<&mut T> -> Query<&T>`. The primary benefit IMO is just that `ReadOnlyRefMut<T>` and its associated fetch would have to reimplement all of the logic that the `&T` world query impl does but this solution avoids that :) --- ## Changelog/Migration Guide The trait `ReadOnlyFetch` has been replaced with `ReadOnlyWorldQuery` along with the `WorldQueryGats::ReadOnlyFetch` assoc type which has been replaced with `<WorldQuery::ReadOnly as WorldQueryGats>::Fetch` - Any where clauses such as `QueryFetch<Q>: ReadOnlyFetch` should be replaced with `Q: ReadOnlyWorldQuery`. - Any custom world query impls should implement `ReadOnlyWorldQuery` insead of `ReadOnlyFetch` Functions `update_component_access` and `update_archetype_component_access` have been moved from the `FetchState` trait to `WorldQuery` - Any callers should now call `Q::update_component_access(state` instead of `state.update_component_access` (and `update_archetype_component_access` respectively) - Any custom world query impls should move the functions from the `FetchState` impl to `WorldQuery` impl `WorldQuery` has been made an `unsafe trait`, `FetchState` has been made a safe `trait`. (I think this is how it should have always been, but regardless this is _definitely_ necessary now that the two functions have been moved to `WorldQuery`) - If you have a custom `FetchState` impl make it a normal `impl` instead of `unsafe impl` - If you have a custom `WorldQuery` impl make it an `unsafe impl`, if your code was sound before it is going to still be sound
2022-06-13 23:35:54 +00:00
assert!(
!access.access().has_write(component_id),
Replace `ReadOnlyFetch` with `ReadOnlyWorldQuery` (#4626) # Objective - Fix a type inference regression introduced by #3001 - Make read only bounds on world queries more user friendly ptrification required you to write `Q::Fetch: ReadOnlyFetch` as `for<'w> QueryFetch<'w, Q>: ReadOnlyFetch` which has the same type inference problem as `for<'w> QueryFetch<'w, Q>: FilterFetch<'w>` had, i.e. the following code would error: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: WorldQuery>(a: Query<Q, ()>) where for<'w> QueryFetch<'w, Q>: ReadOnlyFetch, { } ``` `for<..>` bounds are also rather user unfriendly.. ## Solution Remove the `ReadOnlyFetch` trait in favour of a `ReadOnlyWorldQuery` trait, and remove `WorldQueryGats::ReadOnlyFetch` in favor of `WorldQuery::ReadOnly` allowing the previous code snippet to be written as: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: ReadOnlyWorldQuery>(a: Query<Q, ()>) {} ``` This avoids the `for<...>` bound which makes the code simpler and also fixes the type inference issue. The reason for moving the two functions out of `FetchState` and into `WorldQuery` is to allow the world query `&mut T` to share a `State` with the `&T` world query so that it can have `type ReadOnly = &T`. Presumably it would be possible to instead have a `ReadOnlyRefMut<T>` world query and then do `type ReadOnly = ReadOnlyRefMut<T>` much like how (before this PR) we had a `ReadOnlyWriteFetch<T>`. A side benefit of the current solution in this PR is that it will likely make it easier in the future to support an API such as `Query<&mut T> -> Query<&T>`. The primary benefit IMO is just that `ReadOnlyRefMut<T>` and its associated fetch would have to reimplement all of the logic that the `&T` world query impl does but this solution avoids that :) --- ## Changelog/Migration Guide The trait `ReadOnlyFetch` has been replaced with `ReadOnlyWorldQuery` along with the `WorldQueryGats::ReadOnlyFetch` assoc type which has been replaced with `<WorldQuery::ReadOnly as WorldQueryGats>::Fetch` - Any where clauses such as `QueryFetch<Q>: ReadOnlyFetch` should be replaced with `Q: ReadOnlyWorldQuery`. - Any custom world query impls should implement `ReadOnlyWorldQuery` insead of `ReadOnlyFetch` Functions `update_component_access` and `update_archetype_component_access` have been moved from the `FetchState` trait to `WorldQuery` - Any callers should now call `Q::update_component_access(state` instead of `state.update_component_access` (and `update_archetype_component_access` respectively) - Any custom world query impls should move the functions from the `FetchState` impl to `WorldQuery` impl `WorldQuery` has been made an `unsafe trait`, `FetchState` has been made a safe `trait`. (I think this is how it should have always been, but regardless this is _definitely_ necessary now that the two functions have been moved to `WorldQuery`) - If you have a custom `FetchState` impl make it a normal `impl` instead of `unsafe impl` - If you have a custom `WorldQuery` impl make it an `unsafe impl`, if your code was sound before it is going to still be sound
2022-06-13 23:35:54 +00:00
"&{} conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.",
std::any::type_name::<T>(),
);
access.add_read(component_id);
Replace `ReadOnlyFetch` with `ReadOnlyWorldQuery` (#4626) # Objective - Fix a type inference regression introduced by #3001 - Make read only bounds on world queries more user friendly ptrification required you to write `Q::Fetch: ReadOnlyFetch` as `for<'w> QueryFetch<'w, Q>: ReadOnlyFetch` which has the same type inference problem as `for<'w> QueryFetch<'w, Q>: FilterFetch<'w>` had, i.e. the following code would error: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: WorldQuery>(a: Query<Q, ()>) where for<'w> QueryFetch<'w, Q>: ReadOnlyFetch, { } ``` `for<..>` bounds are also rather user unfriendly.. ## Solution Remove the `ReadOnlyFetch` trait in favour of a `ReadOnlyWorldQuery` trait, and remove `WorldQueryGats::ReadOnlyFetch` in favor of `WorldQuery::ReadOnly` allowing the previous code snippet to be written as: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: ReadOnlyWorldQuery>(a: Query<Q, ()>) {} ``` This avoids the `for<...>` bound which makes the code simpler and also fixes the type inference issue. The reason for moving the two functions out of `FetchState` and into `WorldQuery` is to allow the world query `&mut T` to share a `State` with the `&T` world query so that it can have `type ReadOnly = &T`. Presumably it would be possible to instead have a `ReadOnlyRefMut<T>` world query and then do `type ReadOnly = ReadOnlyRefMut<T>` much like how (before this PR) we had a `ReadOnlyWriteFetch<T>`. A side benefit of the current solution in this PR is that it will likely make it easier in the future to support an API such as `Query<&mut T> -> Query<&T>`. The primary benefit IMO is just that `ReadOnlyRefMut<T>` and its associated fetch would have to reimplement all of the logic that the `&T` world query impl does but this solution avoids that :) --- ## Changelog/Migration Guide The trait `ReadOnlyFetch` has been replaced with `ReadOnlyWorldQuery` along with the `WorldQueryGats::ReadOnlyFetch` assoc type which has been replaced with `<WorldQuery::ReadOnly as WorldQueryGats>::Fetch` - Any where clauses such as `QueryFetch<Q>: ReadOnlyFetch` should be replaced with `Q: ReadOnlyWorldQuery`. - Any custom world query impls should implement `ReadOnlyWorldQuery` insead of `ReadOnlyFetch` Functions `update_component_access` and `update_archetype_component_access` have been moved from the `FetchState` trait to `WorldQuery` - Any callers should now call `Q::update_component_access(state` instead of `state.update_component_access` (and `update_archetype_component_access` respectively) - Any custom world query impls should move the functions from the `FetchState` impl to `WorldQuery` impl `WorldQuery` has been made an `unsafe trait`, `FetchState` has been made a safe `trait`. (I think this is how it should have always been, but regardless this is _definitely_ necessary now that the two functions have been moved to `WorldQuery`) - If you have a custom `FetchState` impl make it a normal `impl` instead of `unsafe impl` - If you have a custom `WorldQuery` impl make it an `unsafe impl`, if your code was sound before it is going to still be sound
2022-06-13 23:35:54 +00:00
}
fn init_state(world: &mut World) -> ComponentId {
world.init_component::<T>()
}
Use lifetimed, type erased pointers in bevy_ecs (#3001) # Objective `bevy_ecs` has large amounts of unsafe code which is hard to get right and makes it difficult to audit for soundness. ## Solution Introduce lifetimed, type-erased pointers: `Ptr<'a>` `PtrMut<'a>` `OwningPtr<'a>'` and `ThinSlicePtr<'a, T>` which are newtypes around a raw pointer with a lifetime and conceptually representing strong invariants about the pointee and validity of the pointer. The process of converting bevy_ecs to use these has already caught multiple cases of unsound behavior. ## Changelog TL;DR for release notes: `bevy_ecs` now uses lifetimed, type-erased pointers internally, significantly improving safety and legibility without sacrificing performance. This should have approximately no end user impact, unless you were meddling with the (unfortunately public) internals of `bevy_ecs`. - `Fetch`, `FilterFetch` and `ReadOnlyFetch` trait no longer have a `'state` lifetime - this was unneeded - `ReadOnly/Fetch` associated types on `WorldQuery` are now on a new `WorldQueryGats<'world>` trait - was required to work around lack of Generic Associated Types (we wish to express `type Fetch<'a>: Fetch<'a>`) - `derive(WorldQuery)` no longer requires `'w` lifetime on struct - this was unneeded, and improves the end user experience - `EntityMut::get_unchecked_mut` returns `&'_ mut T` not `&'w mut T` - allows easier use of unsafe API with less footguns, and can be worked around via lifetime transmutery as a user - `Bundle::from_components` now takes a `ctx` parameter to pass to the `FnMut` closure - required because closure return types can't borrow from captures - `Fetch::init` takes `&'world World`, `Fetch::set_archetype` takes `&'world Archetype` and `&'world Tables`, `Fetch::set_table` takes `&'world Table` - allows types implementing `Fetch` to store borrows into world - `WorldQuery` trait now has a `shrink` fn to shorten the lifetime in `Fetch::<'a>::Item` - this works around lack of subtyping of assoc types, rust doesnt allow you to turn `<T as Fetch<'static>>::Item'` into `<T as Fetch<'a>>::Item'` - `QueryCombinationsIter` requires this - Most types implementing `Fetch` now have a lifetime `'w` - allows the fetches to store borrows of world data instead of using raw pointers ## Migration guide - `EntityMut::get_unchecked_mut` returns a more restricted lifetime, there is no general way to migrate this as it depends on your code - `Bundle::from_components` implementations must pass the `ctx` arg to `func` - `Bundle::from_components` callers have to use a fn arg instead of closure captures for borrowing from world - Remove lifetime args on `derive(WorldQuery)` structs as it is nonsensical - `<Q as WorldQuery>::ReadOnly/Fetch` should be changed to either `RO/QueryFetch<'world>` or `<Q as WorldQueryGats<'world>>::ReadOnly/Fetch` - `<F as Fetch<'w, 's>>` should be changed to `<F as Fetch<'w>>` - Change the fn sigs of `Fetch::init/set_archetype/set_table` to match respective trait fn sigs - Implement the required `fn shrink` on any `WorldQuery` implementations - Move assoc types `Fetch` and `ReadOnlyFetch` on `WorldQuery` impls to `WorldQueryGats` impls - Pass an appropriate `'world` lifetime to whatever fetch struct you are for some reason using ### Type inference regression in some cases rustc may give spurrious errors when attempting to infer the `F` parameter on a query/querystate this can be fixed by manually specifying the type, i.e. `QueryState::new::<_, ()>(world)`. The error is rather confusing: ```rust= error[E0271]: type mismatch resolving `<() as Fetch<'_>>::Item == bool` --> crates/bevy_pbr/src/render/light.rs:1413:30 | 1413 | main_view_query: QueryState::new(world), | ^^^^^^^^^^^^^^^ expected `bool`, found `()` | = note: required because of the requirements on the impl of `for<'x> FilterFetch<'x>` for `<() as WorldQueryGats<'x>>::Fetch` note: required by a bound in `bevy_ecs::query::QueryState::<Q, F>::new` --> crates/bevy_ecs/src/query/state.rs:49:32 | 49 | for<'x> QueryFetch<'x, F>: FilterFetch<'x>, | ^^^^^^^^^^^^^^^ required by this bound in `bevy_ecs::query::QueryState::<Q, F>::new` ``` --- Made with help from @BoxyUwU and @alice-i-cecile Co-authored-by: Boxy <supbscripter@gmail.com>
2022-04-27 23:44:06 +00:00
Dynamic queries and builder API (#9774) # Objective Expand the existing `Query` API to support more dynamic use cases i.e. scripting. ## Prior Art - #6390 - #8308 - #10037 ## Solution - Create a `QueryBuilder` with runtime methods to define the set of component accesses for a built query. - Create new `WorldQueryData` implementations `FilteredEntityMut` and `FilteredEntityRef` as variants of `EntityMut` and `EntityRef` that provide run time checked access to the components included in a given query. - Add new methods to `Query` to create "query lens" with a subset of the access of the initial query. ### Query Builder The `QueryBuilder` API allows you to define a query at runtime. At it's most basic use it will simply create a query with the corresponding type signature: ```rust let query = QueryBuilder::<Entity, With<A>>::new(&mut world).build(); // is equivalent to let query = QueryState::<Entity, With<A>>::new(&mut world); ``` Before calling `.build()` you also have the opportunity to add additional accesses and filters. Here is a simple example where we add additional filter terms: ```rust let entity_a = world.spawn((A(0), B(0))).id(); let entity_b = world.spawn((A(0), C(0))).id(); let mut query_a = QueryBuilder::<Entity>::new(&mut world) .with::<A>() .without::<C>() .build(); assert_eq!(entity_a, query_a.single(&world)); ``` This alone is useful in that allows you to decide which archetypes your query will match at runtime. However it is also very limited, consider a case like the following: ```rust let query_a = QueryBuilder::<&A>::new(&mut world) // Add an additional access .data::<&B>() .build(); ``` This will grant the query an additional read access to component B however we have no way of accessing the data while iterating as the type signature still only includes &A. For an even more concrete example of this consider dynamic components: ```rust let query_a = QueryBuilder::<Entity>::new(&mut world) // Adding a filter is easy since it doesn't need be read later .with_id(component_id_a) // How do I access the data of this component? .ref_id(component_id_b) .build(); ``` With this in mind the `QueryBuilder` API seems somewhat incomplete by itself, we need some way method of accessing the components dynamically. So here's one: ### Query Transmutation If the problem is not having the component in the type signature why not just add it? This PR also adds transmute methods to `QueryBuilder` and `QueryState`. Here's a simple example: ```rust world.spawn(A(0)); world.spawn((A(1), B(0))); let mut query = QueryBuilder::<()>::new(&mut world) .with::<B>() .transmute::<&A>() .build(); query.iter(&world).for_each(|a| assert_eq!(a.0, 1)); ``` The `QueryState` and `QueryBuilder` transmute methods look quite similar but are different in one respect. Transmuting a builder will always succeed as it will just add the additional accesses needed for the new terms if they weren't already included. Transmuting a `QueryState` will panic in the case that the new type signature would give it access it didn't already have, for example: ```rust let query = QueryState::<&A, Option<&B>>::new(&mut world); /// This is fine, the access for Option<&A> is less restrictive than &A query.transmute::<Option<&A>>(&world); /// Oh no, this would allow access to &B on entities that might not have it, so it panics query.transmute::<&B>(&world); /// This is right out query.transmute::<&C>(&world); ``` This is quite an appealing API to also have available on `Query` however it does pose one additional wrinkle: In order to to change the iterator we need to create a new `QueryState` to back it. `Query` doesn't own it's own state though, it just borrows it, so we need a place to borrow it from. This is why `QueryLens` exists, it is a place to store the new state so it can be borrowed when you call `.query()` leaving you with an API like this: ```rust fn function_that_takes_a_query(query: &Query<&A>) { // ... } fn system(query: Query<(&A, &B)>) { let lens = query.transmute_lens::<&A>(); let q = lens.query(); function_that_takes_a_query(&q); } ``` Now you may be thinking: Hey, wait a second, you introduced the problem with dynamic components and then described a solution that only works for static components! Ok, you got me, I guess we need a bit more: ### Filtered Entity References Currently the only way you can access dynamic components on entities through a query is with either `EntityMut` or `EntityRef`, however these can access all components and so conflict with all other accesses. This PR introduces `FilteredEntityMut` and `FilteredEntityRef` as alternatives that have additional runtime checking to prevent accessing components that you shouldn't. This way you can build a query with a `QueryBuilder` and actually access the components you asked for: ```rust let mut query = QueryBuilder::<FilteredEntityRef>::new(&mut world) .ref_id(component_id_a) .with(component_id_b) .build(); let entity_ref = query.single(&world); // Returns Some(Ptr) as we have that component and are allowed to read it let a = entity_ref.get_by_id(component_id_a); // Will return None even though the entity does have the component, as we are not allowed to read it let b = entity_ref.get_by_id(component_id_b); ``` For the most part these new structs have the exact same methods as their non-filtered equivalents. Putting all of this together we can do some truly dynamic ECS queries, check out the `dynamic` example to see it in action: ``` Commands: comp, c Create new components spawn, s Spawn entities query, q Query for entities Enter a command with no parameters for usage. > c A, B, C, Data 4 Component A created with id: 0 Component B created with id: 1 Component C created with id: 2 Component Data created with id: 3 > s A, B, Data 1 Entity spawned with id: 0v0 > s A, C, Data 0 Entity spawned with id: 1v0 > q &Data 0v0: Data: [1, 0, 0, 0] 1v0: Data: [0, 0, 0, 0] > q B, &mut Data 0v0: Data: [2, 1, 1, 1] > q B || C, &Data 0v0: Data: [2, 1, 1, 1] 1v0: Data: [0, 0, 0, 0] ``` ## Changelog - Add new `transmute_lens` methods to `Query`. - Add new types `QueryBuilder`, `FilteredEntityMut`, `FilteredEntityRef` and `QueryLens` - `update_archetype_component_access` has been removed, archetype component accesses are now determined by the accesses set in `update_component_access` - Added method `set_access` to `WorldQuery`, this is called before `update_component_access` for queries that have a restricted set of accesses, such as those built by `QueryBuilder` or `QueryLens`. This is primarily used by the `FilteredEntity*` variants and has an empty trait implementation. - Added method `get_state` to `WorldQuery` as a fallible version of `init_state` when you don't have `&mut World` access. ## Future Work Improve performance of `FilteredEntityMut` and `FilteredEntityRef`, currently they have to determine the accesses a query has in a given archetype during iteration which is far from ideal, especially since we already did the work when matching the archetype in the first place. To avoid making more internal API changes I have left it out of this PR. --------- Co-authored-by: Mike Hsu <mike.hsu@gmail.com>
2024-01-16 19:16:49 +00:00
fn get_state(world: &World) -> Option<Self::State> {
world.component_id::<T>()
}
fn matches_component_set(
&state: &ComponentId,
set_contains_id: &impl Fn(ComponentId) -> bool,
) -> bool {
set_contains_id(state)
Use lifetimed, type erased pointers in bevy_ecs (#3001) # Objective `bevy_ecs` has large amounts of unsafe code which is hard to get right and makes it difficult to audit for soundness. ## Solution Introduce lifetimed, type-erased pointers: `Ptr<'a>` `PtrMut<'a>` `OwningPtr<'a>'` and `ThinSlicePtr<'a, T>` which are newtypes around a raw pointer with a lifetime and conceptually representing strong invariants about the pointee and validity of the pointer. The process of converting bevy_ecs to use these has already caught multiple cases of unsound behavior. ## Changelog TL;DR for release notes: `bevy_ecs` now uses lifetimed, type-erased pointers internally, significantly improving safety and legibility without sacrificing performance. This should have approximately no end user impact, unless you were meddling with the (unfortunately public) internals of `bevy_ecs`. - `Fetch`, `FilterFetch` and `ReadOnlyFetch` trait no longer have a `'state` lifetime - this was unneeded - `ReadOnly/Fetch` associated types on `WorldQuery` are now on a new `WorldQueryGats<'world>` trait - was required to work around lack of Generic Associated Types (we wish to express `type Fetch<'a>: Fetch<'a>`) - `derive(WorldQuery)` no longer requires `'w` lifetime on struct - this was unneeded, and improves the end user experience - `EntityMut::get_unchecked_mut` returns `&'_ mut T` not `&'w mut T` - allows easier use of unsafe API with less footguns, and can be worked around via lifetime transmutery as a user - `Bundle::from_components` now takes a `ctx` parameter to pass to the `FnMut` closure - required because closure return types can't borrow from captures - `Fetch::init` takes `&'world World`, `Fetch::set_archetype` takes `&'world Archetype` and `&'world Tables`, `Fetch::set_table` takes `&'world Table` - allows types implementing `Fetch` to store borrows into world - `WorldQuery` trait now has a `shrink` fn to shorten the lifetime in `Fetch::<'a>::Item` - this works around lack of subtyping of assoc types, rust doesnt allow you to turn `<T as Fetch<'static>>::Item'` into `<T as Fetch<'a>>::Item'` - `QueryCombinationsIter` requires this - Most types implementing `Fetch` now have a lifetime `'w` - allows the fetches to store borrows of world data instead of using raw pointers ## Migration guide - `EntityMut::get_unchecked_mut` returns a more restricted lifetime, there is no general way to migrate this as it depends on your code - `Bundle::from_components` implementations must pass the `ctx` arg to `func` - `Bundle::from_components` callers have to use a fn arg instead of closure captures for borrowing from world - Remove lifetime args on `derive(WorldQuery)` structs as it is nonsensical - `<Q as WorldQuery>::ReadOnly/Fetch` should be changed to either `RO/QueryFetch<'world>` or `<Q as WorldQueryGats<'world>>::ReadOnly/Fetch` - `<F as Fetch<'w, 's>>` should be changed to `<F as Fetch<'w>>` - Change the fn sigs of `Fetch::init/set_archetype/set_table` to match respective trait fn sigs - Implement the required `fn shrink` on any `WorldQuery` implementations - Move assoc types `Fetch` and `ReadOnlyFetch` on `WorldQuery` impls to `WorldQueryGats` impls - Pass an appropriate `'world` lifetime to whatever fetch struct you are for some reason using ### Type inference regression in some cases rustc may give spurrious errors when attempting to infer the `F` parameter on a query/querystate this can be fixed by manually specifying the type, i.e. `QueryState::new::<_, ()>(world)`. The error is rather confusing: ```rust= error[E0271]: type mismatch resolving `<() as Fetch<'_>>::Item == bool` --> crates/bevy_pbr/src/render/light.rs:1413:30 | 1413 | main_view_query: QueryState::new(world), | ^^^^^^^^^^^^^^^ expected `bool`, found `()` | = note: required because of the requirements on the impl of `for<'x> FilterFetch<'x>` for `<() as WorldQueryGats<'x>>::Fetch` note: required by a bound in `bevy_ecs::query::QueryState::<Q, F>::new` --> crates/bevy_ecs/src/query/state.rs:49:32 | 49 | for<'x> QueryFetch<'x, F>: FilterFetch<'x>, | ^^^^^^^^^^^^^^^ required by this bound in `bevy_ecs::query::QueryState::<Q, F>::new` ``` --- Made with help from @BoxyUwU and @alice-i-cecile Co-authored-by: Boxy <supbscripter@gmail.com>
2022-04-27 23:44:06 +00:00
}
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
}
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
/// SAFETY: `Self` is the same as `Self::ReadOnly`
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
unsafe impl<T: Component> QueryData for &T {
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
type ReadOnly = Self;
}
/// SAFETY: access is read only
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
unsafe impl<T: Component> ReadOnlyQueryData for &T {}
#[doc(hidden)]
pub struct RefFetch<'w, T> {
// T::Storage = TableStorage
table_data: Option<(
ThinSlicePtr<'w, UnsafeCell<T>>,
ThinSlicePtr<'w, UnsafeCell<Tick>>,
ThinSlicePtr<'w, UnsafeCell<Tick>>,
)>,
// T::Storage = SparseStorage
sparse_set: Option<&'w ComponentSparseSet>,
last_run: Tick,
this_run: Tick,
}
Fix safety invariants for `WorldQuery::fetch` and simplify cloning (#8246) # Objective Cloning a `WorldQuery` type's "fetch" struct was made unsafe in #5593, by adding the `unsafe fn clone_fetch` to `WorldQuery`. However, as that method's documentation explains, it is not the right place to put the safety invariant: > While calling this method on its own cannot cause UB it is marked `unsafe` as the caller must ensure that the returned value is not used in any way that would cause two `QueryItem<Self>` for the same `archetype_index` or `table_row` to be alive at the same time. You can clone a fetch struct all you want and it will never cause undefined behavior -- in order for something to go wrong, you need to improperly call `WorldQuery::fetch` with it (which is marked unsafe). Additionally, making it unsafe to clone a fetch struct does not even prevent undefined behavior, since there are other ways to incorrectly use a fetch struct. For example, you could just call fetch more than once for the same entity, which is not currently forbidden by any documented invariants. ## Solution Document a safety invariant on `WorldQuery::fetch` that requires the caller to not create aliased `WorldQueryItem`s for mutable types. Remove the `clone_fetch` function, and add the bound `Fetch: Clone` instead. --- ## Changelog - Removed the associated function `WorldQuery::clone_fetch`, and added a `Clone` bound to `WorldQuery::Fetch`. ## Migration Guide ### `fetch` invariants The function `WorldQuery::fetch` has had the following safety invariant added: > If this type does not implement `ReadOnlyWorldQuery`, then the caller must ensure that it is impossible for more than one `Self::Item` to exist for the same entity at any given time. This invariant was always required for soundness, but was previously undocumented. If you called this function manually anywhere, you should check to make sure that this invariant is not violated. ### Removed `clone_fetch` The function `WorldQuery::clone_fetch` has been removed. The associated type `WorldQuery::Fetch` now has the bound `Clone`. Before: ```rust struct MyFetch<'w> { ... } unsafe impl WorldQuery for MyQuery { ... type Fetch<'w> = MyFetch<'w> unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> { MyFetch { field1: fetch.field1, field2: fetch.field2.clone(), ... } } } ``` After: ```rust #[derive(Clone)] struct MyFetch<'w> { ... } unsafe impl WorldQuery for MyQuery { ... type Fetch<'w> = MyFetch<'w>; } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-07-25 21:16:22 +00:00
impl<T> Clone for RefFetch<'_, T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for RefFetch<'_, T> {}
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
/// SAFETY:
/// `fetch` accesses a single component in a readonly way.
/// This is sound because `update_component_access` and `update_archetype_component_access` add read access for that component and panic when appropriate.
/// `update_component_access` adds a `With` filter for a component.
/// This is sound because `matches_component_set` returns whether the set contains that component.
unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
type Item<'w> = Ref<'w, T>;
type Fetch<'w> = RefFetch<'w, T>;
type State = ComponentId;
fn shrink<'wlong: 'wshort, 'wshort>(item: Ref<'wlong, T>) -> Ref<'wshort, T> {
item
}
#[inline]
unsafe fn init_fetch<'w>(
Migrate the rest of the engine to `UnsafeWorldCell` (#8833) # Objective Follow-up to #6404 and #8292. Mutating the world through a shared reference is surprising, and it makes the meaning of `&World` unclear: sometimes it gives read-only access to the entire world, and sometimes it gives interior mutable access to only part of it. This is an up-to-date version of #6972. ## Solution Use `UnsafeWorldCell` for all interior mutability. Now, `&World` *always* gives you read-only access to the entire world. --- ## Changelog TODO - do we still care about changelogs? ## Migration Guide Mutating any world data using `&World` is now considered unsound -- the type `UnsafeWorldCell` must be used to achieve interior mutability. The following methods now accept `UnsafeWorldCell` instead of `&World`: - `QueryState`: `get_unchecked`, `iter_unchecked`, `iter_combinations_unchecked`, `for_each_unchecked`, `get_single_unchecked`, `get_single_unchecked_manual`. - `SystemState`: `get_unchecked_manual` ```rust let mut world = World::new(); let mut query = world.query::<&mut T>(); // Before: let t1 = query.get_unchecked(&world, entity_1); let t2 = query.get_unchecked(&world, entity_2); // After: let world_cell = world.as_unsafe_world_cell(); let t1 = query.get_unchecked(world_cell, entity_1); let t2 = query.get_unchecked(world_cell, entity_2); ``` The methods `QueryState::validate_world` and `SystemState::matches_world` now take a `WorldId` instead of `&World`: ```rust // Before: query_state.validate_world(&world); // After: query_state.validate_world(world.id()); ``` The methods `QueryState::update_archetypes` and `SystemState::update_archetypes` now take `UnsafeWorldCell` instead of `&World`: ```rust // Before: query_state.update_archetypes(&world); // After: query_state.update_archetypes(world.as_unsafe_world_cell_readonly()); ```
2023-06-15 01:31:56 +00:00
world: UnsafeWorldCell<'w>,
&component_id: &ComponentId,
last_run: Tick,
this_run: Tick,
) -> RefFetch<'w, T> {
RefFetch {
table_data: None,
sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet).then(|| {
world
Migrate the rest of the engine to `UnsafeWorldCell` (#8833) # Objective Follow-up to #6404 and #8292. Mutating the world through a shared reference is surprising, and it makes the meaning of `&World` unclear: sometimes it gives read-only access to the entire world, and sometimes it gives interior mutable access to only part of it. This is an up-to-date version of #6972. ## Solution Use `UnsafeWorldCell` for all interior mutability. Now, `&World` *always* gives you read-only access to the entire world. --- ## Changelog TODO - do we still care about changelogs? ## Migration Guide Mutating any world data using `&World` is now considered unsound -- the type `UnsafeWorldCell` must be used to achieve interior mutability. The following methods now accept `UnsafeWorldCell` instead of `&World`: - `QueryState`: `get_unchecked`, `iter_unchecked`, `iter_combinations_unchecked`, `for_each_unchecked`, `get_single_unchecked`, `get_single_unchecked_manual`. - `SystemState`: `get_unchecked_manual` ```rust let mut world = World::new(); let mut query = world.query::<&mut T>(); // Before: let t1 = query.get_unchecked(&world, entity_1); let t2 = query.get_unchecked(&world, entity_2); // After: let world_cell = world.as_unsafe_world_cell(); let t1 = query.get_unchecked(world_cell, entity_1); let t2 = query.get_unchecked(world_cell, entity_2); ``` The methods `QueryState::validate_world` and `SystemState::matches_world` now take a `WorldId` instead of `&World`: ```rust // Before: query_state.validate_world(&world); // After: query_state.validate_world(world.id()); ``` The methods `QueryState::update_archetypes` and `SystemState::update_archetypes` now take `UnsafeWorldCell` instead of `&World`: ```rust // Before: query_state.update_archetypes(&world); // After: query_state.update_archetypes(world.as_unsafe_world_cell_readonly()); ```
2023-06-15 01:31:56 +00:00
// SAFETY: See &T::init_fetch.
.storages()
.sparse_sets
.get(component_id)
.debug_checked_unwrap()
}),
last_run,
this_run,
}
}
const IS_DENSE: bool = {
match T::Storage::STORAGE_TYPE {
StorageType::Table => true,
StorageType::SparseSet => false,
}
};
#[inline]
unsafe fn set_archetype<'w>(
fetch: &mut RefFetch<'w, T>,
component_id: &ComponentId,
_archetype: &'w Archetype,
table: &'w Table,
) {
if Self::IS_DENSE {
Self::set_table(fetch, component_id, table);
}
}
#[inline]
unsafe fn set_table<'w>(
fetch: &mut RefFetch<'w, T>,
&component_id: &ComponentId,
table: &'w Table,
) {
let column = table.get_column(component_id).debug_checked_unwrap();
fetch.table_data = Some((
column.get_data_slice().into(),
column.get_added_ticks_slice().into(),
column.get_changed_ticks_slice().into(),
));
}
#[inline(always)]
unsafe fn fetch<'w>(
fetch: &mut Self::Fetch<'w>,
entity: Entity,
table_row: TableRow,
) -> Self::Item<'w> {
match T::Storage::STORAGE_TYPE {
StorageType::Table => {
let (table_components, added_ticks, changed_ticks) =
fetch.table_data.debug_checked_unwrap();
Ref {
Reduced `TableRow` `as` Casting (#10811) # Objective - Fixes #10806 ## Solution Replaced `new` and `index` methods for both `TableRow` and `TableId` with `from_*` and `as_*` methods. These remove the need to perform casting at call sites, reducing the total number of casts in the Bevy codebase. Within these methods, an appropriate `debug_assertion` ensures the cast will behave in an expected manner (no wrapping, etc.). I am using a `debug_assertion` instead of an `assert` to reduce any possible runtime overhead, however minimal. This choice is something I am open to changing (or leaving up to another PR) if anyone has any strong arguments for it. --- ## Changelog - `ComponentSparseSet::sparse` stores a `TableRow` instead of a `u32` (private change) - Replaced `TableRow::new` and `TableRow::index` methods with `TableRow::from_*` and `TableRow::as_*`, with `debug_assertions` protecting any internal casting. - Replaced `TableId::new` and `TableId::index` methods with `TableId::from_*` and `TableId::as_*`, with `debug_assertions` protecting any internal casting. - All `TableId` methods are now `const` ## Migration Guide - `TableRow::new` -> `TableRow::from_usize` - `TableRow::index` -> `TableRow::as_usize` - `TableId::new` -> `TableId::from_usize` - `TableId::index` -> `TableId::as_usize` --- ## Notes I have chosen to remove the `index` and `new` methods for the following chain of reasoning: - Across the codebase, `new` was called with a mixture of `u32` and `usize` values. Likewise for `index`. - Choosing `new` to either be `usize` or `u32` would break half of these call-sites, requiring `as` casting at the site. - Adding a second method `new_u32` or `new_usize` avoids the above, bu looks visually inconsistent. - Therefore, they should be replaced with `from_*` and `as_*` methods instead. Worth noting is that by updating `ComponentSparseSet`, there are now zero instances of interacting with the inner value of `TableRow` as a `u32`, it is exclusively used as a `usize` value (due to interactions with methods like `len` and slice indexing). I have left the `as_u32` and `from_u32` methods as the "proper" constructors/getters.
2023-12-05 02:44:33 +00:00
value: table_components.get(table_row.as_usize()).deref(),
ticks: Ticks {
Reduced `TableRow` `as` Casting (#10811) # Objective - Fixes #10806 ## Solution Replaced `new` and `index` methods for both `TableRow` and `TableId` with `from_*` and `as_*` methods. These remove the need to perform casting at call sites, reducing the total number of casts in the Bevy codebase. Within these methods, an appropriate `debug_assertion` ensures the cast will behave in an expected manner (no wrapping, etc.). I am using a `debug_assertion` instead of an `assert` to reduce any possible runtime overhead, however minimal. This choice is something I am open to changing (or leaving up to another PR) if anyone has any strong arguments for it. --- ## Changelog - `ComponentSparseSet::sparse` stores a `TableRow` instead of a `u32` (private change) - Replaced `TableRow::new` and `TableRow::index` methods with `TableRow::from_*` and `TableRow::as_*`, with `debug_assertions` protecting any internal casting. - Replaced `TableId::new` and `TableId::index` methods with `TableId::from_*` and `TableId::as_*`, with `debug_assertions` protecting any internal casting. - All `TableId` methods are now `const` ## Migration Guide - `TableRow::new` -> `TableRow::from_usize` - `TableRow::index` -> `TableRow::as_usize` - `TableId::new` -> `TableId::from_usize` - `TableId::index` -> `TableId::as_usize` --- ## Notes I have chosen to remove the `index` and `new` methods for the following chain of reasoning: - Across the codebase, `new` was called with a mixture of `u32` and `usize` values. Likewise for `index`. - Choosing `new` to either be `usize` or `u32` would break half of these call-sites, requiring `as` casting at the site. - Adding a second method `new_u32` or `new_usize` avoids the above, bu looks visually inconsistent. - Therefore, they should be replaced with `from_*` and `as_*` methods instead. Worth noting is that by updating `ComponentSparseSet`, there are now zero instances of interacting with the inner value of `TableRow` as a `u32`, it is exclusively used as a `usize` value (due to interactions with methods like `len` and slice indexing). I have left the `as_u32` and `from_u32` methods as the "proper" constructors/getters.
2023-12-05 02:44:33 +00:00
added: added_ticks.get(table_row.as_usize()).deref(),
changed: changed_ticks.get(table_row.as_usize()).deref(),
this_run: fetch.this_run,
last_run: fetch.last_run,
},
}
}
StorageType::SparseSet => {
let (component, ticks) = fetch
.sparse_set
.debug_checked_unwrap()
.get_with_ticks(entity)
.debug_checked_unwrap();
Ref {
value: component.deref(),
ticks: Ticks::from_tick_cells(ticks, fetch.last_run, fetch.this_run),
}
}
}
}
fn update_component_access(
&component_id: &ComponentId,
access: &mut FilteredAccess<ComponentId>,
) {
assert!(
!access.access().has_write(component_id),
"&{} conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.",
std::any::type_name::<T>(),
);
access.add_read(component_id);
}
fn init_state(world: &mut World) -> ComponentId {
world.init_component::<T>()
}
Dynamic queries and builder API (#9774) # Objective Expand the existing `Query` API to support more dynamic use cases i.e. scripting. ## Prior Art - #6390 - #8308 - #10037 ## Solution - Create a `QueryBuilder` with runtime methods to define the set of component accesses for a built query. - Create new `WorldQueryData` implementations `FilteredEntityMut` and `FilteredEntityRef` as variants of `EntityMut` and `EntityRef` that provide run time checked access to the components included in a given query. - Add new methods to `Query` to create "query lens" with a subset of the access of the initial query. ### Query Builder The `QueryBuilder` API allows you to define a query at runtime. At it's most basic use it will simply create a query with the corresponding type signature: ```rust let query = QueryBuilder::<Entity, With<A>>::new(&mut world).build(); // is equivalent to let query = QueryState::<Entity, With<A>>::new(&mut world); ``` Before calling `.build()` you also have the opportunity to add additional accesses and filters. Here is a simple example where we add additional filter terms: ```rust let entity_a = world.spawn((A(0), B(0))).id(); let entity_b = world.spawn((A(0), C(0))).id(); let mut query_a = QueryBuilder::<Entity>::new(&mut world) .with::<A>() .without::<C>() .build(); assert_eq!(entity_a, query_a.single(&world)); ``` This alone is useful in that allows you to decide which archetypes your query will match at runtime. However it is also very limited, consider a case like the following: ```rust let query_a = QueryBuilder::<&A>::new(&mut world) // Add an additional access .data::<&B>() .build(); ``` This will grant the query an additional read access to component B however we have no way of accessing the data while iterating as the type signature still only includes &A. For an even more concrete example of this consider dynamic components: ```rust let query_a = QueryBuilder::<Entity>::new(&mut world) // Adding a filter is easy since it doesn't need be read later .with_id(component_id_a) // How do I access the data of this component? .ref_id(component_id_b) .build(); ``` With this in mind the `QueryBuilder` API seems somewhat incomplete by itself, we need some way method of accessing the components dynamically. So here's one: ### Query Transmutation If the problem is not having the component in the type signature why not just add it? This PR also adds transmute methods to `QueryBuilder` and `QueryState`. Here's a simple example: ```rust world.spawn(A(0)); world.spawn((A(1), B(0))); let mut query = QueryBuilder::<()>::new(&mut world) .with::<B>() .transmute::<&A>() .build(); query.iter(&world).for_each(|a| assert_eq!(a.0, 1)); ``` The `QueryState` and `QueryBuilder` transmute methods look quite similar but are different in one respect. Transmuting a builder will always succeed as it will just add the additional accesses needed for the new terms if they weren't already included. Transmuting a `QueryState` will panic in the case that the new type signature would give it access it didn't already have, for example: ```rust let query = QueryState::<&A, Option<&B>>::new(&mut world); /// This is fine, the access for Option<&A> is less restrictive than &A query.transmute::<Option<&A>>(&world); /// Oh no, this would allow access to &B on entities that might not have it, so it panics query.transmute::<&B>(&world); /// This is right out query.transmute::<&C>(&world); ``` This is quite an appealing API to also have available on `Query` however it does pose one additional wrinkle: In order to to change the iterator we need to create a new `QueryState` to back it. `Query` doesn't own it's own state though, it just borrows it, so we need a place to borrow it from. This is why `QueryLens` exists, it is a place to store the new state so it can be borrowed when you call `.query()` leaving you with an API like this: ```rust fn function_that_takes_a_query(query: &Query<&A>) { // ... } fn system(query: Query<(&A, &B)>) { let lens = query.transmute_lens::<&A>(); let q = lens.query(); function_that_takes_a_query(&q); } ``` Now you may be thinking: Hey, wait a second, you introduced the problem with dynamic components and then described a solution that only works for static components! Ok, you got me, I guess we need a bit more: ### Filtered Entity References Currently the only way you can access dynamic components on entities through a query is with either `EntityMut` or `EntityRef`, however these can access all components and so conflict with all other accesses. This PR introduces `FilteredEntityMut` and `FilteredEntityRef` as alternatives that have additional runtime checking to prevent accessing components that you shouldn't. This way you can build a query with a `QueryBuilder` and actually access the components you asked for: ```rust let mut query = QueryBuilder::<FilteredEntityRef>::new(&mut world) .ref_id(component_id_a) .with(component_id_b) .build(); let entity_ref = query.single(&world); // Returns Some(Ptr) as we have that component and are allowed to read it let a = entity_ref.get_by_id(component_id_a); // Will return None even though the entity does have the component, as we are not allowed to read it let b = entity_ref.get_by_id(component_id_b); ``` For the most part these new structs have the exact same methods as their non-filtered equivalents. Putting all of this together we can do some truly dynamic ECS queries, check out the `dynamic` example to see it in action: ``` Commands: comp, c Create new components spawn, s Spawn entities query, q Query for entities Enter a command with no parameters for usage. > c A, B, C, Data 4 Component A created with id: 0 Component B created with id: 1 Component C created with id: 2 Component Data created with id: 3 > s A, B, Data 1 Entity spawned with id: 0v0 > s A, C, Data 0 Entity spawned with id: 1v0 > q &Data 0v0: Data: [1, 0, 0, 0] 1v0: Data: [0, 0, 0, 0] > q B, &mut Data 0v0: Data: [2, 1, 1, 1] > q B || C, &Data 0v0: Data: [2, 1, 1, 1] 1v0: Data: [0, 0, 0, 0] ``` ## Changelog - Add new `transmute_lens` methods to `Query`. - Add new types `QueryBuilder`, `FilteredEntityMut`, `FilteredEntityRef` and `QueryLens` - `update_archetype_component_access` has been removed, archetype component accesses are now determined by the accesses set in `update_component_access` - Added method `set_access` to `WorldQuery`, this is called before `update_component_access` for queries that have a restricted set of accesses, such as those built by `QueryBuilder` or `QueryLens`. This is primarily used by the `FilteredEntity*` variants and has an empty trait implementation. - Added method `get_state` to `WorldQuery` as a fallible version of `init_state` when you don't have `&mut World` access. ## Future Work Improve performance of `FilteredEntityMut` and `FilteredEntityRef`, currently they have to determine the accesses a query has in a given archetype during iteration which is far from ideal, especially since we already did the work when matching the archetype in the first place. To avoid making more internal API changes I have left it out of this PR. --------- Co-authored-by: Mike Hsu <mike.hsu@gmail.com>
2024-01-16 19:16:49 +00:00
fn get_state(world: &World) -> Option<Self::State> {
world.component_id::<T>()
}
fn matches_component_set(
&state: &ComponentId,
set_contains_id: &impl Fn(ComponentId) -> bool,
) -> bool {
set_contains_id(state)
}
}
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
/// SAFETY: `Self` is the same as `Self::ReadOnly`
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
unsafe impl<'__w, T: Component> QueryData for Ref<'__w, T> {
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
type ReadOnly = Self;
}
/// SAFETY: access is read only
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
unsafe impl<'__w, T: Component> ReadOnlyQueryData for Ref<'__w, T> {}
#[doc(hidden)]
Use lifetimed, type erased pointers in bevy_ecs (#3001) # Objective `bevy_ecs` has large amounts of unsafe code which is hard to get right and makes it difficult to audit for soundness. ## Solution Introduce lifetimed, type-erased pointers: `Ptr<'a>` `PtrMut<'a>` `OwningPtr<'a>'` and `ThinSlicePtr<'a, T>` which are newtypes around a raw pointer with a lifetime and conceptually representing strong invariants about the pointee and validity of the pointer. The process of converting bevy_ecs to use these has already caught multiple cases of unsound behavior. ## Changelog TL;DR for release notes: `bevy_ecs` now uses lifetimed, type-erased pointers internally, significantly improving safety and legibility without sacrificing performance. This should have approximately no end user impact, unless you were meddling with the (unfortunately public) internals of `bevy_ecs`. - `Fetch`, `FilterFetch` and `ReadOnlyFetch` trait no longer have a `'state` lifetime - this was unneeded - `ReadOnly/Fetch` associated types on `WorldQuery` are now on a new `WorldQueryGats<'world>` trait - was required to work around lack of Generic Associated Types (we wish to express `type Fetch<'a>: Fetch<'a>`) - `derive(WorldQuery)` no longer requires `'w` lifetime on struct - this was unneeded, and improves the end user experience - `EntityMut::get_unchecked_mut` returns `&'_ mut T` not `&'w mut T` - allows easier use of unsafe API with less footguns, and can be worked around via lifetime transmutery as a user - `Bundle::from_components` now takes a `ctx` parameter to pass to the `FnMut` closure - required because closure return types can't borrow from captures - `Fetch::init` takes `&'world World`, `Fetch::set_archetype` takes `&'world Archetype` and `&'world Tables`, `Fetch::set_table` takes `&'world Table` - allows types implementing `Fetch` to store borrows into world - `WorldQuery` trait now has a `shrink` fn to shorten the lifetime in `Fetch::<'a>::Item` - this works around lack of subtyping of assoc types, rust doesnt allow you to turn `<T as Fetch<'static>>::Item'` into `<T as Fetch<'a>>::Item'` - `QueryCombinationsIter` requires this - Most types implementing `Fetch` now have a lifetime `'w` - allows the fetches to store borrows of world data instead of using raw pointers ## Migration guide - `EntityMut::get_unchecked_mut` returns a more restricted lifetime, there is no general way to migrate this as it depends on your code - `Bundle::from_components` implementations must pass the `ctx` arg to `func` - `Bundle::from_components` callers have to use a fn arg instead of closure captures for borrowing from world - Remove lifetime args on `derive(WorldQuery)` structs as it is nonsensical - `<Q as WorldQuery>::ReadOnly/Fetch` should be changed to either `RO/QueryFetch<'world>` or `<Q as WorldQueryGats<'world>>::ReadOnly/Fetch` - `<F as Fetch<'w, 's>>` should be changed to `<F as Fetch<'w>>` - Change the fn sigs of `Fetch::init/set_archetype/set_table` to match respective trait fn sigs - Implement the required `fn shrink` on any `WorldQuery` implementations - Move assoc types `Fetch` and `ReadOnlyFetch` on `WorldQuery` impls to `WorldQueryGats` impls - Pass an appropriate `'world` lifetime to whatever fetch struct you are for some reason using ### Type inference regression in some cases rustc may give spurrious errors when attempting to infer the `F` parameter on a query/querystate this can be fixed by manually specifying the type, i.e. `QueryState::new::<_, ()>(world)`. The error is rather confusing: ```rust= error[E0271]: type mismatch resolving `<() as Fetch<'_>>::Item == bool` --> crates/bevy_pbr/src/render/light.rs:1413:30 | 1413 | main_view_query: QueryState::new(world), | ^^^^^^^^^^^^^^^ expected `bool`, found `()` | = note: required because of the requirements on the impl of `for<'x> FilterFetch<'x>` for `<() as WorldQueryGats<'x>>::Fetch` note: required by a bound in `bevy_ecs::query::QueryState::<Q, F>::new` --> crates/bevy_ecs/src/query/state.rs:49:32 | 49 | for<'x> QueryFetch<'x, F>: FilterFetch<'x>, | ^^^^^^^^^^^^^^^ required by this bound in `bevy_ecs::query::QueryState::<Q, F>::new` ``` --- Made with help from @BoxyUwU and @alice-i-cecile Co-authored-by: Boxy <supbscripter@gmail.com>
2022-04-27 23:44:06 +00:00
pub struct WriteFetch<'w, T> {
// T::Storage = TableStorage
table_data: Option<(
ThinSlicePtr<'w, UnsafeCell<T>>,
Split Component Ticks (#6547) # Objective Fixes #4884. `ComponentTicks` stores both added and changed ticks contiguously in the same 8 bytes. This is convenient when passing around both together, but causes half the bytes fetched from memory for the purposes of change detection to effectively go unused. This is inefficient when most queries (no filter, mutating *something*) only write out to the changed ticks. ## Solution Split the storage for change detection ticks into two separate `Vec`s inside `Column`. Fetch only what is needed during iteration. This also potentially also removes one blocker from autovectorization of dense queries. EDIT: This is confirmed to enable autovectorization of dense queries in `for_each` and `par_for_each` where possible. Unfortunately `iter` has other blockers that prevent it. ### TODO - [x] Microbenchmark - [x] Check if this allows query iteration to autovectorize simple loops. - [x] Clean up all of the spurious tuples now littered throughout the API ### Open Questions - ~~Is `Mut::is_added` absolutely necessary? Can we not just use `Added` or `ChangeTrackers`?~~ It's optimized out if unused. - ~~Does the fetch of the added ticks get optimized out if not used?~~ Yes it is. --- ## Changelog Added: `Tick`, a wrapper around a single change detection tick. Added: `Column::get_added_ticks` Added: `Column::get_column_ticks` Added: `SparseSet::get_added_ticks` Added: `SparseSet::get_column_ticks` Changed: `Column` now stores added and changed ticks separately internally. Changed: Most APIs returning `&UnsafeCell<ComponentTicks>` now returns `TickCells` instead, which contains two separate `&UnsafeCell<Tick>` for either component ticks. Changed: `Query::for_each(_mut)`, `Query::par_for_each(_mut)` will now leverage autovectorization to speed up query iteration where possible. ## Migration Guide TODO
2022-11-21 12:59:09 +00:00
ThinSlicePtr<'w, UnsafeCell<Tick>>,
ThinSlicePtr<'w, UnsafeCell<Tick>>,
)>,
Use lifetimed, type erased pointers in bevy_ecs (#3001) # Objective `bevy_ecs` has large amounts of unsafe code which is hard to get right and makes it difficult to audit for soundness. ## Solution Introduce lifetimed, type-erased pointers: `Ptr<'a>` `PtrMut<'a>` `OwningPtr<'a>'` and `ThinSlicePtr<'a, T>` which are newtypes around a raw pointer with a lifetime and conceptually representing strong invariants about the pointee and validity of the pointer. The process of converting bevy_ecs to use these has already caught multiple cases of unsound behavior. ## Changelog TL;DR for release notes: `bevy_ecs` now uses lifetimed, type-erased pointers internally, significantly improving safety and legibility without sacrificing performance. This should have approximately no end user impact, unless you were meddling with the (unfortunately public) internals of `bevy_ecs`. - `Fetch`, `FilterFetch` and `ReadOnlyFetch` trait no longer have a `'state` lifetime - this was unneeded - `ReadOnly/Fetch` associated types on `WorldQuery` are now on a new `WorldQueryGats<'world>` trait - was required to work around lack of Generic Associated Types (we wish to express `type Fetch<'a>: Fetch<'a>`) - `derive(WorldQuery)` no longer requires `'w` lifetime on struct - this was unneeded, and improves the end user experience - `EntityMut::get_unchecked_mut` returns `&'_ mut T` not `&'w mut T` - allows easier use of unsafe API with less footguns, and can be worked around via lifetime transmutery as a user - `Bundle::from_components` now takes a `ctx` parameter to pass to the `FnMut` closure - required because closure return types can't borrow from captures - `Fetch::init` takes `&'world World`, `Fetch::set_archetype` takes `&'world Archetype` and `&'world Tables`, `Fetch::set_table` takes `&'world Table` - allows types implementing `Fetch` to store borrows into world - `WorldQuery` trait now has a `shrink` fn to shorten the lifetime in `Fetch::<'a>::Item` - this works around lack of subtyping of assoc types, rust doesnt allow you to turn `<T as Fetch<'static>>::Item'` into `<T as Fetch<'a>>::Item'` - `QueryCombinationsIter` requires this - Most types implementing `Fetch` now have a lifetime `'w` - allows the fetches to store borrows of world data instead of using raw pointers ## Migration guide - `EntityMut::get_unchecked_mut` returns a more restricted lifetime, there is no general way to migrate this as it depends on your code - `Bundle::from_components` implementations must pass the `ctx` arg to `func` - `Bundle::from_components` callers have to use a fn arg instead of closure captures for borrowing from world - Remove lifetime args on `derive(WorldQuery)` structs as it is nonsensical - `<Q as WorldQuery>::ReadOnly/Fetch` should be changed to either `RO/QueryFetch<'world>` or `<Q as WorldQueryGats<'world>>::ReadOnly/Fetch` - `<F as Fetch<'w, 's>>` should be changed to `<F as Fetch<'w>>` - Change the fn sigs of `Fetch::init/set_archetype/set_table` to match respective trait fn sigs - Implement the required `fn shrink` on any `WorldQuery` implementations - Move assoc types `Fetch` and `ReadOnlyFetch` on `WorldQuery` impls to `WorldQueryGats` impls - Pass an appropriate `'world` lifetime to whatever fetch struct you are for some reason using ### Type inference regression in some cases rustc may give spurrious errors when attempting to infer the `F` parameter on a query/querystate this can be fixed by manually specifying the type, i.e. `QueryState::new::<_, ()>(world)`. The error is rather confusing: ```rust= error[E0271]: type mismatch resolving `<() as Fetch<'_>>::Item == bool` --> crates/bevy_pbr/src/render/light.rs:1413:30 | 1413 | main_view_query: QueryState::new(world), | ^^^^^^^^^^^^^^^ expected `bool`, found `()` | = note: required because of the requirements on the impl of `for<'x> FilterFetch<'x>` for `<() as WorldQueryGats<'x>>::Fetch` note: required by a bound in `bevy_ecs::query::QueryState::<Q, F>::new` --> crates/bevy_ecs/src/query/state.rs:49:32 | 49 | for<'x> QueryFetch<'x, F>: FilterFetch<'x>, | ^^^^^^^^^^^^^^^ required by this bound in `bevy_ecs::query::QueryState::<Q, F>::new` ``` --- Made with help from @BoxyUwU and @alice-i-cecile Co-authored-by: Boxy <supbscripter@gmail.com>
2022-04-27 23:44:06 +00:00
// T::Storage = SparseStorage
sparse_set: Option<&'w ComponentSparseSet>,
last_run: Tick,
this_run: Tick,
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
}
Fix safety invariants for `WorldQuery::fetch` and simplify cloning (#8246) # Objective Cloning a `WorldQuery` type's "fetch" struct was made unsafe in #5593, by adding the `unsafe fn clone_fetch` to `WorldQuery`. However, as that method's documentation explains, it is not the right place to put the safety invariant: > While calling this method on its own cannot cause UB it is marked `unsafe` as the caller must ensure that the returned value is not used in any way that would cause two `QueryItem<Self>` for the same `archetype_index` or `table_row` to be alive at the same time. You can clone a fetch struct all you want and it will never cause undefined behavior -- in order for something to go wrong, you need to improperly call `WorldQuery::fetch` with it (which is marked unsafe). Additionally, making it unsafe to clone a fetch struct does not even prevent undefined behavior, since there are other ways to incorrectly use a fetch struct. For example, you could just call fetch more than once for the same entity, which is not currently forbidden by any documented invariants. ## Solution Document a safety invariant on `WorldQuery::fetch` that requires the caller to not create aliased `WorldQueryItem`s for mutable types. Remove the `clone_fetch` function, and add the bound `Fetch: Clone` instead. --- ## Changelog - Removed the associated function `WorldQuery::clone_fetch`, and added a `Clone` bound to `WorldQuery::Fetch`. ## Migration Guide ### `fetch` invariants The function `WorldQuery::fetch` has had the following safety invariant added: > If this type does not implement `ReadOnlyWorldQuery`, then the caller must ensure that it is impossible for more than one `Self::Item` to exist for the same entity at any given time. This invariant was always required for soundness, but was previously undocumented. If you called this function manually anywhere, you should check to make sure that this invariant is not violated. ### Removed `clone_fetch` The function `WorldQuery::clone_fetch` has been removed. The associated type `WorldQuery::Fetch` now has the bound `Clone`. Before: ```rust struct MyFetch<'w> { ... } unsafe impl WorldQuery for MyQuery { ... type Fetch<'w> = MyFetch<'w> unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> { MyFetch { field1: fetch.field1, field2: fetch.field2.clone(), ... } } } ``` After: ```rust #[derive(Clone)] struct MyFetch<'w> { ... } unsafe impl WorldQuery for MyQuery { ... type Fetch<'w> = MyFetch<'w>; } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-07-25 21:16:22 +00:00
impl<T> Clone for WriteFetch<'_, T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for WriteFetch<'_, T> {}
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
/// SAFETY:
/// `fetch` accesses a single component mutably.
/// This is sound because `update_component_access` and `update_archetype_component_access` add write access for that component and panic when appropriate.
/// `update_component_access` adds a `With` filter for a component.
/// This is sound because `matches_component_set` returns whether the set contains that component.
unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
type Item<'w> = Mut<'w, T>;
type Fetch<'w> = WriteFetch<'w, T>;
type State = ComponentId;
Use lifetimed, type erased pointers in bevy_ecs (#3001) # Objective `bevy_ecs` has large amounts of unsafe code which is hard to get right and makes it difficult to audit for soundness. ## Solution Introduce lifetimed, type-erased pointers: `Ptr<'a>` `PtrMut<'a>` `OwningPtr<'a>'` and `ThinSlicePtr<'a, T>` which are newtypes around a raw pointer with a lifetime and conceptually representing strong invariants about the pointee and validity of the pointer. The process of converting bevy_ecs to use these has already caught multiple cases of unsound behavior. ## Changelog TL;DR for release notes: `bevy_ecs` now uses lifetimed, type-erased pointers internally, significantly improving safety and legibility without sacrificing performance. This should have approximately no end user impact, unless you were meddling with the (unfortunately public) internals of `bevy_ecs`. - `Fetch`, `FilterFetch` and `ReadOnlyFetch` trait no longer have a `'state` lifetime - this was unneeded - `ReadOnly/Fetch` associated types on `WorldQuery` are now on a new `WorldQueryGats<'world>` trait - was required to work around lack of Generic Associated Types (we wish to express `type Fetch<'a>: Fetch<'a>`) - `derive(WorldQuery)` no longer requires `'w` lifetime on struct - this was unneeded, and improves the end user experience - `EntityMut::get_unchecked_mut` returns `&'_ mut T` not `&'w mut T` - allows easier use of unsafe API with less footguns, and can be worked around via lifetime transmutery as a user - `Bundle::from_components` now takes a `ctx` parameter to pass to the `FnMut` closure - required because closure return types can't borrow from captures - `Fetch::init` takes `&'world World`, `Fetch::set_archetype` takes `&'world Archetype` and `&'world Tables`, `Fetch::set_table` takes `&'world Table` - allows types implementing `Fetch` to store borrows into world - `WorldQuery` trait now has a `shrink` fn to shorten the lifetime in `Fetch::<'a>::Item` - this works around lack of subtyping of assoc types, rust doesnt allow you to turn `<T as Fetch<'static>>::Item'` into `<T as Fetch<'a>>::Item'` - `QueryCombinationsIter` requires this - Most types implementing `Fetch` now have a lifetime `'w` - allows the fetches to store borrows of world data instead of using raw pointers ## Migration guide - `EntityMut::get_unchecked_mut` returns a more restricted lifetime, there is no general way to migrate this as it depends on your code - `Bundle::from_components` implementations must pass the `ctx` arg to `func` - `Bundle::from_components` callers have to use a fn arg instead of closure captures for borrowing from world - Remove lifetime args on `derive(WorldQuery)` structs as it is nonsensical - `<Q as WorldQuery>::ReadOnly/Fetch` should be changed to either `RO/QueryFetch<'world>` or `<Q as WorldQueryGats<'world>>::ReadOnly/Fetch` - `<F as Fetch<'w, 's>>` should be changed to `<F as Fetch<'w>>` - Change the fn sigs of `Fetch::init/set_archetype/set_table` to match respective trait fn sigs - Implement the required `fn shrink` on any `WorldQuery` implementations - Move assoc types `Fetch` and `ReadOnlyFetch` on `WorldQuery` impls to `WorldQueryGats` impls - Pass an appropriate `'world` lifetime to whatever fetch struct you are for some reason using ### Type inference regression in some cases rustc may give spurrious errors when attempting to infer the `F` parameter on a query/querystate this can be fixed by manually specifying the type, i.e. `QueryState::new::<_, ()>(world)`. The error is rather confusing: ```rust= error[E0271]: type mismatch resolving `<() as Fetch<'_>>::Item == bool` --> crates/bevy_pbr/src/render/light.rs:1413:30 | 1413 | main_view_query: QueryState::new(world), | ^^^^^^^^^^^^^^^ expected `bool`, found `()` | = note: required because of the requirements on the impl of `for<'x> FilterFetch<'x>` for `<() as WorldQueryGats<'x>>::Fetch` note: required by a bound in `bevy_ecs::query::QueryState::<Q, F>::new` --> crates/bevy_ecs/src/query/state.rs:49:32 | 49 | for<'x> QueryFetch<'x, F>: FilterFetch<'x>, | ^^^^^^^^^^^^^^^ required by this bound in `bevy_ecs::query::QueryState::<Q, F>::new` ``` --- Made with help from @BoxyUwU and @alice-i-cecile Co-authored-by: Boxy <supbscripter@gmail.com>
2022-04-27 23:44:06 +00:00
fn shrink<'wlong: 'wshort, 'wshort>(item: Mut<'wlong, T>) -> Mut<'wshort, T> {
item
}
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
#[inline]
unsafe fn init_fetch<'w>(
Migrate the rest of the engine to `UnsafeWorldCell` (#8833) # Objective Follow-up to #6404 and #8292. Mutating the world through a shared reference is surprising, and it makes the meaning of `&World` unclear: sometimes it gives read-only access to the entire world, and sometimes it gives interior mutable access to only part of it. This is an up-to-date version of #6972. ## Solution Use `UnsafeWorldCell` for all interior mutability. Now, `&World` *always* gives you read-only access to the entire world. --- ## Changelog TODO - do we still care about changelogs? ## Migration Guide Mutating any world data using `&World` is now considered unsound -- the type `UnsafeWorldCell` must be used to achieve interior mutability. The following methods now accept `UnsafeWorldCell` instead of `&World`: - `QueryState`: `get_unchecked`, `iter_unchecked`, `iter_combinations_unchecked`, `for_each_unchecked`, `get_single_unchecked`, `get_single_unchecked_manual`. - `SystemState`: `get_unchecked_manual` ```rust let mut world = World::new(); let mut query = world.query::<&mut T>(); // Before: let t1 = query.get_unchecked(&world, entity_1); let t2 = query.get_unchecked(&world, entity_2); // After: let world_cell = world.as_unsafe_world_cell(); let t1 = query.get_unchecked(world_cell, entity_1); let t2 = query.get_unchecked(world_cell, entity_2); ``` The methods `QueryState::validate_world` and `SystemState::matches_world` now take a `WorldId` instead of `&World`: ```rust // Before: query_state.validate_world(&world); // After: query_state.validate_world(world.id()); ``` The methods `QueryState::update_archetypes` and `SystemState::update_archetypes` now take `UnsafeWorldCell` instead of `&World`: ```rust // Before: query_state.update_archetypes(&world); // After: query_state.update_archetypes(world.as_unsafe_world_cell_readonly()); ```
2023-06-15 01:31:56 +00:00
world: UnsafeWorldCell<'w>,
&component_id: &ComponentId,
last_run: Tick,
this_run: Tick,
) -> WriteFetch<'w, T> {
WriteFetch {
table_data: None,
sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet).then(|| {
world
Migrate the rest of the engine to `UnsafeWorldCell` (#8833) # Objective Follow-up to #6404 and #8292. Mutating the world through a shared reference is surprising, and it makes the meaning of `&World` unclear: sometimes it gives read-only access to the entire world, and sometimes it gives interior mutable access to only part of it. This is an up-to-date version of #6972. ## Solution Use `UnsafeWorldCell` for all interior mutability. Now, `&World` *always* gives you read-only access to the entire world. --- ## Changelog TODO - do we still care about changelogs? ## Migration Guide Mutating any world data using `&World` is now considered unsound -- the type `UnsafeWorldCell` must be used to achieve interior mutability. The following methods now accept `UnsafeWorldCell` instead of `&World`: - `QueryState`: `get_unchecked`, `iter_unchecked`, `iter_combinations_unchecked`, `for_each_unchecked`, `get_single_unchecked`, `get_single_unchecked_manual`. - `SystemState`: `get_unchecked_manual` ```rust let mut world = World::new(); let mut query = world.query::<&mut T>(); // Before: let t1 = query.get_unchecked(&world, entity_1); let t2 = query.get_unchecked(&world, entity_2); // After: let world_cell = world.as_unsafe_world_cell(); let t1 = query.get_unchecked(world_cell, entity_1); let t2 = query.get_unchecked(world_cell, entity_2); ``` The methods `QueryState::validate_world` and `SystemState::matches_world` now take a `WorldId` instead of `&World`: ```rust // Before: query_state.validate_world(&world); // After: query_state.validate_world(world.id()); ``` The methods `QueryState::update_archetypes` and `SystemState::update_archetypes` now take `UnsafeWorldCell` instead of `&World`: ```rust // Before: query_state.update_archetypes(&world); // After: query_state.update_archetypes(world.as_unsafe_world_cell_readonly()); ```
2023-06-15 01:31:56 +00:00
// SAFETY: See &T::init_fetch.
.storages()
.sparse_sets
.get(component_id)
Remove unnecesary branches/panics from Query accesses (#6461) # Objective Supercedes #6452. Upon inspection of the [generated assembly](https://gist.github.com/james7132/c2740c6941b80d7912f1e8888e223cbb#file-original-s) of a [simple Bevy binary](https://gist.github.com/james7132/c2740c6941b80d7912f1e8888e223cbb#file-source-rs) compiled with `cargo rustc --release -- --emit asm`, it's apparent that there are multiple unnecessary branches in the generated assembly: ```assembly .LBB5_5: cmpq %r10, %r11 je .LBB5_15 movq (%r11), %rcx movq 328(%r15), %rdx cmpq %rdx, %rcx jae .LBB5_14 movq 312(%r15), %rdi leaq (%rcx,%rcx,2), %rcx shlq $5, %rcx movq 336(%r12), %rdx movq 64(%rdi,%rcx), %rax cmpq %rdx, %rax jbe .LBB5_4 leaq (%rdi,%rcx), %rsi movq 48(%rsi), %rbp shlq $4, %rdx cmpq $0, (%rbp,%rdx) je .LBB5_4 movq 344(%r12), %rbx cmpq %rbx, %rax jbe .LBB5_4 shlq $4, %rbx cmpq $0, (%rbp,%rbx) je .LBB5_4 addq $8, %r11 movq 88(%rdi,%rcx), %rcx testq %rcx, %rcx je .LBB5_5 movq (%rsi), %rax movq 8(%rbp,%rdx), %rdx leaq (%rdx,%rdx,4), %rdi shlq $4, %rdi movq 32(%rax,%rdi), %rdx movq 56(%rax,%rdi), %r8 movq 8(%rbp,%rbx), %rbp leaq (%rbp,%rbp,4), %rbp shlq $4, %rbp movq 32(%rax,%rbp), %r9 xorl %ebp, %ebp jmp .LBB5_13 .p2align 4, 0x90 ``` Almost every one of the instructions starting with `j` is a potential branch, which can significantly slow down accesses. Of these, two labels are both common and never used: ```asm .LBB5_14: leaq __unnamed_2(%rip), %r8 callq _ZN4core9panicking18panic_bounds_check17h70367088e72af65aE ud2 .LBB5_4: callq _ZN8bevy_ecs5query25debug_checked_unreachable17h0855ff520ceaea77E ud2 .seh_endproc ``` These correpsond to subprocedure calls to panicking due to out of bounds from indexing `Tables` and `debug_checked_unreadable`. Both of which should be inlined and optimized out, but are not. ## Solution Make `debug_checked_unreachable` a macro to forcibly inline either `unreachable!()` in debug builds, and `std::hint::unreachable_unchecked()` in release mode. Replace the `Tables` and `Archetype` index access with `get(id).unwrap_or_else(|| debug_checked_unreachable!())` to assume that the table or archetype provided exists. This has no external breaking change of any kind. The equivalent section of code with these changes removes most of the conditional jump instructions: ```asm .LBB5_5: movss (%rbx,%rbp,4), %xmm0 movl %r14d, 4(%r8,%rbp,8) addss (%rdi,%rbp,4), %xmm0 movss %xmm0, (%rdi,%rbp,4) incq %rbp .LBB5_1: cmpq %rdx, %rbp jne .LBB5_5 .p2align 4, 0x90 .LBB5_2: cmpq %rcx, %rax je .LBB5_6 movq (%rax), %rdx addq $8, %rax movq 312(%rsi), %rbp leaq (%rdx,%rdx,2), %rbx shlq $5, %rbx movq 88(%rbp,%rbx), %rdx testq %rdx, %rdx je .LBB5_2 leaq (%rbx,%rbp), %r8 movq 336(%r15), %rdi movq 344(%r15), %r9 movq 48(%rbp,%rbx), %r10 shlq $4, %rdi movq (%r8), %rbx movq 8(%r10,%rdi), %rdi leaq (%rdi,%rdi,4), %rbp shlq $4, %rbp movq 32(%rbx,%rbp), %rdi movq 56(%rbx,%rbp), %r8 shlq $4, %r9 movq 8(%r10,%r9), %rbp leaq (%rbp,%rbp,4), %rbp shlq $4, %rbp movq 32(%rbx,%rbp), %rbx xorl %ebp, %ebp jmp .LBB5_5 .LBB5_6: addq $40, %rsp popq %rbx popq %rbp popq %rdi popq %rsi popq %r14 popq %r15 retq .seh_endproc ``` ## Performance Microbenchmarks results: <details> ``` group main no-panic-query ----- ---- -------------- busy_systems/01x_entities_03_systems 1.20 42.4±2.66µs ? ?/sec 1.00 35.3±1.68µs ? ?/sec busy_systems/01x_entities_06_systems 1.32 83.8±3.50µs ? ?/sec 1.00 63.6±1.72µs ? ?/sec busy_systems/01x_entities_09_systems 1.15 113.3±8.90µs ? ?/sec 1.00 98.2±6.15µs ? ?/sec busy_systems/01x_entities_12_systems 1.27 160.8±32.44µs ? ?/sec 1.00 126.6±4.70µs ? ?/sec busy_systems/01x_entities_15_systems 1.12 179.6±3.71µs ? ?/sec 1.00 160.3±11.03µs ? ?/sec busy_systems/02x_entities_03_systems 1.18 76.8±3.14µs ? ?/sec 1.00 65.2±3.17µs ? ?/sec busy_systems/02x_entities_06_systems 1.16 144.6±6.10µs ? ?/sec 1.00 124.5±5.14µs ? ?/sec busy_systems/02x_entities_09_systems 1.19 215.3±9.18µs ? ?/sec 1.00 181.5±5.67µs ? ?/sec busy_systems/02x_entities_12_systems 1.20 266.7±8.33µs ? ?/sec 1.00 222.0±9.53µs ? ?/sec busy_systems/02x_entities_15_systems 1.23 338.8±10.53µs ? ?/sec 1.00 276.3±6.94µs ? ?/sec busy_systems/03x_entities_03_systems 1.43 113.5±5.06µs ? ?/sec 1.00 79.6±1.49µs ? ?/sec busy_systems/03x_entities_06_systems 1.38 217.3±12.67µs ? ?/sec 1.00 157.5±3.07µs ? ?/sec busy_systems/03x_entities_09_systems 1.23 308.8±24.75µs ? ?/sec 1.00 251.6±8.93µs ? ?/sec busy_systems/03x_entities_12_systems 1.05 347.7±12.43µs ? ?/sec 1.00 330.6±11.43µs ? ?/sec busy_systems/03x_entities_15_systems 1.13 455.5±13.88µs ? ?/sec 1.00 401.7±17.29µs ? ?/sec busy_systems/04x_entities_03_systems 1.24 144.7±5.89µs ? ?/sec 1.00 116.9±6.29µs ? ?/sec busy_systems/04x_entities_06_systems 1.24 282.8±21.40µs ? ?/sec 1.00 228.6±21.31µs ? ?/sec busy_systems/04x_entities_09_systems 1.35 431.8±14.10µs ? ?/sec 1.00 319.6±9.83µs ? ?/sec busy_systems/04x_entities_12_systems 1.16 493.8±22.87µs ? ?/sec 1.00 424.9±15.24µs ? ?/sec busy_systems/04x_entities_15_systems 1.10 587.5±23.25µs ? ?/sec 1.00 531.7±16.32µs ? ?/sec busy_systems/05x_entities_03_systems 1.14 148.2±9.61µs ? ?/sec 1.00 129.5±4.32µs ? ?/sec busy_systems/05x_entities_06_systems 1.31 359.7±17.46µs ? ?/sec 1.00 273.6±10.55µs ? ?/sec busy_systems/05x_entities_09_systems 1.22 473.5±23.11µs ? ?/sec 1.00 389.3±13.62µs ? ?/sec busy_systems/05x_entities_12_systems 1.05 562.9±20.76µs ? ?/sec 1.00 536.5±24.35µs ? ?/sec busy_systems/05x_entities_15_systems 1.23 818.5±28.70µs ? ?/sec 1.00 666.6±45.87µs ? ?/sec contrived/01x_entities_03_systems 1.27 27.5±0.49µs ? ?/sec 1.00 21.6±1.71µs ? ?/sec contrived/01x_entities_06_systems 1.22 49.9±1.18µs ? ?/sec 1.00 40.7±2.62µs ? ?/sec contrived/01x_entities_09_systems 1.30 72.3±2.39µs ? ?/sec 1.00 55.4±2.60µs ? ?/sec contrived/01x_entities_12_systems 1.28 94.3±9.44µs ? ?/sec 1.00 73.7±3.62µs ? ?/sec contrived/01x_entities_15_systems 1.25 118.0±2.43µs ? ?/sec 1.00 94.1±3.99µs ? ?/sec contrived/02x_entities_03_systems 1.23 41.6±1.71µs ? ?/sec 1.00 33.7±2.30µs ? ?/sec contrived/02x_entities_06_systems 1.19 78.6±2.63µs ? ?/sec 1.00 65.9±2.35µs ? ?/sec contrived/02x_entities_09_systems 1.28 113.6±3.60µs ? ?/sec 1.00 88.6±3.60µs ? ?/sec contrived/02x_entities_12_systems 1.20 146.4±5.75µs ? ?/sec 1.00 121.7±3.35µs ? ?/sec contrived/02x_entities_15_systems 1.23 178.5±4.86µs ? ?/sec 1.00 145.7±4.00µs ? ?/sec contrived/03x_entities_03_systems 1.42 58.3±2.77µs ? ?/sec 1.00 41.1±1.54µs ? ?/sec contrived/03x_entities_06_systems 1.32 108.5±7.30µs ? ?/sec 1.00 82.4±4.86µs ? ?/sec contrived/03x_entities_09_systems 1.23 153.7±4.61µs ? ?/sec 1.00 125.0±4.76µs ? ?/sec contrived/03x_entities_12_systems 1.18 197.5±5.12µs ? ?/sec 1.00 166.8±8.14µs ? ?/sec contrived/03x_entities_15_systems 1.23 238.8±6.38µs ? ?/sec 1.00 194.6±4.55µs ? ?/sec contrived/04x_entities_03_systems 1.34 66.4±3.42µs ? ?/sec 1.00 49.5±1.98µs ? ?/sec contrived/04x_entities_06_systems 1.27 134.3±4.86µs ? ?/sec 1.00 105.8±3.58µs ? ?/sec contrived/04x_entities_09_systems 1.26 193.2±3.83µs ? ?/sec 1.00 153.0±5.60µs ? ?/sec contrived/04x_entities_12_systems 1.16 237.1±5.78µs ? ?/sec 1.00 204.9±18.77µs ? ?/sec contrived/04x_entities_15_systems 1.17 289.2±4.76µs ? ?/sec 1.00 246.3±8.57µs ? ?/sec contrived/05x_entities_03_systems 1.26 80.4±2.90µs ? ?/sec 1.00 63.7±3.07µs ? ?/sec contrived/05x_entities_06_systems 1.27 161.6±13.47µs ? ?/sec 1.00 127.2±5.59µs ? ?/sec contrived/05x_entities_09_systems 1.22 228.0±7.76µs ? ?/sec 1.00 186.2±7.68µs ? ?/sec contrived/05x_entities_12_systems 1.20 289.5±6.21µs ? ?/sec 1.00 241.8±7.52µs ? ?/sec contrived/05x_entities_15_systems 1.18 357.3±11.24µs ? ?/sec 1.00 302.7±7.21µs ? ?/sec heavy_compute/base 1.01 302.4±3.52µs ? ?/sec 1.00 300.2±3.40µs ? ?/sec iter_fragmented/base 1.00 348.1±7.51ns ? ?/sec 1.01 351.9±8.32ns ? ?/sec iter_fragmented/foreach 1.03 239.8±23.78ns ? ?/sec 1.00 233.8±18.12ns ? ?/sec iter_fragmented/foreach_wide 1.00 3.9±0.13µs ? ?/sec 1.02 4.0±0.22µs ? ?/sec iter_fragmented/wide 1.18 4.6±0.15µs ? ?/sec 1.00 3.9±0.10µs ? ?/sec iter_fragmented_sparse/base 1.02 8.1±0.15ns ? ?/sec 1.00 7.9±0.56ns ? ?/sec iter_fragmented_sparse/foreach 1.00 7.8±0.22ns ? ?/sec 1.01 7.9±0.62ns ? ?/sec iter_fragmented_sparse/foreach_wide 1.00 37.2±1.17ns ? ?/sec 1.10 40.9±0.95ns ? ?/sec iter_fragmented_sparse/wide 1.09 48.4±2.13ns ? ?/sec 1.00 44.5±18.34ns ? ?/sec iter_simple/base 1.02 8.4±0.10µs ? ?/sec 1.00 8.2±0.14µs ? ?/sec iter_simple/foreach 1.01 8.3±0.07µs ? ?/sec 1.00 8.2±0.09µs ? ?/sec iter_simple/foreach_sparse_set 1.00 25.3±0.32µs ? ?/sec 1.02 25.7±0.42µs ? ?/sec iter_simple/foreach_wide 1.03 41.1±0.94µs ? ?/sec 1.00 39.9±0.41µs ? ?/sec iter_simple/foreach_wide_sparse_set 1.05 123.6±2.05µs ? ?/sec 1.00 118.1±2.78µs ? ?/sec iter_simple/sparse_set 1.14 30.5±1.40µs ? ?/sec 1.00 26.9±0.64µs ? ?/sec iter_simple/system 1.01 8.4±0.25µs ? ?/sec 1.00 8.4±0.11µs ? ?/sec iter_simple/wide 1.18 48.2±0.62µs ? ?/sec 1.00 40.7±0.38µs ? ?/sec iter_simple/wide_sparse_set 1.12 140.8±21.56µs ? ?/sec 1.00 126.0±2.30µs ? ?/sec query_get/50000_entities_sparse 1.17 378.6±7.60µs ? ?/sec 1.00 324.1±23.17µs ? ?/sec query_get/50000_entities_table 1.08 330.9±10.90µs ? ?/sec 1.00 306.8±4.98µs ? ?/sec query_get_component/50000_entities_sparse 1.00 976.7±19.55µs ? ?/sec 1.00 979.8±35.87µs ? ?/sec query_get_component/50000_entities_table 1.00 1029.0±15.11µs ? ?/sec 1.05 1080.0±59.18µs ? ?/sec query_get_component_simple/system 1.13 839.7±14.18µs ? ?/sec 1.00 742.8±10.72µs ? ?/sec query_get_component_simple/unchecked 1.01 909.0±15.17µs ? ?/sec 1.00 898.0±13.56µs ? ?/sec query_get_many_10/50000_calls_sparse 1.04 5.5±0.54ms ? ?/sec 1.00 5.3±0.67ms ? ?/sec query_get_many_10/50000_calls_table 1.01 4.9±0.49ms ? ?/sec 1.00 4.8±0.45ms ? ?/sec query_get_many_2/50000_calls_sparse 1.28 848.4±210.89µs ? ?/sec 1.00 664.8±47.69µs ? ?/sec query_get_many_2/50000_calls_table 1.05 779.0±73.85µs ? ?/sec 1.00 739.2±83.02µs ? ?/sec query_get_many_5/50000_calls_sparse 1.05 2.4±0.37ms ? ?/sec 1.00 2.3±0.33ms ? ?/sec query_get_many_5/50000_calls_table 1.00 1939.9±75.22µs ? ?/sec 1.04 2.0±0.19ms ? ?/sec run_criteria/yes_using_query/001_systems 1.00 3.7±0.38µs ? ?/sec 1.30 4.9±0.14µs ? ?/sec run_criteria/yes_using_query/006_systems 1.00 8.9±0.40µs ? ?/sec 1.17 10.3±0.57µs ? ?/sec run_criteria/yes_using_query/011_systems 1.00 13.9±0.49µs ? ?/sec 1.08 15.0±0.89µs ? ?/sec run_criteria/yes_using_query/016_systems 1.00 18.8±0.74µs ? ?/sec 1.00 18.8±1.43µs ? ?/sec run_criteria/yes_using_query/021_systems 1.07 24.1±0.87µs ? ?/sec 1.00 22.6±1.58µs ? ?/sec run_criteria/yes_using_query/026_systems 1.04 27.9±0.62µs ? ?/sec 1.00 26.8±1.71µs ? ?/sec run_criteria/yes_using_query/031_systems 1.09 33.3±1.03µs ? ?/sec 1.00 30.5±2.18µs ? ?/sec run_criteria/yes_using_query/036_systems 1.14 38.7±0.80µs ? ?/sec 1.00 33.9±1.75µs ? ?/sec run_criteria/yes_using_query/041_systems 1.18 43.7±1.07µs ? ?/sec 1.00 37.0±2.39µs ? ?/sec run_criteria/yes_using_query/046_systems 1.14 47.6±1.16µs ? ?/sec 1.00 41.9±2.09µs ? ?/sec run_criteria/yes_using_query/051_systems 1.17 52.9±2.04µs ? ?/sec 1.00 45.3±1.75µs ? ?/sec run_criteria/yes_using_query/056_systems 1.25 59.2±2.38µs ? ?/sec 1.00 47.2±2.01µs ? ?/sec run_criteria/yes_using_query/061_systems 1.28 66.1±15.84µs ? ?/sec 1.00 51.5±2.47µs ? ?/sec run_criteria/yes_using_query/066_systems 1.28 70.2±2.57µs ? ?/sec 1.00 54.7±2.58µs ? ?/sec run_criteria/yes_using_query/071_systems 1.30 75.5±2.27µs ? ?/sec 1.00 58.2±3.31µs ? ?/sec run_criteria/yes_using_query/076_systems 1.26 81.5±2.66µs ? ?/sec 1.00 64.5±3.13µs ? ?/sec run_criteria/yes_using_query/081_systems 1.29 89.7±2.58µs ? ?/sec 1.00 69.3±3.47µs ? ?/sec run_criteria/yes_using_query/086_systems 1.33 95.6±3.39µs ? ?/sec 1.00 71.8±3.48µs ? ?/sec run_criteria/yes_using_query/091_systems 1.25 102.0±3.67µs ? ?/sec 1.00 81.4±4.82µs ? ?/sec run_criteria/yes_using_query/096_systems 1.33 111.7±3.29µs ? ?/sec 1.00 83.8±4.15µs ? ?/sec run_criteria/yes_using_query/101_systems 1.29 113.2±12.04µs ? ?/sec 1.00 87.7±5.15µs ? ?/sec world_query_for_each/50000_entities_sparse 1.00 47.4±0.51µs ? ?/sec 1.00 47.3±0.33µs ? ?/sec world_query_for_each/50000_entities_table 1.00 27.2±0.50µs ? ?/sec 1.00 27.2±0.17µs ? ?/sec world_query_get/50000_entities_sparse_wide 1.09 210.5±1.78µs ? ?/sec 1.00 192.5±2.61µs ? ?/sec world_query_get/50000_entities_table 1.00 127.7±2.09µs ? ?/sec 1.07 136.2±5.95µs ? ?/sec world_query_get/50000_entities_table_wide 1.00 209.8±2.37µs ? ?/sec 1.15 240.6±2.04µs ? ?/sec world_query_iter/50000_entities_sparse 1.00 54.2±0.36µs ? ?/sec 1.01 54.7±0.61µs ? ?/sec world_query_iter/50000_entities_table 1.00 27.2±0.31µs ? ?/sec 1.00 27.3±0.64µs ? ?/sec ``` </details> NOTE: This PR includes a change to enable LTO on our benchmarks to get a "fully optimized" baseline for our benchmarks. Both the main and the current PR's results were with LTO enabled.
2022-11-04 06:04:55 +00:00
.debug_checked_unwrap()
}),
last_run,
this_run,
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
}
}
const IS_DENSE: bool = {
match T::Storage::STORAGE_TYPE {
StorageType::Table => true,
StorageType::SparseSet => false,
}
};
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
#[inline]
unsafe fn set_archetype<'w>(
fetch: &mut WriteFetch<'w, T>,
component_id: &ComponentId,
_archetype: &'w Archetype,
table: &'w Table,
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
) {
if Self::IS_DENSE {
Self::set_table(fetch, component_id, table);
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
}
}
#[inline]
unsafe fn set_table<'w>(
fetch: &mut WriteFetch<'w, T>,
&component_id: &ComponentId,
table: &'w Table,
) {
Remove unnecesary branches/panics from Query accesses (#6461) # Objective Supercedes #6452. Upon inspection of the [generated assembly](https://gist.github.com/james7132/c2740c6941b80d7912f1e8888e223cbb#file-original-s) of a [simple Bevy binary](https://gist.github.com/james7132/c2740c6941b80d7912f1e8888e223cbb#file-source-rs) compiled with `cargo rustc --release -- --emit asm`, it's apparent that there are multiple unnecessary branches in the generated assembly: ```assembly .LBB5_5: cmpq %r10, %r11 je .LBB5_15 movq (%r11), %rcx movq 328(%r15), %rdx cmpq %rdx, %rcx jae .LBB5_14 movq 312(%r15), %rdi leaq (%rcx,%rcx,2), %rcx shlq $5, %rcx movq 336(%r12), %rdx movq 64(%rdi,%rcx), %rax cmpq %rdx, %rax jbe .LBB5_4 leaq (%rdi,%rcx), %rsi movq 48(%rsi), %rbp shlq $4, %rdx cmpq $0, (%rbp,%rdx) je .LBB5_4 movq 344(%r12), %rbx cmpq %rbx, %rax jbe .LBB5_4 shlq $4, %rbx cmpq $0, (%rbp,%rbx) je .LBB5_4 addq $8, %r11 movq 88(%rdi,%rcx), %rcx testq %rcx, %rcx je .LBB5_5 movq (%rsi), %rax movq 8(%rbp,%rdx), %rdx leaq (%rdx,%rdx,4), %rdi shlq $4, %rdi movq 32(%rax,%rdi), %rdx movq 56(%rax,%rdi), %r8 movq 8(%rbp,%rbx), %rbp leaq (%rbp,%rbp,4), %rbp shlq $4, %rbp movq 32(%rax,%rbp), %r9 xorl %ebp, %ebp jmp .LBB5_13 .p2align 4, 0x90 ``` Almost every one of the instructions starting with `j` is a potential branch, which can significantly slow down accesses. Of these, two labels are both common and never used: ```asm .LBB5_14: leaq __unnamed_2(%rip), %r8 callq _ZN4core9panicking18panic_bounds_check17h70367088e72af65aE ud2 .LBB5_4: callq _ZN8bevy_ecs5query25debug_checked_unreachable17h0855ff520ceaea77E ud2 .seh_endproc ``` These correpsond to subprocedure calls to panicking due to out of bounds from indexing `Tables` and `debug_checked_unreadable`. Both of which should be inlined and optimized out, but are not. ## Solution Make `debug_checked_unreachable` a macro to forcibly inline either `unreachable!()` in debug builds, and `std::hint::unreachable_unchecked()` in release mode. Replace the `Tables` and `Archetype` index access with `get(id).unwrap_or_else(|| debug_checked_unreachable!())` to assume that the table or archetype provided exists. This has no external breaking change of any kind. The equivalent section of code with these changes removes most of the conditional jump instructions: ```asm .LBB5_5: movss (%rbx,%rbp,4), %xmm0 movl %r14d, 4(%r8,%rbp,8) addss (%rdi,%rbp,4), %xmm0 movss %xmm0, (%rdi,%rbp,4) incq %rbp .LBB5_1: cmpq %rdx, %rbp jne .LBB5_5 .p2align 4, 0x90 .LBB5_2: cmpq %rcx, %rax je .LBB5_6 movq (%rax), %rdx addq $8, %rax movq 312(%rsi), %rbp leaq (%rdx,%rdx,2), %rbx shlq $5, %rbx movq 88(%rbp,%rbx), %rdx testq %rdx, %rdx je .LBB5_2 leaq (%rbx,%rbp), %r8 movq 336(%r15), %rdi movq 344(%r15), %r9 movq 48(%rbp,%rbx), %r10 shlq $4, %rdi movq (%r8), %rbx movq 8(%r10,%rdi), %rdi leaq (%rdi,%rdi,4), %rbp shlq $4, %rbp movq 32(%rbx,%rbp), %rdi movq 56(%rbx,%rbp), %r8 shlq $4, %r9 movq 8(%r10,%r9), %rbp leaq (%rbp,%rbp,4), %rbp shlq $4, %rbp movq 32(%rbx,%rbp), %rbx xorl %ebp, %ebp jmp .LBB5_5 .LBB5_6: addq $40, %rsp popq %rbx popq %rbp popq %rdi popq %rsi popq %r14 popq %r15 retq .seh_endproc ``` ## Performance Microbenchmarks results: <details> ``` group main no-panic-query ----- ---- -------------- busy_systems/01x_entities_03_systems 1.20 42.4±2.66µs ? ?/sec 1.00 35.3±1.68µs ? ?/sec busy_systems/01x_entities_06_systems 1.32 83.8±3.50µs ? ?/sec 1.00 63.6±1.72µs ? ?/sec busy_systems/01x_entities_09_systems 1.15 113.3±8.90µs ? ?/sec 1.00 98.2±6.15µs ? ?/sec busy_systems/01x_entities_12_systems 1.27 160.8±32.44µs ? ?/sec 1.00 126.6±4.70µs ? ?/sec busy_systems/01x_entities_15_systems 1.12 179.6±3.71µs ? ?/sec 1.00 160.3±11.03µs ? ?/sec busy_systems/02x_entities_03_systems 1.18 76.8±3.14µs ? ?/sec 1.00 65.2±3.17µs ? ?/sec busy_systems/02x_entities_06_systems 1.16 144.6±6.10µs ? ?/sec 1.00 124.5±5.14µs ? ?/sec busy_systems/02x_entities_09_systems 1.19 215.3±9.18µs ? ?/sec 1.00 181.5±5.67µs ? ?/sec busy_systems/02x_entities_12_systems 1.20 266.7±8.33µs ? ?/sec 1.00 222.0±9.53µs ? ?/sec busy_systems/02x_entities_15_systems 1.23 338.8±10.53µs ? ?/sec 1.00 276.3±6.94µs ? ?/sec busy_systems/03x_entities_03_systems 1.43 113.5±5.06µs ? ?/sec 1.00 79.6±1.49µs ? ?/sec busy_systems/03x_entities_06_systems 1.38 217.3±12.67µs ? ?/sec 1.00 157.5±3.07µs ? ?/sec busy_systems/03x_entities_09_systems 1.23 308.8±24.75µs ? ?/sec 1.00 251.6±8.93µs ? ?/sec busy_systems/03x_entities_12_systems 1.05 347.7±12.43µs ? ?/sec 1.00 330.6±11.43µs ? ?/sec busy_systems/03x_entities_15_systems 1.13 455.5±13.88µs ? ?/sec 1.00 401.7±17.29µs ? ?/sec busy_systems/04x_entities_03_systems 1.24 144.7±5.89µs ? ?/sec 1.00 116.9±6.29µs ? ?/sec busy_systems/04x_entities_06_systems 1.24 282.8±21.40µs ? ?/sec 1.00 228.6±21.31µs ? ?/sec busy_systems/04x_entities_09_systems 1.35 431.8±14.10µs ? ?/sec 1.00 319.6±9.83µs ? ?/sec busy_systems/04x_entities_12_systems 1.16 493.8±22.87µs ? ?/sec 1.00 424.9±15.24µs ? ?/sec busy_systems/04x_entities_15_systems 1.10 587.5±23.25µs ? ?/sec 1.00 531.7±16.32µs ? ?/sec busy_systems/05x_entities_03_systems 1.14 148.2±9.61µs ? ?/sec 1.00 129.5±4.32µs ? ?/sec busy_systems/05x_entities_06_systems 1.31 359.7±17.46µs ? ?/sec 1.00 273.6±10.55µs ? ?/sec busy_systems/05x_entities_09_systems 1.22 473.5±23.11µs ? ?/sec 1.00 389.3±13.62µs ? ?/sec busy_systems/05x_entities_12_systems 1.05 562.9±20.76µs ? ?/sec 1.00 536.5±24.35µs ? ?/sec busy_systems/05x_entities_15_systems 1.23 818.5±28.70µs ? ?/sec 1.00 666.6±45.87µs ? ?/sec contrived/01x_entities_03_systems 1.27 27.5±0.49µs ? ?/sec 1.00 21.6±1.71µs ? ?/sec contrived/01x_entities_06_systems 1.22 49.9±1.18µs ? ?/sec 1.00 40.7±2.62µs ? ?/sec contrived/01x_entities_09_systems 1.30 72.3±2.39µs ? ?/sec 1.00 55.4±2.60µs ? ?/sec contrived/01x_entities_12_systems 1.28 94.3±9.44µs ? ?/sec 1.00 73.7±3.62µs ? ?/sec contrived/01x_entities_15_systems 1.25 118.0±2.43µs ? ?/sec 1.00 94.1±3.99µs ? ?/sec contrived/02x_entities_03_systems 1.23 41.6±1.71µs ? ?/sec 1.00 33.7±2.30µs ? ?/sec contrived/02x_entities_06_systems 1.19 78.6±2.63µs ? ?/sec 1.00 65.9±2.35µs ? ?/sec contrived/02x_entities_09_systems 1.28 113.6±3.60µs ? ?/sec 1.00 88.6±3.60µs ? ?/sec contrived/02x_entities_12_systems 1.20 146.4±5.75µs ? ?/sec 1.00 121.7±3.35µs ? ?/sec contrived/02x_entities_15_systems 1.23 178.5±4.86µs ? ?/sec 1.00 145.7±4.00µs ? ?/sec contrived/03x_entities_03_systems 1.42 58.3±2.77µs ? ?/sec 1.00 41.1±1.54µs ? ?/sec contrived/03x_entities_06_systems 1.32 108.5±7.30µs ? ?/sec 1.00 82.4±4.86µs ? ?/sec contrived/03x_entities_09_systems 1.23 153.7±4.61µs ? ?/sec 1.00 125.0±4.76µs ? ?/sec contrived/03x_entities_12_systems 1.18 197.5±5.12µs ? ?/sec 1.00 166.8±8.14µs ? ?/sec contrived/03x_entities_15_systems 1.23 238.8±6.38µs ? ?/sec 1.00 194.6±4.55µs ? ?/sec contrived/04x_entities_03_systems 1.34 66.4±3.42µs ? ?/sec 1.00 49.5±1.98µs ? ?/sec contrived/04x_entities_06_systems 1.27 134.3±4.86µs ? ?/sec 1.00 105.8±3.58µs ? ?/sec contrived/04x_entities_09_systems 1.26 193.2±3.83µs ? ?/sec 1.00 153.0±5.60µs ? ?/sec contrived/04x_entities_12_systems 1.16 237.1±5.78µs ? ?/sec 1.00 204.9±18.77µs ? ?/sec contrived/04x_entities_15_systems 1.17 289.2±4.76µs ? ?/sec 1.00 246.3±8.57µs ? ?/sec contrived/05x_entities_03_systems 1.26 80.4±2.90µs ? ?/sec 1.00 63.7±3.07µs ? ?/sec contrived/05x_entities_06_systems 1.27 161.6±13.47µs ? ?/sec 1.00 127.2±5.59µs ? ?/sec contrived/05x_entities_09_systems 1.22 228.0±7.76µs ? ?/sec 1.00 186.2±7.68µs ? ?/sec contrived/05x_entities_12_systems 1.20 289.5±6.21µs ? ?/sec 1.00 241.8±7.52µs ? ?/sec contrived/05x_entities_15_systems 1.18 357.3±11.24µs ? ?/sec 1.00 302.7±7.21µs ? ?/sec heavy_compute/base 1.01 302.4±3.52µs ? ?/sec 1.00 300.2±3.40µs ? ?/sec iter_fragmented/base 1.00 348.1±7.51ns ? ?/sec 1.01 351.9±8.32ns ? ?/sec iter_fragmented/foreach 1.03 239.8±23.78ns ? ?/sec 1.00 233.8±18.12ns ? ?/sec iter_fragmented/foreach_wide 1.00 3.9±0.13µs ? ?/sec 1.02 4.0±0.22µs ? ?/sec iter_fragmented/wide 1.18 4.6±0.15µs ? ?/sec 1.00 3.9±0.10µs ? ?/sec iter_fragmented_sparse/base 1.02 8.1±0.15ns ? ?/sec 1.00 7.9±0.56ns ? ?/sec iter_fragmented_sparse/foreach 1.00 7.8±0.22ns ? ?/sec 1.01 7.9±0.62ns ? ?/sec iter_fragmented_sparse/foreach_wide 1.00 37.2±1.17ns ? ?/sec 1.10 40.9±0.95ns ? ?/sec iter_fragmented_sparse/wide 1.09 48.4±2.13ns ? ?/sec 1.00 44.5±18.34ns ? ?/sec iter_simple/base 1.02 8.4±0.10µs ? ?/sec 1.00 8.2±0.14µs ? ?/sec iter_simple/foreach 1.01 8.3±0.07µs ? ?/sec 1.00 8.2±0.09µs ? ?/sec iter_simple/foreach_sparse_set 1.00 25.3±0.32µs ? ?/sec 1.02 25.7±0.42µs ? ?/sec iter_simple/foreach_wide 1.03 41.1±0.94µs ? ?/sec 1.00 39.9±0.41µs ? ?/sec iter_simple/foreach_wide_sparse_set 1.05 123.6±2.05µs ? ?/sec 1.00 118.1±2.78µs ? ?/sec iter_simple/sparse_set 1.14 30.5±1.40µs ? ?/sec 1.00 26.9±0.64µs ? ?/sec iter_simple/system 1.01 8.4±0.25µs ? ?/sec 1.00 8.4±0.11µs ? ?/sec iter_simple/wide 1.18 48.2±0.62µs ? ?/sec 1.00 40.7±0.38µs ? ?/sec iter_simple/wide_sparse_set 1.12 140.8±21.56µs ? ?/sec 1.00 126.0±2.30µs ? ?/sec query_get/50000_entities_sparse 1.17 378.6±7.60µs ? ?/sec 1.00 324.1±23.17µs ? ?/sec query_get/50000_entities_table 1.08 330.9±10.90µs ? ?/sec 1.00 306.8±4.98µs ? ?/sec query_get_component/50000_entities_sparse 1.00 976.7±19.55µs ? ?/sec 1.00 979.8±35.87µs ? ?/sec query_get_component/50000_entities_table 1.00 1029.0±15.11µs ? ?/sec 1.05 1080.0±59.18µs ? ?/sec query_get_component_simple/system 1.13 839.7±14.18µs ? ?/sec 1.00 742.8±10.72µs ? ?/sec query_get_component_simple/unchecked 1.01 909.0±15.17µs ? ?/sec 1.00 898.0±13.56µs ? ?/sec query_get_many_10/50000_calls_sparse 1.04 5.5±0.54ms ? ?/sec 1.00 5.3±0.67ms ? ?/sec query_get_many_10/50000_calls_table 1.01 4.9±0.49ms ? ?/sec 1.00 4.8±0.45ms ? ?/sec query_get_many_2/50000_calls_sparse 1.28 848.4±210.89µs ? ?/sec 1.00 664.8±47.69µs ? ?/sec query_get_many_2/50000_calls_table 1.05 779.0±73.85µs ? ?/sec 1.00 739.2±83.02µs ? ?/sec query_get_many_5/50000_calls_sparse 1.05 2.4±0.37ms ? ?/sec 1.00 2.3±0.33ms ? ?/sec query_get_many_5/50000_calls_table 1.00 1939.9±75.22µs ? ?/sec 1.04 2.0±0.19ms ? ?/sec run_criteria/yes_using_query/001_systems 1.00 3.7±0.38µs ? ?/sec 1.30 4.9±0.14µs ? ?/sec run_criteria/yes_using_query/006_systems 1.00 8.9±0.40µs ? ?/sec 1.17 10.3±0.57µs ? ?/sec run_criteria/yes_using_query/011_systems 1.00 13.9±0.49µs ? ?/sec 1.08 15.0±0.89µs ? ?/sec run_criteria/yes_using_query/016_systems 1.00 18.8±0.74µs ? ?/sec 1.00 18.8±1.43µs ? ?/sec run_criteria/yes_using_query/021_systems 1.07 24.1±0.87µs ? ?/sec 1.00 22.6±1.58µs ? ?/sec run_criteria/yes_using_query/026_systems 1.04 27.9±0.62µs ? ?/sec 1.00 26.8±1.71µs ? ?/sec run_criteria/yes_using_query/031_systems 1.09 33.3±1.03µs ? ?/sec 1.00 30.5±2.18µs ? ?/sec run_criteria/yes_using_query/036_systems 1.14 38.7±0.80µs ? ?/sec 1.00 33.9±1.75µs ? ?/sec run_criteria/yes_using_query/041_systems 1.18 43.7±1.07µs ? ?/sec 1.00 37.0±2.39µs ? ?/sec run_criteria/yes_using_query/046_systems 1.14 47.6±1.16µs ? ?/sec 1.00 41.9±2.09µs ? ?/sec run_criteria/yes_using_query/051_systems 1.17 52.9±2.04µs ? ?/sec 1.00 45.3±1.75µs ? ?/sec run_criteria/yes_using_query/056_systems 1.25 59.2±2.38µs ? ?/sec 1.00 47.2±2.01µs ? ?/sec run_criteria/yes_using_query/061_systems 1.28 66.1±15.84µs ? ?/sec 1.00 51.5±2.47µs ? ?/sec run_criteria/yes_using_query/066_systems 1.28 70.2±2.57µs ? ?/sec 1.00 54.7±2.58µs ? ?/sec run_criteria/yes_using_query/071_systems 1.30 75.5±2.27µs ? ?/sec 1.00 58.2±3.31µs ? ?/sec run_criteria/yes_using_query/076_systems 1.26 81.5±2.66µs ? ?/sec 1.00 64.5±3.13µs ? ?/sec run_criteria/yes_using_query/081_systems 1.29 89.7±2.58µs ? ?/sec 1.00 69.3±3.47µs ? ?/sec run_criteria/yes_using_query/086_systems 1.33 95.6±3.39µs ? ?/sec 1.00 71.8±3.48µs ? ?/sec run_criteria/yes_using_query/091_systems 1.25 102.0±3.67µs ? ?/sec 1.00 81.4±4.82µs ? ?/sec run_criteria/yes_using_query/096_systems 1.33 111.7±3.29µs ? ?/sec 1.00 83.8±4.15µs ? ?/sec run_criteria/yes_using_query/101_systems 1.29 113.2±12.04µs ? ?/sec 1.00 87.7±5.15µs ? ?/sec world_query_for_each/50000_entities_sparse 1.00 47.4±0.51µs ? ?/sec 1.00 47.3±0.33µs ? ?/sec world_query_for_each/50000_entities_table 1.00 27.2±0.50µs ? ?/sec 1.00 27.2±0.17µs ? ?/sec world_query_get/50000_entities_sparse_wide 1.09 210.5±1.78µs ? ?/sec 1.00 192.5±2.61µs ? ?/sec world_query_get/50000_entities_table 1.00 127.7±2.09µs ? ?/sec 1.07 136.2±5.95µs ? ?/sec world_query_get/50000_entities_table_wide 1.00 209.8±2.37µs ? ?/sec 1.15 240.6±2.04µs ? ?/sec world_query_iter/50000_entities_sparse 1.00 54.2±0.36µs ? ?/sec 1.01 54.7±0.61µs ? ?/sec world_query_iter/50000_entities_table 1.00 27.2±0.31µs ? ?/sec 1.00 27.3±0.64µs ? ?/sec ``` </details> NOTE: This PR includes a change to enable LTO on our benchmarks to get a "fully optimized" baseline for our benchmarks. Both the main and the current PR's results were with LTO enabled.
2022-11-04 06:04:55 +00:00
let column = table.get_column(component_id).debug_checked_unwrap();
fetch.table_data = Some((
column.get_data_slice().into(),
Split Component Ticks (#6547) # Objective Fixes #4884. `ComponentTicks` stores both added and changed ticks contiguously in the same 8 bytes. This is convenient when passing around both together, but causes half the bytes fetched from memory for the purposes of change detection to effectively go unused. This is inefficient when most queries (no filter, mutating *something*) only write out to the changed ticks. ## Solution Split the storage for change detection ticks into two separate `Vec`s inside `Column`. Fetch only what is needed during iteration. This also potentially also removes one blocker from autovectorization of dense queries. EDIT: This is confirmed to enable autovectorization of dense queries in `for_each` and `par_for_each` where possible. Unfortunately `iter` has other blockers that prevent it. ### TODO - [x] Microbenchmark - [x] Check if this allows query iteration to autovectorize simple loops. - [x] Clean up all of the spurious tuples now littered throughout the API ### Open Questions - ~~Is `Mut::is_added` absolutely necessary? Can we not just use `Added` or `ChangeTrackers`?~~ It's optimized out if unused. - ~~Does the fetch of the added ticks get optimized out if not used?~~ Yes it is. --- ## Changelog Added: `Tick`, a wrapper around a single change detection tick. Added: `Column::get_added_ticks` Added: `Column::get_column_ticks` Added: `SparseSet::get_added_ticks` Added: `SparseSet::get_column_ticks` Changed: `Column` now stores added and changed ticks separately internally. Changed: Most APIs returning `&UnsafeCell<ComponentTicks>` now returns `TickCells` instead, which contains two separate `&UnsafeCell<Tick>` for either component ticks. Changed: `Query::for_each(_mut)`, `Query::par_for_each(_mut)` will now leverage autovectorization to speed up query iteration where possible. ## Migration Guide TODO
2022-11-21 12:59:09 +00:00
column.get_added_ticks_slice().into(),
column.get_changed_ticks_slice().into(),
));
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
}
#[inline(always)]
unsafe fn fetch<'w>(
fetch: &mut Self::Fetch<'w>,
entity: Entity,
table_row: TableRow,
) -> Self::Item<'w> {
match T::Storage::STORAGE_TYPE {
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
StorageType::Table => {
Split Component Ticks (#6547) # Objective Fixes #4884. `ComponentTicks` stores both added and changed ticks contiguously in the same 8 bytes. This is convenient when passing around both together, but causes half the bytes fetched from memory for the purposes of change detection to effectively go unused. This is inefficient when most queries (no filter, mutating *something*) only write out to the changed ticks. ## Solution Split the storage for change detection ticks into two separate `Vec`s inside `Column`. Fetch only what is needed during iteration. This also potentially also removes one blocker from autovectorization of dense queries. EDIT: This is confirmed to enable autovectorization of dense queries in `for_each` and `par_for_each` where possible. Unfortunately `iter` has other blockers that prevent it. ### TODO - [x] Microbenchmark - [x] Check if this allows query iteration to autovectorize simple loops. - [x] Clean up all of the spurious tuples now littered throughout the API ### Open Questions - ~~Is `Mut::is_added` absolutely necessary? Can we not just use `Added` or `ChangeTrackers`?~~ It's optimized out if unused. - ~~Does the fetch of the added ticks get optimized out if not used?~~ Yes it is. --- ## Changelog Added: `Tick`, a wrapper around a single change detection tick. Added: `Column::get_added_ticks` Added: `Column::get_column_ticks` Added: `SparseSet::get_added_ticks` Added: `SparseSet::get_column_ticks` Changed: `Column` now stores added and changed ticks separately internally. Changed: Most APIs returning `&UnsafeCell<ComponentTicks>` now returns `TickCells` instead, which contains two separate `&UnsafeCell<Tick>` for either component ticks. Changed: `Query::for_each(_mut)`, `Query::par_for_each(_mut)` will now leverage autovectorization to speed up query iteration where possible. ## Migration Guide TODO
2022-11-21 12:59:09 +00:00
let (table_components, added_ticks, changed_ticks) =
fetch.table_data.debug_checked_unwrap();
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
Mut {
Reduced `TableRow` `as` Casting (#10811) # Objective - Fixes #10806 ## Solution Replaced `new` and `index` methods for both `TableRow` and `TableId` with `from_*` and `as_*` methods. These remove the need to perform casting at call sites, reducing the total number of casts in the Bevy codebase. Within these methods, an appropriate `debug_assertion` ensures the cast will behave in an expected manner (no wrapping, etc.). I am using a `debug_assertion` instead of an `assert` to reduce any possible runtime overhead, however minimal. This choice is something I am open to changing (or leaving up to another PR) if anyone has any strong arguments for it. --- ## Changelog - `ComponentSparseSet::sparse` stores a `TableRow` instead of a `u32` (private change) - Replaced `TableRow::new` and `TableRow::index` methods with `TableRow::from_*` and `TableRow::as_*`, with `debug_assertions` protecting any internal casting. - Replaced `TableId::new` and `TableId::index` methods with `TableId::from_*` and `TableId::as_*`, with `debug_assertions` protecting any internal casting. - All `TableId` methods are now `const` ## Migration Guide - `TableRow::new` -> `TableRow::from_usize` - `TableRow::index` -> `TableRow::as_usize` - `TableId::new` -> `TableId::from_usize` - `TableId::index` -> `TableId::as_usize` --- ## Notes I have chosen to remove the `index` and `new` methods for the following chain of reasoning: - Across the codebase, `new` was called with a mixture of `u32` and `usize` values. Likewise for `index`. - Choosing `new` to either be `usize` or `u32` would break half of these call-sites, requiring `as` casting at the site. - Adding a second method `new_u32` or `new_usize` avoids the above, bu looks visually inconsistent. - Therefore, they should be replaced with `from_*` and `as_*` methods instead. Worth noting is that by updating `ComponentSparseSet`, there are now zero instances of interacting with the inner value of `TableRow` as a `u32`, it is exclusively used as a `usize` value (due to interactions with methods like `len` and slice indexing). I have left the `as_u32` and `from_u32` methods as the "proper" constructors/getters.
2023-12-05 02:44:33 +00:00
value: table_components.get(table_row.as_usize()).deref_mut(),
ticks: TicksMut {
Reduced `TableRow` `as` Casting (#10811) # Objective - Fixes #10806 ## Solution Replaced `new` and `index` methods for both `TableRow` and `TableId` with `from_*` and `as_*` methods. These remove the need to perform casting at call sites, reducing the total number of casts in the Bevy codebase. Within these methods, an appropriate `debug_assertion` ensures the cast will behave in an expected manner (no wrapping, etc.). I am using a `debug_assertion` instead of an `assert` to reduce any possible runtime overhead, however minimal. This choice is something I am open to changing (or leaving up to another PR) if anyone has any strong arguments for it. --- ## Changelog - `ComponentSparseSet::sparse` stores a `TableRow` instead of a `u32` (private change) - Replaced `TableRow::new` and `TableRow::index` methods with `TableRow::from_*` and `TableRow::as_*`, with `debug_assertions` protecting any internal casting. - Replaced `TableId::new` and `TableId::index` methods with `TableId::from_*` and `TableId::as_*`, with `debug_assertions` protecting any internal casting. - All `TableId` methods are now `const` ## Migration Guide - `TableRow::new` -> `TableRow::from_usize` - `TableRow::index` -> `TableRow::as_usize` - `TableId::new` -> `TableId::from_usize` - `TableId::index` -> `TableId::as_usize` --- ## Notes I have chosen to remove the `index` and `new` methods for the following chain of reasoning: - Across the codebase, `new` was called with a mixture of `u32` and `usize` values. Likewise for `index`. - Choosing `new` to either be `usize` or `u32` would break half of these call-sites, requiring `as` casting at the site. - Adding a second method `new_u32` or `new_usize` avoids the above, bu looks visually inconsistent. - Therefore, they should be replaced with `from_*` and `as_*` methods instead. Worth noting is that by updating `ComponentSparseSet`, there are now zero instances of interacting with the inner value of `TableRow` as a `u32`, it is exclusively used as a `usize` value (due to interactions with methods like `len` and slice indexing). I have left the `as_u32` and `from_u32` methods as the "proper" constructors/getters.
2023-12-05 02:44:33 +00:00
added: added_ticks.get(table_row.as_usize()).deref_mut(),
changed: changed_ticks.get(table_row.as_usize()).deref_mut(),
this_run: fetch.this_run,
last_run: fetch.last_run,
},
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
}
}
StorageType::SparseSet => {
Split Component Ticks (#6547) # Objective Fixes #4884. `ComponentTicks` stores both added and changed ticks contiguously in the same 8 bytes. This is convenient when passing around both together, but causes half the bytes fetched from memory for the purposes of change detection to effectively go unused. This is inefficient when most queries (no filter, mutating *something*) only write out to the changed ticks. ## Solution Split the storage for change detection ticks into two separate `Vec`s inside `Column`. Fetch only what is needed during iteration. This also potentially also removes one blocker from autovectorization of dense queries. EDIT: This is confirmed to enable autovectorization of dense queries in `for_each` and `par_for_each` where possible. Unfortunately `iter` has other blockers that prevent it. ### TODO - [x] Microbenchmark - [x] Check if this allows query iteration to autovectorize simple loops. - [x] Clean up all of the spurious tuples now littered throughout the API ### Open Questions - ~~Is `Mut::is_added` absolutely necessary? Can we not just use `Added` or `ChangeTrackers`?~~ It's optimized out if unused. - ~~Does the fetch of the added ticks get optimized out if not used?~~ Yes it is. --- ## Changelog Added: `Tick`, a wrapper around a single change detection tick. Added: `Column::get_added_ticks` Added: `Column::get_column_ticks` Added: `SparseSet::get_added_ticks` Added: `SparseSet::get_column_ticks` Changed: `Column` now stores added and changed ticks separately internally. Changed: Most APIs returning `&UnsafeCell<ComponentTicks>` now returns `TickCells` instead, which contains two separate `&UnsafeCell<Tick>` for either component ticks. Changed: `Query::for_each(_mut)`, `Query::par_for_each(_mut)` will now leverage autovectorization to speed up query iteration where possible. ## Migration Guide TODO
2022-11-21 12:59:09 +00:00
let (component, ticks) = fetch
.sparse_set
Remove unnecesary branches/panics from Query accesses (#6461) # Objective Supercedes #6452. Upon inspection of the [generated assembly](https://gist.github.com/james7132/c2740c6941b80d7912f1e8888e223cbb#file-original-s) of a [simple Bevy binary](https://gist.github.com/james7132/c2740c6941b80d7912f1e8888e223cbb#file-source-rs) compiled with `cargo rustc --release -- --emit asm`, it's apparent that there are multiple unnecessary branches in the generated assembly: ```assembly .LBB5_5: cmpq %r10, %r11 je .LBB5_15 movq (%r11), %rcx movq 328(%r15), %rdx cmpq %rdx, %rcx jae .LBB5_14 movq 312(%r15), %rdi leaq (%rcx,%rcx,2), %rcx shlq $5, %rcx movq 336(%r12), %rdx movq 64(%rdi,%rcx), %rax cmpq %rdx, %rax jbe .LBB5_4 leaq (%rdi,%rcx), %rsi movq 48(%rsi), %rbp shlq $4, %rdx cmpq $0, (%rbp,%rdx) je .LBB5_4 movq 344(%r12), %rbx cmpq %rbx, %rax jbe .LBB5_4 shlq $4, %rbx cmpq $0, (%rbp,%rbx) je .LBB5_4 addq $8, %r11 movq 88(%rdi,%rcx), %rcx testq %rcx, %rcx je .LBB5_5 movq (%rsi), %rax movq 8(%rbp,%rdx), %rdx leaq (%rdx,%rdx,4), %rdi shlq $4, %rdi movq 32(%rax,%rdi), %rdx movq 56(%rax,%rdi), %r8 movq 8(%rbp,%rbx), %rbp leaq (%rbp,%rbp,4), %rbp shlq $4, %rbp movq 32(%rax,%rbp), %r9 xorl %ebp, %ebp jmp .LBB5_13 .p2align 4, 0x90 ``` Almost every one of the instructions starting with `j` is a potential branch, which can significantly slow down accesses. Of these, two labels are both common and never used: ```asm .LBB5_14: leaq __unnamed_2(%rip), %r8 callq _ZN4core9panicking18panic_bounds_check17h70367088e72af65aE ud2 .LBB5_4: callq _ZN8bevy_ecs5query25debug_checked_unreachable17h0855ff520ceaea77E ud2 .seh_endproc ``` These correpsond to subprocedure calls to panicking due to out of bounds from indexing `Tables` and `debug_checked_unreadable`. Both of which should be inlined and optimized out, but are not. ## Solution Make `debug_checked_unreachable` a macro to forcibly inline either `unreachable!()` in debug builds, and `std::hint::unreachable_unchecked()` in release mode. Replace the `Tables` and `Archetype` index access with `get(id).unwrap_or_else(|| debug_checked_unreachable!())` to assume that the table or archetype provided exists. This has no external breaking change of any kind. The equivalent section of code with these changes removes most of the conditional jump instructions: ```asm .LBB5_5: movss (%rbx,%rbp,4), %xmm0 movl %r14d, 4(%r8,%rbp,8) addss (%rdi,%rbp,4), %xmm0 movss %xmm0, (%rdi,%rbp,4) incq %rbp .LBB5_1: cmpq %rdx, %rbp jne .LBB5_5 .p2align 4, 0x90 .LBB5_2: cmpq %rcx, %rax je .LBB5_6 movq (%rax), %rdx addq $8, %rax movq 312(%rsi), %rbp leaq (%rdx,%rdx,2), %rbx shlq $5, %rbx movq 88(%rbp,%rbx), %rdx testq %rdx, %rdx je .LBB5_2 leaq (%rbx,%rbp), %r8 movq 336(%r15), %rdi movq 344(%r15), %r9 movq 48(%rbp,%rbx), %r10 shlq $4, %rdi movq (%r8), %rbx movq 8(%r10,%rdi), %rdi leaq (%rdi,%rdi,4), %rbp shlq $4, %rbp movq 32(%rbx,%rbp), %rdi movq 56(%rbx,%rbp), %r8 shlq $4, %r9 movq 8(%r10,%r9), %rbp leaq (%rbp,%rbp,4), %rbp shlq $4, %rbp movq 32(%rbx,%rbp), %rbx xorl %ebp, %ebp jmp .LBB5_5 .LBB5_6: addq $40, %rsp popq %rbx popq %rbp popq %rdi popq %rsi popq %r14 popq %r15 retq .seh_endproc ``` ## Performance Microbenchmarks results: <details> ``` group main no-panic-query ----- ---- -------------- busy_systems/01x_entities_03_systems 1.20 42.4±2.66µs ? ?/sec 1.00 35.3±1.68µs ? ?/sec busy_systems/01x_entities_06_systems 1.32 83.8±3.50µs ? ?/sec 1.00 63.6±1.72µs ? ?/sec busy_systems/01x_entities_09_systems 1.15 113.3±8.90µs ? ?/sec 1.00 98.2±6.15µs ? ?/sec busy_systems/01x_entities_12_systems 1.27 160.8±32.44µs ? ?/sec 1.00 126.6±4.70µs ? ?/sec busy_systems/01x_entities_15_systems 1.12 179.6±3.71µs ? ?/sec 1.00 160.3±11.03µs ? ?/sec busy_systems/02x_entities_03_systems 1.18 76.8±3.14µs ? ?/sec 1.00 65.2±3.17µs ? ?/sec busy_systems/02x_entities_06_systems 1.16 144.6±6.10µs ? ?/sec 1.00 124.5±5.14µs ? ?/sec busy_systems/02x_entities_09_systems 1.19 215.3±9.18µs ? ?/sec 1.00 181.5±5.67µs ? ?/sec busy_systems/02x_entities_12_systems 1.20 266.7±8.33µs ? ?/sec 1.00 222.0±9.53µs ? ?/sec busy_systems/02x_entities_15_systems 1.23 338.8±10.53µs ? ?/sec 1.00 276.3±6.94µs ? ?/sec busy_systems/03x_entities_03_systems 1.43 113.5±5.06µs ? ?/sec 1.00 79.6±1.49µs ? ?/sec busy_systems/03x_entities_06_systems 1.38 217.3±12.67µs ? ?/sec 1.00 157.5±3.07µs ? ?/sec busy_systems/03x_entities_09_systems 1.23 308.8±24.75µs ? ?/sec 1.00 251.6±8.93µs ? ?/sec busy_systems/03x_entities_12_systems 1.05 347.7±12.43µs ? ?/sec 1.00 330.6±11.43µs ? ?/sec busy_systems/03x_entities_15_systems 1.13 455.5±13.88µs ? ?/sec 1.00 401.7±17.29µs ? ?/sec busy_systems/04x_entities_03_systems 1.24 144.7±5.89µs ? ?/sec 1.00 116.9±6.29µs ? ?/sec busy_systems/04x_entities_06_systems 1.24 282.8±21.40µs ? ?/sec 1.00 228.6±21.31µs ? ?/sec busy_systems/04x_entities_09_systems 1.35 431.8±14.10µs ? ?/sec 1.00 319.6±9.83µs ? ?/sec busy_systems/04x_entities_12_systems 1.16 493.8±22.87µs ? ?/sec 1.00 424.9±15.24µs ? ?/sec busy_systems/04x_entities_15_systems 1.10 587.5±23.25µs ? ?/sec 1.00 531.7±16.32µs ? ?/sec busy_systems/05x_entities_03_systems 1.14 148.2±9.61µs ? ?/sec 1.00 129.5±4.32µs ? ?/sec busy_systems/05x_entities_06_systems 1.31 359.7±17.46µs ? ?/sec 1.00 273.6±10.55µs ? ?/sec busy_systems/05x_entities_09_systems 1.22 473.5±23.11µs ? ?/sec 1.00 389.3±13.62µs ? ?/sec busy_systems/05x_entities_12_systems 1.05 562.9±20.76µs ? ?/sec 1.00 536.5±24.35µs ? ?/sec busy_systems/05x_entities_15_systems 1.23 818.5±28.70µs ? ?/sec 1.00 666.6±45.87µs ? ?/sec contrived/01x_entities_03_systems 1.27 27.5±0.49µs ? ?/sec 1.00 21.6±1.71µs ? ?/sec contrived/01x_entities_06_systems 1.22 49.9±1.18µs ? ?/sec 1.00 40.7±2.62µs ? ?/sec contrived/01x_entities_09_systems 1.30 72.3±2.39µs ? ?/sec 1.00 55.4±2.60µs ? ?/sec contrived/01x_entities_12_systems 1.28 94.3±9.44µs ? ?/sec 1.00 73.7±3.62µs ? ?/sec contrived/01x_entities_15_systems 1.25 118.0±2.43µs ? ?/sec 1.00 94.1±3.99µs ? ?/sec contrived/02x_entities_03_systems 1.23 41.6±1.71µs ? ?/sec 1.00 33.7±2.30µs ? ?/sec contrived/02x_entities_06_systems 1.19 78.6±2.63µs ? ?/sec 1.00 65.9±2.35µs ? ?/sec contrived/02x_entities_09_systems 1.28 113.6±3.60µs ? ?/sec 1.00 88.6±3.60µs ? ?/sec contrived/02x_entities_12_systems 1.20 146.4±5.75µs ? ?/sec 1.00 121.7±3.35µs ? ?/sec contrived/02x_entities_15_systems 1.23 178.5±4.86µs ? ?/sec 1.00 145.7±4.00µs ? ?/sec contrived/03x_entities_03_systems 1.42 58.3±2.77µs ? ?/sec 1.00 41.1±1.54µs ? ?/sec contrived/03x_entities_06_systems 1.32 108.5±7.30µs ? ?/sec 1.00 82.4±4.86µs ? ?/sec contrived/03x_entities_09_systems 1.23 153.7±4.61µs ? ?/sec 1.00 125.0±4.76µs ? ?/sec contrived/03x_entities_12_systems 1.18 197.5±5.12µs ? ?/sec 1.00 166.8±8.14µs ? ?/sec contrived/03x_entities_15_systems 1.23 238.8±6.38µs ? ?/sec 1.00 194.6±4.55µs ? ?/sec contrived/04x_entities_03_systems 1.34 66.4±3.42µs ? ?/sec 1.00 49.5±1.98µs ? ?/sec contrived/04x_entities_06_systems 1.27 134.3±4.86µs ? ?/sec 1.00 105.8±3.58µs ? ?/sec contrived/04x_entities_09_systems 1.26 193.2±3.83µs ? ?/sec 1.00 153.0±5.60µs ? ?/sec contrived/04x_entities_12_systems 1.16 237.1±5.78µs ? ?/sec 1.00 204.9±18.77µs ? ?/sec contrived/04x_entities_15_systems 1.17 289.2±4.76µs ? ?/sec 1.00 246.3±8.57µs ? ?/sec contrived/05x_entities_03_systems 1.26 80.4±2.90µs ? ?/sec 1.00 63.7±3.07µs ? ?/sec contrived/05x_entities_06_systems 1.27 161.6±13.47µs ? ?/sec 1.00 127.2±5.59µs ? ?/sec contrived/05x_entities_09_systems 1.22 228.0±7.76µs ? ?/sec 1.00 186.2±7.68µs ? ?/sec contrived/05x_entities_12_systems 1.20 289.5±6.21µs ? ?/sec 1.00 241.8±7.52µs ? ?/sec contrived/05x_entities_15_systems 1.18 357.3±11.24µs ? ?/sec 1.00 302.7±7.21µs ? ?/sec heavy_compute/base 1.01 302.4±3.52µs ? ?/sec 1.00 300.2±3.40µs ? ?/sec iter_fragmented/base 1.00 348.1±7.51ns ? ?/sec 1.01 351.9±8.32ns ? ?/sec iter_fragmented/foreach 1.03 239.8±23.78ns ? ?/sec 1.00 233.8±18.12ns ? ?/sec iter_fragmented/foreach_wide 1.00 3.9±0.13µs ? ?/sec 1.02 4.0±0.22µs ? ?/sec iter_fragmented/wide 1.18 4.6±0.15µs ? ?/sec 1.00 3.9±0.10µs ? ?/sec iter_fragmented_sparse/base 1.02 8.1±0.15ns ? ?/sec 1.00 7.9±0.56ns ? ?/sec iter_fragmented_sparse/foreach 1.00 7.8±0.22ns ? ?/sec 1.01 7.9±0.62ns ? ?/sec iter_fragmented_sparse/foreach_wide 1.00 37.2±1.17ns ? ?/sec 1.10 40.9±0.95ns ? ?/sec iter_fragmented_sparse/wide 1.09 48.4±2.13ns ? ?/sec 1.00 44.5±18.34ns ? ?/sec iter_simple/base 1.02 8.4±0.10µs ? ?/sec 1.00 8.2±0.14µs ? ?/sec iter_simple/foreach 1.01 8.3±0.07µs ? ?/sec 1.00 8.2±0.09µs ? ?/sec iter_simple/foreach_sparse_set 1.00 25.3±0.32µs ? ?/sec 1.02 25.7±0.42µs ? ?/sec iter_simple/foreach_wide 1.03 41.1±0.94µs ? ?/sec 1.00 39.9±0.41µs ? ?/sec iter_simple/foreach_wide_sparse_set 1.05 123.6±2.05µs ? ?/sec 1.00 118.1±2.78µs ? ?/sec iter_simple/sparse_set 1.14 30.5±1.40µs ? ?/sec 1.00 26.9±0.64µs ? ?/sec iter_simple/system 1.01 8.4±0.25µs ? ?/sec 1.00 8.4±0.11µs ? ?/sec iter_simple/wide 1.18 48.2±0.62µs ? ?/sec 1.00 40.7±0.38µs ? ?/sec iter_simple/wide_sparse_set 1.12 140.8±21.56µs ? ?/sec 1.00 126.0±2.30µs ? ?/sec query_get/50000_entities_sparse 1.17 378.6±7.60µs ? ?/sec 1.00 324.1±23.17µs ? ?/sec query_get/50000_entities_table 1.08 330.9±10.90µs ? ?/sec 1.00 306.8±4.98µs ? ?/sec query_get_component/50000_entities_sparse 1.00 976.7±19.55µs ? ?/sec 1.00 979.8±35.87µs ? ?/sec query_get_component/50000_entities_table 1.00 1029.0±15.11µs ? ?/sec 1.05 1080.0±59.18µs ? ?/sec query_get_component_simple/system 1.13 839.7±14.18µs ? ?/sec 1.00 742.8±10.72µs ? ?/sec query_get_component_simple/unchecked 1.01 909.0±15.17µs ? ?/sec 1.00 898.0±13.56µs ? ?/sec query_get_many_10/50000_calls_sparse 1.04 5.5±0.54ms ? ?/sec 1.00 5.3±0.67ms ? ?/sec query_get_many_10/50000_calls_table 1.01 4.9±0.49ms ? ?/sec 1.00 4.8±0.45ms ? ?/sec query_get_many_2/50000_calls_sparse 1.28 848.4±210.89µs ? ?/sec 1.00 664.8±47.69µs ? ?/sec query_get_many_2/50000_calls_table 1.05 779.0±73.85µs ? ?/sec 1.00 739.2±83.02µs ? ?/sec query_get_many_5/50000_calls_sparse 1.05 2.4±0.37ms ? ?/sec 1.00 2.3±0.33ms ? ?/sec query_get_many_5/50000_calls_table 1.00 1939.9±75.22µs ? ?/sec 1.04 2.0±0.19ms ? ?/sec run_criteria/yes_using_query/001_systems 1.00 3.7±0.38µs ? ?/sec 1.30 4.9±0.14µs ? ?/sec run_criteria/yes_using_query/006_systems 1.00 8.9±0.40µs ? ?/sec 1.17 10.3±0.57µs ? ?/sec run_criteria/yes_using_query/011_systems 1.00 13.9±0.49µs ? ?/sec 1.08 15.0±0.89µs ? ?/sec run_criteria/yes_using_query/016_systems 1.00 18.8±0.74µs ? ?/sec 1.00 18.8±1.43µs ? ?/sec run_criteria/yes_using_query/021_systems 1.07 24.1±0.87µs ? ?/sec 1.00 22.6±1.58µs ? ?/sec run_criteria/yes_using_query/026_systems 1.04 27.9±0.62µs ? ?/sec 1.00 26.8±1.71µs ? ?/sec run_criteria/yes_using_query/031_systems 1.09 33.3±1.03µs ? ?/sec 1.00 30.5±2.18µs ? ?/sec run_criteria/yes_using_query/036_systems 1.14 38.7±0.80µs ? ?/sec 1.00 33.9±1.75µs ? ?/sec run_criteria/yes_using_query/041_systems 1.18 43.7±1.07µs ? ?/sec 1.00 37.0±2.39µs ? ?/sec run_criteria/yes_using_query/046_systems 1.14 47.6±1.16µs ? ?/sec 1.00 41.9±2.09µs ? ?/sec run_criteria/yes_using_query/051_systems 1.17 52.9±2.04µs ? ?/sec 1.00 45.3±1.75µs ? ?/sec run_criteria/yes_using_query/056_systems 1.25 59.2±2.38µs ? ?/sec 1.00 47.2±2.01µs ? ?/sec run_criteria/yes_using_query/061_systems 1.28 66.1±15.84µs ? ?/sec 1.00 51.5±2.47µs ? ?/sec run_criteria/yes_using_query/066_systems 1.28 70.2±2.57µs ? ?/sec 1.00 54.7±2.58µs ? ?/sec run_criteria/yes_using_query/071_systems 1.30 75.5±2.27µs ? ?/sec 1.00 58.2±3.31µs ? ?/sec run_criteria/yes_using_query/076_systems 1.26 81.5±2.66µs ? ?/sec 1.00 64.5±3.13µs ? ?/sec run_criteria/yes_using_query/081_systems 1.29 89.7±2.58µs ? ?/sec 1.00 69.3±3.47µs ? ?/sec run_criteria/yes_using_query/086_systems 1.33 95.6±3.39µs ? ?/sec 1.00 71.8±3.48µs ? ?/sec run_criteria/yes_using_query/091_systems 1.25 102.0±3.67µs ? ?/sec 1.00 81.4±4.82µs ? ?/sec run_criteria/yes_using_query/096_systems 1.33 111.7±3.29µs ? ?/sec 1.00 83.8±4.15µs ? ?/sec run_criteria/yes_using_query/101_systems 1.29 113.2±12.04µs ? ?/sec 1.00 87.7±5.15µs ? ?/sec world_query_for_each/50000_entities_sparse 1.00 47.4±0.51µs ? ?/sec 1.00 47.3±0.33µs ? ?/sec world_query_for_each/50000_entities_table 1.00 27.2±0.50µs ? ?/sec 1.00 27.2±0.17µs ? ?/sec world_query_get/50000_entities_sparse_wide 1.09 210.5±1.78µs ? ?/sec 1.00 192.5±2.61µs ? ?/sec world_query_get/50000_entities_table 1.00 127.7±2.09µs ? ?/sec 1.07 136.2±5.95µs ? ?/sec world_query_get/50000_entities_table_wide 1.00 209.8±2.37µs ? ?/sec 1.15 240.6±2.04µs ? ?/sec world_query_iter/50000_entities_sparse 1.00 54.2±0.36µs ? ?/sec 1.01 54.7±0.61µs ? ?/sec world_query_iter/50000_entities_table 1.00 27.2±0.31µs ? ?/sec 1.00 27.3±0.64µs ? ?/sec ``` </details> NOTE: This PR includes a change to enable LTO on our benchmarks to get a "fully optimized" baseline for our benchmarks. Both the main and the current PR's results were with LTO enabled.
2022-11-04 06:04:55 +00:00
.debug_checked_unwrap()
Use lifetimed, type erased pointers in bevy_ecs (#3001) # Objective `bevy_ecs` has large amounts of unsafe code which is hard to get right and makes it difficult to audit for soundness. ## Solution Introduce lifetimed, type-erased pointers: `Ptr<'a>` `PtrMut<'a>` `OwningPtr<'a>'` and `ThinSlicePtr<'a, T>` which are newtypes around a raw pointer with a lifetime and conceptually representing strong invariants about the pointee and validity of the pointer. The process of converting bevy_ecs to use these has already caught multiple cases of unsound behavior. ## Changelog TL;DR for release notes: `bevy_ecs` now uses lifetimed, type-erased pointers internally, significantly improving safety and legibility without sacrificing performance. This should have approximately no end user impact, unless you were meddling with the (unfortunately public) internals of `bevy_ecs`. - `Fetch`, `FilterFetch` and `ReadOnlyFetch` trait no longer have a `'state` lifetime - this was unneeded - `ReadOnly/Fetch` associated types on `WorldQuery` are now on a new `WorldQueryGats<'world>` trait - was required to work around lack of Generic Associated Types (we wish to express `type Fetch<'a>: Fetch<'a>`) - `derive(WorldQuery)` no longer requires `'w` lifetime on struct - this was unneeded, and improves the end user experience - `EntityMut::get_unchecked_mut` returns `&'_ mut T` not `&'w mut T` - allows easier use of unsafe API with less footguns, and can be worked around via lifetime transmutery as a user - `Bundle::from_components` now takes a `ctx` parameter to pass to the `FnMut` closure - required because closure return types can't borrow from captures - `Fetch::init` takes `&'world World`, `Fetch::set_archetype` takes `&'world Archetype` and `&'world Tables`, `Fetch::set_table` takes `&'world Table` - allows types implementing `Fetch` to store borrows into world - `WorldQuery` trait now has a `shrink` fn to shorten the lifetime in `Fetch::<'a>::Item` - this works around lack of subtyping of assoc types, rust doesnt allow you to turn `<T as Fetch<'static>>::Item'` into `<T as Fetch<'a>>::Item'` - `QueryCombinationsIter` requires this - Most types implementing `Fetch` now have a lifetime `'w` - allows the fetches to store borrows of world data instead of using raw pointers ## Migration guide - `EntityMut::get_unchecked_mut` returns a more restricted lifetime, there is no general way to migrate this as it depends on your code - `Bundle::from_components` implementations must pass the `ctx` arg to `func` - `Bundle::from_components` callers have to use a fn arg instead of closure captures for borrowing from world - Remove lifetime args on `derive(WorldQuery)` structs as it is nonsensical - `<Q as WorldQuery>::ReadOnly/Fetch` should be changed to either `RO/QueryFetch<'world>` or `<Q as WorldQueryGats<'world>>::ReadOnly/Fetch` - `<F as Fetch<'w, 's>>` should be changed to `<F as Fetch<'w>>` - Change the fn sigs of `Fetch::init/set_archetype/set_table` to match respective trait fn sigs - Implement the required `fn shrink` on any `WorldQuery` implementations - Move assoc types `Fetch` and `ReadOnlyFetch` on `WorldQuery` impls to `WorldQueryGats` impls - Pass an appropriate `'world` lifetime to whatever fetch struct you are for some reason using ### Type inference regression in some cases rustc may give spurrious errors when attempting to infer the `F` parameter on a query/querystate this can be fixed by manually specifying the type, i.e. `QueryState::new::<_, ()>(world)`. The error is rather confusing: ```rust= error[E0271]: type mismatch resolving `<() as Fetch<'_>>::Item == bool` --> crates/bevy_pbr/src/render/light.rs:1413:30 | 1413 | main_view_query: QueryState::new(world), | ^^^^^^^^^^^^^^^ expected `bool`, found `()` | = note: required because of the requirements on the impl of `for<'x> FilterFetch<'x>` for `<() as WorldQueryGats<'x>>::Fetch` note: required by a bound in `bevy_ecs::query::QueryState::<Q, F>::new` --> crates/bevy_ecs/src/query/state.rs:49:32 | 49 | for<'x> QueryFetch<'x, F>: FilterFetch<'x>, | ^^^^^^^^^^^^^^^ required by this bound in `bevy_ecs::query::QueryState::<Q, F>::new` ``` --- Made with help from @BoxyUwU and @alice-i-cecile Co-authored-by: Boxy <supbscripter@gmail.com>
2022-04-27 23:44:06 +00:00
.get_with_ticks(entity)
Remove unnecesary branches/panics from Query accesses (#6461) # Objective Supercedes #6452. Upon inspection of the [generated assembly](https://gist.github.com/james7132/c2740c6941b80d7912f1e8888e223cbb#file-original-s) of a [simple Bevy binary](https://gist.github.com/james7132/c2740c6941b80d7912f1e8888e223cbb#file-source-rs) compiled with `cargo rustc --release -- --emit asm`, it's apparent that there are multiple unnecessary branches in the generated assembly: ```assembly .LBB5_5: cmpq %r10, %r11 je .LBB5_15 movq (%r11), %rcx movq 328(%r15), %rdx cmpq %rdx, %rcx jae .LBB5_14 movq 312(%r15), %rdi leaq (%rcx,%rcx,2), %rcx shlq $5, %rcx movq 336(%r12), %rdx movq 64(%rdi,%rcx), %rax cmpq %rdx, %rax jbe .LBB5_4 leaq (%rdi,%rcx), %rsi movq 48(%rsi), %rbp shlq $4, %rdx cmpq $0, (%rbp,%rdx) je .LBB5_4 movq 344(%r12), %rbx cmpq %rbx, %rax jbe .LBB5_4 shlq $4, %rbx cmpq $0, (%rbp,%rbx) je .LBB5_4 addq $8, %r11 movq 88(%rdi,%rcx), %rcx testq %rcx, %rcx je .LBB5_5 movq (%rsi), %rax movq 8(%rbp,%rdx), %rdx leaq (%rdx,%rdx,4), %rdi shlq $4, %rdi movq 32(%rax,%rdi), %rdx movq 56(%rax,%rdi), %r8 movq 8(%rbp,%rbx), %rbp leaq (%rbp,%rbp,4), %rbp shlq $4, %rbp movq 32(%rax,%rbp), %r9 xorl %ebp, %ebp jmp .LBB5_13 .p2align 4, 0x90 ``` Almost every one of the instructions starting with `j` is a potential branch, which can significantly slow down accesses. Of these, two labels are both common and never used: ```asm .LBB5_14: leaq __unnamed_2(%rip), %r8 callq _ZN4core9panicking18panic_bounds_check17h70367088e72af65aE ud2 .LBB5_4: callq _ZN8bevy_ecs5query25debug_checked_unreachable17h0855ff520ceaea77E ud2 .seh_endproc ``` These correpsond to subprocedure calls to panicking due to out of bounds from indexing `Tables` and `debug_checked_unreadable`. Both of which should be inlined and optimized out, but are not. ## Solution Make `debug_checked_unreachable` a macro to forcibly inline either `unreachable!()` in debug builds, and `std::hint::unreachable_unchecked()` in release mode. Replace the `Tables` and `Archetype` index access with `get(id).unwrap_or_else(|| debug_checked_unreachable!())` to assume that the table or archetype provided exists. This has no external breaking change of any kind. The equivalent section of code with these changes removes most of the conditional jump instructions: ```asm .LBB5_5: movss (%rbx,%rbp,4), %xmm0 movl %r14d, 4(%r8,%rbp,8) addss (%rdi,%rbp,4), %xmm0 movss %xmm0, (%rdi,%rbp,4) incq %rbp .LBB5_1: cmpq %rdx, %rbp jne .LBB5_5 .p2align 4, 0x90 .LBB5_2: cmpq %rcx, %rax je .LBB5_6 movq (%rax), %rdx addq $8, %rax movq 312(%rsi), %rbp leaq (%rdx,%rdx,2), %rbx shlq $5, %rbx movq 88(%rbp,%rbx), %rdx testq %rdx, %rdx je .LBB5_2 leaq (%rbx,%rbp), %r8 movq 336(%r15), %rdi movq 344(%r15), %r9 movq 48(%rbp,%rbx), %r10 shlq $4, %rdi movq (%r8), %rbx movq 8(%r10,%rdi), %rdi leaq (%rdi,%rdi,4), %rbp shlq $4, %rbp movq 32(%rbx,%rbp), %rdi movq 56(%rbx,%rbp), %r8 shlq $4, %r9 movq 8(%r10,%r9), %rbp leaq (%rbp,%rbp,4), %rbp shlq $4, %rbp movq 32(%rbx,%rbp), %rbx xorl %ebp, %ebp jmp .LBB5_5 .LBB5_6: addq $40, %rsp popq %rbx popq %rbp popq %rdi popq %rsi popq %r14 popq %r15 retq .seh_endproc ``` ## Performance Microbenchmarks results: <details> ``` group main no-panic-query ----- ---- -------------- busy_systems/01x_entities_03_systems 1.20 42.4±2.66µs ? ?/sec 1.00 35.3±1.68µs ? ?/sec busy_systems/01x_entities_06_systems 1.32 83.8±3.50µs ? ?/sec 1.00 63.6±1.72µs ? ?/sec busy_systems/01x_entities_09_systems 1.15 113.3±8.90µs ? ?/sec 1.00 98.2±6.15µs ? ?/sec busy_systems/01x_entities_12_systems 1.27 160.8±32.44µs ? ?/sec 1.00 126.6±4.70µs ? ?/sec busy_systems/01x_entities_15_systems 1.12 179.6±3.71µs ? ?/sec 1.00 160.3±11.03µs ? ?/sec busy_systems/02x_entities_03_systems 1.18 76.8±3.14µs ? ?/sec 1.00 65.2±3.17µs ? ?/sec busy_systems/02x_entities_06_systems 1.16 144.6±6.10µs ? ?/sec 1.00 124.5±5.14µs ? ?/sec busy_systems/02x_entities_09_systems 1.19 215.3±9.18µs ? ?/sec 1.00 181.5±5.67µs ? ?/sec busy_systems/02x_entities_12_systems 1.20 266.7±8.33µs ? ?/sec 1.00 222.0±9.53µs ? ?/sec busy_systems/02x_entities_15_systems 1.23 338.8±10.53µs ? ?/sec 1.00 276.3±6.94µs ? ?/sec busy_systems/03x_entities_03_systems 1.43 113.5±5.06µs ? ?/sec 1.00 79.6±1.49µs ? ?/sec busy_systems/03x_entities_06_systems 1.38 217.3±12.67µs ? ?/sec 1.00 157.5±3.07µs ? ?/sec busy_systems/03x_entities_09_systems 1.23 308.8±24.75µs ? ?/sec 1.00 251.6±8.93µs ? ?/sec busy_systems/03x_entities_12_systems 1.05 347.7±12.43µs ? ?/sec 1.00 330.6±11.43µs ? ?/sec busy_systems/03x_entities_15_systems 1.13 455.5±13.88µs ? ?/sec 1.00 401.7±17.29µs ? ?/sec busy_systems/04x_entities_03_systems 1.24 144.7±5.89µs ? ?/sec 1.00 116.9±6.29µs ? ?/sec busy_systems/04x_entities_06_systems 1.24 282.8±21.40µs ? ?/sec 1.00 228.6±21.31µs ? ?/sec busy_systems/04x_entities_09_systems 1.35 431.8±14.10µs ? ?/sec 1.00 319.6±9.83µs ? ?/sec busy_systems/04x_entities_12_systems 1.16 493.8±22.87µs ? ?/sec 1.00 424.9±15.24µs ? ?/sec busy_systems/04x_entities_15_systems 1.10 587.5±23.25µs ? ?/sec 1.00 531.7±16.32µs ? ?/sec busy_systems/05x_entities_03_systems 1.14 148.2±9.61µs ? ?/sec 1.00 129.5±4.32µs ? ?/sec busy_systems/05x_entities_06_systems 1.31 359.7±17.46µs ? ?/sec 1.00 273.6±10.55µs ? ?/sec busy_systems/05x_entities_09_systems 1.22 473.5±23.11µs ? ?/sec 1.00 389.3±13.62µs ? ?/sec busy_systems/05x_entities_12_systems 1.05 562.9±20.76µs ? ?/sec 1.00 536.5±24.35µs ? ?/sec busy_systems/05x_entities_15_systems 1.23 818.5±28.70µs ? ?/sec 1.00 666.6±45.87µs ? ?/sec contrived/01x_entities_03_systems 1.27 27.5±0.49µs ? ?/sec 1.00 21.6±1.71µs ? ?/sec contrived/01x_entities_06_systems 1.22 49.9±1.18µs ? ?/sec 1.00 40.7±2.62µs ? ?/sec contrived/01x_entities_09_systems 1.30 72.3±2.39µs ? ?/sec 1.00 55.4±2.60µs ? ?/sec contrived/01x_entities_12_systems 1.28 94.3±9.44µs ? ?/sec 1.00 73.7±3.62µs ? ?/sec contrived/01x_entities_15_systems 1.25 118.0±2.43µs ? ?/sec 1.00 94.1±3.99µs ? ?/sec contrived/02x_entities_03_systems 1.23 41.6±1.71µs ? ?/sec 1.00 33.7±2.30µs ? ?/sec contrived/02x_entities_06_systems 1.19 78.6±2.63µs ? ?/sec 1.00 65.9±2.35µs ? ?/sec contrived/02x_entities_09_systems 1.28 113.6±3.60µs ? ?/sec 1.00 88.6±3.60µs ? ?/sec contrived/02x_entities_12_systems 1.20 146.4±5.75µs ? ?/sec 1.00 121.7±3.35µs ? ?/sec contrived/02x_entities_15_systems 1.23 178.5±4.86µs ? ?/sec 1.00 145.7±4.00µs ? ?/sec contrived/03x_entities_03_systems 1.42 58.3±2.77µs ? ?/sec 1.00 41.1±1.54µs ? ?/sec contrived/03x_entities_06_systems 1.32 108.5±7.30µs ? ?/sec 1.00 82.4±4.86µs ? ?/sec contrived/03x_entities_09_systems 1.23 153.7±4.61µs ? ?/sec 1.00 125.0±4.76µs ? ?/sec contrived/03x_entities_12_systems 1.18 197.5±5.12µs ? ?/sec 1.00 166.8±8.14µs ? ?/sec contrived/03x_entities_15_systems 1.23 238.8±6.38µs ? ?/sec 1.00 194.6±4.55µs ? ?/sec contrived/04x_entities_03_systems 1.34 66.4±3.42µs ? ?/sec 1.00 49.5±1.98µs ? ?/sec contrived/04x_entities_06_systems 1.27 134.3±4.86µs ? ?/sec 1.00 105.8±3.58µs ? ?/sec contrived/04x_entities_09_systems 1.26 193.2±3.83µs ? ?/sec 1.00 153.0±5.60µs ? ?/sec contrived/04x_entities_12_systems 1.16 237.1±5.78µs ? ?/sec 1.00 204.9±18.77µs ? ?/sec contrived/04x_entities_15_systems 1.17 289.2±4.76µs ? ?/sec 1.00 246.3±8.57µs ? ?/sec contrived/05x_entities_03_systems 1.26 80.4±2.90µs ? ?/sec 1.00 63.7±3.07µs ? ?/sec contrived/05x_entities_06_systems 1.27 161.6±13.47µs ? ?/sec 1.00 127.2±5.59µs ? ?/sec contrived/05x_entities_09_systems 1.22 228.0±7.76µs ? ?/sec 1.00 186.2±7.68µs ? ?/sec contrived/05x_entities_12_systems 1.20 289.5±6.21µs ? ?/sec 1.00 241.8±7.52µs ? ?/sec contrived/05x_entities_15_systems 1.18 357.3±11.24µs ? ?/sec 1.00 302.7±7.21µs ? ?/sec heavy_compute/base 1.01 302.4±3.52µs ? ?/sec 1.00 300.2±3.40µs ? ?/sec iter_fragmented/base 1.00 348.1±7.51ns ? ?/sec 1.01 351.9±8.32ns ? ?/sec iter_fragmented/foreach 1.03 239.8±23.78ns ? ?/sec 1.00 233.8±18.12ns ? ?/sec iter_fragmented/foreach_wide 1.00 3.9±0.13µs ? ?/sec 1.02 4.0±0.22µs ? ?/sec iter_fragmented/wide 1.18 4.6±0.15µs ? ?/sec 1.00 3.9±0.10µs ? ?/sec iter_fragmented_sparse/base 1.02 8.1±0.15ns ? ?/sec 1.00 7.9±0.56ns ? ?/sec iter_fragmented_sparse/foreach 1.00 7.8±0.22ns ? ?/sec 1.01 7.9±0.62ns ? ?/sec iter_fragmented_sparse/foreach_wide 1.00 37.2±1.17ns ? ?/sec 1.10 40.9±0.95ns ? ?/sec iter_fragmented_sparse/wide 1.09 48.4±2.13ns ? ?/sec 1.00 44.5±18.34ns ? ?/sec iter_simple/base 1.02 8.4±0.10µs ? ?/sec 1.00 8.2±0.14µs ? ?/sec iter_simple/foreach 1.01 8.3±0.07µs ? ?/sec 1.00 8.2±0.09µs ? ?/sec iter_simple/foreach_sparse_set 1.00 25.3±0.32µs ? ?/sec 1.02 25.7±0.42µs ? ?/sec iter_simple/foreach_wide 1.03 41.1±0.94µs ? ?/sec 1.00 39.9±0.41µs ? ?/sec iter_simple/foreach_wide_sparse_set 1.05 123.6±2.05µs ? ?/sec 1.00 118.1±2.78µs ? ?/sec iter_simple/sparse_set 1.14 30.5±1.40µs ? ?/sec 1.00 26.9±0.64µs ? ?/sec iter_simple/system 1.01 8.4±0.25µs ? ?/sec 1.00 8.4±0.11µs ? ?/sec iter_simple/wide 1.18 48.2±0.62µs ? ?/sec 1.00 40.7±0.38µs ? ?/sec iter_simple/wide_sparse_set 1.12 140.8±21.56µs ? ?/sec 1.00 126.0±2.30µs ? ?/sec query_get/50000_entities_sparse 1.17 378.6±7.60µs ? ?/sec 1.00 324.1±23.17µs ? ?/sec query_get/50000_entities_table 1.08 330.9±10.90µs ? ?/sec 1.00 306.8±4.98µs ? ?/sec query_get_component/50000_entities_sparse 1.00 976.7±19.55µs ? ?/sec 1.00 979.8±35.87µs ? ?/sec query_get_component/50000_entities_table 1.00 1029.0±15.11µs ? ?/sec 1.05 1080.0±59.18µs ? ?/sec query_get_component_simple/system 1.13 839.7±14.18µs ? ?/sec 1.00 742.8±10.72µs ? ?/sec query_get_component_simple/unchecked 1.01 909.0±15.17µs ? ?/sec 1.00 898.0±13.56µs ? ?/sec query_get_many_10/50000_calls_sparse 1.04 5.5±0.54ms ? ?/sec 1.00 5.3±0.67ms ? ?/sec query_get_many_10/50000_calls_table 1.01 4.9±0.49ms ? ?/sec 1.00 4.8±0.45ms ? ?/sec query_get_many_2/50000_calls_sparse 1.28 848.4±210.89µs ? ?/sec 1.00 664.8±47.69µs ? ?/sec query_get_many_2/50000_calls_table 1.05 779.0±73.85µs ? ?/sec 1.00 739.2±83.02µs ? ?/sec query_get_many_5/50000_calls_sparse 1.05 2.4±0.37ms ? ?/sec 1.00 2.3±0.33ms ? ?/sec query_get_many_5/50000_calls_table 1.00 1939.9±75.22µs ? ?/sec 1.04 2.0±0.19ms ? ?/sec run_criteria/yes_using_query/001_systems 1.00 3.7±0.38µs ? ?/sec 1.30 4.9±0.14µs ? ?/sec run_criteria/yes_using_query/006_systems 1.00 8.9±0.40µs ? ?/sec 1.17 10.3±0.57µs ? ?/sec run_criteria/yes_using_query/011_systems 1.00 13.9±0.49µs ? ?/sec 1.08 15.0±0.89µs ? ?/sec run_criteria/yes_using_query/016_systems 1.00 18.8±0.74µs ? ?/sec 1.00 18.8±1.43µs ? ?/sec run_criteria/yes_using_query/021_systems 1.07 24.1±0.87µs ? ?/sec 1.00 22.6±1.58µs ? ?/sec run_criteria/yes_using_query/026_systems 1.04 27.9±0.62µs ? ?/sec 1.00 26.8±1.71µs ? ?/sec run_criteria/yes_using_query/031_systems 1.09 33.3±1.03µs ? ?/sec 1.00 30.5±2.18µs ? ?/sec run_criteria/yes_using_query/036_systems 1.14 38.7±0.80µs ? ?/sec 1.00 33.9±1.75µs ? ?/sec run_criteria/yes_using_query/041_systems 1.18 43.7±1.07µs ? ?/sec 1.00 37.0±2.39µs ? ?/sec run_criteria/yes_using_query/046_systems 1.14 47.6±1.16µs ? ?/sec 1.00 41.9±2.09µs ? ?/sec run_criteria/yes_using_query/051_systems 1.17 52.9±2.04µs ? ?/sec 1.00 45.3±1.75µs ? ?/sec run_criteria/yes_using_query/056_systems 1.25 59.2±2.38µs ? ?/sec 1.00 47.2±2.01µs ? ?/sec run_criteria/yes_using_query/061_systems 1.28 66.1±15.84µs ? ?/sec 1.00 51.5±2.47µs ? ?/sec run_criteria/yes_using_query/066_systems 1.28 70.2±2.57µs ? ?/sec 1.00 54.7±2.58µs ? ?/sec run_criteria/yes_using_query/071_systems 1.30 75.5±2.27µs ? ?/sec 1.00 58.2±3.31µs ? ?/sec run_criteria/yes_using_query/076_systems 1.26 81.5±2.66µs ? ?/sec 1.00 64.5±3.13µs ? ?/sec run_criteria/yes_using_query/081_systems 1.29 89.7±2.58µs ? ?/sec 1.00 69.3±3.47µs ? ?/sec run_criteria/yes_using_query/086_systems 1.33 95.6±3.39µs ? ?/sec 1.00 71.8±3.48µs ? ?/sec run_criteria/yes_using_query/091_systems 1.25 102.0±3.67µs ? ?/sec 1.00 81.4±4.82µs ? ?/sec run_criteria/yes_using_query/096_systems 1.33 111.7±3.29µs ? ?/sec 1.00 83.8±4.15µs ? ?/sec run_criteria/yes_using_query/101_systems 1.29 113.2±12.04µs ? ?/sec 1.00 87.7±5.15µs ? ?/sec world_query_for_each/50000_entities_sparse 1.00 47.4±0.51µs ? ?/sec 1.00 47.3±0.33µs ? ?/sec world_query_for_each/50000_entities_table 1.00 27.2±0.50µs ? ?/sec 1.00 27.2±0.17µs ? ?/sec world_query_get/50000_entities_sparse_wide 1.09 210.5±1.78µs ? ?/sec 1.00 192.5±2.61µs ? ?/sec world_query_get/50000_entities_table 1.00 127.7±2.09µs ? ?/sec 1.07 136.2±5.95µs ? ?/sec world_query_get/50000_entities_table_wide 1.00 209.8±2.37µs ? ?/sec 1.15 240.6±2.04µs ? ?/sec world_query_iter/50000_entities_sparse 1.00 54.2±0.36µs ? ?/sec 1.01 54.7±0.61µs ? ?/sec world_query_iter/50000_entities_table 1.00 27.2±0.31µs ? ?/sec 1.00 27.3±0.64µs ? ?/sec ``` </details> NOTE: This PR includes a change to enable LTO on our benchmarks to get a "fully optimized" baseline for our benchmarks. Both the main and the current PR's results were with LTO enabled.
2022-11-04 06:04:55 +00:00
.debug_checked_unwrap();
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
Mut {
Use lifetimed, type erased pointers in bevy_ecs (#3001) # Objective `bevy_ecs` has large amounts of unsafe code which is hard to get right and makes it difficult to audit for soundness. ## Solution Introduce lifetimed, type-erased pointers: `Ptr<'a>` `PtrMut<'a>` `OwningPtr<'a>'` and `ThinSlicePtr<'a, T>` which are newtypes around a raw pointer with a lifetime and conceptually representing strong invariants about the pointee and validity of the pointer. The process of converting bevy_ecs to use these has already caught multiple cases of unsound behavior. ## Changelog TL;DR for release notes: `bevy_ecs` now uses lifetimed, type-erased pointers internally, significantly improving safety and legibility without sacrificing performance. This should have approximately no end user impact, unless you were meddling with the (unfortunately public) internals of `bevy_ecs`. - `Fetch`, `FilterFetch` and `ReadOnlyFetch` trait no longer have a `'state` lifetime - this was unneeded - `ReadOnly/Fetch` associated types on `WorldQuery` are now on a new `WorldQueryGats<'world>` trait - was required to work around lack of Generic Associated Types (we wish to express `type Fetch<'a>: Fetch<'a>`) - `derive(WorldQuery)` no longer requires `'w` lifetime on struct - this was unneeded, and improves the end user experience - `EntityMut::get_unchecked_mut` returns `&'_ mut T` not `&'w mut T` - allows easier use of unsafe API with less footguns, and can be worked around via lifetime transmutery as a user - `Bundle::from_components` now takes a `ctx` parameter to pass to the `FnMut` closure - required because closure return types can't borrow from captures - `Fetch::init` takes `&'world World`, `Fetch::set_archetype` takes `&'world Archetype` and `&'world Tables`, `Fetch::set_table` takes `&'world Table` - allows types implementing `Fetch` to store borrows into world - `WorldQuery` trait now has a `shrink` fn to shorten the lifetime in `Fetch::<'a>::Item` - this works around lack of subtyping of assoc types, rust doesnt allow you to turn `<T as Fetch<'static>>::Item'` into `<T as Fetch<'a>>::Item'` - `QueryCombinationsIter` requires this - Most types implementing `Fetch` now have a lifetime `'w` - allows the fetches to store borrows of world data instead of using raw pointers ## Migration guide - `EntityMut::get_unchecked_mut` returns a more restricted lifetime, there is no general way to migrate this as it depends on your code - `Bundle::from_components` implementations must pass the `ctx` arg to `func` - `Bundle::from_components` callers have to use a fn arg instead of closure captures for borrowing from world - Remove lifetime args on `derive(WorldQuery)` structs as it is nonsensical - `<Q as WorldQuery>::ReadOnly/Fetch` should be changed to either `RO/QueryFetch<'world>` or `<Q as WorldQueryGats<'world>>::ReadOnly/Fetch` - `<F as Fetch<'w, 's>>` should be changed to `<F as Fetch<'w>>` - Change the fn sigs of `Fetch::init/set_archetype/set_table` to match respective trait fn sigs - Implement the required `fn shrink` on any `WorldQuery` implementations - Move assoc types `Fetch` and `ReadOnlyFetch` on `WorldQuery` impls to `WorldQueryGats` impls - Pass an appropriate `'world` lifetime to whatever fetch struct you are for some reason using ### Type inference regression in some cases rustc may give spurrious errors when attempting to infer the `F` parameter on a query/querystate this can be fixed by manually specifying the type, i.e. `QueryState::new::<_, ()>(world)`. The error is rather confusing: ```rust= error[E0271]: type mismatch resolving `<() as Fetch<'_>>::Item == bool` --> crates/bevy_pbr/src/render/light.rs:1413:30 | 1413 | main_view_query: QueryState::new(world), | ^^^^^^^^^^^^^^^ expected `bool`, found `()` | = note: required because of the requirements on the impl of `for<'x> FilterFetch<'x>` for `<() as WorldQueryGats<'x>>::Fetch` note: required by a bound in `bevy_ecs::query::QueryState::<Q, F>::new` --> crates/bevy_ecs/src/query/state.rs:49:32 | 49 | for<'x> QueryFetch<'x, F>: FilterFetch<'x>, | ^^^^^^^^^^^^^^^ required by this bound in `bevy_ecs::query::QueryState::<Q, F>::new` ``` --- Made with help from @BoxyUwU and @alice-i-cecile Co-authored-by: Boxy <supbscripter@gmail.com>
2022-04-27 23:44:06 +00:00
value: component.assert_unique().deref_mut(),
ticks: TicksMut::from_tick_cells(ticks, fetch.last_run, fetch.this_run),
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
}
}
}
}
fn update_component_access(
&component_id: &ComponentId,
access: &mut FilteredAccess<ComponentId>,
) {
Replace `ReadOnlyFetch` with `ReadOnlyWorldQuery` (#4626) # Objective - Fix a type inference regression introduced by #3001 - Make read only bounds on world queries more user friendly ptrification required you to write `Q::Fetch: ReadOnlyFetch` as `for<'w> QueryFetch<'w, Q>: ReadOnlyFetch` which has the same type inference problem as `for<'w> QueryFetch<'w, Q>: FilterFetch<'w>` had, i.e. the following code would error: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: WorldQuery>(a: Query<Q, ()>) where for<'w> QueryFetch<'w, Q>: ReadOnlyFetch, { } ``` `for<..>` bounds are also rather user unfriendly.. ## Solution Remove the `ReadOnlyFetch` trait in favour of a `ReadOnlyWorldQuery` trait, and remove `WorldQueryGats::ReadOnlyFetch` in favor of `WorldQuery::ReadOnly` allowing the previous code snippet to be written as: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: ReadOnlyWorldQuery>(a: Query<Q, ()>) {} ``` This avoids the `for<...>` bound which makes the code simpler and also fixes the type inference issue. The reason for moving the two functions out of `FetchState` and into `WorldQuery` is to allow the world query `&mut T` to share a `State` with the `&T` world query so that it can have `type ReadOnly = &T`. Presumably it would be possible to instead have a `ReadOnlyRefMut<T>` world query and then do `type ReadOnly = ReadOnlyRefMut<T>` much like how (before this PR) we had a `ReadOnlyWriteFetch<T>`. A side benefit of the current solution in this PR is that it will likely make it easier in the future to support an API such as `Query<&mut T> -> Query<&T>`. The primary benefit IMO is just that `ReadOnlyRefMut<T>` and its associated fetch would have to reimplement all of the logic that the `&T` world query impl does but this solution avoids that :) --- ## Changelog/Migration Guide The trait `ReadOnlyFetch` has been replaced with `ReadOnlyWorldQuery` along with the `WorldQueryGats::ReadOnlyFetch` assoc type which has been replaced with `<WorldQuery::ReadOnly as WorldQueryGats>::Fetch` - Any where clauses such as `QueryFetch<Q>: ReadOnlyFetch` should be replaced with `Q: ReadOnlyWorldQuery`. - Any custom world query impls should implement `ReadOnlyWorldQuery` insead of `ReadOnlyFetch` Functions `update_component_access` and `update_archetype_component_access` have been moved from the `FetchState` trait to `WorldQuery` - Any callers should now call `Q::update_component_access(state` instead of `state.update_component_access` (and `update_archetype_component_access` respectively) - Any custom world query impls should move the functions from the `FetchState` impl to `WorldQuery` impl `WorldQuery` has been made an `unsafe trait`, `FetchState` has been made a safe `trait`. (I think this is how it should have always been, but regardless this is _definitely_ necessary now that the two functions have been moved to `WorldQuery`) - If you have a custom `FetchState` impl make it a normal `impl` instead of `unsafe impl` - If you have a custom `WorldQuery` impl make it an `unsafe impl`, if your code was sound before it is going to still be sound
2022-06-13 23:35:54 +00:00
assert!(
!access.access().has_read(component_id),
Replace `ReadOnlyFetch` with `ReadOnlyWorldQuery` (#4626) # Objective - Fix a type inference regression introduced by #3001 - Make read only bounds on world queries more user friendly ptrification required you to write `Q::Fetch: ReadOnlyFetch` as `for<'w> QueryFetch<'w, Q>: ReadOnlyFetch` which has the same type inference problem as `for<'w> QueryFetch<'w, Q>: FilterFetch<'w>` had, i.e. the following code would error: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: WorldQuery>(a: Query<Q, ()>) where for<'w> QueryFetch<'w, Q>: ReadOnlyFetch, { } ``` `for<..>` bounds are also rather user unfriendly.. ## Solution Remove the `ReadOnlyFetch` trait in favour of a `ReadOnlyWorldQuery` trait, and remove `WorldQueryGats::ReadOnlyFetch` in favor of `WorldQuery::ReadOnly` allowing the previous code snippet to be written as: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: ReadOnlyWorldQuery>(a: Query<Q, ()>) {} ``` This avoids the `for<...>` bound which makes the code simpler and also fixes the type inference issue. The reason for moving the two functions out of `FetchState` and into `WorldQuery` is to allow the world query `&mut T` to share a `State` with the `&T` world query so that it can have `type ReadOnly = &T`. Presumably it would be possible to instead have a `ReadOnlyRefMut<T>` world query and then do `type ReadOnly = ReadOnlyRefMut<T>` much like how (before this PR) we had a `ReadOnlyWriteFetch<T>`. A side benefit of the current solution in this PR is that it will likely make it easier in the future to support an API such as `Query<&mut T> -> Query<&T>`. The primary benefit IMO is just that `ReadOnlyRefMut<T>` and its associated fetch would have to reimplement all of the logic that the `&T` world query impl does but this solution avoids that :) --- ## Changelog/Migration Guide The trait `ReadOnlyFetch` has been replaced with `ReadOnlyWorldQuery` along with the `WorldQueryGats::ReadOnlyFetch` assoc type which has been replaced with `<WorldQuery::ReadOnly as WorldQueryGats>::Fetch` - Any where clauses such as `QueryFetch<Q>: ReadOnlyFetch` should be replaced with `Q: ReadOnlyWorldQuery`. - Any custom world query impls should implement `ReadOnlyWorldQuery` insead of `ReadOnlyFetch` Functions `update_component_access` and `update_archetype_component_access` have been moved from the `FetchState` trait to `WorldQuery` - Any callers should now call `Q::update_component_access(state` instead of `state.update_component_access` (and `update_archetype_component_access` respectively) - Any custom world query impls should move the functions from the `FetchState` impl to `WorldQuery` impl `WorldQuery` has been made an `unsafe trait`, `FetchState` has been made a safe `trait`. (I think this is how it should have always been, but regardless this is _definitely_ necessary now that the two functions have been moved to `WorldQuery`) - If you have a custom `FetchState` impl make it a normal `impl` instead of `unsafe impl` - If you have a custom `WorldQuery` impl make it an `unsafe impl`, if your code was sound before it is going to still be sound
2022-06-13 23:35:54 +00:00
"&mut {} conflicts with a previous access in this query. Mutable component access must be unique.",
std::any::type_name::<T>(),
);
access.add_write(component_id);
}
fn init_state(world: &mut World) -> ComponentId {
world.init_component::<T>()
}
Use lifetimed, type erased pointers in bevy_ecs (#3001) # Objective `bevy_ecs` has large amounts of unsafe code which is hard to get right and makes it difficult to audit for soundness. ## Solution Introduce lifetimed, type-erased pointers: `Ptr<'a>` `PtrMut<'a>` `OwningPtr<'a>'` and `ThinSlicePtr<'a, T>` which are newtypes around a raw pointer with a lifetime and conceptually representing strong invariants about the pointee and validity of the pointer. The process of converting bevy_ecs to use these has already caught multiple cases of unsound behavior. ## Changelog TL;DR for release notes: `bevy_ecs` now uses lifetimed, type-erased pointers internally, significantly improving safety and legibility without sacrificing performance. This should have approximately no end user impact, unless you were meddling with the (unfortunately public) internals of `bevy_ecs`. - `Fetch`, `FilterFetch` and `ReadOnlyFetch` trait no longer have a `'state` lifetime - this was unneeded - `ReadOnly/Fetch` associated types on `WorldQuery` are now on a new `WorldQueryGats<'world>` trait - was required to work around lack of Generic Associated Types (we wish to express `type Fetch<'a>: Fetch<'a>`) - `derive(WorldQuery)` no longer requires `'w` lifetime on struct - this was unneeded, and improves the end user experience - `EntityMut::get_unchecked_mut` returns `&'_ mut T` not `&'w mut T` - allows easier use of unsafe API with less footguns, and can be worked around via lifetime transmutery as a user - `Bundle::from_components` now takes a `ctx` parameter to pass to the `FnMut` closure - required because closure return types can't borrow from captures - `Fetch::init` takes `&'world World`, `Fetch::set_archetype` takes `&'world Archetype` and `&'world Tables`, `Fetch::set_table` takes `&'world Table` - allows types implementing `Fetch` to store borrows into world - `WorldQuery` trait now has a `shrink` fn to shorten the lifetime in `Fetch::<'a>::Item` - this works around lack of subtyping of assoc types, rust doesnt allow you to turn `<T as Fetch<'static>>::Item'` into `<T as Fetch<'a>>::Item'` - `QueryCombinationsIter` requires this - Most types implementing `Fetch` now have a lifetime `'w` - allows the fetches to store borrows of world data instead of using raw pointers ## Migration guide - `EntityMut::get_unchecked_mut` returns a more restricted lifetime, there is no general way to migrate this as it depends on your code - `Bundle::from_components` implementations must pass the `ctx` arg to `func` - `Bundle::from_components` callers have to use a fn arg instead of closure captures for borrowing from world - Remove lifetime args on `derive(WorldQuery)` structs as it is nonsensical - `<Q as WorldQuery>::ReadOnly/Fetch` should be changed to either `RO/QueryFetch<'world>` or `<Q as WorldQueryGats<'world>>::ReadOnly/Fetch` - `<F as Fetch<'w, 's>>` should be changed to `<F as Fetch<'w>>` - Change the fn sigs of `Fetch::init/set_archetype/set_table` to match respective trait fn sigs - Implement the required `fn shrink` on any `WorldQuery` implementations - Move assoc types `Fetch` and `ReadOnlyFetch` on `WorldQuery` impls to `WorldQueryGats` impls - Pass an appropriate `'world` lifetime to whatever fetch struct you are for some reason using ### Type inference regression in some cases rustc may give spurrious errors when attempting to infer the `F` parameter on a query/querystate this can be fixed by manually specifying the type, i.e. `QueryState::new::<_, ()>(world)`. The error is rather confusing: ```rust= error[E0271]: type mismatch resolving `<() as Fetch<'_>>::Item == bool` --> crates/bevy_pbr/src/render/light.rs:1413:30 | 1413 | main_view_query: QueryState::new(world), | ^^^^^^^^^^^^^^^ expected `bool`, found `()` | = note: required because of the requirements on the impl of `for<'x> FilterFetch<'x>` for `<() as WorldQueryGats<'x>>::Fetch` note: required by a bound in `bevy_ecs::query::QueryState::<Q, F>::new` --> crates/bevy_ecs/src/query/state.rs:49:32 | 49 | for<'x> QueryFetch<'x, F>: FilterFetch<'x>, | ^^^^^^^^^^^^^^^ required by this bound in `bevy_ecs::query::QueryState::<Q, F>::new` ``` --- Made with help from @BoxyUwU and @alice-i-cecile Co-authored-by: Boxy <supbscripter@gmail.com>
2022-04-27 23:44:06 +00:00
Dynamic queries and builder API (#9774) # Objective Expand the existing `Query` API to support more dynamic use cases i.e. scripting. ## Prior Art - #6390 - #8308 - #10037 ## Solution - Create a `QueryBuilder` with runtime methods to define the set of component accesses for a built query. - Create new `WorldQueryData` implementations `FilteredEntityMut` and `FilteredEntityRef` as variants of `EntityMut` and `EntityRef` that provide run time checked access to the components included in a given query. - Add new methods to `Query` to create "query lens" with a subset of the access of the initial query. ### Query Builder The `QueryBuilder` API allows you to define a query at runtime. At it's most basic use it will simply create a query with the corresponding type signature: ```rust let query = QueryBuilder::<Entity, With<A>>::new(&mut world).build(); // is equivalent to let query = QueryState::<Entity, With<A>>::new(&mut world); ``` Before calling `.build()` you also have the opportunity to add additional accesses and filters. Here is a simple example where we add additional filter terms: ```rust let entity_a = world.spawn((A(0), B(0))).id(); let entity_b = world.spawn((A(0), C(0))).id(); let mut query_a = QueryBuilder::<Entity>::new(&mut world) .with::<A>() .without::<C>() .build(); assert_eq!(entity_a, query_a.single(&world)); ``` This alone is useful in that allows you to decide which archetypes your query will match at runtime. However it is also very limited, consider a case like the following: ```rust let query_a = QueryBuilder::<&A>::new(&mut world) // Add an additional access .data::<&B>() .build(); ``` This will grant the query an additional read access to component B however we have no way of accessing the data while iterating as the type signature still only includes &A. For an even more concrete example of this consider dynamic components: ```rust let query_a = QueryBuilder::<Entity>::new(&mut world) // Adding a filter is easy since it doesn't need be read later .with_id(component_id_a) // How do I access the data of this component? .ref_id(component_id_b) .build(); ``` With this in mind the `QueryBuilder` API seems somewhat incomplete by itself, we need some way method of accessing the components dynamically. So here's one: ### Query Transmutation If the problem is not having the component in the type signature why not just add it? This PR also adds transmute methods to `QueryBuilder` and `QueryState`. Here's a simple example: ```rust world.spawn(A(0)); world.spawn((A(1), B(0))); let mut query = QueryBuilder::<()>::new(&mut world) .with::<B>() .transmute::<&A>() .build(); query.iter(&world).for_each(|a| assert_eq!(a.0, 1)); ``` The `QueryState` and `QueryBuilder` transmute methods look quite similar but are different in one respect. Transmuting a builder will always succeed as it will just add the additional accesses needed for the new terms if they weren't already included. Transmuting a `QueryState` will panic in the case that the new type signature would give it access it didn't already have, for example: ```rust let query = QueryState::<&A, Option<&B>>::new(&mut world); /// This is fine, the access for Option<&A> is less restrictive than &A query.transmute::<Option<&A>>(&world); /// Oh no, this would allow access to &B on entities that might not have it, so it panics query.transmute::<&B>(&world); /// This is right out query.transmute::<&C>(&world); ``` This is quite an appealing API to also have available on `Query` however it does pose one additional wrinkle: In order to to change the iterator we need to create a new `QueryState` to back it. `Query` doesn't own it's own state though, it just borrows it, so we need a place to borrow it from. This is why `QueryLens` exists, it is a place to store the new state so it can be borrowed when you call `.query()` leaving you with an API like this: ```rust fn function_that_takes_a_query(query: &Query<&A>) { // ... } fn system(query: Query<(&A, &B)>) { let lens = query.transmute_lens::<&A>(); let q = lens.query(); function_that_takes_a_query(&q); } ``` Now you may be thinking: Hey, wait a second, you introduced the problem with dynamic components and then described a solution that only works for static components! Ok, you got me, I guess we need a bit more: ### Filtered Entity References Currently the only way you can access dynamic components on entities through a query is with either `EntityMut` or `EntityRef`, however these can access all components and so conflict with all other accesses. This PR introduces `FilteredEntityMut` and `FilteredEntityRef` as alternatives that have additional runtime checking to prevent accessing components that you shouldn't. This way you can build a query with a `QueryBuilder` and actually access the components you asked for: ```rust let mut query = QueryBuilder::<FilteredEntityRef>::new(&mut world) .ref_id(component_id_a) .with(component_id_b) .build(); let entity_ref = query.single(&world); // Returns Some(Ptr) as we have that component and are allowed to read it let a = entity_ref.get_by_id(component_id_a); // Will return None even though the entity does have the component, as we are not allowed to read it let b = entity_ref.get_by_id(component_id_b); ``` For the most part these new structs have the exact same methods as their non-filtered equivalents. Putting all of this together we can do some truly dynamic ECS queries, check out the `dynamic` example to see it in action: ``` Commands: comp, c Create new components spawn, s Spawn entities query, q Query for entities Enter a command with no parameters for usage. > c A, B, C, Data 4 Component A created with id: 0 Component B created with id: 1 Component C created with id: 2 Component Data created with id: 3 > s A, B, Data 1 Entity spawned with id: 0v0 > s A, C, Data 0 Entity spawned with id: 1v0 > q &Data 0v0: Data: [1, 0, 0, 0] 1v0: Data: [0, 0, 0, 0] > q B, &mut Data 0v0: Data: [2, 1, 1, 1] > q B || C, &Data 0v0: Data: [2, 1, 1, 1] 1v0: Data: [0, 0, 0, 0] ``` ## Changelog - Add new `transmute_lens` methods to `Query`. - Add new types `QueryBuilder`, `FilteredEntityMut`, `FilteredEntityRef` and `QueryLens` - `update_archetype_component_access` has been removed, archetype component accesses are now determined by the accesses set in `update_component_access` - Added method `set_access` to `WorldQuery`, this is called before `update_component_access` for queries that have a restricted set of accesses, such as those built by `QueryBuilder` or `QueryLens`. This is primarily used by the `FilteredEntity*` variants and has an empty trait implementation. - Added method `get_state` to `WorldQuery` as a fallible version of `init_state` when you don't have `&mut World` access. ## Future Work Improve performance of `FilteredEntityMut` and `FilteredEntityRef`, currently they have to determine the accesses a query has in a given archetype during iteration which is far from ideal, especially since we already did the work when matching the archetype in the first place. To avoid making more internal API changes I have left it out of this PR. --------- Co-authored-by: Mike Hsu <mike.hsu@gmail.com>
2024-01-16 19:16:49 +00:00
fn get_state(world: &World) -> Option<Self::State> {
world.component_id::<T>()
}
fn matches_component_set(
&state: &ComponentId,
set_contains_id: &impl Fn(ComponentId) -> bool,
) -> bool {
set_contains_id(state)
Use lifetimed, type erased pointers in bevy_ecs (#3001) # Objective `bevy_ecs` has large amounts of unsafe code which is hard to get right and makes it difficult to audit for soundness. ## Solution Introduce lifetimed, type-erased pointers: `Ptr<'a>` `PtrMut<'a>` `OwningPtr<'a>'` and `ThinSlicePtr<'a, T>` which are newtypes around a raw pointer with a lifetime and conceptually representing strong invariants about the pointee and validity of the pointer. The process of converting bevy_ecs to use these has already caught multiple cases of unsound behavior. ## Changelog TL;DR for release notes: `bevy_ecs` now uses lifetimed, type-erased pointers internally, significantly improving safety and legibility without sacrificing performance. This should have approximately no end user impact, unless you were meddling with the (unfortunately public) internals of `bevy_ecs`. - `Fetch`, `FilterFetch` and `ReadOnlyFetch` trait no longer have a `'state` lifetime - this was unneeded - `ReadOnly/Fetch` associated types on `WorldQuery` are now on a new `WorldQueryGats<'world>` trait - was required to work around lack of Generic Associated Types (we wish to express `type Fetch<'a>: Fetch<'a>`) - `derive(WorldQuery)` no longer requires `'w` lifetime on struct - this was unneeded, and improves the end user experience - `EntityMut::get_unchecked_mut` returns `&'_ mut T` not `&'w mut T` - allows easier use of unsafe API with less footguns, and can be worked around via lifetime transmutery as a user - `Bundle::from_components` now takes a `ctx` parameter to pass to the `FnMut` closure - required because closure return types can't borrow from captures - `Fetch::init` takes `&'world World`, `Fetch::set_archetype` takes `&'world Archetype` and `&'world Tables`, `Fetch::set_table` takes `&'world Table` - allows types implementing `Fetch` to store borrows into world - `WorldQuery` trait now has a `shrink` fn to shorten the lifetime in `Fetch::<'a>::Item` - this works around lack of subtyping of assoc types, rust doesnt allow you to turn `<T as Fetch<'static>>::Item'` into `<T as Fetch<'a>>::Item'` - `QueryCombinationsIter` requires this - Most types implementing `Fetch` now have a lifetime `'w` - allows the fetches to store borrows of world data instead of using raw pointers ## Migration guide - `EntityMut::get_unchecked_mut` returns a more restricted lifetime, there is no general way to migrate this as it depends on your code - `Bundle::from_components` implementations must pass the `ctx` arg to `func` - `Bundle::from_components` callers have to use a fn arg instead of closure captures for borrowing from world - Remove lifetime args on `derive(WorldQuery)` structs as it is nonsensical - `<Q as WorldQuery>::ReadOnly/Fetch` should be changed to either `RO/QueryFetch<'world>` or `<Q as WorldQueryGats<'world>>::ReadOnly/Fetch` - `<F as Fetch<'w, 's>>` should be changed to `<F as Fetch<'w>>` - Change the fn sigs of `Fetch::init/set_archetype/set_table` to match respective trait fn sigs - Implement the required `fn shrink` on any `WorldQuery` implementations - Move assoc types `Fetch` and `ReadOnlyFetch` on `WorldQuery` impls to `WorldQueryGats` impls - Pass an appropriate `'world` lifetime to whatever fetch struct you are for some reason using ### Type inference regression in some cases rustc may give spurrious errors when attempting to infer the `F` parameter on a query/querystate this can be fixed by manually specifying the type, i.e. `QueryState::new::<_, ()>(world)`. The error is rather confusing: ```rust= error[E0271]: type mismatch resolving `<() as Fetch<'_>>::Item == bool` --> crates/bevy_pbr/src/render/light.rs:1413:30 | 1413 | main_view_query: QueryState::new(world), | ^^^^^^^^^^^^^^^ expected `bool`, found `()` | = note: required because of the requirements on the impl of `for<'x> FilterFetch<'x>` for `<() as WorldQueryGats<'x>>::Fetch` note: required by a bound in `bevy_ecs::query::QueryState::<Q, F>::new` --> crates/bevy_ecs/src/query/state.rs:49:32 | 49 | for<'x> QueryFetch<'x, F>: FilterFetch<'x>, | ^^^^^^^^^^^^^^^ required by this bound in `bevy_ecs::query::QueryState::<Q, F>::new` ``` --- Made with help from @BoxyUwU and @alice-i-cecile Co-authored-by: Boxy <supbscripter@gmail.com>
2022-04-27 23:44:06 +00:00
}
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
}
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
/// SAFETY: access of `&T` is a subset of `&mut T`
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
unsafe impl<'__w, T: Component> QueryData for &'__w mut T {
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
type ReadOnly = &'__w T;
}
#[doc(hidden)]
pub struct OptionFetch<'w, T: WorldQuery> {
fetch: T::Fetch<'w>,
matches: bool,
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
}
Fix safety invariants for `WorldQuery::fetch` and simplify cloning (#8246) # Objective Cloning a `WorldQuery` type's "fetch" struct was made unsafe in #5593, by adding the `unsafe fn clone_fetch` to `WorldQuery`. However, as that method's documentation explains, it is not the right place to put the safety invariant: > While calling this method on its own cannot cause UB it is marked `unsafe` as the caller must ensure that the returned value is not used in any way that would cause two `QueryItem<Self>` for the same `archetype_index` or `table_row` to be alive at the same time. You can clone a fetch struct all you want and it will never cause undefined behavior -- in order for something to go wrong, you need to improperly call `WorldQuery::fetch` with it (which is marked unsafe). Additionally, making it unsafe to clone a fetch struct does not even prevent undefined behavior, since there are other ways to incorrectly use a fetch struct. For example, you could just call fetch more than once for the same entity, which is not currently forbidden by any documented invariants. ## Solution Document a safety invariant on `WorldQuery::fetch` that requires the caller to not create aliased `WorldQueryItem`s for mutable types. Remove the `clone_fetch` function, and add the bound `Fetch: Clone` instead. --- ## Changelog - Removed the associated function `WorldQuery::clone_fetch`, and added a `Clone` bound to `WorldQuery::Fetch`. ## Migration Guide ### `fetch` invariants The function `WorldQuery::fetch` has had the following safety invariant added: > If this type does not implement `ReadOnlyWorldQuery`, then the caller must ensure that it is impossible for more than one `Self::Item` to exist for the same entity at any given time. This invariant was always required for soundness, but was previously undocumented. If you called this function manually anywhere, you should check to make sure that this invariant is not violated. ### Removed `clone_fetch` The function `WorldQuery::clone_fetch` has been removed. The associated type `WorldQuery::Fetch` now has the bound `Clone`. Before: ```rust struct MyFetch<'w> { ... } unsafe impl WorldQuery for MyQuery { ... type Fetch<'w> = MyFetch<'w> unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> { MyFetch { field1: fetch.field1, field2: fetch.field2.clone(), ... } } } ``` After: ```rust #[derive(Clone)] struct MyFetch<'w> { ... } unsafe impl WorldQuery for MyQuery { ... type Fetch<'w> = MyFetch<'w>; } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-07-25 21:16:22 +00:00
impl<T: WorldQuery> Clone for OptionFetch<'_, T> {
fn clone(&self) -> Self {
Self {
fetch: self.fetch.clone(),
matches: self.matches,
}
}
}
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
/// SAFETY:
/// `fetch` might access any components that `T` accesses.
/// This is sound because `update_component_access` and `update_archetype_component_access` add the same accesses as `T`.
/// Filters are unchanged.
unsafe impl<T: WorldQuery> WorldQuery for Option<T> {
type Item<'w> = Option<T::Item<'w>>;
type Fetch<'w> = OptionFetch<'w, T>;
type State = T::State;
Use lifetimed, type erased pointers in bevy_ecs (#3001) # Objective `bevy_ecs` has large amounts of unsafe code which is hard to get right and makes it difficult to audit for soundness. ## Solution Introduce lifetimed, type-erased pointers: `Ptr<'a>` `PtrMut<'a>` `OwningPtr<'a>'` and `ThinSlicePtr<'a, T>` which are newtypes around a raw pointer with a lifetime and conceptually representing strong invariants about the pointee and validity of the pointer. The process of converting bevy_ecs to use these has already caught multiple cases of unsound behavior. ## Changelog TL;DR for release notes: `bevy_ecs` now uses lifetimed, type-erased pointers internally, significantly improving safety and legibility without sacrificing performance. This should have approximately no end user impact, unless you were meddling with the (unfortunately public) internals of `bevy_ecs`. - `Fetch`, `FilterFetch` and `ReadOnlyFetch` trait no longer have a `'state` lifetime - this was unneeded - `ReadOnly/Fetch` associated types on `WorldQuery` are now on a new `WorldQueryGats<'world>` trait - was required to work around lack of Generic Associated Types (we wish to express `type Fetch<'a>: Fetch<'a>`) - `derive(WorldQuery)` no longer requires `'w` lifetime on struct - this was unneeded, and improves the end user experience - `EntityMut::get_unchecked_mut` returns `&'_ mut T` not `&'w mut T` - allows easier use of unsafe API with less footguns, and can be worked around via lifetime transmutery as a user - `Bundle::from_components` now takes a `ctx` parameter to pass to the `FnMut` closure - required because closure return types can't borrow from captures - `Fetch::init` takes `&'world World`, `Fetch::set_archetype` takes `&'world Archetype` and `&'world Tables`, `Fetch::set_table` takes `&'world Table` - allows types implementing `Fetch` to store borrows into world - `WorldQuery` trait now has a `shrink` fn to shorten the lifetime in `Fetch::<'a>::Item` - this works around lack of subtyping of assoc types, rust doesnt allow you to turn `<T as Fetch<'static>>::Item'` into `<T as Fetch<'a>>::Item'` - `QueryCombinationsIter` requires this - Most types implementing `Fetch` now have a lifetime `'w` - allows the fetches to store borrows of world data instead of using raw pointers ## Migration guide - `EntityMut::get_unchecked_mut` returns a more restricted lifetime, there is no general way to migrate this as it depends on your code - `Bundle::from_components` implementations must pass the `ctx` arg to `func` - `Bundle::from_components` callers have to use a fn arg instead of closure captures for borrowing from world - Remove lifetime args on `derive(WorldQuery)` structs as it is nonsensical - `<Q as WorldQuery>::ReadOnly/Fetch` should be changed to either `RO/QueryFetch<'world>` or `<Q as WorldQueryGats<'world>>::ReadOnly/Fetch` - `<F as Fetch<'w, 's>>` should be changed to `<F as Fetch<'w>>` - Change the fn sigs of `Fetch::init/set_archetype/set_table` to match respective trait fn sigs - Implement the required `fn shrink` on any `WorldQuery` implementations - Move assoc types `Fetch` and `ReadOnlyFetch` on `WorldQuery` impls to `WorldQueryGats` impls - Pass an appropriate `'world` lifetime to whatever fetch struct you are for some reason using ### Type inference regression in some cases rustc may give spurrious errors when attempting to infer the `F` parameter on a query/querystate this can be fixed by manually specifying the type, i.e. `QueryState::new::<_, ()>(world)`. The error is rather confusing: ```rust= error[E0271]: type mismatch resolving `<() as Fetch<'_>>::Item == bool` --> crates/bevy_pbr/src/render/light.rs:1413:30 | 1413 | main_view_query: QueryState::new(world), | ^^^^^^^^^^^^^^^ expected `bool`, found `()` | = note: required because of the requirements on the impl of `for<'x> FilterFetch<'x>` for `<() as WorldQueryGats<'x>>::Fetch` note: required by a bound in `bevy_ecs::query::QueryState::<Q, F>::new` --> crates/bevy_ecs/src/query/state.rs:49:32 | 49 | for<'x> QueryFetch<'x, F>: FilterFetch<'x>, | ^^^^^^^^^^^^^^^ required by this bound in `bevy_ecs::query::QueryState::<Q, F>::new` ``` --- Made with help from @BoxyUwU and @alice-i-cecile Co-authored-by: Boxy <supbscripter@gmail.com>
2022-04-27 23:44:06 +00:00
fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> {
item.map(T::shrink)
}
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
#[inline]
unsafe fn init_fetch<'w>(
Migrate the rest of the engine to `UnsafeWorldCell` (#8833) # Objective Follow-up to #6404 and #8292. Mutating the world through a shared reference is surprising, and it makes the meaning of `&World` unclear: sometimes it gives read-only access to the entire world, and sometimes it gives interior mutable access to only part of it. This is an up-to-date version of #6972. ## Solution Use `UnsafeWorldCell` for all interior mutability. Now, `&World` *always* gives you read-only access to the entire world. --- ## Changelog TODO - do we still care about changelogs? ## Migration Guide Mutating any world data using `&World` is now considered unsound -- the type `UnsafeWorldCell` must be used to achieve interior mutability. The following methods now accept `UnsafeWorldCell` instead of `&World`: - `QueryState`: `get_unchecked`, `iter_unchecked`, `iter_combinations_unchecked`, `for_each_unchecked`, `get_single_unchecked`, `get_single_unchecked_manual`. - `SystemState`: `get_unchecked_manual` ```rust let mut world = World::new(); let mut query = world.query::<&mut T>(); // Before: let t1 = query.get_unchecked(&world, entity_1); let t2 = query.get_unchecked(&world, entity_2); // After: let world_cell = world.as_unsafe_world_cell(); let t1 = query.get_unchecked(world_cell, entity_1); let t2 = query.get_unchecked(world_cell, entity_2); ``` The methods `QueryState::validate_world` and `SystemState::matches_world` now take a `WorldId` instead of `&World`: ```rust // Before: query_state.validate_world(&world); // After: query_state.validate_world(world.id()); ``` The methods `QueryState::update_archetypes` and `SystemState::update_archetypes` now take `UnsafeWorldCell` instead of `&World`: ```rust // Before: query_state.update_archetypes(&world); // After: query_state.update_archetypes(world.as_unsafe_world_cell_readonly()); ```
2023-06-15 01:31:56 +00:00
world: UnsafeWorldCell<'w>,
state: &T::State,
last_run: Tick,
this_run: Tick,
) -> OptionFetch<'w, T> {
OptionFetch {
fetch: T::init_fetch(world, state, last_run, this_run),
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
matches: false,
}
}
const IS_DENSE: bool = T::IS_DENSE;
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
#[inline]
unsafe fn set_archetype<'w>(
fetch: &mut OptionFetch<'w, T>,
state: &T::State,
Use lifetimed, type erased pointers in bevy_ecs (#3001) # Objective `bevy_ecs` has large amounts of unsafe code which is hard to get right and makes it difficult to audit for soundness. ## Solution Introduce lifetimed, type-erased pointers: `Ptr<'a>` `PtrMut<'a>` `OwningPtr<'a>'` and `ThinSlicePtr<'a, T>` which are newtypes around a raw pointer with a lifetime and conceptually representing strong invariants about the pointee and validity of the pointer. The process of converting bevy_ecs to use these has already caught multiple cases of unsound behavior. ## Changelog TL;DR for release notes: `bevy_ecs` now uses lifetimed, type-erased pointers internally, significantly improving safety and legibility without sacrificing performance. This should have approximately no end user impact, unless you were meddling with the (unfortunately public) internals of `bevy_ecs`. - `Fetch`, `FilterFetch` and `ReadOnlyFetch` trait no longer have a `'state` lifetime - this was unneeded - `ReadOnly/Fetch` associated types on `WorldQuery` are now on a new `WorldQueryGats<'world>` trait - was required to work around lack of Generic Associated Types (we wish to express `type Fetch<'a>: Fetch<'a>`) - `derive(WorldQuery)` no longer requires `'w` lifetime on struct - this was unneeded, and improves the end user experience - `EntityMut::get_unchecked_mut` returns `&'_ mut T` not `&'w mut T` - allows easier use of unsafe API with less footguns, and can be worked around via lifetime transmutery as a user - `Bundle::from_components` now takes a `ctx` parameter to pass to the `FnMut` closure - required because closure return types can't borrow from captures - `Fetch::init` takes `&'world World`, `Fetch::set_archetype` takes `&'world Archetype` and `&'world Tables`, `Fetch::set_table` takes `&'world Table` - allows types implementing `Fetch` to store borrows into world - `WorldQuery` trait now has a `shrink` fn to shorten the lifetime in `Fetch::<'a>::Item` - this works around lack of subtyping of assoc types, rust doesnt allow you to turn `<T as Fetch<'static>>::Item'` into `<T as Fetch<'a>>::Item'` - `QueryCombinationsIter` requires this - Most types implementing `Fetch` now have a lifetime `'w` - allows the fetches to store borrows of world data instead of using raw pointers ## Migration guide - `EntityMut::get_unchecked_mut` returns a more restricted lifetime, there is no general way to migrate this as it depends on your code - `Bundle::from_components` implementations must pass the `ctx` arg to `func` - `Bundle::from_components` callers have to use a fn arg instead of closure captures for borrowing from world - Remove lifetime args on `derive(WorldQuery)` structs as it is nonsensical - `<Q as WorldQuery>::ReadOnly/Fetch` should be changed to either `RO/QueryFetch<'world>` or `<Q as WorldQueryGats<'world>>::ReadOnly/Fetch` - `<F as Fetch<'w, 's>>` should be changed to `<F as Fetch<'w>>` - Change the fn sigs of `Fetch::init/set_archetype/set_table` to match respective trait fn sigs - Implement the required `fn shrink` on any `WorldQuery` implementations - Move assoc types `Fetch` and `ReadOnlyFetch` on `WorldQuery` impls to `WorldQueryGats` impls - Pass an appropriate `'world` lifetime to whatever fetch struct you are for some reason using ### Type inference regression in some cases rustc may give spurrious errors when attempting to infer the `F` parameter on a query/querystate this can be fixed by manually specifying the type, i.e. `QueryState::new::<_, ()>(world)`. The error is rather confusing: ```rust= error[E0271]: type mismatch resolving `<() as Fetch<'_>>::Item == bool` --> crates/bevy_pbr/src/render/light.rs:1413:30 | 1413 | main_view_query: QueryState::new(world), | ^^^^^^^^^^^^^^^ expected `bool`, found `()` | = note: required because of the requirements on the impl of `for<'x> FilterFetch<'x>` for `<() as WorldQueryGats<'x>>::Fetch` note: required by a bound in `bevy_ecs::query::QueryState::<Q, F>::new` --> crates/bevy_ecs/src/query/state.rs:49:32 | 49 | for<'x> QueryFetch<'x, F>: FilterFetch<'x>, | ^^^^^^^^^^^^^^^ required by this bound in `bevy_ecs::query::QueryState::<Q, F>::new` ``` --- Made with help from @BoxyUwU and @alice-i-cecile Co-authored-by: Boxy <supbscripter@gmail.com>
2022-04-27 23:44:06 +00:00
archetype: &'w Archetype,
table: &'w Table,
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
) {
fetch.matches = T::matches_component_set(state, &|id| archetype.contains(id));
if fetch.matches {
T::set_archetype(&mut fetch.fetch, state, archetype, table);
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
}
}
#[inline]
unsafe fn set_table<'w>(fetch: &mut OptionFetch<'w, T>, state: &T::State, table: &'w Table) {
fetch.matches = T::matches_component_set(state, &|id| table.has_column(id));
if fetch.matches {
T::set_table(&mut fetch.fetch, state, table);
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
}
}
#[inline(always)]
unsafe fn fetch<'w>(
fetch: &mut Self::Fetch<'w>,
entity: Entity,
table_row: TableRow,
) -> Self::Item<'w> {
fetch
.matches
.then(|| T::fetch(&mut fetch.fetch, entity, table_row))
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
}
Replace `ReadOnlyFetch` with `ReadOnlyWorldQuery` (#4626) # Objective - Fix a type inference regression introduced by #3001 - Make read only bounds on world queries more user friendly ptrification required you to write `Q::Fetch: ReadOnlyFetch` as `for<'w> QueryFetch<'w, Q>: ReadOnlyFetch` which has the same type inference problem as `for<'w> QueryFetch<'w, Q>: FilterFetch<'w>` had, i.e. the following code would error: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: WorldQuery>(a: Query<Q, ()>) where for<'w> QueryFetch<'w, Q>: ReadOnlyFetch, { } ``` `for<..>` bounds are also rather user unfriendly.. ## Solution Remove the `ReadOnlyFetch` trait in favour of a `ReadOnlyWorldQuery` trait, and remove `WorldQueryGats::ReadOnlyFetch` in favor of `WorldQuery::ReadOnly` allowing the previous code snippet to be written as: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: ReadOnlyWorldQuery>(a: Query<Q, ()>) {} ``` This avoids the `for<...>` bound which makes the code simpler and also fixes the type inference issue. The reason for moving the two functions out of `FetchState` and into `WorldQuery` is to allow the world query `&mut T` to share a `State` with the `&T` world query so that it can have `type ReadOnly = &T`. Presumably it would be possible to instead have a `ReadOnlyRefMut<T>` world query and then do `type ReadOnly = ReadOnlyRefMut<T>` much like how (before this PR) we had a `ReadOnlyWriteFetch<T>`. A side benefit of the current solution in this PR is that it will likely make it easier in the future to support an API such as `Query<&mut T> -> Query<&T>`. The primary benefit IMO is just that `ReadOnlyRefMut<T>` and its associated fetch would have to reimplement all of the logic that the `&T` world query impl does but this solution avoids that :) --- ## Changelog/Migration Guide The trait `ReadOnlyFetch` has been replaced with `ReadOnlyWorldQuery` along with the `WorldQueryGats::ReadOnlyFetch` assoc type which has been replaced with `<WorldQuery::ReadOnly as WorldQueryGats>::Fetch` - Any where clauses such as `QueryFetch<Q>: ReadOnlyFetch` should be replaced with `Q: ReadOnlyWorldQuery`. - Any custom world query impls should implement `ReadOnlyWorldQuery` insead of `ReadOnlyFetch` Functions `update_component_access` and `update_archetype_component_access` have been moved from the `FetchState` trait to `WorldQuery` - Any callers should now call `Q::update_component_access(state` instead of `state.update_component_access` (and `update_archetype_component_access` respectively) - Any custom world query impls should move the functions from the `FetchState` impl to `WorldQuery` impl `WorldQuery` has been made an `unsafe trait`, `FetchState` has been made a safe `trait`. (I think this is how it should have always been, but regardless this is _definitely_ necessary now that the two functions have been moved to `WorldQuery`) - If you have a custom `FetchState` impl make it a normal `impl` instead of `unsafe impl` - If you have a custom `WorldQuery` impl make it an `unsafe impl`, if your code was sound before it is going to still be sound
2022-06-13 23:35:54 +00:00
fn update_component_access(state: &T::State, access: &mut FilteredAccess<ComponentId>) {
// FilteredAccess::add_[write,read] adds the component to the `with` filter.
// Those methods are called on `access` in `T::update_component_access`.
// But in `Option<T>`, we specifically don't filter on `T`,
// since `(Option<T>, &OtherComponent)` should be a valid item, even
// if `Option<T>` is `None`.
//
// We pass a clone of the `FilteredAccess` to `T`, and only update the `Access`
// using `extend_access` so that we can apply `T`'s component_access
// without updating the `with` filters of `access`.
Replace `ReadOnlyFetch` with `ReadOnlyWorldQuery` (#4626) # Objective - Fix a type inference regression introduced by #3001 - Make read only bounds on world queries more user friendly ptrification required you to write `Q::Fetch: ReadOnlyFetch` as `for<'w> QueryFetch<'w, Q>: ReadOnlyFetch` which has the same type inference problem as `for<'w> QueryFetch<'w, Q>: FilterFetch<'w>` had, i.e. the following code would error: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: WorldQuery>(a: Query<Q, ()>) where for<'w> QueryFetch<'w, Q>: ReadOnlyFetch, { } ``` `for<..>` bounds are also rather user unfriendly.. ## Solution Remove the `ReadOnlyFetch` trait in favour of a `ReadOnlyWorldQuery` trait, and remove `WorldQueryGats::ReadOnlyFetch` in favor of `WorldQuery::ReadOnly` allowing the previous code snippet to be written as: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: ReadOnlyWorldQuery>(a: Query<Q, ()>) {} ``` This avoids the `for<...>` bound which makes the code simpler and also fixes the type inference issue. The reason for moving the two functions out of `FetchState` and into `WorldQuery` is to allow the world query `&mut T` to share a `State` with the `&T` world query so that it can have `type ReadOnly = &T`. Presumably it would be possible to instead have a `ReadOnlyRefMut<T>` world query and then do `type ReadOnly = ReadOnlyRefMut<T>` much like how (before this PR) we had a `ReadOnlyWriteFetch<T>`. A side benefit of the current solution in this PR is that it will likely make it easier in the future to support an API such as `Query<&mut T> -> Query<&T>`. The primary benefit IMO is just that `ReadOnlyRefMut<T>` and its associated fetch would have to reimplement all of the logic that the `&T` world query impl does but this solution avoids that :) --- ## Changelog/Migration Guide The trait `ReadOnlyFetch` has been replaced with `ReadOnlyWorldQuery` along with the `WorldQueryGats::ReadOnlyFetch` assoc type which has been replaced with `<WorldQuery::ReadOnly as WorldQueryGats>::Fetch` - Any where clauses such as `QueryFetch<Q>: ReadOnlyFetch` should be replaced with `Q: ReadOnlyWorldQuery`. - Any custom world query impls should implement `ReadOnlyWorldQuery` insead of `ReadOnlyFetch` Functions `update_component_access` and `update_archetype_component_access` have been moved from the `FetchState` trait to `WorldQuery` - Any callers should now call `Q::update_component_access(state` instead of `state.update_component_access` (and `update_archetype_component_access` respectively) - Any custom world query impls should move the functions from the `FetchState` impl to `WorldQuery` impl `WorldQuery` has been made an `unsafe trait`, `FetchState` has been made a safe `trait`. (I think this is how it should have always been, but regardless this is _definitely_ necessary now that the two functions have been moved to `WorldQuery`) - If you have a custom `FetchState` impl make it a normal `impl` instead of `unsafe impl` - If you have a custom `WorldQuery` impl make it an `unsafe impl`, if your code was sound before it is going to still be sound
2022-06-13 23:35:54 +00:00
let mut intermediate = access.clone();
T::update_component_access(state, &mut intermediate);
Replace `ReadOnlyFetch` with `ReadOnlyWorldQuery` (#4626) # Objective - Fix a type inference regression introduced by #3001 - Make read only bounds on world queries more user friendly ptrification required you to write `Q::Fetch: ReadOnlyFetch` as `for<'w> QueryFetch<'w, Q>: ReadOnlyFetch` which has the same type inference problem as `for<'w> QueryFetch<'w, Q>: FilterFetch<'w>` had, i.e. the following code would error: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: WorldQuery>(a: Query<Q, ()>) where for<'w> QueryFetch<'w, Q>: ReadOnlyFetch, { } ``` `for<..>` bounds are also rather user unfriendly.. ## Solution Remove the `ReadOnlyFetch` trait in favour of a `ReadOnlyWorldQuery` trait, and remove `WorldQueryGats::ReadOnlyFetch` in favor of `WorldQuery::ReadOnly` allowing the previous code snippet to be written as: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: ReadOnlyWorldQuery>(a: Query<Q, ()>) {} ``` This avoids the `for<...>` bound which makes the code simpler and also fixes the type inference issue. The reason for moving the two functions out of `FetchState` and into `WorldQuery` is to allow the world query `&mut T` to share a `State` with the `&T` world query so that it can have `type ReadOnly = &T`. Presumably it would be possible to instead have a `ReadOnlyRefMut<T>` world query and then do `type ReadOnly = ReadOnlyRefMut<T>` much like how (before this PR) we had a `ReadOnlyWriteFetch<T>`. A side benefit of the current solution in this PR is that it will likely make it easier in the future to support an API such as `Query<&mut T> -> Query<&T>`. The primary benefit IMO is just that `ReadOnlyRefMut<T>` and its associated fetch would have to reimplement all of the logic that the `&T` world query impl does but this solution avoids that :) --- ## Changelog/Migration Guide The trait `ReadOnlyFetch` has been replaced with `ReadOnlyWorldQuery` along with the `WorldQueryGats::ReadOnlyFetch` assoc type which has been replaced with `<WorldQuery::ReadOnly as WorldQueryGats>::Fetch` - Any where clauses such as `QueryFetch<Q>: ReadOnlyFetch` should be replaced with `Q: ReadOnlyWorldQuery`. - Any custom world query impls should implement `ReadOnlyWorldQuery` insead of `ReadOnlyFetch` Functions `update_component_access` and `update_archetype_component_access` have been moved from the `FetchState` trait to `WorldQuery` - Any callers should now call `Q::update_component_access(state` instead of `state.update_component_access` (and `update_archetype_component_access` respectively) - Any custom world query impls should move the functions from the `FetchState` impl to `WorldQuery` impl `WorldQuery` has been made an `unsafe trait`, `FetchState` has been made a safe `trait`. (I think this is how it should have always been, but regardless this is _definitely_ necessary now that the two functions have been moved to `WorldQuery`) - If you have a custom `FetchState` impl make it a normal `impl` instead of `unsafe impl` - If you have a custom `WorldQuery` impl make it an `unsafe impl`, if your code was sound before it is going to still be sound
2022-06-13 23:35:54 +00:00
access.extend_access(&intermediate);
}
fn init_state(world: &mut World) -> T::State {
T::init_state(world)
}
Dynamic queries and builder API (#9774) # Objective Expand the existing `Query` API to support more dynamic use cases i.e. scripting. ## Prior Art - #6390 - #8308 - #10037 ## Solution - Create a `QueryBuilder` with runtime methods to define the set of component accesses for a built query. - Create new `WorldQueryData` implementations `FilteredEntityMut` and `FilteredEntityRef` as variants of `EntityMut` and `EntityRef` that provide run time checked access to the components included in a given query. - Add new methods to `Query` to create "query lens" with a subset of the access of the initial query. ### Query Builder The `QueryBuilder` API allows you to define a query at runtime. At it's most basic use it will simply create a query with the corresponding type signature: ```rust let query = QueryBuilder::<Entity, With<A>>::new(&mut world).build(); // is equivalent to let query = QueryState::<Entity, With<A>>::new(&mut world); ``` Before calling `.build()` you also have the opportunity to add additional accesses and filters. Here is a simple example where we add additional filter terms: ```rust let entity_a = world.spawn((A(0), B(0))).id(); let entity_b = world.spawn((A(0), C(0))).id(); let mut query_a = QueryBuilder::<Entity>::new(&mut world) .with::<A>() .without::<C>() .build(); assert_eq!(entity_a, query_a.single(&world)); ``` This alone is useful in that allows you to decide which archetypes your query will match at runtime. However it is also very limited, consider a case like the following: ```rust let query_a = QueryBuilder::<&A>::new(&mut world) // Add an additional access .data::<&B>() .build(); ``` This will grant the query an additional read access to component B however we have no way of accessing the data while iterating as the type signature still only includes &A. For an even more concrete example of this consider dynamic components: ```rust let query_a = QueryBuilder::<Entity>::new(&mut world) // Adding a filter is easy since it doesn't need be read later .with_id(component_id_a) // How do I access the data of this component? .ref_id(component_id_b) .build(); ``` With this in mind the `QueryBuilder` API seems somewhat incomplete by itself, we need some way method of accessing the components dynamically. So here's one: ### Query Transmutation If the problem is not having the component in the type signature why not just add it? This PR also adds transmute methods to `QueryBuilder` and `QueryState`. Here's a simple example: ```rust world.spawn(A(0)); world.spawn((A(1), B(0))); let mut query = QueryBuilder::<()>::new(&mut world) .with::<B>() .transmute::<&A>() .build(); query.iter(&world).for_each(|a| assert_eq!(a.0, 1)); ``` The `QueryState` and `QueryBuilder` transmute methods look quite similar but are different in one respect. Transmuting a builder will always succeed as it will just add the additional accesses needed for the new terms if they weren't already included. Transmuting a `QueryState` will panic in the case that the new type signature would give it access it didn't already have, for example: ```rust let query = QueryState::<&A, Option<&B>>::new(&mut world); /// This is fine, the access for Option<&A> is less restrictive than &A query.transmute::<Option<&A>>(&world); /// Oh no, this would allow access to &B on entities that might not have it, so it panics query.transmute::<&B>(&world); /// This is right out query.transmute::<&C>(&world); ``` This is quite an appealing API to also have available on `Query` however it does pose one additional wrinkle: In order to to change the iterator we need to create a new `QueryState` to back it. `Query` doesn't own it's own state though, it just borrows it, so we need a place to borrow it from. This is why `QueryLens` exists, it is a place to store the new state so it can be borrowed when you call `.query()` leaving you with an API like this: ```rust fn function_that_takes_a_query(query: &Query<&A>) { // ... } fn system(query: Query<(&A, &B)>) { let lens = query.transmute_lens::<&A>(); let q = lens.query(); function_that_takes_a_query(&q); } ``` Now you may be thinking: Hey, wait a second, you introduced the problem with dynamic components and then described a solution that only works for static components! Ok, you got me, I guess we need a bit more: ### Filtered Entity References Currently the only way you can access dynamic components on entities through a query is with either `EntityMut` or `EntityRef`, however these can access all components and so conflict with all other accesses. This PR introduces `FilteredEntityMut` and `FilteredEntityRef` as alternatives that have additional runtime checking to prevent accessing components that you shouldn't. This way you can build a query with a `QueryBuilder` and actually access the components you asked for: ```rust let mut query = QueryBuilder::<FilteredEntityRef>::new(&mut world) .ref_id(component_id_a) .with(component_id_b) .build(); let entity_ref = query.single(&world); // Returns Some(Ptr) as we have that component and are allowed to read it let a = entity_ref.get_by_id(component_id_a); // Will return None even though the entity does have the component, as we are not allowed to read it let b = entity_ref.get_by_id(component_id_b); ``` For the most part these new structs have the exact same methods as their non-filtered equivalents. Putting all of this together we can do some truly dynamic ECS queries, check out the `dynamic` example to see it in action: ``` Commands: comp, c Create new components spawn, s Spawn entities query, q Query for entities Enter a command with no parameters for usage. > c A, B, C, Data 4 Component A created with id: 0 Component B created with id: 1 Component C created with id: 2 Component Data created with id: 3 > s A, B, Data 1 Entity spawned with id: 0v0 > s A, C, Data 0 Entity spawned with id: 1v0 > q &Data 0v0: Data: [1, 0, 0, 0] 1v0: Data: [0, 0, 0, 0] > q B, &mut Data 0v0: Data: [2, 1, 1, 1] > q B || C, &Data 0v0: Data: [2, 1, 1, 1] 1v0: Data: [0, 0, 0, 0] ``` ## Changelog - Add new `transmute_lens` methods to `Query`. - Add new types `QueryBuilder`, `FilteredEntityMut`, `FilteredEntityRef` and `QueryLens` - `update_archetype_component_access` has been removed, archetype component accesses are now determined by the accesses set in `update_component_access` - Added method `set_access` to `WorldQuery`, this is called before `update_component_access` for queries that have a restricted set of accesses, such as those built by `QueryBuilder` or `QueryLens`. This is primarily used by the `FilteredEntity*` variants and has an empty trait implementation. - Added method `get_state` to `WorldQuery` as a fallible version of `init_state` when you don't have `&mut World` access. ## Future Work Improve performance of `FilteredEntityMut` and `FilteredEntityRef`, currently they have to determine the accesses a query has in a given archetype during iteration which is far from ideal, especially since we already did the work when matching the archetype in the first place. To avoid making more internal API changes I have left it out of this PR. --------- Co-authored-by: Mike Hsu <mike.hsu@gmail.com>
2024-01-16 19:16:49 +00:00
fn get_state(world: &World) -> Option<Self::State> {
T::get_state(world)
}
fn matches_component_set(
_state: &T::State,
_set_contains_id: &impl Fn(ComponentId) -> bool,
) -> bool {
true
}
}
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
// SAFETY: defers to soundness of `T: WorldQuery` impl
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
unsafe impl<T: QueryData> QueryData for Option<T> {
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
type ReadOnly = Option<T::ReadOnly>;
}
/// SAFETY: [`OptionFetch`] is read only because `T` is read only
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
unsafe impl<T: ReadOnlyQueryData> ReadOnlyQueryData for Option<T> {}
/// Returns a bool that describes if an entity has the component `T`.
///
/// This can be used in a [`Query`](crate::system::Query) if you want to know whether or not entities
/// have the component `T` but don't actually care about the component's value.
///
/// # Footguns
///
/// Note that a `Query<Has<T>>` will match all existing entities.
/// Beware! Even if it matches all entities, it doesn't mean that `query.get(entity)`
/// will always return `Ok(bool)`.
///
/// In the case of a non-existent entity, such as a despawned one, it will return `Err`.
/// A workaround is to replace `query.get(entity).unwrap()` by
/// `query.get(entity).unwrap_or_default()`.
///
/// # Examples
///
/// ```
/// # use bevy_ecs::component::Component;
/// # use bevy_ecs::query::Has;
/// # use bevy_ecs::system::IntoSystem;
/// # use bevy_ecs::system::Query;
/// #
/// # #[derive(Component)]
/// # struct IsHungry;
/// # #[derive(Component)]
/// # struct Name { name: &'static str };
/// #
/// fn food_entity_system(query: Query<(&Name, Has<IsHungry>) >) {
/// for (name, is_hungry) in &query {
/// if is_hungry{
/// println!("{} would like some food.", name.name);
/// } else {
/// println!("{} has had sufficient.", name.name);
/// }
/// }
/// }
/// # bevy_ecs::system::assert_is_system(food_entity_system);
/// ```
///
/// ```
/// # use bevy_ecs::component::Component;
/// # use bevy_ecs::query::Has;
/// # use bevy_ecs::system::IntoSystem;
/// # use bevy_ecs::system::Query;
/// #
/// # #[derive(Component)]
/// # struct Alpha{has_beta: bool};
/// # #[derive(Component)]
/// # struct Beta { has_alpha: bool };
/// #
/// // Unlike `Option<&T>`, `Has<T>` is compatible with `&mut T`
/// // as it does not actually access any data.
/// fn alphabet_entity_system(mut alphas: Query<(&mut Alpha, Has<Beta>)>, mut betas: Query<(&mut Beta, Has<Alpha>)>) {
/// for (mut alpha, has_beta) in alphas.iter_mut() {
/// alpha.has_beta = has_beta;
/// }
/// for (mut beta, has_alpha) in betas.iter_mut() {
/// beta.has_alpha = has_alpha;
/// }
/// }
/// # bevy_ecs::system::assert_is_system(alphabet_entity_system);
/// ```
pub struct Has<T>(PhantomData<T>);
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
/// SAFETY:
/// `update_component_access` and `update_archetype_component_access` do nothing.
/// This is sound because `fetch` does not access components.
unsafe impl<T: Component> WorldQuery for Has<T> {
type Item<'w> = bool;
type Fetch<'w> = bool;
type State = ComponentId;
fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> {
item
}
#[inline]
unsafe fn init_fetch<'w>(
_world: UnsafeWorldCell<'w>,
_state: &Self::State,
_last_run: Tick,
_this_run: Tick,
) -> Self::Fetch<'w> {
false
}
const IS_DENSE: bool = {
match T::Storage::STORAGE_TYPE {
StorageType::Table => true,
StorageType::SparseSet => false,
}
};
#[inline]
unsafe fn set_archetype<'w>(
fetch: &mut Self::Fetch<'w>,
state: &Self::State,
archetype: &'w Archetype,
_table: &Table,
) {
*fetch = archetype.contains(*state);
}
#[inline]
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) {
*fetch = table.has_column(*state);
}
#[inline(always)]
unsafe fn fetch<'w>(
fetch: &mut Self::Fetch<'w>,
_entity: Entity,
_table_row: TableRow,
) -> Self::Item<'w> {
*fetch
}
fn update_component_access(
&component_id: &Self::State,
access: &mut FilteredAccess<ComponentId>,
) {
access.access_mut().add_archetypal(component_id);
}
fn init_state(world: &mut World) -> ComponentId {
world.init_component::<T>()
}
Dynamic queries and builder API (#9774) # Objective Expand the existing `Query` API to support more dynamic use cases i.e. scripting. ## Prior Art - #6390 - #8308 - #10037 ## Solution - Create a `QueryBuilder` with runtime methods to define the set of component accesses for a built query. - Create new `WorldQueryData` implementations `FilteredEntityMut` and `FilteredEntityRef` as variants of `EntityMut` and `EntityRef` that provide run time checked access to the components included in a given query. - Add new methods to `Query` to create "query lens" with a subset of the access of the initial query. ### Query Builder The `QueryBuilder` API allows you to define a query at runtime. At it's most basic use it will simply create a query with the corresponding type signature: ```rust let query = QueryBuilder::<Entity, With<A>>::new(&mut world).build(); // is equivalent to let query = QueryState::<Entity, With<A>>::new(&mut world); ``` Before calling `.build()` you also have the opportunity to add additional accesses and filters. Here is a simple example where we add additional filter terms: ```rust let entity_a = world.spawn((A(0), B(0))).id(); let entity_b = world.spawn((A(0), C(0))).id(); let mut query_a = QueryBuilder::<Entity>::new(&mut world) .with::<A>() .without::<C>() .build(); assert_eq!(entity_a, query_a.single(&world)); ``` This alone is useful in that allows you to decide which archetypes your query will match at runtime. However it is also very limited, consider a case like the following: ```rust let query_a = QueryBuilder::<&A>::new(&mut world) // Add an additional access .data::<&B>() .build(); ``` This will grant the query an additional read access to component B however we have no way of accessing the data while iterating as the type signature still only includes &A. For an even more concrete example of this consider dynamic components: ```rust let query_a = QueryBuilder::<Entity>::new(&mut world) // Adding a filter is easy since it doesn't need be read later .with_id(component_id_a) // How do I access the data of this component? .ref_id(component_id_b) .build(); ``` With this in mind the `QueryBuilder` API seems somewhat incomplete by itself, we need some way method of accessing the components dynamically. So here's one: ### Query Transmutation If the problem is not having the component in the type signature why not just add it? This PR also adds transmute methods to `QueryBuilder` and `QueryState`. Here's a simple example: ```rust world.spawn(A(0)); world.spawn((A(1), B(0))); let mut query = QueryBuilder::<()>::new(&mut world) .with::<B>() .transmute::<&A>() .build(); query.iter(&world).for_each(|a| assert_eq!(a.0, 1)); ``` The `QueryState` and `QueryBuilder` transmute methods look quite similar but are different in one respect. Transmuting a builder will always succeed as it will just add the additional accesses needed for the new terms if they weren't already included. Transmuting a `QueryState` will panic in the case that the new type signature would give it access it didn't already have, for example: ```rust let query = QueryState::<&A, Option<&B>>::new(&mut world); /// This is fine, the access for Option<&A> is less restrictive than &A query.transmute::<Option<&A>>(&world); /// Oh no, this would allow access to &B on entities that might not have it, so it panics query.transmute::<&B>(&world); /// This is right out query.transmute::<&C>(&world); ``` This is quite an appealing API to also have available on `Query` however it does pose one additional wrinkle: In order to to change the iterator we need to create a new `QueryState` to back it. `Query` doesn't own it's own state though, it just borrows it, so we need a place to borrow it from. This is why `QueryLens` exists, it is a place to store the new state so it can be borrowed when you call `.query()` leaving you with an API like this: ```rust fn function_that_takes_a_query(query: &Query<&A>) { // ... } fn system(query: Query<(&A, &B)>) { let lens = query.transmute_lens::<&A>(); let q = lens.query(); function_that_takes_a_query(&q); } ``` Now you may be thinking: Hey, wait a second, you introduced the problem with dynamic components and then described a solution that only works for static components! Ok, you got me, I guess we need a bit more: ### Filtered Entity References Currently the only way you can access dynamic components on entities through a query is with either `EntityMut` or `EntityRef`, however these can access all components and so conflict with all other accesses. This PR introduces `FilteredEntityMut` and `FilteredEntityRef` as alternatives that have additional runtime checking to prevent accessing components that you shouldn't. This way you can build a query with a `QueryBuilder` and actually access the components you asked for: ```rust let mut query = QueryBuilder::<FilteredEntityRef>::new(&mut world) .ref_id(component_id_a) .with(component_id_b) .build(); let entity_ref = query.single(&world); // Returns Some(Ptr) as we have that component and are allowed to read it let a = entity_ref.get_by_id(component_id_a); // Will return None even though the entity does have the component, as we are not allowed to read it let b = entity_ref.get_by_id(component_id_b); ``` For the most part these new structs have the exact same methods as their non-filtered equivalents. Putting all of this together we can do some truly dynamic ECS queries, check out the `dynamic` example to see it in action: ``` Commands: comp, c Create new components spawn, s Spawn entities query, q Query for entities Enter a command with no parameters for usage. > c A, B, C, Data 4 Component A created with id: 0 Component B created with id: 1 Component C created with id: 2 Component Data created with id: 3 > s A, B, Data 1 Entity spawned with id: 0v0 > s A, C, Data 0 Entity spawned with id: 1v0 > q &Data 0v0: Data: [1, 0, 0, 0] 1v0: Data: [0, 0, 0, 0] > q B, &mut Data 0v0: Data: [2, 1, 1, 1] > q B || C, &Data 0v0: Data: [2, 1, 1, 1] 1v0: Data: [0, 0, 0, 0] ``` ## Changelog - Add new `transmute_lens` methods to `Query`. - Add new types `QueryBuilder`, `FilteredEntityMut`, `FilteredEntityRef` and `QueryLens` - `update_archetype_component_access` has been removed, archetype component accesses are now determined by the accesses set in `update_component_access` - Added method `set_access` to `WorldQuery`, this is called before `update_component_access` for queries that have a restricted set of accesses, such as those built by `QueryBuilder` or `QueryLens`. This is primarily used by the `FilteredEntity*` variants and has an empty trait implementation. - Added method `get_state` to `WorldQuery` as a fallible version of `init_state` when you don't have `&mut World` access. ## Future Work Improve performance of `FilteredEntityMut` and `FilteredEntityRef`, currently they have to determine the accesses a query has in a given archetype during iteration which is far from ideal, especially since we already did the work when matching the archetype in the first place. To avoid making more internal API changes I have left it out of this PR. --------- Co-authored-by: Mike Hsu <mike.hsu@gmail.com>
2024-01-16 19:16:49 +00:00
fn get_state(world: &World) -> Option<Self::State> {
world.component_id::<T>()
}
fn matches_component_set(
_state: &Self::State,
_set_contains_id: &impl Fn(ComponentId) -> bool,
) -> bool {
// `Has<T>` always matches
true
}
}
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
/// SAFETY: `Self` is the same as `Self::ReadOnly`
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
unsafe impl<T: Component> QueryData for Has<T> {
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
type ReadOnly = Self;
}
/// SAFETY: [`Has`] is read only
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
unsafe impl<T: Component> ReadOnlyQueryData for Has<T> {}
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
/// The `AnyOf` query parameter fetches entities with any of the component types included in T.
///
/// `Query<AnyOf<(&A, &B, &mut C)>>` is equivalent to `Query<(Option<&A>, Option<&B>, Option<&mut C>), Or<(With<A>, With<B>, With<C>)>>`.
/// Each of the components in `T` is returned as an `Option`, as with `Option<A>` queries.
/// Entities are guaranteed to have at least one of the components in `T`.
pub struct AnyOf<T>(PhantomData<T>);
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
macro_rules! impl_tuple_query_data {
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
($(($name: ident, $state: ident)),*) => {
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
#[allow(non_snake_case)]
#[allow(clippy::unused_unit)]
// SAFETY: defers to soundness `$name: WorldQuery` impl
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
unsafe impl<$($name: QueryData),*> QueryData for ($($name,)*) {
type ReadOnly = ($($name::ReadOnly,)*);
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
}
/// SAFETY: each item in the tuple is read only
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
unsafe impl<$($name: ReadOnlyQueryData),*> ReadOnlyQueryData for ($($name,)*) {}
Bevy ECS V2 (#1525) # Bevy ECS V2 This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details: * Complete World rewrite * Multiple component storage types: * Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes) * Sparse Sets: fast add/remove, slower iteration * Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now) * Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364) * Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work) * Archetypes are now "just metadata", component storage is separate * Archetype Graph (for faster archetype changes) * Component Metadata * Configure component storage type * Retrieve information about component size/type/name/layout/send-ness/etc * Components are uniquely identified by a densely packed ComponentId * TypeIds are now totally optional (which should make implementing scripting easier) * Super fast "for_each" query iterators * Merged Resources into World. Resources are now just a special type of component * EntityRef/EntityMut builder apis (more efficient and more ergonomic) * Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere * Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime) * With/Without are still taken into account for conflicts, so this should still be comfy to use * Much simpler `IntoSystem` impl * Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId) * Safety Improvements * Entity reservation uses a normal world reference instead of unsafe transmute * QuerySets no longer transmute lifetimes * Made traits "unsafe" where relevant * More thorough safety docs * WorldCell * Exposes safe mutable access to multiple resources at a time in a World * Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)` * Simpler Bundle implementation * Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection" * Removed `Mut<T>` query impl. it is better to only support one way: `&mut T` * Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default * Components now have is_send property (currently only resources support non-send) * More granular module organization * New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()` * `world.resource_scope()` for mutable access to resources and world at the same time * WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it * Significantly slimmed down SystemState in favor of individual SystemParam state * System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference) Fixes #1320 ## `World` Rewrite This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own! (the only shared code between the projects is the entity id allocator, which is already basically ideal) A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details. ## Component Storage (The Problem) Two ECS storage paradigms have gained a lot of traction over the years: * **Archetypal ECS**: * Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity. * Each "archetype" has its own table. Adding/removing an entity's component changes the archetype. * Enables super-fast Query iteration due to its cache-friendly data layout * Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table" * **Sparse Set ECS**: * Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids) * Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array. * Adding/removing components is a cheap, constant time operation Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate. Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because: 1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform. 2. users need to take manual action to optimize Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance. ## Hybrid Component Storage (The Solution) In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed): * **Tables** (aka "archetypal" storage) * The default storage. If you don't configure anything, this is what you get * Fast iteration by default * Slower add/remove operations * **Sparse Sets** * Opt-in * Slower iteration * Faster add/remove operations These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set": ```rust world.register_component( ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet) ).unwrap(); ``` ## Archetypes Archetypes are now "just metadata" ... they no longer store components directly. They do store: * The `ComponentId`s of each of the Archetype's components (and that component's storage type) * Archetypes are uniquely defined by their component layouts * For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype. * The `TableId` associated with the archetype * For now each archetype has exactly one table (which can have no components), * There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it: * Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components. * This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later) * A list of entities that are in the archetype and the row id of the table they are in * ArchetypeComponentIds * unique densely packed identifiers for (ArchetypeId, ComponentId) pairs * used by the schedule executor for cheap system access control * "Archetype Graph Edges" (see the next section) ## The "Archetype Graph" Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage. The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes. Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph. As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations. ## Stateful Queries World queries are now stateful. This allows us to: 1. Cache archetype (and table) matches * This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs). 2. Cache Fetch and Filter state * The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed 3. Incrementally build up state * When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes) As a result, the direct `World` query api now looks like this: ```rust let mut query = world.query::<(&A, &mut B)>(); for (a, mut b) in query.iter_mut(&mut world) { } ``` Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world). However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam. ## Stateful SystemParams Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources). SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now. Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params). (credit goes to @DJMcNab for the initial idea and draft pr here #1364) ## Configurable SystemParams @DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters: ```rust fn foo(value: Local<usize>) { } app.add_system(foo.system().config(|c| c.0 = Some(10))); ``` ## Uber Fast "for_each" Query Iterators Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration. ```rust fn system(query: Query<(&A, &mut B)>) { // you now have the option to do this for a speed boost query.for_each_mut(|(a, mut b)| { }); // however normal iterators are still available for (a, mut b) in query.iter_mut() { } } ``` I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`. We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr). ## Component Metadata `World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type. ## Significantly Cheaper `Access<T>` We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed. This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s. ## Merged Resources into World Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity). Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state. I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally). This pr merges Resources into World: ```rust world.insert_resource(1); world.insert_resource(2.0); let a = world.get_resource::<i32>().unwrap(); let mut b = world.get_resource_mut::<f64>().unwrap(); *b = 3.0; ``` Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier. _But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably! ## WorldCell WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access: ```rust let world_cell = world.cell(); let a = world_cell.get_resource_mut::<i32>().unwrap(); let b = world_cell.get_resource_mut::<f64>().unwrap(); ``` This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped. World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation. WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer. The api is currently limited to resource access, but it can and should be extended to queries / entity component access. ## Resource Scopes WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal! Instead developers can use a "resource scope" ```rust world.resource_scope(|world: &mut World, a: &mut A| { }) ``` This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation. If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty. ## Query Conflicts Use ComponentId Instead of ArchetypeComponentId For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters: ```rust // these queries will never conflict due to their filters fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) { } ``` But it also has a significant downside: ```rust // these queries will not conflict _until_ an entity with A, B, and C is spawned fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) { } ``` The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing. In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace. To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict. ## EntityRef / EntityMut World entity operations on `main` require that the user passes in an `entity` id to each operation: ```rust let entity = world.spawn((A, )); // create a new entity with A world.get::<A>(entity); world.insert(entity, (B, C)); world.insert_one(entity, D); ``` This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required). These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity: ```rust // spawn now takes no inputs and returns an EntityMut let entity = world.spawn() .insert(A) // insert a single component into the entity .insert_bundle((B, C)) // insert a bundle of components into the entity .id() // id returns the Entity id // Returns EntityMut (or panics if the entity does not exist) world.entity_mut(entity) .insert(D) .insert_bundle(SomeBundle::default()); { // returns EntityRef (or panics if the entity does not exist) let d = world.entity(entity) .get::<D>() // gets the D component .unwrap(); // world.get still exists for ergonomics let d = world.get::<D>(entity).unwrap(); } // These variants return Options if you want to check existence instead of panicing world.get_entity_mut(entity) .unwrap() .insert(E); if let Some(entity_ref) = world.get_entity(entity) { let d = entity_ref.get::<D>().unwrap(); } ``` This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change. ## Safety Improvements * Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute * QuerySets no longer transmutes lifetimes * Made traits "unsafe" when implementing a trait incorrectly could cause unsafety * More thorough safety docs ## RemovedComponents SystemParam The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState: ```rust fn system(removed: RemovedComponents<T>) { for entity in removed.iter() { } } ``` ## Simpler Bundle implementation Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used. ## Unified WorldQuery and QueryFilter types (don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change) WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful). QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool. This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit. ## More Granular Modules World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here). ## Remaining Draft Work (to be done in this pr) * ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~ * ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~ * ~~batch_iter / par_iter (currently stubbed out)~~ * ~~ChangedRes~~ * ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~. * ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~ * ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~ * ~~Nested Bundles (if i find time)~~ ## Potential Future Work * Expand WorldCell to support queries. * Consider not allocating in the empty archetype on `world.spawn()` * ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op * this actually regressed performance last time i tried it, but in theory it should be faster * Optimize SparseSet::insert (see `PERF` comment on insert) * Replace SparseArray `Option<T>` with T::MAX to cut down on branching * would enable cheaper get_unchecked() operations * upstream fixedbitset optimizations * fixedbitset could be allocation free for small block counts (store blocks in a SmallVec) * fixedbitset could have a const constructor * Consider implementing Tags (archetype-specific by-value data that affects archetype identity) * ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different. * this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage. * Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation * all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints) * but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code) * Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell * this is basically just "systems" so maybe it's not worth it * Add more world ops * `world.clear()` * `world.reserve<T: Bundle>(count: usize)` * Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :) * Adapt Commands apis for consistency with new World apis ## Benchmarks key: * `bevy_old`: bevy `main` branch * `bevy`: this branch * `_foreach`: uses an optimized for_each iterator * ` _sparse`: uses sparse set storage (if unspecified assume table storage) * `_system`: runs inside a system (if unspecified assume test happens via direct world ops) ### Simple Insert (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png) ### Simpler Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png) ### Fragment Iter (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png) ### Sparse Fragmented Iter Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes ![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png) ### Schedule (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png) ### Add Remove Component (from ecs_bench_suite) ![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png) ### Add Remove Component Big Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed ![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png) ### Get Component Looks up a single component value a large number of times ![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
};
}
macro_rules! impl_anytuple_fetch {
($(($name: ident, $state: ident)),*) => {
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
Use lifetimed, type erased pointers in bevy_ecs (#3001) # Objective `bevy_ecs` has large amounts of unsafe code which is hard to get right and makes it difficult to audit for soundness. ## Solution Introduce lifetimed, type-erased pointers: `Ptr<'a>` `PtrMut<'a>` `OwningPtr<'a>'` and `ThinSlicePtr<'a, T>` which are newtypes around a raw pointer with a lifetime and conceptually representing strong invariants about the pointee and validity of the pointer. The process of converting bevy_ecs to use these has already caught multiple cases of unsound behavior. ## Changelog TL;DR for release notes: `bevy_ecs` now uses lifetimed, type-erased pointers internally, significantly improving safety and legibility without sacrificing performance. This should have approximately no end user impact, unless you were meddling with the (unfortunately public) internals of `bevy_ecs`. - `Fetch`, `FilterFetch` and `ReadOnlyFetch` trait no longer have a `'state` lifetime - this was unneeded - `ReadOnly/Fetch` associated types on `WorldQuery` are now on a new `WorldQueryGats<'world>` trait - was required to work around lack of Generic Associated Types (we wish to express `type Fetch<'a>: Fetch<'a>`) - `derive(WorldQuery)` no longer requires `'w` lifetime on struct - this was unneeded, and improves the end user experience - `EntityMut::get_unchecked_mut` returns `&'_ mut T` not `&'w mut T` - allows easier use of unsafe API with less footguns, and can be worked around via lifetime transmutery as a user - `Bundle::from_components` now takes a `ctx` parameter to pass to the `FnMut` closure - required because closure return types can't borrow from captures - `Fetch::init` takes `&'world World`, `Fetch::set_archetype` takes `&'world Archetype` and `&'world Tables`, `Fetch::set_table` takes `&'world Table` - allows types implementing `Fetch` to store borrows into world - `WorldQuery` trait now has a `shrink` fn to shorten the lifetime in `Fetch::<'a>::Item` - this works around lack of subtyping of assoc types, rust doesnt allow you to turn `<T as Fetch<'static>>::Item'` into `<T as Fetch<'a>>::Item'` - `QueryCombinationsIter` requires this - Most types implementing `Fetch` now have a lifetime `'w` - allows the fetches to store borrows of world data instead of using raw pointers ## Migration guide - `EntityMut::get_unchecked_mut` returns a more restricted lifetime, there is no general way to migrate this as it depends on your code - `Bundle::from_components` implementations must pass the `ctx` arg to `func` - `Bundle::from_components` callers have to use a fn arg instead of closure captures for borrowing from world - Remove lifetime args on `derive(WorldQuery)` structs as it is nonsensical - `<Q as WorldQuery>::ReadOnly/Fetch` should be changed to either `RO/QueryFetch<'world>` or `<Q as WorldQueryGats<'world>>::ReadOnly/Fetch` - `<F as Fetch<'w, 's>>` should be changed to `<F as Fetch<'w>>` - Change the fn sigs of `Fetch::init/set_archetype/set_table` to match respective trait fn sigs - Implement the required `fn shrink` on any `WorldQuery` implementations - Move assoc types `Fetch` and `ReadOnlyFetch` on `WorldQuery` impls to `WorldQueryGats` impls - Pass an appropriate `'world` lifetime to whatever fetch struct you are for some reason using ### Type inference regression in some cases rustc may give spurrious errors when attempting to infer the `F` parameter on a query/querystate this can be fixed by manually specifying the type, i.e. `QueryState::new::<_, ()>(world)`. The error is rather confusing: ```rust= error[E0271]: type mismatch resolving `<() as Fetch<'_>>::Item == bool` --> crates/bevy_pbr/src/render/light.rs:1413:30 | 1413 | main_view_query: QueryState::new(world), | ^^^^^^^^^^^^^^^ expected `bool`, found `()` | = note: required because of the requirements on the impl of `for<'x> FilterFetch<'x>` for `<() as WorldQueryGats<'x>>::Fetch` note: required by a bound in `bevy_ecs::query::QueryState::<Q, F>::new` --> crates/bevy_ecs/src/query/state.rs:49:32 | 49 | for<'x> QueryFetch<'x, F>: FilterFetch<'x>, | ^^^^^^^^^^^^^^^ required by this bound in `bevy_ecs::query::QueryState::<Q, F>::new` ``` --- Made with help from @BoxyUwU and @alice-i-cecile Co-authored-by: Boxy <supbscripter@gmail.com>
2022-04-27 23:44:06 +00:00
#[allow(non_snake_case)]
#[allow(clippy::unused_unit)]
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
/// SAFETY:
/// `fetch` accesses are a subset of the subqueries' accesses
/// This is sound because `update_component_access` and `update_archetype_component_access` adds accesses according to the implementations of all the subqueries.
/// `update_component_access` replaces the filters with a disjunction where every element is a conjunction of the previous filters and the filters of one of the subqueries.
/// This is sound because `matches_component_set` returns a disjunction of the results of the subqueries' implementations.
unsafe impl<$($name: WorldQuery),*> WorldQuery for AnyOf<($($name,)*)> {
type Fetch<'w> = ($(($name::Fetch<'w>, bool),)*);
type Item<'w> = ($(Option<$name::Item<'w>>,)*);
type State = ($($name::State,)*);
fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> {
let ($($name,)*) = item;
($(
$name.map($name::shrink),
)*)
}
#[inline]
#[allow(clippy::unused_unit)]
Migrate the rest of the engine to `UnsafeWorldCell` (#8833) # Objective Follow-up to #6404 and #8292. Mutating the world through a shared reference is surprising, and it makes the meaning of `&World` unclear: sometimes it gives read-only access to the entire world, and sometimes it gives interior mutable access to only part of it. This is an up-to-date version of #6972. ## Solution Use `UnsafeWorldCell` for all interior mutability. Now, `&World` *always* gives you read-only access to the entire world. --- ## Changelog TODO - do we still care about changelogs? ## Migration Guide Mutating any world data using `&World` is now considered unsound -- the type `UnsafeWorldCell` must be used to achieve interior mutability. The following methods now accept `UnsafeWorldCell` instead of `&World`: - `QueryState`: `get_unchecked`, `iter_unchecked`, `iter_combinations_unchecked`, `for_each_unchecked`, `get_single_unchecked`, `get_single_unchecked_manual`. - `SystemState`: `get_unchecked_manual` ```rust let mut world = World::new(); let mut query = world.query::<&mut T>(); // Before: let t1 = query.get_unchecked(&world, entity_1); let t2 = query.get_unchecked(&world, entity_2); // After: let world_cell = world.as_unsafe_world_cell(); let t1 = query.get_unchecked(world_cell, entity_1); let t2 = query.get_unchecked(world_cell, entity_2); ``` The methods `QueryState::validate_world` and `SystemState::matches_world` now take a `WorldId` instead of `&World`: ```rust // Before: query_state.validate_world(&world); // After: query_state.validate_world(world.id()); ``` The methods `QueryState::update_archetypes` and `SystemState::update_archetypes` now take `UnsafeWorldCell` instead of `&World`: ```rust // Before: query_state.update_archetypes(&world); // After: query_state.update_archetypes(world.as_unsafe_world_cell_readonly()); ```
2023-06-15 01:31:56 +00:00
unsafe fn init_fetch<'w>(_world: UnsafeWorldCell<'w>, state: &Self::State, _last_run: Tick, _this_run: Tick) -> Self::Fetch<'w> {
let ($($name,)*) = state;
($(($name::init_fetch(_world, $name, _last_run, _this_run), false),)*)
}
const IS_DENSE: bool = true $(&& $name::IS_DENSE)*;
#[inline]
unsafe fn set_archetype<'w>(
_fetch: &mut Self::Fetch<'w>,
_state: &Self::State,
_archetype: &'w Archetype,
_table: &'w Table
) {
let ($($name,)*) = _fetch;
let ($($state,)*) = _state;
$(
$name.1 = $name::matches_component_set($state, &|id| _archetype.contains(id));
if $name.1 {
$name::set_archetype(&mut $name.0, $state, _archetype, _table);
}
)*
}
#[inline]
unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) {
let ($($name,)*) = _fetch;
let ($($state,)*) = _state;
$(
$name.1 = $name::matches_component_set($state, &|id| _table.has_column(id));
if $name.1 {
$name::set_table(&mut $name.0, $state, _table);
}
)*
}
#[inline(always)]
#[allow(clippy::unused_unit)]
unsafe fn fetch<'w>(
_fetch: &mut Self::Fetch<'w>,
_entity: Entity,
_table_row: TableRow
) -> Self::Item<'w> {
let ($($name,)*) = _fetch;
($(
$name.1.then(|| $name::fetch(&mut $name.0, _entity, _table_row)),
)*)
}
Replace `ReadOnlyFetch` with `ReadOnlyWorldQuery` (#4626) # Objective - Fix a type inference regression introduced by #3001 - Make read only bounds on world queries more user friendly ptrification required you to write `Q::Fetch: ReadOnlyFetch` as `for<'w> QueryFetch<'w, Q>: ReadOnlyFetch` which has the same type inference problem as `for<'w> QueryFetch<'w, Q>: FilterFetch<'w>` had, i.e. the following code would error: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: WorldQuery>(a: Query<Q, ()>) where for<'w> QueryFetch<'w, Q>: ReadOnlyFetch, { } ``` `for<..>` bounds are also rather user unfriendly.. ## Solution Remove the `ReadOnlyFetch` trait in favour of a `ReadOnlyWorldQuery` trait, and remove `WorldQueryGats::ReadOnlyFetch` in favor of `WorldQuery::ReadOnly` allowing the previous code snippet to be written as: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: ReadOnlyWorldQuery>(a: Query<Q, ()>) {} ``` This avoids the `for<...>` bound which makes the code simpler and also fixes the type inference issue. The reason for moving the two functions out of `FetchState` and into `WorldQuery` is to allow the world query `&mut T` to share a `State` with the `&T` world query so that it can have `type ReadOnly = &T`. Presumably it would be possible to instead have a `ReadOnlyRefMut<T>` world query and then do `type ReadOnly = ReadOnlyRefMut<T>` much like how (before this PR) we had a `ReadOnlyWriteFetch<T>`. A side benefit of the current solution in this PR is that it will likely make it easier in the future to support an API such as `Query<&mut T> -> Query<&T>`. The primary benefit IMO is just that `ReadOnlyRefMut<T>` and its associated fetch would have to reimplement all of the logic that the `&T` world query impl does but this solution avoids that :) --- ## Changelog/Migration Guide The trait `ReadOnlyFetch` has been replaced with `ReadOnlyWorldQuery` along with the `WorldQueryGats::ReadOnlyFetch` assoc type which has been replaced with `<WorldQuery::ReadOnly as WorldQueryGats>::Fetch` - Any where clauses such as `QueryFetch<Q>: ReadOnlyFetch` should be replaced with `Q: ReadOnlyWorldQuery`. - Any custom world query impls should implement `ReadOnlyWorldQuery` insead of `ReadOnlyFetch` Functions `update_component_access` and `update_archetype_component_access` have been moved from the `FetchState` trait to `WorldQuery` - Any callers should now call `Q::update_component_access(state` instead of `state.update_component_access` (and `update_archetype_component_access` respectively) - Any custom world query impls should move the functions from the `FetchState` impl to `WorldQuery` impl `WorldQuery` has been made an `unsafe trait`, `FetchState` has been made a safe `trait`. (I think this is how it should have always been, but regardless this is _definitely_ necessary now that the two functions have been moved to `WorldQuery`) - If you have a custom `FetchState` impl make it a normal `impl` instead of `unsafe impl` - If you have a custom `WorldQuery` impl make it an `unsafe impl`, if your code was sound before it is going to still be sound
2022-06-13 23:35:54 +00:00
fn update_component_access(state: &Self::State, _access: &mut FilteredAccess<ComponentId>) {
let ($($name,)*) = state;
let mut _new_access = _access.clone();
let mut _not_first = false;
$(
if _not_first {
let mut intermediate = _access.clone();
Replace `ReadOnlyFetch` with `ReadOnlyWorldQuery` (#4626) # Objective - Fix a type inference regression introduced by #3001 - Make read only bounds on world queries more user friendly ptrification required you to write `Q::Fetch: ReadOnlyFetch` as `for<'w> QueryFetch<'w, Q>: ReadOnlyFetch` which has the same type inference problem as `for<'w> QueryFetch<'w, Q>: FilterFetch<'w>` had, i.e. the following code would error: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: WorldQuery>(a: Query<Q, ()>) where for<'w> QueryFetch<'w, Q>: ReadOnlyFetch, { } ``` `for<..>` bounds are also rather user unfriendly.. ## Solution Remove the `ReadOnlyFetch` trait in favour of a `ReadOnlyWorldQuery` trait, and remove `WorldQueryGats::ReadOnlyFetch` in favor of `WorldQuery::ReadOnly` allowing the previous code snippet to be written as: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: ReadOnlyWorldQuery>(a: Query<Q, ()>) {} ``` This avoids the `for<...>` bound which makes the code simpler and also fixes the type inference issue. The reason for moving the two functions out of `FetchState` and into `WorldQuery` is to allow the world query `&mut T` to share a `State` with the `&T` world query so that it can have `type ReadOnly = &T`. Presumably it would be possible to instead have a `ReadOnlyRefMut<T>` world query and then do `type ReadOnly = ReadOnlyRefMut<T>` much like how (before this PR) we had a `ReadOnlyWriteFetch<T>`. A side benefit of the current solution in this PR is that it will likely make it easier in the future to support an API such as `Query<&mut T> -> Query<&T>`. The primary benefit IMO is just that `ReadOnlyRefMut<T>` and its associated fetch would have to reimplement all of the logic that the `&T` world query impl does but this solution avoids that :) --- ## Changelog/Migration Guide The trait `ReadOnlyFetch` has been replaced with `ReadOnlyWorldQuery` along with the `WorldQueryGats::ReadOnlyFetch` assoc type which has been replaced with `<WorldQuery::ReadOnly as WorldQueryGats>::Fetch` - Any where clauses such as `QueryFetch<Q>: ReadOnlyFetch` should be replaced with `Q: ReadOnlyWorldQuery`. - Any custom world query impls should implement `ReadOnlyWorldQuery` insead of `ReadOnlyFetch` Functions `update_component_access` and `update_archetype_component_access` have been moved from the `FetchState` trait to `WorldQuery` - Any callers should now call `Q::update_component_access(state` instead of `state.update_component_access` (and `update_archetype_component_access` respectively) - Any custom world query impls should move the functions from the `FetchState` impl to `WorldQuery` impl `WorldQuery` has been made an `unsafe trait`, `FetchState` has been made a safe `trait`. (I think this is how it should have always been, but regardless this is _definitely_ necessary now that the two functions have been moved to `WorldQuery`) - If you have a custom `FetchState` impl make it a normal `impl` instead of `unsafe impl` - If you have a custom `WorldQuery` impl make it an `unsafe impl`, if your code was sound before it is going to still be sound
2022-06-13 23:35:54 +00:00
$name::update_component_access($name, &mut intermediate);
_new_access.append_or(&intermediate);
_new_access.extend_access(&intermediate);
} else {
$name::update_component_access($name, &mut _new_access);
Dynamic queries and builder API (#9774) # Objective Expand the existing `Query` API to support more dynamic use cases i.e. scripting. ## Prior Art - #6390 - #8308 - #10037 ## Solution - Create a `QueryBuilder` with runtime methods to define the set of component accesses for a built query. - Create new `WorldQueryData` implementations `FilteredEntityMut` and `FilteredEntityRef` as variants of `EntityMut` and `EntityRef` that provide run time checked access to the components included in a given query. - Add new methods to `Query` to create "query lens" with a subset of the access of the initial query. ### Query Builder The `QueryBuilder` API allows you to define a query at runtime. At it's most basic use it will simply create a query with the corresponding type signature: ```rust let query = QueryBuilder::<Entity, With<A>>::new(&mut world).build(); // is equivalent to let query = QueryState::<Entity, With<A>>::new(&mut world); ``` Before calling `.build()` you also have the opportunity to add additional accesses and filters. Here is a simple example where we add additional filter terms: ```rust let entity_a = world.spawn((A(0), B(0))).id(); let entity_b = world.spawn((A(0), C(0))).id(); let mut query_a = QueryBuilder::<Entity>::new(&mut world) .with::<A>() .without::<C>() .build(); assert_eq!(entity_a, query_a.single(&world)); ``` This alone is useful in that allows you to decide which archetypes your query will match at runtime. However it is also very limited, consider a case like the following: ```rust let query_a = QueryBuilder::<&A>::new(&mut world) // Add an additional access .data::<&B>() .build(); ``` This will grant the query an additional read access to component B however we have no way of accessing the data while iterating as the type signature still only includes &A. For an even more concrete example of this consider dynamic components: ```rust let query_a = QueryBuilder::<Entity>::new(&mut world) // Adding a filter is easy since it doesn't need be read later .with_id(component_id_a) // How do I access the data of this component? .ref_id(component_id_b) .build(); ``` With this in mind the `QueryBuilder` API seems somewhat incomplete by itself, we need some way method of accessing the components dynamically. So here's one: ### Query Transmutation If the problem is not having the component in the type signature why not just add it? This PR also adds transmute methods to `QueryBuilder` and `QueryState`. Here's a simple example: ```rust world.spawn(A(0)); world.spawn((A(1), B(0))); let mut query = QueryBuilder::<()>::new(&mut world) .with::<B>() .transmute::<&A>() .build(); query.iter(&world).for_each(|a| assert_eq!(a.0, 1)); ``` The `QueryState` and `QueryBuilder` transmute methods look quite similar but are different in one respect. Transmuting a builder will always succeed as it will just add the additional accesses needed for the new terms if they weren't already included. Transmuting a `QueryState` will panic in the case that the new type signature would give it access it didn't already have, for example: ```rust let query = QueryState::<&A, Option<&B>>::new(&mut world); /// This is fine, the access for Option<&A> is less restrictive than &A query.transmute::<Option<&A>>(&world); /// Oh no, this would allow access to &B on entities that might not have it, so it panics query.transmute::<&B>(&world); /// This is right out query.transmute::<&C>(&world); ``` This is quite an appealing API to also have available on `Query` however it does pose one additional wrinkle: In order to to change the iterator we need to create a new `QueryState` to back it. `Query` doesn't own it's own state though, it just borrows it, so we need a place to borrow it from. This is why `QueryLens` exists, it is a place to store the new state so it can be borrowed when you call `.query()` leaving you with an API like this: ```rust fn function_that_takes_a_query(query: &Query<&A>) { // ... } fn system(query: Query<(&A, &B)>) { let lens = query.transmute_lens::<&A>(); let q = lens.query(); function_that_takes_a_query(&q); } ``` Now you may be thinking: Hey, wait a second, you introduced the problem with dynamic components and then described a solution that only works for static components! Ok, you got me, I guess we need a bit more: ### Filtered Entity References Currently the only way you can access dynamic components on entities through a query is with either `EntityMut` or `EntityRef`, however these can access all components and so conflict with all other accesses. This PR introduces `FilteredEntityMut` and `FilteredEntityRef` as alternatives that have additional runtime checking to prevent accessing components that you shouldn't. This way you can build a query with a `QueryBuilder` and actually access the components you asked for: ```rust let mut query = QueryBuilder::<FilteredEntityRef>::new(&mut world) .ref_id(component_id_a) .with(component_id_b) .build(); let entity_ref = query.single(&world); // Returns Some(Ptr) as we have that component and are allowed to read it let a = entity_ref.get_by_id(component_id_a); // Will return None even though the entity does have the component, as we are not allowed to read it let b = entity_ref.get_by_id(component_id_b); ``` For the most part these new structs have the exact same methods as their non-filtered equivalents. Putting all of this together we can do some truly dynamic ECS queries, check out the `dynamic` example to see it in action: ``` Commands: comp, c Create new components spawn, s Spawn entities query, q Query for entities Enter a command with no parameters for usage. > c A, B, C, Data 4 Component A created with id: 0 Component B created with id: 1 Component C created with id: 2 Component Data created with id: 3 > s A, B, Data 1 Entity spawned with id: 0v0 > s A, C, Data 0 Entity spawned with id: 1v0 > q &Data 0v0: Data: [1, 0, 0, 0] 1v0: Data: [0, 0, 0, 0] > q B, &mut Data 0v0: Data: [2, 1, 1, 1] > q B || C, &Data 0v0: Data: [2, 1, 1, 1] 1v0: Data: [0, 0, 0, 0] ``` ## Changelog - Add new `transmute_lens` methods to `Query`. - Add new types `QueryBuilder`, `FilteredEntityMut`, `FilteredEntityRef` and `QueryLens` - `update_archetype_component_access` has been removed, archetype component accesses are now determined by the accesses set in `update_component_access` - Added method `set_access` to `WorldQuery`, this is called before `update_component_access` for queries that have a restricted set of accesses, such as those built by `QueryBuilder` or `QueryLens`. This is primarily used by the `FilteredEntity*` variants and has an empty trait implementation. - Added method `get_state` to `WorldQuery` as a fallible version of `init_state` when you don't have `&mut World` access. ## Future Work Improve performance of `FilteredEntityMut` and `FilteredEntityRef`, currently they have to determine the accesses a query has in a given archetype during iteration which is far from ideal, especially since we already did the work when matching the archetype in the first place. To avoid making more internal API changes I have left it out of this PR. --------- Co-authored-by: Mike Hsu <mike.hsu@gmail.com>
2024-01-16 19:16:49 +00:00
_new_access.required = _access.required.clone();
_not_first = true;
}
)*
*_access = _new_access;
}
fn init_state(_world: &mut World) -> Self::State {
($($name::init_state(_world),)*)
}
Use lifetimed, type erased pointers in bevy_ecs (#3001) # Objective `bevy_ecs` has large amounts of unsafe code which is hard to get right and makes it difficult to audit for soundness. ## Solution Introduce lifetimed, type-erased pointers: `Ptr<'a>` `PtrMut<'a>` `OwningPtr<'a>'` and `ThinSlicePtr<'a, T>` which are newtypes around a raw pointer with a lifetime and conceptually representing strong invariants about the pointee and validity of the pointer. The process of converting bevy_ecs to use these has already caught multiple cases of unsound behavior. ## Changelog TL;DR for release notes: `bevy_ecs` now uses lifetimed, type-erased pointers internally, significantly improving safety and legibility without sacrificing performance. This should have approximately no end user impact, unless you were meddling with the (unfortunately public) internals of `bevy_ecs`. - `Fetch`, `FilterFetch` and `ReadOnlyFetch` trait no longer have a `'state` lifetime - this was unneeded - `ReadOnly/Fetch` associated types on `WorldQuery` are now on a new `WorldQueryGats<'world>` trait - was required to work around lack of Generic Associated Types (we wish to express `type Fetch<'a>: Fetch<'a>`) - `derive(WorldQuery)` no longer requires `'w` lifetime on struct - this was unneeded, and improves the end user experience - `EntityMut::get_unchecked_mut` returns `&'_ mut T` not `&'w mut T` - allows easier use of unsafe API with less footguns, and can be worked around via lifetime transmutery as a user - `Bundle::from_components` now takes a `ctx` parameter to pass to the `FnMut` closure - required because closure return types can't borrow from captures - `Fetch::init` takes `&'world World`, `Fetch::set_archetype` takes `&'world Archetype` and `&'world Tables`, `Fetch::set_table` takes `&'world Table` - allows types implementing `Fetch` to store borrows into world - `WorldQuery` trait now has a `shrink` fn to shorten the lifetime in `Fetch::<'a>::Item` - this works around lack of subtyping of assoc types, rust doesnt allow you to turn `<T as Fetch<'static>>::Item'` into `<T as Fetch<'a>>::Item'` - `QueryCombinationsIter` requires this - Most types implementing `Fetch` now have a lifetime `'w` - allows the fetches to store borrows of world data instead of using raw pointers ## Migration guide - `EntityMut::get_unchecked_mut` returns a more restricted lifetime, there is no general way to migrate this as it depends on your code - `Bundle::from_components` implementations must pass the `ctx` arg to `func` - `Bundle::from_components` callers have to use a fn arg instead of closure captures for borrowing from world - Remove lifetime args on `derive(WorldQuery)` structs as it is nonsensical - `<Q as WorldQuery>::ReadOnly/Fetch` should be changed to either `RO/QueryFetch<'world>` or `<Q as WorldQueryGats<'world>>::ReadOnly/Fetch` - `<F as Fetch<'w, 's>>` should be changed to `<F as Fetch<'w>>` - Change the fn sigs of `Fetch::init/set_archetype/set_table` to match respective trait fn sigs - Implement the required `fn shrink` on any `WorldQuery` implementations - Move assoc types `Fetch` and `ReadOnlyFetch` on `WorldQuery` impls to `WorldQueryGats` impls - Pass an appropriate `'world` lifetime to whatever fetch struct you are for some reason using ### Type inference regression in some cases rustc may give spurrious errors when attempting to infer the `F` parameter on a query/querystate this can be fixed by manually specifying the type, i.e. `QueryState::new::<_, ()>(world)`. The error is rather confusing: ```rust= error[E0271]: type mismatch resolving `<() as Fetch<'_>>::Item == bool` --> crates/bevy_pbr/src/render/light.rs:1413:30 | 1413 | main_view_query: QueryState::new(world), | ^^^^^^^^^^^^^^^ expected `bool`, found `()` | = note: required because of the requirements on the impl of `for<'x> FilterFetch<'x>` for `<() as WorldQueryGats<'x>>::Fetch` note: required by a bound in `bevy_ecs::query::QueryState::<Q, F>::new` --> crates/bevy_ecs/src/query/state.rs:49:32 | 49 | for<'x> QueryFetch<'x, F>: FilterFetch<'x>, | ^^^^^^^^^^^^^^^ required by this bound in `bevy_ecs::query::QueryState::<Q, F>::new` ``` --- Made with help from @BoxyUwU and @alice-i-cecile Co-authored-by: Boxy <supbscripter@gmail.com>
2022-04-27 23:44:06 +00:00
Dynamic queries and builder API (#9774) # Objective Expand the existing `Query` API to support more dynamic use cases i.e. scripting. ## Prior Art - #6390 - #8308 - #10037 ## Solution - Create a `QueryBuilder` with runtime methods to define the set of component accesses for a built query. - Create new `WorldQueryData` implementations `FilteredEntityMut` and `FilteredEntityRef` as variants of `EntityMut` and `EntityRef` that provide run time checked access to the components included in a given query. - Add new methods to `Query` to create "query lens" with a subset of the access of the initial query. ### Query Builder The `QueryBuilder` API allows you to define a query at runtime. At it's most basic use it will simply create a query with the corresponding type signature: ```rust let query = QueryBuilder::<Entity, With<A>>::new(&mut world).build(); // is equivalent to let query = QueryState::<Entity, With<A>>::new(&mut world); ``` Before calling `.build()` you also have the opportunity to add additional accesses and filters. Here is a simple example where we add additional filter terms: ```rust let entity_a = world.spawn((A(0), B(0))).id(); let entity_b = world.spawn((A(0), C(0))).id(); let mut query_a = QueryBuilder::<Entity>::new(&mut world) .with::<A>() .without::<C>() .build(); assert_eq!(entity_a, query_a.single(&world)); ``` This alone is useful in that allows you to decide which archetypes your query will match at runtime. However it is also very limited, consider a case like the following: ```rust let query_a = QueryBuilder::<&A>::new(&mut world) // Add an additional access .data::<&B>() .build(); ``` This will grant the query an additional read access to component B however we have no way of accessing the data while iterating as the type signature still only includes &A. For an even more concrete example of this consider dynamic components: ```rust let query_a = QueryBuilder::<Entity>::new(&mut world) // Adding a filter is easy since it doesn't need be read later .with_id(component_id_a) // How do I access the data of this component? .ref_id(component_id_b) .build(); ``` With this in mind the `QueryBuilder` API seems somewhat incomplete by itself, we need some way method of accessing the components dynamically. So here's one: ### Query Transmutation If the problem is not having the component in the type signature why not just add it? This PR also adds transmute methods to `QueryBuilder` and `QueryState`. Here's a simple example: ```rust world.spawn(A(0)); world.spawn((A(1), B(0))); let mut query = QueryBuilder::<()>::new(&mut world) .with::<B>() .transmute::<&A>() .build(); query.iter(&world).for_each(|a| assert_eq!(a.0, 1)); ``` The `QueryState` and `QueryBuilder` transmute methods look quite similar but are different in one respect. Transmuting a builder will always succeed as it will just add the additional accesses needed for the new terms if they weren't already included. Transmuting a `QueryState` will panic in the case that the new type signature would give it access it didn't already have, for example: ```rust let query = QueryState::<&A, Option<&B>>::new(&mut world); /// This is fine, the access for Option<&A> is less restrictive than &A query.transmute::<Option<&A>>(&world); /// Oh no, this would allow access to &B on entities that might not have it, so it panics query.transmute::<&B>(&world); /// This is right out query.transmute::<&C>(&world); ``` This is quite an appealing API to also have available on `Query` however it does pose one additional wrinkle: In order to to change the iterator we need to create a new `QueryState` to back it. `Query` doesn't own it's own state though, it just borrows it, so we need a place to borrow it from. This is why `QueryLens` exists, it is a place to store the new state so it can be borrowed when you call `.query()` leaving you with an API like this: ```rust fn function_that_takes_a_query(query: &Query<&A>) { // ... } fn system(query: Query<(&A, &B)>) { let lens = query.transmute_lens::<&A>(); let q = lens.query(); function_that_takes_a_query(&q); } ``` Now you may be thinking: Hey, wait a second, you introduced the problem with dynamic components and then described a solution that only works for static components! Ok, you got me, I guess we need a bit more: ### Filtered Entity References Currently the only way you can access dynamic components on entities through a query is with either `EntityMut` or `EntityRef`, however these can access all components and so conflict with all other accesses. This PR introduces `FilteredEntityMut` and `FilteredEntityRef` as alternatives that have additional runtime checking to prevent accessing components that you shouldn't. This way you can build a query with a `QueryBuilder` and actually access the components you asked for: ```rust let mut query = QueryBuilder::<FilteredEntityRef>::new(&mut world) .ref_id(component_id_a) .with(component_id_b) .build(); let entity_ref = query.single(&world); // Returns Some(Ptr) as we have that component and are allowed to read it let a = entity_ref.get_by_id(component_id_a); // Will return None even though the entity does have the component, as we are not allowed to read it let b = entity_ref.get_by_id(component_id_b); ``` For the most part these new structs have the exact same methods as their non-filtered equivalents. Putting all of this together we can do some truly dynamic ECS queries, check out the `dynamic` example to see it in action: ``` Commands: comp, c Create new components spawn, s Spawn entities query, q Query for entities Enter a command with no parameters for usage. > c A, B, C, Data 4 Component A created with id: 0 Component B created with id: 1 Component C created with id: 2 Component Data created with id: 3 > s A, B, Data 1 Entity spawned with id: 0v0 > s A, C, Data 0 Entity spawned with id: 1v0 > q &Data 0v0: Data: [1, 0, 0, 0] 1v0: Data: [0, 0, 0, 0] > q B, &mut Data 0v0: Data: [2, 1, 1, 1] > q B || C, &Data 0v0: Data: [2, 1, 1, 1] 1v0: Data: [0, 0, 0, 0] ``` ## Changelog - Add new `transmute_lens` methods to `Query`. - Add new types `QueryBuilder`, `FilteredEntityMut`, `FilteredEntityRef` and `QueryLens` - `update_archetype_component_access` has been removed, archetype component accesses are now determined by the accesses set in `update_component_access` - Added method `set_access` to `WorldQuery`, this is called before `update_component_access` for queries that have a restricted set of accesses, such as those built by `QueryBuilder` or `QueryLens`. This is primarily used by the `FilteredEntity*` variants and has an empty trait implementation. - Added method `get_state` to `WorldQuery` as a fallible version of `init_state` when you don't have `&mut World` access. ## Future Work Improve performance of `FilteredEntityMut` and `FilteredEntityRef`, currently they have to determine the accesses a query has in a given archetype during iteration which is far from ideal, especially since we already did the work when matching the archetype in the first place. To avoid making more internal API changes I have left it out of this PR. --------- Co-authored-by: Mike Hsu <mike.hsu@gmail.com>
2024-01-16 19:16:49 +00:00
fn get_state(_world: &World) -> Option<Self::State> {
Some(($($name::get_state(_world)?,)*))
}
fn matches_component_set(_state: &Self::State, _set_contains_id: &impl Fn(ComponentId) -> bool) -> bool {
let ($($name,)*) = _state;
false $(|| $name::matches_component_set($name, _set_contains_id))*
Use lifetimed, type erased pointers in bevy_ecs (#3001) # Objective `bevy_ecs` has large amounts of unsafe code which is hard to get right and makes it difficult to audit for soundness. ## Solution Introduce lifetimed, type-erased pointers: `Ptr<'a>` `PtrMut<'a>` `OwningPtr<'a>'` and `ThinSlicePtr<'a, T>` which are newtypes around a raw pointer with a lifetime and conceptually representing strong invariants about the pointee and validity of the pointer. The process of converting bevy_ecs to use these has already caught multiple cases of unsound behavior. ## Changelog TL;DR for release notes: `bevy_ecs` now uses lifetimed, type-erased pointers internally, significantly improving safety and legibility without sacrificing performance. This should have approximately no end user impact, unless you were meddling with the (unfortunately public) internals of `bevy_ecs`. - `Fetch`, `FilterFetch` and `ReadOnlyFetch` trait no longer have a `'state` lifetime - this was unneeded - `ReadOnly/Fetch` associated types on `WorldQuery` are now on a new `WorldQueryGats<'world>` trait - was required to work around lack of Generic Associated Types (we wish to express `type Fetch<'a>: Fetch<'a>`) - `derive(WorldQuery)` no longer requires `'w` lifetime on struct - this was unneeded, and improves the end user experience - `EntityMut::get_unchecked_mut` returns `&'_ mut T` not `&'w mut T` - allows easier use of unsafe API with less footguns, and can be worked around via lifetime transmutery as a user - `Bundle::from_components` now takes a `ctx` parameter to pass to the `FnMut` closure - required because closure return types can't borrow from captures - `Fetch::init` takes `&'world World`, `Fetch::set_archetype` takes `&'world Archetype` and `&'world Tables`, `Fetch::set_table` takes `&'world Table` - allows types implementing `Fetch` to store borrows into world - `WorldQuery` trait now has a `shrink` fn to shorten the lifetime in `Fetch::<'a>::Item` - this works around lack of subtyping of assoc types, rust doesnt allow you to turn `<T as Fetch<'static>>::Item'` into `<T as Fetch<'a>>::Item'` - `QueryCombinationsIter` requires this - Most types implementing `Fetch` now have a lifetime `'w` - allows the fetches to store borrows of world data instead of using raw pointers ## Migration guide - `EntityMut::get_unchecked_mut` returns a more restricted lifetime, there is no general way to migrate this as it depends on your code - `Bundle::from_components` implementations must pass the `ctx` arg to `func` - `Bundle::from_components` callers have to use a fn arg instead of closure captures for borrowing from world - Remove lifetime args on `derive(WorldQuery)` structs as it is nonsensical - `<Q as WorldQuery>::ReadOnly/Fetch` should be changed to either `RO/QueryFetch<'world>` or `<Q as WorldQueryGats<'world>>::ReadOnly/Fetch` - `<F as Fetch<'w, 's>>` should be changed to `<F as Fetch<'w>>` - Change the fn sigs of `Fetch::init/set_archetype/set_table` to match respective trait fn sigs - Implement the required `fn shrink` on any `WorldQuery` implementations - Move assoc types `Fetch` and `ReadOnlyFetch` on `WorldQuery` impls to `WorldQueryGats` impls - Pass an appropriate `'world` lifetime to whatever fetch struct you are for some reason using ### Type inference regression in some cases rustc may give spurrious errors when attempting to infer the `F` parameter on a query/querystate this can be fixed by manually specifying the type, i.e. `QueryState::new::<_, ()>(world)`. The error is rather confusing: ```rust= error[E0271]: type mismatch resolving `<() as Fetch<'_>>::Item == bool` --> crates/bevy_pbr/src/render/light.rs:1413:30 | 1413 | main_view_query: QueryState::new(world), | ^^^^^^^^^^^^^^^ expected `bool`, found `()` | = note: required because of the requirements on the impl of `for<'x> FilterFetch<'x>` for `<() as WorldQueryGats<'x>>::Fetch` note: required by a bound in `bevy_ecs::query::QueryState::<Q, F>::new` --> crates/bevy_ecs/src/query/state.rs:49:32 | 49 | for<'x> QueryFetch<'x, F>: FilterFetch<'x>, | ^^^^^^^^^^^^^^^ required by this bound in `bevy_ecs::query::QueryState::<Q, F>::new` ``` --- Made with help from @BoxyUwU and @alice-i-cecile Co-authored-by: Boxy <supbscripter@gmail.com>
2022-04-27 23:44:06 +00:00
}
}
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
#[allow(non_snake_case)]
#[allow(clippy::unused_unit)]
// SAFETY: defers to soundness of `$name: WorldQuery` impl
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
unsafe impl<$($name: QueryData),*> QueryData for AnyOf<($($name,)*)> {
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
type ReadOnly = AnyOf<($($name::ReadOnly,)*)>;
}
/// SAFETY: each item in the tuple is read only
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
unsafe impl<$($name: ReadOnlyQueryData),*> ReadOnlyQueryData for AnyOf<($($name,)*)> {}
};
}
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
all_tuples!(impl_tuple_query_data, 0, 15, F, S);
all_tuples!(impl_anytuple_fetch, 0, 15, F, S);
/// [`WorldQuery`] used to nullify queries by turning `Query<D>` into `Query<NopWorldQuery<D>>`
///
/// This will rarely be useful to consumers of `bevy_ecs`.
pub struct NopWorldQuery<D: QueryData>(PhantomData<D>);
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
/// SAFETY:
/// `update_component_access` and `update_archetype_component_access` do nothing.
/// This is sound because `fetch` does not access components.
unsafe impl<D: QueryData> WorldQuery for NopWorldQuery<D> {
type Item<'w> = ();
type Fetch<'w> = ();
type State = D::State;
fn shrink<'wlong: 'wshort, 'wshort>(_: ()) {}
#[inline(always)]
Migrate the rest of the engine to `UnsafeWorldCell` (#8833) # Objective Follow-up to #6404 and #8292. Mutating the world through a shared reference is surprising, and it makes the meaning of `&World` unclear: sometimes it gives read-only access to the entire world, and sometimes it gives interior mutable access to only part of it. This is an up-to-date version of #6972. ## Solution Use `UnsafeWorldCell` for all interior mutability. Now, `&World` *always* gives you read-only access to the entire world. --- ## Changelog TODO - do we still care about changelogs? ## Migration Guide Mutating any world data using `&World` is now considered unsound -- the type `UnsafeWorldCell` must be used to achieve interior mutability. The following methods now accept `UnsafeWorldCell` instead of `&World`: - `QueryState`: `get_unchecked`, `iter_unchecked`, `iter_combinations_unchecked`, `for_each_unchecked`, `get_single_unchecked`, `get_single_unchecked_manual`. - `SystemState`: `get_unchecked_manual` ```rust let mut world = World::new(); let mut query = world.query::<&mut T>(); // Before: let t1 = query.get_unchecked(&world, entity_1); let t2 = query.get_unchecked(&world, entity_2); // After: let world_cell = world.as_unsafe_world_cell(); let t1 = query.get_unchecked(world_cell, entity_1); let t2 = query.get_unchecked(world_cell, entity_2); ``` The methods `QueryState::validate_world` and `SystemState::matches_world` now take a `WorldId` instead of `&World`: ```rust // Before: query_state.validate_world(&world); // After: query_state.validate_world(world.id()); ``` The methods `QueryState::update_archetypes` and `SystemState::update_archetypes` now take `UnsafeWorldCell` instead of `&World`: ```rust // Before: query_state.update_archetypes(&world); // After: query_state.update_archetypes(world.as_unsafe_world_cell_readonly()); ```
2023-06-15 01:31:56 +00:00
unsafe fn init_fetch(
_world: UnsafeWorldCell,
_state: &D::State,
Migrate the rest of the engine to `UnsafeWorldCell` (#8833) # Objective Follow-up to #6404 and #8292. Mutating the world through a shared reference is surprising, and it makes the meaning of `&World` unclear: sometimes it gives read-only access to the entire world, and sometimes it gives interior mutable access to only part of it. This is an up-to-date version of #6972. ## Solution Use `UnsafeWorldCell` for all interior mutability. Now, `&World` *always* gives you read-only access to the entire world. --- ## Changelog TODO - do we still care about changelogs? ## Migration Guide Mutating any world data using `&World` is now considered unsound -- the type `UnsafeWorldCell` must be used to achieve interior mutability. The following methods now accept `UnsafeWorldCell` instead of `&World`: - `QueryState`: `get_unchecked`, `iter_unchecked`, `iter_combinations_unchecked`, `for_each_unchecked`, `get_single_unchecked`, `get_single_unchecked_manual`. - `SystemState`: `get_unchecked_manual` ```rust let mut world = World::new(); let mut query = world.query::<&mut T>(); // Before: let t1 = query.get_unchecked(&world, entity_1); let t2 = query.get_unchecked(&world, entity_2); // After: let world_cell = world.as_unsafe_world_cell(); let t1 = query.get_unchecked(world_cell, entity_1); let t2 = query.get_unchecked(world_cell, entity_2); ``` The methods `QueryState::validate_world` and `SystemState::matches_world` now take a `WorldId` instead of `&World`: ```rust // Before: query_state.validate_world(&world); // After: query_state.validate_world(world.id()); ``` The methods `QueryState::update_archetypes` and `SystemState::update_archetypes` now take `UnsafeWorldCell` instead of `&World`: ```rust // Before: query_state.update_archetypes(&world); // After: query_state.update_archetypes(world.as_unsafe_world_cell_readonly()); ```
2023-06-15 01:31:56 +00:00
_last_run: Tick,
_this_run: Tick,
) {
}
const IS_DENSE: bool = D::IS_DENSE;
#[inline(always)]
unsafe fn set_archetype(
_fetch: &mut (),
_state: &D::State,
_archetype: &Archetype,
_tables: &Table,
) {
}
#[inline(always)]
unsafe fn set_table<'w>(_fetch: &mut (), _state: &D::State, _table: &Table) {}
#[inline(always)]
unsafe fn fetch<'w>(
_fetch: &mut Self::Fetch<'w>,
_entity: Entity,
_table_row: TableRow,
) -> Self::Item<'w> {
}
Replace `ReadOnlyFetch` with `ReadOnlyWorldQuery` (#4626) # Objective - Fix a type inference regression introduced by #3001 - Make read only bounds on world queries more user friendly ptrification required you to write `Q::Fetch: ReadOnlyFetch` as `for<'w> QueryFetch<'w, Q>: ReadOnlyFetch` which has the same type inference problem as `for<'w> QueryFetch<'w, Q>: FilterFetch<'w>` had, i.e. the following code would error: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: WorldQuery>(a: Query<Q, ()>) where for<'w> QueryFetch<'w, Q>: ReadOnlyFetch, { } ``` `for<..>` bounds are also rather user unfriendly.. ## Solution Remove the `ReadOnlyFetch` trait in favour of a `ReadOnlyWorldQuery` trait, and remove `WorldQueryGats::ReadOnlyFetch` in favor of `WorldQuery::ReadOnly` allowing the previous code snippet to be written as: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: ReadOnlyWorldQuery>(a: Query<Q, ()>) {} ``` This avoids the `for<...>` bound which makes the code simpler and also fixes the type inference issue. The reason for moving the two functions out of `FetchState` and into `WorldQuery` is to allow the world query `&mut T` to share a `State` with the `&T` world query so that it can have `type ReadOnly = &T`. Presumably it would be possible to instead have a `ReadOnlyRefMut<T>` world query and then do `type ReadOnly = ReadOnlyRefMut<T>` much like how (before this PR) we had a `ReadOnlyWriteFetch<T>`. A side benefit of the current solution in this PR is that it will likely make it easier in the future to support an API such as `Query<&mut T> -> Query<&T>`. The primary benefit IMO is just that `ReadOnlyRefMut<T>` and its associated fetch would have to reimplement all of the logic that the `&T` world query impl does but this solution avoids that :) --- ## Changelog/Migration Guide The trait `ReadOnlyFetch` has been replaced with `ReadOnlyWorldQuery` along with the `WorldQueryGats::ReadOnlyFetch` assoc type which has been replaced with `<WorldQuery::ReadOnly as WorldQueryGats>::Fetch` - Any where clauses such as `QueryFetch<Q>: ReadOnlyFetch` should be replaced with `Q: ReadOnlyWorldQuery`. - Any custom world query impls should implement `ReadOnlyWorldQuery` insead of `ReadOnlyFetch` Functions `update_component_access` and `update_archetype_component_access` have been moved from the `FetchState` trait to `WorldQuery` - Any callers should now call `Q::update_component_access(state` instead of `state.update_component_access` (and `update_archetype_component_access` respectively) - Any custom world query impls should move the functions from the `FetchState` impl to `WorldQuery` impl `WorldQuery` has been made an `unsafe trait`, `FetchState` has been made a safe `trait`. (I think this is how it should have always been, but regardless this is _definitely_ necessary now that the two functions have been moved to `WorldQuery`) - If you have a custom `FetchState` impl make it a normal `impl` instead of `unsafe impl` - If you have a custom `WorldQuery` impl make it an `unsafe impl`, if your code was sound before it is going to still be sound
2022-06-13 23:35:54 +00:00
fn update_component_access(_state: &D::State, _access: &mut FilteredAccess<ComponentId>) {}
Replace `ReadOnlyFetch` with `ReadOnlyWorldQuery` (#4626) # Objective - Fix a type inference regression introduced by #3001 - Make read only bounds on world queries more user friendly ptrification required you to write `Q::Fetch: ReadOnlyFetch` as `for<'w> QueryFetch<'w, Q>: ReadOnlyFetch` which has the same type inference problem as `for<'w> QueryFetch<'w, Q>: FilterFetch<'w>` had, i.e. the following code would error: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: WorldQuery>(a: Query<Q, ()>) where for<'w> QueryFetch<'w, Q>: ReadOnlyFetch, { } ``` `for<..>` bounds are also rather user unfriendly.. ## Solution Remove the `ReadOnlyFetch` trait in favour of a `ReadOnlyWorldQuery` trait, and remove `WorldQueryGats::ReadOnlyFetch` in favor of `WorldQuery::ReadOnly` allowing the previous code snippet to be written as: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: ReadOnlyWorldQuery>(a: Query<Q, ()>) {} ``` This avoids the `for<...>` bound which makes the code simpler and also fixes the type inference issue. The reason for moving the two functions out of `FetchState` and into `WorldQuery` is to allow the world query `&mut T` to share a `State` with the `&T` world query so that it can have `type ReadOnly = &T`. Presumably it would be possible to instead have a `ReadOnlyRefMut<T>` world query and then do `type ReadOnly = ReadOnlyRefMut<T>` much like how (before this PR) we had a `ReadOnlyWriteFetch<T>`. A side benefit of the current solution in this PR is that it will likely make it easier in the future to support an API such as `Query<&mut T> -> Query<&T>`. The primary benefit IMO is just that `ReadOnlyRefMut<T>` and its associated fetch would have to reimplement all of the logic that the `&T` world query impl does but this solution avoids that :) --- ## Changelog/Migration Guide The trait `ReadOnlyFetch` has been replaced with `ReadOnlyWorldQuery` along with the `WorldQueryGats::ReadOnlyFetch` assoc type which has been replaced with `<WorldQuery::ReadOnly as WorldQueryGats>::Fetch` - Any where clauses such as `QueryFetch<Q>: ReadOnlyFetch` should be replaced with `Q: ReadOnlyWorldQuery`. - Any custom world query impls should implement `ReadOnlyWorldQuery` insead of `ReadOnlyFetch` Functions `update_component_access` and `update_archetype_component_access` have been moved from the `FetchState` trait to `WorldQuery` - Any callers should now call `Q::update_component_access(state` instead of `state.update_component_access` (and `update_archetype_component_access` respectively) - Any custom world query impls should move the functions from the `FetchState` impl to `WorldQuery` impl `WorldQuery` has been made an `unsafe trait`, `FetchState` has been made a safe `trait`. (I think this is how it should have always been, but regardless this is _definitely_ necessary now that the two functions have been moved to `WorldQuery`) - If you have a custom `FetchState` impl make it a normal `impl` instead of `unsafe impl` - If you have a custom `WorldQuery` impl make it an `unsafe impl`, if your code was sound before it is going to still be sound
2022-06-13 23:35:54 +00:00
fn init_state(world: &mut World) -> Self::State {
D::init_state(world)
}
Dynamic queries and builder API (#9774) # Objective Expand the existing `Query` API to support more dynamic use cases i.e. scripting. ## Prior Art - #6390 - #8308 - #10037 ## Solution - Create a `QueryBuilder` with runtime methods to define the set of component accesses for a built query. - Create new `WorldQueryData` implementations `FilteredEntityMut` and `FilteredEntityRef` as variants of `EntityMut` and `EntityRef` that provide run time checked access to the components included in a given query. - Add new methods to `Query` to create "query lens" with a subset of the access of the initial query. ### Query Builder The `QueryBuilder` API allows you to define a query at runtime. At it's most basic use it will simply create a query with the corresponding type signature: ```rust let query = QueryBuilder::<Entity, With<A>>::new(&mut world).build(); // is equivalent to let query = QueryState::<Entity, With<A>>::new(&mut world); ``` Before calling `.build()` you also have the opportunity to add additional accesses and filters. Here is a simple example where we add additional filter terms: ```rust let entity_a = world.spawn((A(0), B(0))).id(); let entity_b = world.spawn((A(0), C(0))).id(); let mut query_a = QueryBuilder::<Entity>::new(&mut world) .with::<A>() .without::<C>() .build(); assert_eq!(entity_a, query_a.single(&world)); ``` This alone is useful in that allows you to decide which archetypes your query will match at runtime. However it is also very limited, consider a case like the following: ```rust let query_a = QueryBuilder::<&A>::new(&mut world) // Add an additional access .data::<&B>() .build(); ``` This will grant the query an additional read access to component B however we have no way of accessing the data while iterating as the type signature still only includes &A. For an even more concrete example of this consider dynamic components: ```rust let query_a = QueryBuilder::<Entity>::new(&mut world) // Adding a filter is easy since it doesn't need be read later .with_id(component_id_a) // How do I access the data of this component? .ref_id(component_id_b) .build(); ``` With this in mind the `QueryBuilder` API seems somewhat incomplete by itself, we need some way method of accessing the components dynamically. So here's one: ### Query Transmutation If the problem is not having the component in the type signature why not just add it? This PR also adds transmute methods to `QueryBuilder` and `QueryState`. Here's a simple example: ```rust world.spawn(A(0)); world.spawn((A(1), B(0))); let mut query = QueryBuilder::<()>::new(&mut world) .with::<B>() .transmute::<&A>() .build(); query.iter(&world).for_each(|a| assert_eq!(a.0, 1)); ``` The `QueryState` and `QueryBuilder` transmute methods look quite similar but are different in one respect. Transmuting a builder will always succeed as it will just add the additional accesses needed for the new terms if they weren't already included. Transmuting a `QueryState` will panic in the case that the new type signature would give it access it didn't already have, for example: ```rust let query = QueryState::<&A, Option<&B>>::new(&mut world); /// This is fine, the access for Option<&A> is less restrictive than &A query.transmute::<Option<&A>>(&world); /// Oh no, this would allow access to &B on entities that might not have it, so it panics query.transmute::<&B>(&world); /// This is right out query.transmute::<&C>(&world); ``` This is quite an appealing API to also have available on `Query` however it does pose one additional wrinkle: In order to to change the iterator we need to create a new `QueryState` to back it. `Query` doesn't own it's own state though, it just borrows it, so we need a place to borrow it from. This is why `QueryLens` exists, it is a place to store the new state so it can be borrowed when you call `.query()` leaving you with an API like this: ```rust fn function_that_takes_a_query(query: &Query<&A>) { // ... } fn system(query: Query<(&A, &B)>) { let lens = query.transmute_lens::<&A>(); let q = lens.query(); function_that_takes_a_query(&q); } ``` Now you may be thinking: Hey, wait a second, you introduced the problem with dynamic components and then described a solution that only works for static components! Ok, you got me, I guess we need a bit more: ### Filtered Entity References Currently the only way you can access dynamic components on entities through a query is with either `EntityMut` or `EntityRef`, however these can access all components and so conflict with all other accesses. This PR introduces `FilteredEntityMut` and `FilteredEntityRef` as alternatives that have additional runtime checking to prevent accessing components that you shouldn't. This way you can build a query with a `QueryBuilder` and actually access the components you asked for: ```rust let mut query = QueryBuilder::<FilteredEntityRef>::new(&mut world) .ref_id(component_id_a) .with(component_id_b) .build(); let entity_ref = query.single(&world); // Returns Some(Ptr) as we have that component and are allowed to read it let a = entity_ref.get_by_id(component_id_a); // Will return None even though the entity does have the component, as we are not allowed to read it let b = entity_ref.get_by_id(component_id_b); ``` For the most part these new structs have the exact same methods as their non-filtered equivalents. Putting all of this together we can do some truly dynamic ECS queries, check out the `dynamic` example to see it in action: ``` Commands: comp, c Create new components spawn, s Spawn entities query, q Query for entities Enter a command with no parameters for usage. > c A, B, C, Data 4 Component A created with id: 0 Component B created with id: 1 Component C created with id: 2 Component Data created with id: 3 > s A, B, Data 1 Entity spawned with id: 0v0 > s A, C, Data 0 Entity spawned with id: 1v0 > q &Data 0v0: Data: [1, 0, 0, 0] 1v0: Data: [0, 0, 0, 0] > q B, &mut Data 0v0: Data: [2, 1, 1, 1] > q B || C, &Data 0v0: Data: [2, 1, 1, 1] 1v0: Data: [0, 0, 0, 0] ``` ## Changelog - Add new `transmute_lens` methods to `Query`. - Add new types `QueryBuilder`, `FilteredEntityMut`, `FilteredEntityRef` and `QueryLens` - `update_archetype_component_access` has been removed, archetype component accesses are now determined by the accesses set in `update_component_access` - Added method `set_access` to `WorldQuery`, this is called before `update_component_access` for queries that have a restricted set of accesses, such as those built by `QueryBuilder` or `QueryLens`. This is primarily used by the `FilteredEntity*` variants and has an empty trait implementation. - Added method `get_state` to `WorldQuery` as a fallible version of `init_state` when you don't have `&mut World` access. ## Future Work Improve performance of `FilteredEntityMut` and `FilteredEntityRef`, currently they have to determine the accesses a query has in a given archetype during iteration which is far from ideal, especially since we already did the work when matching the archetype in the first place. To avoid making more internal API changes I have left it out of this PR. --------- Co-authored-by: Mike Hsu <mike.hsu@gmail.com>
2024-01-16 19:16:49 +00:00
fn get_state(world: &World) -> Option<Self::State> {
D::get_state(world)
}
fn matches_component_set(
state: &Self::State,
set_contains_id: &impl Fn(ComponentId) -> bool,
) -> bool {
D::matches_component_set(state, set_contains_id)
}
}
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
/// SAFETY: `Self::ReadOnly` is `Self`
unsafe impl<D: QueryData> QueryData for NopWorldQuery<D> {
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
type ReadOnly = Self;
}
/// SAFETY: `NopFetch` never accesses any data
unsafe impl<D: QueryData> ReadOnlyQueryData for NopWorldQuery<D> {}
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
/// SAFETY:
/// `update_component_access` and `update_archetype_component_access` do nothing.
/// This is sound because `fetch` does not access components.
unsafe impl<T: ?Sized> WorldQuery for PhantomData<T> {
type Item<'a> = ();
type Fetch<'a> = ();
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
type State = ();
fn shrink<'wlong: 'wshort, 'wshort>(_item: Self::Item<'wlong>) -> Self::Item<'wshort> {}
unsafe fn init_fetch<'w>(
Migrate the rest of the engine to `UnsafeWorldCell` (#8833) # Objective Follow-up to #6404 and #8292. Mutating the world through a shared reference is surprising, and it makes the meaning of `&World` unclear: sometimes it gives read-only access to the entire world, and sometimes it gives interior mutable access to only part of it. This is an up-to-date version of #6972. ## Solution Use `UnsafeWorldCell` for all interior mutability. Now, `&World` *always* gives you read-only access to the entire world. --- ## Changelog TODO - do we still care about changelogs? ## Migration Guide Mutating any world data using `&World` is now considered unsound -- the type `UnsafeWorldCell` must be used to achieve interior mutability. The following methods now accept `UnsafeWorldCell` instead of `&World`: - `QueryState`: `get_unchecked`, `iter_unchecked`, `iter_combinations_unchecked`, `for_each_unchecked`, `get_single_unchecked`, `get_single_unchecked_manual`. - `SystemState`: `get_unchecked_manual` ```rust let mut world = World::new(); let mut query = world.query::<&mut T>(); // Before: let t1 = query.get_unchecked(&world, entity_1); let t2 = query.get_unchecked(&world, entity_2); // After: let world_cell = world.as_unsafe_world_cell(); let t1 = query.get_unchecked(world_cell, entity_1); let t2 = query.get_unchecked(world_cell, entity_2); ``` The methods `QueryState::validate_world` and `SystemState::matches_world` now take a `WorldId` instead of `&World`: ```rust // Before: query_state.validate_world(&world); // After: query_state.validate_world(world.id()); ``` The methods `QueryState::update_archetypes` and `SystemState::update_archetypes` now take `UnsafeWorldCell` instead of `&World`: ```rust // Before: query_state.update_archetypes(&world); // After: query_state.update_archetypes(world.as_unsafe_world_cell_readonly()); ```
2023-06-15 01:31:56 +00:00
_world: UnsafeWorldCell<'w>,
_state: &Self::State,
_last_run: Tick,
_this_run: Tick,
) -> Self::Fetch<'w> {
}
// `PhantomData` does not match any components, so all components it matches
// are stored in a Table (vacuous truth).
const IS_DENSE: bool = true;
unsafe fn set_archetype<'w>(
_fetch: &mut Self::Fetch<'w>,
_state: &Self::State,
_archetype: &'w Archetype,
_table: &'w Table,
) {
}
unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) {
}
unsafe fn fetch<'w>(
_fetch: &mut Self::Fetch<'w>,
_entity: Entity,
_table_row: TableRow,
) -> Self::Item<'w> {
}
fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess<ComponentId>) {}
fn init_state(_world: &mut World) -> Self::State {}
Dynamic queries and builder API (#9774) # Objective Expand the existing `Query` API to support more dynamic use cases i.e. scripting. ## Prior Art - #6390 - #8308 - #10037 ## Solution - Create a `QueryBuilder` with runtime methods to define the set of component accesses for a built query. - Create new `WorldQueryData` implementations `FilteredEntityMut` and `FilteredEntityRef` as variants of `EntityMut` and `EntityRef` that provide run time checked access to the components included in a given query. - Add new methods to `Query` to create "query lens" with a subset of the access of the initial query. ### Query Builder The `QueryBuilder` API allows you to define a query at runtime. At it's most basic use it will simply create a query with the corresponding type signature: ```rust let query = QueryBuilder::<Entity, With<A>>::new(&mut world).build(); // is equivalent to let query = QueryState::<Entity, With<A>>::new(&mut world); ``` Before calling `.build()` you also have the opportunity to add additional accesses and filters. Here is a simple example where we add additional filter terms: ```rust let entity_a = world.spawn((A(0), B(0))).id(); let entity_b = world.spawn((A(0), C(0))).id(); let mut query_a = QueryBuilder::<Entity>::new(&mut world) .with::<A>() .without::<C>() .build(); assert_eq!(entity_a, query_a.single(&world)); ``` This alone is useful in that allows you to decide which archetypes your query will match at runtime. However it is also very limited, consider a case like the following: ```rust let query_a = QueryBuilder::<&A>::new(&mut world) // Add an additional access .data::<&B>() .build(); ``` This will grant the query an additional read access to component B however we have no way of accessing the data while iterating as the type signature still only includes &A. For an even more concrete example of this consider dynamic components: ```rust let query_a = QueryBuilder::<Entity>::new(&mut world) // Adding a filter is easy since it doesn't need be read later .with_id(component_id_a) // How do I access the data of this component? .ref_id(component_id_b) .build(); ``` With this in mind the `QueryBuilder` API seems somewhat incomplete by itself, we need some way method of accessing the components dynamically. So here's one: ### Query Transmutation If the problem is not having the component in the type signature why not just add it? This PR also adds transmute methods to `QueryBuilder` and `QueryState`. Here's a simple example: ```rust world.spawn(A(0)); world.spawn((A(1), B(0))); let mut query = QueryBuilder::<()>::new(&mut world) .with::<B>() .transmute::<&A>() .build(); query.iter(&world).for_each(|a| assert_eq!(a.0, 1)); ``` The `QueryState` and `QueryBuilder` transmute methods look quite similar but are different in one respect. Transmuting a builder will always succeed as it will just add the additional accesses needed for the new terms if they weren't already included. Transmuting a `QueryState` will panic in the case that the new type signature would give it access it didn't already have, for example: ```rust let query = QueryState::<&A, Option<&B>>::new(&mut world); /// This is fine, the access for Option<&A> is less restrictive than &A query.transmute::<Option<&A>>(&world); /// Oh no, this would allow access to &B on entities that might not have it, so it panics query.transmute::<&B>(&world); /// This is right out query.transmute::<&C>(&world); ``` This is quite an appealing API to also have available on `Query` however it does pose one additional wrinkle: In order to to change the iterator we need to create a new `QueryState` to back it. `Query` doesn't own it's own state though, it just borrows it, so we need a place to borrow it from. This is why `QueryLens` exists, it is a place to store the new state so it can be borrowed when you call `.query()` leaving you with an API like this: ```rust fn function_that_takes_a_query(query: &Query<&A>) { // ... } fn system(query: Query<(&A, &B)>) { let lens = query.transmute_lens::<&A>(); let q = lens.query(); function_that_takes_a_query(&q); } ``` Now you may be thinking: Hey, wait a second, you introduced the problem with dynamic components and then described a solution that only works for static components! Ok, you got me, I guess we need a bit more: ### Filtered Entity References Currently the only way you can access dynamic components on entities through a query is with either `EntityMut` or `EntityRef`, however these can access all components and so conflict with all other accesses. This PR introduces `FilteredEntityMut` and `FilteredEntityRef` as alternatives that have additional runtime checking to prevent accessing components that you shouldn't. This way you can build a query with a `QueryBuilder` and actually access the components you asked for: ```rust let mut query = QueryBuilder::<FilteredEntityRef>::new(&mut world) .ref_id(component_id_a) .with(component_id_b) .build(); let entity_ref = query.single(&world); // Returns Some(Ptr) as we have that component and are allowed to read it let a = entity_ref.get_by_id(component_id_a); // Will return None even though the entity does have the component, as we are not allowed to read it let b = entity_ref.get_by_id(component_id_b); ``` For the most part these new structs have the exact same methods as their non-filtered equivalents. Putting all of this together we can do some truly dynamic ECS queries, check out the `dynamic` example to see it in action: ``` Commands: comp, c Create new components spawn, s Spawn entities query, q Query for entities Enter a command with no parameters for usage. > c A, B, C, Data 4 Component A created with id: 0 Component B created with id: 1 Component C created with id: 2 Component Data created with id: 3 > s A, B, Data 1 Entity spawned with id: 0v0 > s A, C, Data 0 Entity spawned with id: 1v0 > q &Data 0v0: Data: [1, 0, 0, 0] 1v0: Data: [0, 0, 0, 0] > q B, &mut Data 0v0: Data: [2, 1, 1, 1] > q B || C, &Data 0v0: Data: [2, 1, 1, 1] 1v0: Data: [0, 0, 0, 0] ``` ## Changelog - Add new `transmute_lens` methods to `Query`. - Add new types `QueryBuilder`, `FilteredEntityMut`, `FilteredEntityRef` and `QueryLens` - `update_archetype_component_access` has been removed, archetype component accesses are now determined by the accesses set in `update_component_access` - Added method `set_access` to `WorldQuery`, this is called before `update_component_access` for queries that have a restricted set of accesses, such as those built by `QueryBuilder` or `QueryLens`. This is primarily used by the `FilteredEntity*` variants and has an empty trait implementation. - Added method `get_state` to `WorldQuery` as a fallible version of `init_state` when you don't have `&mut World` access. ## Future Work Improve performance of `FilteredEntityMut` and `FilteredEntityRef`, currently they have to determine the accesses a query has in a given archetype during iteration which is far from ideal, especially since we already did the work when matching the archetype in the first place. To avoid making more internal API changes I have left it out of this PR. --------- Co-authored-by: Mike Hsu <mike.hsu@gmail.com>
2024-01-16 19:16:49 +00:00
fn get_state(_world: &World) -> Option<Self::State> {
Some(())
}
fn matches_component_set(
_state: &Self::State,
_set_contains_id: &impl Fn(ComponentId) -> bool,
) -> bool {
true
}
}
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
/// SAFETY: `Self::ReadOnly` is `Self`
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
unsafe impl<T: ?Sized> QueryData for PhantomData<T> {
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
type ReadOnly = Self;
}
/// SAFETY: `PhantomData` never accesses any world data.
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
unsafe impl<T: ?Sized> ReadOnlyQueryData for PhantomData<T> {}
#[cfg(test)]
mod tests {
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
use bevy_ecs_macros::QueryData;
Split WorldQuery into WorldQueryData and WorldQueryFilter (#9918) # Objective - Fixes #7680 - This is an updated for https://github.com/bevyengine/bevy/pull/8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-28 03:56:07 +00:00
use super::*;
use crate::{
self as bevy_ecs,
system::{assert_is_system, Query},
};
#[derive(Component)]
pub struct A;
#[derive(Component)]
pub struct B;
// Tests that each variant of struct can be used as a `WorldQuery`.
#[test]
fn world_query_struct_variants() {
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
#[derive(QueryData)]
pub struct NamedQuery {
id: Entity,
a: &'static A,
}
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
#[derive(QueryData)]
pub struct TupleQuery(&'static A, &'static B);
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
#[derive(QueryData)]
pub struct UnitQuery;
fn my_system(_: Query<(NamedQuery, TupleQuery, UnitQuery)>) {}
assert_is_system(my_system);
}
// Compile test for https://github.com/bevyengine/bevy/pull/8030.
#[test]
fn world_query_phantom_data() {
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
#[derive(QueryData)]
pub struct IgnoredQuery<Marker> {
id: Entity,
_marker: PhantomData<Marker>,
}
fn ignored_system(_: Query<IgnoredQuery<()>>) {}
assert_is_system(ignored_system);
}
// Ensures that each field of a `WorldQuery` struct's read-only variant
// has the same visibility as its corresponding mutable field.
#[test]
fn read_only_field_visibility() {
mod private {
use super::*;
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
#[derive(QueryData)]
#[query_data(mutable)]
pub struct D {
pub a: &'static mut A,
}
}
let _ = private::DReadOnly { a: &A };
fn my_system(query: Query<private::D>) {
for q in &query {
let _ = &q.a;
}
}
assert_is_system(my_system);
}
// Ensures that metadata types generated by the WorldQuery macro
// do not conflict with user-defined types.
// Regression test for https://github.com/bevyengine/bevy/issues/8010.
#[test]
fn world_query_metadata_collision() {
// The metadata types generated would be named `ClientState` and `ClientFetch`,
// but they should rename themselves to avoid conflicts.
Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` (#10779) # Rename `WorldQueryData` & `WorldQueryFilter` to `QueryData` & `QueryFilter` Fixes #10776 ## Solution Traits `WorldQueryData` & `WorldQueryFilter` were renamed to `QueryData` and `QueryFilter`, respectively. Related Trait types were also renamed. --- ## Changelog - Trait `WorldQueryData` has been renamed to `QueryData`. Derive macro's `QueryData` attribute `world_query_data` has been renamed to `query_data`. - Trait `WorldQueryFilter` has been renamed to `QueryFilter`. Derive macro's `QueryFilter` attribute `world_query_filter` has been renamed to `query_filter`. - Trait's `ExtractComponent` type `Query` has been renamed to `Data`. - Trait's `GetBatchData` types `Query` & `QueryFilter` has been renamed to `Data` & `Filter`, respectively. - Trait's `ExtractInstance` type `Query` has been renamed to `Data`. - Trait's `ViewNode` type `ViewQuery` has been renamed to `ViewData`. - Trait's `RenderCommand` types `ViewWorldQuery` & `ItemWorldQuery` has been renamed to `ViewData` & `ItemData`, respectively. ## Migration Guide Note: if merged before 0.13 is released, this should instead modify the migration guide of #10776 with the updated names. - Rename `WorldQueryData` & `WorldQueryFilter` trait usages to `QueryData` & `QueryFilter` and their respective derive macro attributes `world_query_data` & `world_query_filter` to `query_data` & `query_filter`. - Rename the following trait type usages: - Trait's `ExtractComponent` type `Query` to `Data`. - Trait's `GetBatchData` type `Query` to `Data`. - Trait's `ExtractInstance` type `Query` to `Data`. - Trait's `ViewNode` type `ViewQuery` to `ViewData`' - Trait's `RenderCommand` types `ViewWolrdQuery` & `ItemWorldQuery` to `ViewData` & `ItemData`, respectively. ```rust // Before #[derive(WorldQueryData)] #[world_query_data(derive(Debug))] struct EmptyQuery { empty: (), } // After #[derive(QueryData)] #[query_data(derive(Debug))] struct EmptyQuery { empty: (), } // Before #[derive(WorldQueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // After #[derive(QueryFilter)] struct CustomQueryFilter<T: Component, P: Component> { _c: With<ComponentC>, _d: With<ComponentD>, _or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>, _generic_tuple: (With<T>, With<P>), } // Before impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Query = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { //... } } // After impl ExtractComponent for ContrastAdaptiveSharpeningSettings { type Data = &'static Self; type Filter = With<Camera>; type Out = (DenoiseCAS, CASUniform); fn extract_component(item: QueryItem<Self::Data>) -> Option<Self::Out> { //... } } // Before impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Query = Entity; type QueryFilter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Query>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // After impl GetBatchData for MeshPipeline { type Param = SRes<RenderMeshInstances>; type Data = Entity; type Filter = With<Mesh3d>; type CompareData = (MaterialBindGroupId, AssetId<Mesh>); type BufferData = MeshUniform; fn get_batch_data( mesh_instances: &SystemParamItem<Self::Param>, entity: &QueryItem<Self::Data>, ) -> (Self::BufferData, Option<Self::CompareData>) { // .... } } // Before impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Query = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { Some(item.id()) } } // After impl<A> ExtractInstance for AssetId<A> where A: Asset, { type Data = Read<Handle<A>>; type Filter = (); fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> { Some(item.id()) } } // Before impl ViewNode for PostProcessNode { type ViewQuery = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewQuery>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // After impl ViewNode for PostProcessNode { type ViewData = ( &'static ViewTarget, &'static PostProcessSettings, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, (view_target, _post_process_settings): QueryItem<Self::ViewData>, world: &World, ) -> Result<(), NodeRunError> { // ... } } // Before impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewWorldQuery = (); type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } // After impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline { type Param = SRes<PipelineCache>; type ViewData = (); type ItemData = (); #[inline] fn render<'w>( item: &P, _view: (), _entity: (), pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { // ... } } ```
2023-12-12 19:45:50 +00:00
#[derive(QueryData)]
pub struct Client<S: ClientState> {
pub state: &'static S,
pub fetch: &'static ClientFetch,
}
pub trait ClientState: Component {}
#[derive(Component)]
pub struct ClientFetch;
#[derive(Component)]
pub struct C;
impl ClientState for C {}
fn client_system(_: Query<Client<C>>) {}
assert_is_system(client_system);
}
}