mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 04:33:37 +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:🆕:<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)
This commit is contained in:
parent
d9fb61d474
commit
3a2a68852c
233 changed files with 12822 additions and 10004 deletions
|
@ -1,19 +1,16 @@
|
|||
[package]
|
||||
name = "benches"
|
||||
version = "0.1.0"
|
||||
authors = ["Carter Anderson <mcanders1@gmail.com>"]
|
||||
authors = [
|
||||
"Bevy Contributors <bevyengine@gmail.com>",
|
||||
"Carter Anderson <mcanders1@gmail.com>",
|
||||
]
|
||||
edition = "2018"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.3"
|
||||
bevy = { path = "../" }
|
||||
|
||||
[[bench]]
|
||||
name = "bench"
|
||||
path = "benches/bevy_ecs/bench.rs"
|
||||
harness = false
|
||||
required-features = ["macros"]
|
||||
|
||||
[[bench]]
|
||||
name = "system_stage"
|
||||
path = "benches/bevy_ecs/stages.rs"
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// modified by Bevy contributors
|
||||
|
||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
||||
use bevy_ecs::*;
|
||||
|
||||
struct Position(f32);
|
||||
struct Velocity(f32);
|
||||
|
||||
fn spawn_tuple(b: &mut Bencher) {
|
||||
let mut world = World::new();
|
||||
b.iter(|| {
|
||||
world.spawn((Position(0.0), Velocity(0.0)));
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_static(b: &mut Bencher) {
|
||||
#[derive(Bundle)]
|
||||
struct Bundle {
|
||||
pos: Position,
|
||||
vel: Velocity,
|
||||
}
|
||||
|
||||
let mut world = World::new();
|
||||
b.iter(|| {
|
||||
world.spawn(Bundle {
|
||||
pos: Position(0.0),
|
||||
vel: Velocity(0.0),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_batch(b: &mut Bencher) {
|
||||
#[derive(Bundle)]
|
||||
struct Bundle {
|
||||
pos: Position,
|
||||
vel: Velocity,
|
||||
}
|
||||
|
||||
let mut world = World::new();
|
||||
b.iter(|| {
|
||||
world
|
||||
.spawn_batch((0..1_000).map(|_| Bundle {
|
||||
pos: Position(0.0),
|
||||
vel: Velocity(0.0),
|
||||
}))
|
||||
.for_each(|_| {});
|
||||
world.clear();
|
||||
});
|
||||
}
|
||||
|
||||
fn iterate_100k(b: &mut Bencher) {
|
||||
let mut world = World::new();
|
||||
for i in 0..100_000 {
|
||||
world.spawn((Position(-(i as f32)), Velocity(i as f32)));
|
||||
}
|
||||
b.iter(|| {
|
||||
for (mut pos, vel) in &mut world.query_mut::<(&mut Position, &Velocity)>() {
|
||||
pos.0 += vel.0;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn build(b: &mut Bencher) {
|
||||
let mut world = World::new();
|
||||
let mut builder = EntityBuilder::new();
|
||||
b.iter(|| {
|
||||
builder.add(Position(0.0)).add(Velocity(0.0));
|
||||
world.spawn(builder.build());
|
||||
});
|
||||
}
|
||||
|
||||
benchmark_group!(
|
||||
benches,
|
||||
spawn_tuple,
|
||||
spawn_static,
|
||||
spawn_batch,
|
||||
iterate_100k,
|
||||
build
|
||||
);
|
||||
benchmark_main!(benches);
|
|
@ -1,13 +1,15 @@
|
|||
use bevy::ecs::{IntoSystem,World,SystemStage,Resources,Query,Stage};
|
||||
use bevy::ecs::{
|
||||
world::World,
|
||||
schedule::{Stage, SystemStage},
|
||||
system::{IntoSystem, Query},
|
||||
};
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
criterion_group!(benches, empty_systems, busy_systems, contrived);
|
||||
criterion_main!(benches);
|
||||
|
||||
fn run_stage(stage: &mut SystemStage, world: &mut World, resources: &mut Resources) {
|
||||
// !!NB!! Uncomment next line when running with old executor.
|
||||
//stage.initialize(world, resources);
|
||||
stage.run(world, resources);
|
||||
fn run_stage(stage: &mut SystemStage, world: &mut World) {
|
||||
stage.run(world);
|
||||
}
|
||||
|
||||
struct A(f32);
|
||||
|
@ -20,7 +22,6 @@ const ENTITY_BUNCH: usize = 5000;
|
|||
|
||||
fn empty_systems(criterion: &mut Criterion) {
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
let mut group = criterion.benchmark_group("empty_systems");
|
||||
group.warm_up_time(std::time::Duration::from_millis(500));
|
||||
group.measurement_time(std::time::Duration::from_secs(3));
|
||||
|
@ -30,10 +31,10 @@ fn empty_systems(criterion: &mut Criterion) {
|
|||
for _ in 0..amount {
|
||||
stage.add_system(empty.system());
|
||||
}
|
||||
run_stage(&mut stage, &mut world, &mut resources);
|
||||
run_stage(&mut stage, &mut world);
|
||||
group.bench_function(&format!("{:03}_systems", amount), |bencher| {
|
||||
bencher.iter(|| {
|
||||
run_stage(&mut stage, &mut world, &mut resources);
|
||||
run_stage(&mut stage, &mut world);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -47,10 +48,10 @@ fn empty_systems(criterion: &mut Criterion) {
|
|||
.add_system(empty.system())
|
||||
.add_system(empty.system());
|
||||
}
|
||||
run_stage(&mut stage, &mut world, &mut resources);
|
||||
run_stage(&mut stage, &mut world);
|
||||
group.bench_function(&format!("{:03}_systems", 5 * amount), |bencher| {
|
||||
bencher.iter(|| {
|
||||
run_stage(&mut stage, &mut world, &mut resources);
|
||||
run_stage(&mut stage, &mut world);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -59,22 +60,21 @@ fn empty_systems(criterion: &mut Criterion) {
|
|||
|
||||
fn busy_systems(criterion: &mut Criterion) {
|
||||
fn ab(mut q: Query<(&mut A, &mut B)>) {
|
||||
for (mut a, mut b) in q.iter_mut() {
|
||||
q.for_each_mut(|(mut a, mut b)| {
|
||||
std::mem::swap(&mut a.0, &mut b.0);
|
||||
}
|
||||
});
|
||||
}
|
||||
fn cd(mut q: Query<(&mut C, &mut D)>) {
|
||||
for (mut c, mut d) in q.iter_mut() {
|
||||
q.for_each_mut(|(mut c, mut d)| {
|
||||
std::mem::swap(&mut c.0, &mut d.0);
|
||||
}
|
||||
});
|
||||
}
|
||||
fn ce(mut q: Query<(&mut C, &mut E)>) {
|
||||
for (mut c, mut e) in q.iter_mut() {
|
||||
q.for_each_mut(|(mut c, mut e)| {
|
||||
std::mem::swap(&mut c.0, &mut e.0);
|
||||
}
|
||||
});
|
||||
}
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
let mut group = criterion.benchmark_group("busy_systems");
|
||||
group.warm_up_time(std::time::Duration::from_millis(500));
|
||||
group.measurement_time(std::time::Duration::from_secs(3));
|
||||
|
@ -95,7 +95,7 @@ fn busy_systems(criterion: &mut Criterion) {
|
|||
.add_system(cd.system())
|
||||
.add_system(ce.system());
|
||||
}
|
||||
run_stage(&mut stage, &mut world, &mut resources);
|
||||
run_stage(&mut stage, &mut world);
|
||||
group.bench_function(
|
||||
&format!(
|
||||
"{:02}x_entities_{:02}_systems",
|
||||
|
@ -104,7 +104,7 @@ fn busy_systems(criterion: &mut Criterion) {
|
|||
),
|
||||
|bencher| {
|
||||
bencher.iter(|| {
|
||||
run_stage(&mut stage, &mut world, &mut resources);
|
||||
run_stage(&mut stage, &mut world);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
@ -115,25 +115,24 @@ fn busy_systems(criterion: &mut Criterion) {
|
|||
|
||||
fn contrived(criterion: &mut Criterion) {
|
||||
fn s_0(mut q_0: Query<(&mut A, &mut B)>) {
|
||||
for (mut c_0, mut c_1) in q_0.iter_mut() {
|
||||
q_0.for_each_mut(|(mut c_0, mut c_1)| {
|
||||
std::mem::swap(&mut c_0.0, &mut c_1.0);
|
||||
}
|
||||
});
|
||||
}
|
||||
fn s_1(mut q_0: Query<(&mut A, &mut C)>, mut q_1: Query<(&mut B, &mut D)>) {
|
||||
for (mut c_0, mut c_1) in q_0.iter_mut() {
|
||||
q_0.for_each_mut(|(mut c_0, mut c_1)| {
|
||||
std::mem::swap(&mut c_0.0, &mut c_1.0);
|
||||
}
|
||||
for (mut c_0, mut c_1) in q_1.iter_mut() {
|
||||
});
|
||||
q_1.for_each_mut(|(mut c_0, mut c_1)| {
|
||||
std::mem::swap(&mut c_0.0, &mut c_1.0);
|
||||
}
|
||||
});
|
||||
}
|
||||
fn s_2(mut q_0: Query<(&mut C, &mut D)>) {
|
||||
for (mut c_0, mut c_1) in q_0.iter_mut() {
|
||||
q_0.for_each_mut(|(mut c_0, mut c_1)| {
|
||||
std::mem::swap(&mut c_0.0, &mut c_1.0);
|
||||
}
|
||||
});
|
||||
}
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
let mut group = criterion.benchmark_group("contrived");
|
||||
group.warm_up_time(std::time::Duration::from_millis(500));
|
||||
group.measurement_time(std::time::Duration::from_secs(3));
|
||||
|
@ -153,7 +152,7 @@ fn contrived(criterion: &mut Criterion) {
|
|||
.add_system(s_1.system())
|
||||
.add_system(s_2.system());
|
||||
}
|
||||
run_stage(&mut stage, &mut world, &mut resources);
|
||||
run_stage(&mut stage, &mut world);
|
||||
group.bench_function(
|
||||
&format!(
|
||||
"{:02}x_entities_{:02}_systems",
|
||||
|
@ -162,7 +161,7 @@ fn contrived(criterion: &mut Criterion) {
|
|||
),
|
||||
|bencher| {
|
||||
bencher.iter(|| {
|
||||
run_stage(&mut stage, &mut world, &mut resources);
|
||||
run_stage(&mut stage, &mut world);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
|
|
@ -26,7 +26,7 @@ where
|
|||
}
|
||||
|
||||
fn bench_overhead(c: &mut Criterion) {
|
||||
fn noop(_: &mut usize) {};
|
||||
fn noop(_: &mut usize) {}
|
||||
|
||||
let mut v = (0..10000).collect::<Vec<usize>>();
|
||||
c.bench_function("overhead_iter", |b| {
|
||||
|
|
|
@ -14,11 +14,13 @@ keywords = ["bevy"]
|
|||
|
||||
[features]
|
||||
trace = []
|
||||
default = ["bevy_reflect"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.4.0" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.4.0" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.4.0", optional = true }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.4.0" }
|
||||
|
||||
# other
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
use crate::app_builder::AppBuilder;
|
||||
use bevy_ecs::{Resources, Schedule, Stage, World};
|
||||
use bevy_ecs::{
|
||||
schedule::{Schedule, Stage},
|
||||
world::World,
|
||||
};
|
||||
#[cfg(feature = "trace")]
|
||||
use bevy_utils::tracing::info_span;
|
||||
|
||||
|
@ -27,7 +30,6 @@ use bevy_utils::tracing::info_span;
|
|||
/// ```
|
||||
pub struct App {
|
||||
pub world: World,
|
||||
pub resources: Resources,
|
||||
pub runner: Box<dyn Fn(App)>,
|
||||
pub schedule: Schedule,
|
||||
}
|
||||
|
@ -36,7 +38,6 @@ impl Default for App {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
world: Default::default(),
|
||||
resources: Default::default(),
|
||||
schedule: Default::default(),
|
||||
runner: Box::new(run_once),
|
||||
}
|
||||
|
@ -53,7 +54,7 @@ impl App {
|
|||
}
|
||||
|
||||
pub fn update(&mut self) {
|
||||
self.schedule.run(&mut self.world, &mut self.resources);
|
||||
self.schedule.run(&mut self.world);
|
||||
}
|
||||
|
||||
pub fn run(mut self) {
|
||||
|
|
|
@ -5,9 +5,12 @@ use crate::{
|
|||
CoreStage, PluginGroup, PluginGroupBuilder, StartupStage,
|
||||
};
|
||||
use bevy_ecs::{
|
||||
clear_trackers_system, FromResources, IntoExclusiveSystem, IntoSystem, Resource, Resources,
|
||||
RunOnce, Schedule, Stage, StageLabel, StateStage, SystemDescriptor, SystemSet, SystemStage,
|
||||
World,
|
||||
component::Component,
|
||||
schedule::{
|
||||
RunOnce, Schedule, Stage, StageLabel, StateStage, SystemDescriptor, SystemSet, SystemStage,
|
||||
},
|
||||
system::{IntoExclusiveSystem, IntoSystem},
|
||||
world::{FromWorld, World},
|
||||
};
|
||||
use bevy_utils::tracing::debug;
|
||||
|
||||
|
@ -22,10 +25,13 @@ impl Default for AppBuilder {
|
|||
app: App::default(),
|
||||
};
|
||||
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
app_builder.init_resource::<bevy_reflect::TypeRegistryArc>();
|
||||
|
||||
app_builder
|
||||
.add_default_stages()
|
||||
.add_event::<AppExit>()
|
||||
.add_system_to_stage(CoreStage::Last, clear_trackers_system.exclusive_system());
|
||||
.add_system_to_stage(CoreStage::Last, World::clear_trackers.exclusive_system());
|
||||
app_builder
|
||||
}
|
||||
}
|
||||
|
@ -37,19 +43,19 @@ impl AppBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn resources(&self) -> &Resources {
|
||||
&self.app.resources
|
||||
}
|
||||
|
||||
pub fn resources_mut(&mut self) -> &mut Resources {
|
||||
&mut self.app.resources
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
let app = std::mem::take(&mut self.app);
|
||||
app.run();
|
||||
}
|
||||
|
||||
pub fn world(&mut self) -> &World {
|
||||
&self.app.world
|
||||
}
|
||||
|
||||
pub fn world_mut(&mut self) -> &mut World {
|
||||
&mut self.app.world
|
||||
}
|
||||
|
||||
pub fn set_world(&mut self, world: World) -> &mut Self {
|
||||
self.app.world = world;
|
||||
self
|
||||
|
@ -171,7 +177,7 @@ impl AppBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn on_state_enter<T: Clone + Resource>(
|
||||
pub fn on_state_enter<T: Clone + Component>(
|
||||
&mut self,
|
||||
stage: impl StageLabel,
|
||||
state: T,
|
||||
|
@ -182,7 +188,7 @@ impl AppBuilder {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn on_state_update<T: Clone + Resource>(
|
||||
pub fn on_state_update<T: Clone + Component>(
|
||||
&mut self,
|
||||
stage: impl StageLabel,
|
||||
state: T,
|
||||
|
@ -193,7 +199,7 @@ impl AppBuilder {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn on_state_exit<T: Clone + Resource>(
|
||||
pub fn on_state_exit<T: Clone + Component>(
|
||||
&mut self,
|
||||
stage: impl StageLabel,
|
||||
state: T,
|
||||
|
@ -224,7 +230,7 @@ impl AppBuilder {
|
|||
|
||||
pub fn add_event<T>(&mut self) -> &mut Self
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
T: Component,
|
||||
{
|
||||
self.insert_resource(Events::<T>::default())
|
||||
.add_system_to_stage(CoreStage::Event, Events::<T>::update_system.system())
|
||||
|
@ -233,9 +239,9 @@ impl AppBuilder {
|
|||
/// Inserts a resource to the current [App] and overwrites any resource previously added of the same type.
|
||||
pub fn insert_resource<T>(&mut self, resource: T) -> &mut Self
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
T: Component,
|
||||
{
|
||||
self.app.resources.insert(resource);
|
||||
self.app.world.insert_resource(resource);
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -243,19 +249,19 @@ impl AppBuilder {
|
|||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.app.resources.insert_non_send(resource);
|
||||
self.app.world.insert_non_send(resource);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn init_resource<R>(&mut self) -> &mut Self
|
||||
where
|
||||
R: FromResources + Send + Sync + 'static,
|
||||
R: FromWorld + Send + Sync + 'static,
|
||||
{
|
||||
// PERF: We could avoid double hashing here, since the `from_resources` call is guaranteed not to
|
||||
// modify the map. However, we would need to be borrowing resources both mutably and immutably,
|
||||
// so we would need to be extremely certain this is correct
|
||||
if !self.resources().contains::<R>() {
|
||||
let resource = R::from_resources(&self.resources());
|
||||
if !self.world_mut().contains_resource::<R>() {
|
||||
let resource = R::from_world(self.world_mut());
|
||||
self.insert_resource(resource);
|
||||
}
|
||||
self
|
||||
|
@ -263,12 +269,12 @@ impl AppBuilder {
|
|||
|
||||
pub fn init_non_send_resource<R>(&mut self) -> &mut Self
|
||||
where
|
||||
R: FromResources + 'static,
|
||||
R: FromWorld + 'static,
|
||||
{
|
||||
// See perf comment in init_resource
|
||||
if self.app.resources.get_non_send::<R>().is_none() {
|
||||
let resource = R::from_resources(&self.app.resources);
|
||||
self.app.resources.insert_non_send(resource);
|
||||
if self.app.world.get_non_send_resource::<R>().is_none() {
|
||||
let resource = R::from_world(self.world_mut());
|
||||
self.app.world.insert_non_send(resource);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
@ -305,4 +311,16 @@ impl AppBuilder {
|
|||
plugin_group_builder.finish(self);
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
pub fn register_type<T: bevy_reflect::GetTypeRegistration>(&mut self) -> &mut Self {
|
||||
{
|
||||
let registry = self
|
||||
.world_mut()
|
||||
.get_resource_mut::<bevy_reflect::TypeRegistryArc>()
|
||||
.unwrap();
|
||||
registry.write().register::<T>();
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use bevy_ecs::{Local, Res, ResMut, SystemParam};
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
system::{Local, Res, ResMut, SystemParam},
|
||||
};
|
||||
use bevy_utils::tracing::trace;
|
||||
use std::{fmt, marker::PhantomData};
|
||||
|
||||
|
@ -123,7 +126,7 @@ fn map_instance_event<T>(event_instance: &EventInstance<T>) -> &T {
|
|||
|
||||
/// Reads events of type `T` in order and tracks which events have already been read.
|
||||
#[derive(SystemParam)]
|
||||
pub struct EventReader<'a, T: bevy_ecs::Resource> {
|
||||
pub struct EventReader<'a, T: Component> {
|
||||
last_event_count: Local<'a, (usize, PhantomData<T>)>,
|
||||
events: Res<'a, Events<T>>,
|
||||
}
|
||||
|
@ -207,7 +210,7 @@ fn internal_event_reader<'a, T>(
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, T: bevy_ecs::Resource> EventReader<'a, T> {
|
||||
impl<'a, T: Component> EventReader<'a, T> {
|
||||
/// Iterates over the events this EventReader has not seen yet. This updates the EventReader's
|
||||
/// event counter, which means subsequent event reads will not include events that happened before now.
|
||||
pub fn iter(&mut self) -> impl DoubleEndedIterator<Item = &T> {
|
||||
|
@ -223,7 +226,7 @@ impl<'a, T: bevy_ecs::Resource> EventReader<'a, T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: bevy_ecs::Resource> Events<T> {
|
||||
impl<T: Component> Events<T> {
|
||||
/// "Sends" an `event` by writing it to the current event buffer. [EventReader]s can then read the event.
|
||||
pub fn send(&mut self, event: T) {
|
||||
let event_id = EventId {
|
||||
|
|
|
@ -22,7 +22,7 @@ pub mod prelude {
|
|||
};
|
||||
}
|
||||
|
||||
use bevy_ecs::StageLabel;
|
||||
use bevy_ecs::schedule::StageLabel;
|
||||
|
||||
/// The names of the default App stages
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)]
|
||||
|
|
|
@ -41,15 +41,15 @@ impl ScheduleRunnerSettings {
|
|||
}
|
||||
}
|
||||
|
||||
/// Configures an App to run its [Schedule](bevy_ecs::Schedule) according to a given [RunMode]
|
||||
/// Configures an App to run its [Schedule](bevy_ecs::schedule::Schedule) according to a given [RunMode]
|
||||
#[derive(Default)]
|
||||
pub struct ScheduleRunnerPlugin {}
|
||||
|
||||
impl Plugin for ScheduleRunnerPlugin {
|
||||
fn build(&self, app: &mut AppBuilder) {
|
||||
let settings = app
|
||||
.resources_mut()
|
||||
.get_or_insert_with(ScheduleRunnerSettings::default)
|
||||
.world_mut()
|
||||
.get_resource_or_insert_with(ScheduleRunnerSettings::default)
|
||||
.to_owned();
|
||||
app.set_runner(move |mut app: App| {
|
||||
let mut app_exit_event_reader = ManualEventReader::<AppExit>::default();
|
||||
|
@ -63,7 +63,9 @@ impl Plugin for ScheduleRunnerPlugin {
|
|||
-> Result<Option<Duration>, AppExit> {
|
||||
let start_time = Instant::now();
|
||||
|
||||
if let Some(app_exit_events) = app.resources.get_mut::<Events<AppExit>>() {
|
||||
if let Some(app_exit_events) =
|
||||
app.world.get_resource_mut::<Events<AppExit>>()
|
||||
{
|
||||
if let Some(exit) = app_exit_event_reader.iter(&app_exit_events).last()
|
||||
{
|
||||
return Err(exit.clone());
|
||||
|
@ -72,7 +74,9 @@ impl Plugin for ScheduleRunnerPlugin {
|
|||
|
||||
app.update();
|
||||
|
||||
if let Some(app_exit_events) = app.resources.get_mut::<Events<AppExit>>() {
|
||||
if let Some(app_exit_events) =
|
||||
app.world.get_resource_mut::<Events<AppExit>>()
|
||||
{
|
||||
if let Some(exit) = app_exit_event_reader.iter(&app_exit_events).last()
|
||||
{
|
||||
return Err(exit.clone());
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::{
|
|||
RefChange, RefChangeChannel, SourceInfo, SourceMeta,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use bevy_ecs::Res;
|
||||
use bevy_ecs::system::Res;
|
||||
use bevy_log::warn;
|
||||
use bevy_tasks::TaskPool;
|
||||
use bevy_utils::{HashMap, Uuid};
|
||||
|
|
|
@ -3,8 +3,10 @@ use crate::{
|
|||
RefChange,
|
||||
};
|
||||
use bevy_app::{prelude::Events, AppBuilder};
|
||||
use bevy_ecs::{FromResources, IntoSystem, ResMut};
|
||||
use bevy_reflect::RegisterTypeBuilder;
|
||||
use bevy_ecs::{
|
||||
system::{IntoSystem, ResMut},
|
||||
world::FromWorld,
|
||||
};
|
||||
use bevy_utils::HashMap;
|
||||
use crossbeam_channel::Sender;
|
||||
use std::fmt::Debug;
|
||||
|
@ -202,7 +204,7 @@ pub trait AddAsset {
|
|||
T: Asset;
|
||||
fn init_asset_loader<T>(&mut self) -> &mut Self
|
||||
where
|
||||
T: AssetLoader + FromResources;
|
||||
T: AssetLoader + FromWorld;
|
||||
fn add_asset_loader<T>(&mut self, loader: T) -> &mut Self
|
||||
where
|
||||
T: AssetLoader;
|
||||
|
@ -214,7 +216,7 @@ impl AddAsset for AppBuilder {
|
|||
T: Asset,
|
||||
{
|
||||
let assets = {
|
||||
let asset_server = self.resources().get::<AssetServer>().unwrap();
|
||||
let asset_server = self.world().get_resource::<AssetServer>().unwrap();
|
||||
asset_server.register_asset_type::<T>()
|
||||
};
|
||||
|
||||
|
@ -233,17 +235,18 @@ impl AddAsset for AppBuilder {
|
|||
|
||||
fn init_asset_loader<T>(&mut self) -> &mut Self
|
||||
where
|
||||
T: AssetLoader + FromResources,
|
||||
T: AssetLoader + FromWorld,
|
||||
{
|
||||
self.add_asset_loader(T::from_resources(self.resources()))
|
||||
let result = T::from_world(self.world_mut());
|
||||
self.add_asset_loader(result)
|
||||
}
|
||||
|
||||
fn add_asset_loader<T>(&mut self, loader: T) -> &mut Self
|
||||
where
|
||||
T: AssetLoader,
|
||||
{
|
||||
self.resources()
|
||||
.get_mut::<AssetServer>()
|
||||
self.world_mut()
|
||||
.get_resource_mut::<AssetServer>()
|
||||
.expect("AssetServer does not exist. Consider adding it as a resource.")
|
||||
.add_loader(loader);
|
||||
self
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{Asset, Assets};
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_diagnostic::{Diagnostic, DiagnosticId, Diagnostics};
|
||||
use bevy_ecs::{IntoSystem, Res, ResMut};
|
||||
use bevy_ecs::system::{IntoSystem, Res, ResMut};
|
||||
|
||||
/// Adds "asset count" diagnostic to an App
|
||||
#[derive(Default)]
|
||||
|
|
|
@ -9,7 +9,8 @@ use crate::{
|
|||
path::{AssetPath, AssetPathId},
|
||||
Asset, Assets,
|
||||
};
|
||||
use bevy_reflect::{Reflect, ReflectComponent, ReflectDeserialize};
|
||||
use bevy_ecs::reflect::ReflectComponent;
|
||||
use bevy_reflect::{Reflect, ReflectDeserialize};
|
||||
use bevy_utils::Uuid;
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{AssetIo, AssetIoError};
|
||||
use anyhow::Result;
|
||||
use bevy_ecs::bevy_utils::BoxedFuture;
|
||||
use bevy_utils::BoxedFuture;
|
||||
use std::{
|
||||
ffi::CString,
|
||||
path::{Path, PathBuf},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{filesystem_watcher::FilesystemWatcher, AssetIo, AssetIoError, AssetServer};
|
||||
use anyhow::Result;
|
||||
use bevy_ecs::{bevy_utils::BoxedFuture, Res};
|
||||
use bevy_utils::HashSet;
|
||||
use bevy_ecs::system::Res;
|
||||
use bevy_utils::{BoxedFuture, HashSet};
|
||||
use crossbeam_channel::TryRecvError;
|
||||
use fs::File;
|
||||
use io::Read;
|
||||
|
|
|
@ -13,7 +13,7 @@ pub use file_asset_io::*;
|
|||
pub use wasm_asset_io::*;
|
||||
|
||||
use anyhow::Result;
|
||||
use bevy_ecs::bevy_utils::BoxedFuture;
|
||||
use bevy_utils::BoxedFuture;
|
||||
use downcast_rs::{impl_downcast, Downcast};
|
||||
use std::{
|
||||
io,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{AssetIo, AssetIoError};
|
||||
use anyhow::Result;
|
||||
use bevy_ecs::bevy_utils::BoxedFuture;
|
||||
use bevy_utils::BoxedFuture;
|
||||
use js_sys::Uint8Array;
|
||||
use std::path::{Path, PathBuf};
|
||||
use wasm_bindgen::JsCast;
|
||||
|
|
|
@ -12,21 +12,24 @@ mod io;
|
|||
mod loader;
|
||||
mod path;
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::{AddAsset, AssetEvent, AssetServer, Assets, Handle, HandleUntyped};
|
||||
}
|
||||
|
||||
pub use asset_server::*;
|
||||
pub use assets::*;
|
||||
pub use bevy_utils::BoxedFuture;
|
||||
pub use handle::*;
|
||||
pub use info::*;
|
||||
pub use io::*;
|
||||
pub use loader::*;
|
||||
pub use path::*;
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::{AddAsset, AssetEvent, AssetServer, Assets, Handle, HandleUntyped};
|
||||
}
|
||||
|
||||
use bevy_app::{prelude::Plugin, AppBuilder};
|
||||
use bevy_ecs::{IntoSystem, StageLabel, SystemStage};
|
||||
use bevy_reflect::RegisterTypeBuilder;
|
||||
use bevy_ecs::{
|
||||
schedule::{StageLabel, SystemStage},
|
||||
system::IntoSystem,
|
||||
};
|
||||
use bevy_tasks::IoTaskPool;
|
||||
|
||||
/// The names of asset stages in an App Schedule
|
||||
|
@ -59,8 +62,8 @@ impl Default for AssetServerSettings {
|
|||
/// delegate to the default `AssetIo` for the platform.
|
||||
pub fn create_platform_default_asset_io(app: &mut AppBuilder) -> Box<dyn AssetIo> {
|
||||
let settings = app
|
||||
.resources_mut()
|
||||
.get_or_insert_with(AssetServerSettings::default);
|
||||
.world_mut()
|
||||
.get_resource_or_insert_with(AssetServerSettings::default);
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
|
||||
let source = FileAssetIo::new(&settings.asset_folder);
|
||||
|
@ -74,10 +77,10 @@ pub fn create_platform_default_asset_io(app: &mut AppBuilder) -> Box<dyn AssetIo
|
|||
|
||||
impl Plugin for AssetPlugin {
|
||||
fn build(&self, app: &mut AppBuilder) {
|
||||
if app.resources().get::<AssetServer>().is_none() {
|
||||
if app.world().get_resource::<AssetServer>().is_none() {
|
||||
let task_pool = app
|
||||
.resources()
|
||||
.get::<IoTaskPool>()
|
||||
.world()
|
||||
.get_resource::<IoTaskPool>()
|
||||
.expect("`IoTaskPool` resource not found.")
|
||||
.0
|
||||
.clone();
|
||||
|
|
|
@ -3,7 +3,10 @@ use crate::{
|
|||
RefChangeChannel,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use bevy_ecs::{Res, ResMut, Resource};
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
system::{Res, ResMut},
|
||||
};
|
||||
use bevy_reflect::{TypeUuid, TypeUuidDynamic};
|
||||
use bevy_utils::{BoxedFuture, HashMap};
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
|
@ -136,7 +139,7 @@ impl<'a> LoadContext<'a> {
|
|||
|
||||
/// The result of loading an asset of type `T`
|
||||
#[derive(Debug)]
|
||||
pub struct AssetResult<T: Resource> {
|
||||
pub struct AssetResult<T: Component> {
|
||||
pub asset: T,
|
||||
pub id: HandleId,
|
||||
pub version: usize,
|
||||
|
@ -144,12 +147,12 @@ pub struct AssetResult<T: Resource> {
|
|||
|
||||
/// A channel to send and receive [AssetResult]s
|
||||
#[derive(Debug)]
|
||||
pub struct AssetLifecycleChannel<T: Resource> {
|
||||
pub struct AssetLifecycleChannel<T: Component> {
|
||||
pub sender: Sender<AssetLifecycleEvent<T>>,
|
||||
pub receiver: Receiver<AssetLifecycleEvent<T>>,
|
||||
}
|
||||
|
||||
pub enum AssetLifecycleEvent<T: Resource> {
|
||||
pub enum AssetLifecycleEvent<T: Component> {
|
||||
Create(AssetResult<T>),
|
||||
Free(HandleId),
|
||||
}
|
||||
|
@ -183,7 +186,7 @@ impl<T: AssetDynamic> AssetLifecycle for AssetLifecycleChannel<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: Resource> Default for AssetLifecycleChannel<T> {
|
||||
impl<T: Component> Default for AssetLifecycleChannel<T> {
|
||||
fn default() -> Self {
|
||||
let (sender, receiver) = crossbeam_channel::unbounded();
|
||||
AssetLifecycleChannel { sender, receiver }
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{Audio, AudioSource, Decodable};
|
||||
use bevy_asset::{Asset, Assets};
|
||||
use bevy_ecs::{Resources, World};
|
||||
use bevy_ecs::world::World;
|
||||
use rodio::{OutputStream, OutputStreamHandle, Sink};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
|
@ -59,16 +59,17 @@ where
|
|||
}
|
||||
|
||||
/// Plays audio currently queued in the [Audio] resource through the [AudioOutput] resource
|
||||
pub fn play_queued_audio_system<P: Asset>(_world: &mut World, resources: &mut Resources)
|
||||
pub fn play_queued_audio_system<P: Asset>(world: &mut World)
|
||||
where
|
||||
P: Decodable,
|
||||
<P as Decodable>::Decoder: rodio::Source + Send + Sync,
|
||||
<<P as Decodable>::Decoder as Iterator>::Item: rodio::Sample + Send + Sync,
|
||||
{
|
||||
let audio_output = resources.get_non_send::<AudioOutput<P>>().unwrap();
|
||||
let mut audio = resources.get_mut::<Audio<P>>().unwrap();
|
||||
let world = world.cell();
|
||||
let audio_output = world.get_non_send::<AudioOutput<P>>().unwrap();
|
||||
let mut audio = world.get_resource_mut::<Audio<P>>().unwrap();
|
||||
|
||||
if let Some(audio_sources) = resources.get::<Assets<P>>() {
|
||||
if let Some(audio_sources) = world.get_resource::<Assets<P>>() {
|
||||
audio_output.try_play_queued(&*audio_sources, &mut *audio);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ pub use audio_source::*;
|
|||
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_asset::AddAsset;
|
||||
use bevy_ecs::IntoExclusiveSystem;
|
||||
use bevy_ecs::system::IntoExclusiveSystem;
|
||||
|
||||
/// Adds support for audio playback to an App
|
||||
#[derive(Default)]
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
use bevy_ecs::prelude::*;
|
||||
use bevy_reflect::{Reflect, ReflectComponent};
|
||||
use bevy_ecs::{
|
||||
entity::Entity,
|
||||
query::Changed,
|
||||
reflect::ReflectComponent,
|
||||
system::{Query, RemovedComponents, ResMut},
|
||||
};
|
||||
use bevy_reflect::Reflect;
|
||||
use bevy_utils::{HashMap, HashSet};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
|
@ -74,15 +79,16 @@ impl EntityLabels {
|
|||
|
||||
pub(crate) fn entity_labels_system(
|
||||
mut entity_labels: ResMut<EntityLabels>,
|
||||
removed_labels: RemovedComponents<Labels>,
|
||||
query: Query<(Entity, &Labels), Changed<Labels>>,
|
||||
) {
|
||||
let entity_labels = entity_labels.deref_mut();
|
||||
|
||||
for entity in query.removed::<Labels>() {
|
||||
if let Some(labels) = entity_labels.entity_labels.get(entity) {
|
||||
for entity in removed_labels.iter() {
|
||||
if let Some(labels) = entity_labels.entity_labels.get(&entity) {
|
||||
for label in labels.iter() {
|
||||
if let Some(entities) = entity_labels.label_entities.get_mut(label) {
|
||||
entities.retain(|e| e != entity);
|
||||
entities.retain(|e| *e != entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -114,17 +120,21 @@ pub(crate) fn entity_labels_system(
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bevy_ecs::Stage;
|
||||
use bevy_ecs::{
|
||||
schedule::{Schedule, Stage, SystemStage},
|
||||
system::IntoSystem,
|
||||
world::World,
|
||||
};
|
||||
|
||||
fn setup() -> (World, Resources, bevy_ecs::Schedule) {
|
||||
let world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(EntityLabels::default());
|
||||
let mut schedule = bevy_ecs::Schedule::default();
|
||||
use super::*;
|
||||
|
||||
fn setup() -> (World, Schedule) {
|
||||
let mut world = World::new();
|
||||
world.insert_resource(EntityLabels::default());
|
||||
let mut schedule = Schedule::default();
|
||||
schedule.add_stage("test", SystemStage::single_threaded());
|
||||
schedule.add_system_to_stage("test", entity_labels_system.system());
|
||||
(world, resources, schedule)
|
||||
(world, schedule)
|
||||
}
|
||||
|
||||
fn holy_cow() -> Labels {
|
||||
|
@ -137,12 +147,12 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn adds_spawned_entity() {
|
||||
let (mut world, mut resources, mut schedule) = setup();
|
||||
let (mut world, mut schedule) = setup();
|
||||
|
||||
let e1 = world.spawn((holy_cow(),));
|
||||
schedule.run(&mut world, &mut resources);
|
||||
let e1 = world.spawn().insert(holy_cow()).id();
|
||||
schedule.run(&mut world);
|
||||
|
||||
let entity_labels = resources.get::<EntityLabels>().unwrap();
|
||||
let entity_labels = world.get_resource::<EntityLabels>().unwrap();
|
||||
assert_eq!(entity_labels.get("holy"), &[e1], "holy");
|
||||
assert_eq!(entity_labels.get("cow"), &[e1], "cow");
|
||||
assert_eq!(entity_labels.get("shalau"), &[], "shalau");
|
||||
|
@ -150,14 +160,14 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn add_labels() {
|
||||
let (mut world, mut resources, mut schedule) = setup();
|
||||
let e1 = world.spawn((holy_cow(),));
|
||||
schedule.run(&mut world, &mut resources);
|
||||
let (mut world, mut schedule) = setup();
|
||||
let e1 = world.spawn().insert(holy_cow()).id();
|
||||
schedule.run(&mut world);
|
||||
|
||||
world.get_mut::<Labels>(e1).unwrap().insert("shalau");
|
||||
schedule.run(&mut world, &mut resources);
|
||||
schedule.run(&mut world);
|
||||
|
||||
let entity_labels = resources.get::<EntityLabels>().unwrap();
|
||||
let entity_labels = world.get_resource::<EntityLabels>().unwrap();
|
||||
assert_eq!(entity_labels.get("holy"), &[e1], "holy");
|
||||
assert_eq!(entity_labels.get("cow"), &[e1], "cow");
|
||||
assert_eq!(entity_labels.get("shalau"), &[e1], "shalau");
|
||||
|
@ -165,14 +175,14 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn remove_labels() {
|
||||
let (mut world, mut resources, mut schedule) = setup();
|
||||
let e1 = world.spawn((holy_cow(),));
|
||||
schedule.run(&mut world, &mut resources);
|
||||
let (mut world, mut schedule) = setup();
|
||||
let e1 = world.spawn().insert(holy_cow()).id();
|
||||
schedule.run(&mut world);
|
||||
|
||||
world.get_mut::<Labels>(e1).unwrap().remove("holy");
|
||||
schedule.run(&mut world, &mut resources);
|
||||
schedule.run(&mut world);
|
||||
|
||||
let entity_labels = resources.get::<EntityLabels>().unwrap();
|
||||
let entity_labels = world.get_resource::<EntityLabels>().unwrap();
|
||||
assert_eq!(entity_labels.get("holy"), &[], "holy");
|
||||
assert_eq!(entity_labels.get("cow"), &[e1], "cow");
|
||||
assert_eq!(entity_labels.get("shalau"), &[], "shalau");
|
||||
|
@ -180,14 +190,14 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn removes_despawned_entity() {
|
||||
let (mut world, mut resources, mut schedule) = setup();
|
||||
let e1 = world.spawn((holy_cow(),));
|
||||
schedule.run(&mut world, &mut resources);
|
||||
let (mut world, mut schedule) = setup();
|
||||
let e1 = world.spawn().insert(holy_cow()).id();
|
||||
schedule.run(&mut world);
|
||||
|
||||
world.despawn(e1).unwrap();
|
||||
schedule.run(&mut world, &mut resources);
|
||||
assert!(world.despawn(e1));
|
||||
schedule.run(&mut world);
|
||||
|
||||
let entity_labels = resources.get::<EntityLabels>().unwrap();
|
||||
let entity_labels = world.get_resource::<EntityLabels>().unwrap();
|
||||
assert_eq!(entity_labels.get("holy"), &[], "holy");
|
||||
assert_eq!(entity_labels.get("cow"), &[], "cow");
|
||||
assert_eq!(entity_labels.get("shalau"), &[], "shalau");
|
||||
|
@ -195,14 +205,14 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn removes_labels_when_component_removed() {
|
||||
let (mut world, mut resources, mut schedule) = setup();
|
||||
let e1 = world.spawn((holy_cow(),));
|
||||
schedule.run(&mut world, &mut resources);
|
||||
let (mut world, mut schedule) = setup();
|
||||
let e1 = world.spawn().insert(holy_cow()).id();
|
||||
schedule.run(&mut world);
|
||||
|
||||
world.remove_one::<Labels>(e1).unwrap();
|
||||
schedule.run(&mut world, &mut resources);
|
||||
world.entity_mut(e1).remove::<Labels>().unwrap();
|
||||
schedule.run(&mut world);
|
||||
|
||||
let entity_labels = resources.get::<EntityLabels>().unwrap();
|
||||
let entity_labels = world.get_resource::<EntityLabels>().unwrap();
|
||||
assert_eq!(entity_labels.get("holy"), &[], "holy");
|
||||
assert_eq!(entity_labels.get("cow"), &[], "cow");
|
||||
assert_eq!(entity_labels.get("shalau"), &[], "shalau");
|
||||
|
@ -210,14 +220,14 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn adds_another_spawned_entity() {
|
||||
let (mut world, mut resources, mut schedule) = setup();
|
||||
let e1 = world.spawn((holy_cow(),));
|
||||
schedule.run(&mut world, &mut resources);
|
||||
let (mut world, mut schedule) = setup();
|
||||
let e1 = world.spawn().insert(holy_cow()).id();
|
||||
schedule.run(&mut world);
|
||||
|
||||
let e2 = world.spawn((holy_shamoni(),));
|
||||
schedule.run(&mut world, &mut resources);
|
||||
let e2 = world.spawn().insert(holy_shamoni()).id();
|
||||
schedule.run(&mut world);
|
||||
|
||||
let entity_labels = resources.get::<EntityLabels>().unwrap();
|
||||
let entity_labels = world.get_resource::<EntityLabels>().unwrap();
|
||||
assert_eq!(entity_labels.get("holy"), &[e1, e2], "holy");
|
||||
assert_eq!(entity_labels.get("cow"), &[e1], "cow");
|
||||
assert_eq!(entity_labels.get("shamoni"), &[e2], "shamoni");
|
||||
|
@ -226,17 +236,17 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn removes_despawned_entity_but_leaves_other() {
|
||||
let (mut world, mut resources, mut schedule) = setup();
|
||||
let e1 = world.spawn((holy_cow(),));
|
||||
schedule.run(&mut world, &mut resources);
|
||||
let (mut world, mut schedule) = setup();
|
||||
let e1 = world.spawn().insert(holy_cow()).id();
|
||||
schedule.run(&mut world);
|
||||
|
||||
let e2 = world.spawn((holy_shamoni(),));
|
||||
schedule.run(&mut world, &mut resources);
|
||||
let e2 = world.spawn().insert(holy_shamoni()).id();
|
||||
schedule.run(&mut world);
|
||||
|
||||
world.despawn(e1).unwrap();
|
||||
schedule.run(&mut world, &mut resources);
|
||||
assert!(world.despawn(e1));
|
||||
schedule.run(&mut world);
|
||||
|
||||
let entity_labels = resources.get::<EntityLabels>().unwrap();
|
||||
let entity_labels = world.get_resource::<EntityLabels>().unwrap();
|
||||
assert_eq!(entity_labels.get("holy"), &[e2], "holy");
|
||||
assert_eq!(entity_labels.get("cow"), &[], "cow");
|
||||
assert_eq!(entity_labels.get("shamoni"), &[e2], "shamoni");
|
||||
|
|
|
@ -5,8 +5,6 @@ mod name;
|
|||
mod task_pool_options;
|
||||
mod time;
|
||||
|
||||
use bevy_ecs::IntoSystem;
|
||||
use bevy_reflect::RegisterTypeBuilder;
|
||||
pub use bytes::*;
|
||||
pub use float_ord::*;
|
||||
pub use label::*;
|
||||
|
@ -19,6 +17,7 @@ pub mod prelude {
|
|||
}
|
||||
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_ecs::{entity::Entity, system::IntoSystem};
|
||||
use std::ops::Range;
|
||||
|
||||
/// Adds core functionality to Apps.
|
||||
|
@ -28,15 +27,16 @@ pub struct CorePlugin;
|
|||
impl Plugin for CorePlugin {
|
||||
fn build(&self, app: &mut AppBuilder) {
|
||||
// Setup the default bevy task pools
|
||||
app.resources_mut()
|
||||
.get_cloned::<DefaultTaskPoolOptions>()
|
||||
app.world_mut()
|
||||
.get_resource::<DefaultTaskPoolOptions>()
|
||||
.cloned()
|
||||
.unwrap_or_else(DefaultTaskPoolOptions::default)
|
||||
.create_default_pools(app.resources_mut());
|
||||
.create_default_pools(app.world_mut());
|
||||
|
||||
app.init_resource::<Time>()
|
||||
.init_resource::<EntityLabels>()
|
||||
.init_resource::<FixedTimesteps>()
|
||||
.register_type::<Option<String>>()
|
||||
.register_type::<Entity>()
|
||||
.register_type::<Name>()
|
||||
.register_type::<Labels>()
|
||||
.register_type::<Range<f32>>()
|
||||
|
@ -44,5 +44,43 @@ impl Plugin for CorePlugin {
|
|||
.add_system_to_stage(CoreStage::First, time_system.system())
|
||||
.add_startup_system_to_stage(StartupStage::PostStartup, entity_labels_system.system())
|
||||
.add_system_to_stage(CoreStage::PostUpdate, entity_labels_system.system());
|
||||
|
||||
register_rust_types(app);
|
||||
register_math_types(app);
|
||||
}
|
||||
}
|
||||
|
||||
fn register_rust_types(app: &mut AppBuilder) {
|
||||
app.register_type::<bool>()
|
||||
.register_type::<u8>()
|
||||
.register_type::<u16>()
|
||||
.register_type::<u32>()
|
||||
.register_type::<u64>()
|
||||
.register_type::<u128>()
|
||||
.register_type::<usize>()
|
||||
.register_type::<i8>()
|
||||
.register_type::<i16>()
|
||||
.register_type::<i32>()
|
||||
.register_type::<i64>()
|
||||
.register_type::<i128>()
|
||||
.register_type::<isize>()
|
||||
.register_type::<f32>()
|
||||
.register_type::<f64>()
|
||||
.register_type::<String>()
|
||||
.register_type::<Option<String>>();
|
||||
}
|
||||
|
||||
fn register_math_types(app: &mut AppBuilder) {
|
||||
app.register_type::<bevy_math::IVec2>()
|
||||
.register_type::<bevy_math::IVec3>()
|
||||
.register_type::<bevy_math::IVec4>()
|
||||
.register_type::<bevy_math::UVec2>()
|
||||
.register_type::<bevy_math::UVec3>()
|
||||
.register_type::<bevy_math::UVec4>()
|
||||
.register_type::<bevy_math::Vec2>()
|
||||
.register_type::<bevy_math::Vec3>()
|
||||
.register_type::<bevy_math::Vec4>()
|
||||
.register_type::<bevy_math::Mat3>()
|
||||
.register_type::<bevy_math::Mat4>()
|
||||
.register_type::<bevy_math::Quat>();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use bevy_reflect::{Reflect, ReflectComponent};
|
||||
use bevy_ecs::reflect::ReflectComponent;
|
||||
use bevy_reflect::Reflect;
|
||||
use bevy_utils::AHasher;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use bevy_ecs::Resources;
|
||||
use bevy_ecs::world::World;
|
||||
use bevy_tasks::{AsyncComputeTaskPool, ComputeTaskPool, IoTaskPool, TaskPoolBuilder};
|
||||
use bevy_utils::tracing::trace;
|
||||
|
||||
|
@ -91,7 +91,7 @@ impl DefaultTaskPoolOptions {
|
|||
}
|
||||
|
||||
/// Inserts the default thread pools into the given resource map based on the configured values
|
||||
pub fn create_default_pools(&self, resources: &mut Resources) {
|
||||
pub fn create_default_pools(&self, world: &mut World) {
|
||||
let total_threads = bevy_math::clamp(
|
||||
bevy_tasks::logical_core_count(),
|
||||
self.min_total_threads,
|
||||
|
@ -101,7 +101,7 @@ impl DefaultTaskPoolOptions {
|
|||
|
||||
let mut remaining_threads = total_threads;
|
||||
|
||||
if !resources.contains::<IoTaskPool>() {
|
||||
if !world.contains_resource::<IoTaskPool>() {
|
||||
// Determine the number of IO threads we will use
|
||||
let io_threads = self
|
||||
.io
|
||||
|
@ -110,7 +110,7 @@ impl DefaultTaskPoolOptions {
|
|||
trace!("IO Threads: {}", io_threads);
|
||||
remaining_threads = remaining_threads.saturating_sub(io_threads);
|
||||
|
||||
resources.insert(IoTaskPool(
|
||||
world.insert_resource(IoTaskPool(
|
||||
TaskPoolBuilder::default()
|
||||
.num_threads(io_threads)
|
||||
.thread_name("IO Task Pool".to_string())
|
||||
|
@ -118,7 +118,7 @@ impl DefaultTaskPoolOptions {
|
|||
));
|
||||
}
|
||||
|
||||
if !resources.contains::<AsyncComputeTaskPool>() {
|
||||
if !world.contains_resource::<AsyncComputeTaskPool>() {
|
||||
// Determine the number of async compute threads we will use
|
||||
let async_compute_threads = self
|
||||
.async_compute
|
||||
|
@ -127,7 +127,7 @@ impl DefaultTaskPoolOptions {
|
|||
trace!("Async Compute Threads: {}", async_compute_threads);
|
||||
remaining_threads = remaining_threads.saturating_sub(async_compute_threads);
|
||||
|
||||
resources.insert(AsyncComputeTaskPool(
|
||||
world.insert_resource(AsyncComputeTaskPool(
|
||||
TaskPoolBuilder::default()
|
||||
.num_threads(async_compute_threads)
|
||||
.thread_name("Async Compute Task Pool".to_string())
|
||||
|
@ -135,7 +135,7 @@ impl DefaultTaskPoolOptions {
|
|||
));
|
||||
}
|
||||
|
||||
if !resources.contains::<ComputeTaskPool>() {
|
||||
if !world.contains_resource::<ComputeTaskPool>() {
|
||||
// Determine the number of compute threads we will use
|
||||
// This is intentionally last so that an end user can specify 1.0 as the percent
|
||||
let compute_threads = self
|
||||
|
@ -143,7 +143,7 @@ impl DefaultTaskPoolOptions {
|
|||
.get_number_of_threads(remaining_threads, total_threads);
|
||||
|
||||
trace!("Compute Threads: {}", compute_threads);
|
||||
resources.insert(ComputeTaskPool(
|
||||
world.insert_resource(ComputeTaskPool(
|
||||
TaskPoolBuilder::default()
|
||||
.num_threads(compute_threads)
|
||||
.thread_name("Compute Task Pool".to_string())
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
use crate::Time;
|
||||
use bevy_ecs::{ArchetypeComponent, ShouldRun, System, SystemId, TypeAccess};
|
||||
use bevy_ecs::{
|
||||
archetype::{Archetype, ArchetypeComponentId},
|
||||
component::ComponentId,
|
||||
query::Access,
|
||||
schedule::ShouldRun,
|
||||
system::{IntoSystem, Local, Res, ResMut, System, SystemId},
|
||||
world::World,
|
||||
};
|
||||
use bevy_utils::HashMap;
|
||||
use std::{any::TypeId, borrow::Cow};
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub struct FixedTimestepState {
|
||||
pub step: f64,
|
||||
|
@ -42,27 +49,15 @@ impl FixedTimesteps {
|
|||
}
|
||||
|
||||
pub struct FixedTimestep {
|
||||
step: f64,
|
||||
accumulator: f64,
|
||||
looping: bool,
|
||||
system_id: SystemId,
|
||||
label: Option<String>, // TODO: consider making this a TypedLabel
|
||||
archetype_access: TypeAccess<ArchetypeComponent>,
|
||||
component_access: TypeAccess<TypeId>,
|
||||
resource_access: TypeAccess<TypeId>,
|
||||
state: State,
|
||||
internal_system: Box<dyn System<In = (), Out = ShouldRun>>,
|
||||
}
|
||||
|
||||
impl Default for FixedTimestep {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
system_id: SystemId::new(),
|
||||
step: 1.0 / 60.0,
|
||||
accumulator: 0.0,
|
||||
looping: false,
|
||||
label: None,
|
||||
component_access: Default::default(),
|
||||
archetype_access: Default::default(),
|
||||
resource_access: Default::default(),
|
||||
state: State::default(),
|
||||
internal_system: Box::new(Self::prepare_system.system()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -70,24 +65,66 @@ impl Default for FixedTimestep {
|
|||
impl FixedTimestep {
|
||||
pub fn step(step: f64) -> Self {
|
||||
Self {
|
||||
step,
|
||||
state: State {
|
||||
step,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn steps_per_second(rate: f64) -> Self {
|
||||
Self {
|
||||
step: 1.0 / rate,
|
||||
state: State {
|
||||
step: 1.0 / rate,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_label(mut self, label: &str) -> Self {
|
||||
self.label = Some(label.to_string());
|
||||
self.state.label = Some(label.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn update(&mut self, time: &Time) -> ShouldRun {
|
||||
fn prepare_system(
|
||||
mut state: Local<State>,
|
||||
time: Res<Time>,
|
||||
mut fixed_timesteps: ResMut<FixedTimesteps>,
|
||||
) -> ShouldRun {
|
||||
let should_run = state.update(&time);
|
||||
if let Some(ref label) = state.label {
|
||||
let res_state = fixed_timesteps.fixed_timesteps.get_mut(label).unwrap();
|
||||
res_state.step = state.step;
|
||||
res_state.accumulator = state.accumulator;
|
||||
}
|
||||
|
||||
should_run
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct State {
|
||||
label: Option<String>, // TODO: consider making this a TypedLabel
|
||||
step: f64,
|
||||
accumulator: f64,
|
||||
looping: bool,
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
step: 1.0 / 60.0,
|
||||
accumulator: 0.0,
|
||||
label: None,
|
||||
looping: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn update(&mut self, time: &Time) -> ShouldRun {
|
||||
if !self.looping {
|
||||
self.accumulator += time.delta_seconds_f64();
|
||||
}
|
||||
|
@ -112,61 +149,49 @@ impl System for FixedTimestep {
|
|||
}
|
||||
|
||||
fn id(&self) -> SystemId {
|
||||
self.system_id
|
||||
self.internal_system.id()
|
||||
}
|
||||
|
||||
fn update_access(&mut self, _world: &bevy_ecs::World) {}
|
||||
|
||||
fn archetype_component_access(&self) -> &TypeAccess<ArchetypeComponent> {
|
||||
&self.archetype_access
|
||||
fn new_archetype(&mut self, archetype: &Archetype) {
|
||||
self.internal_system.new_archetype(archetype);
|
||||
}
|
||||
|
||||
fn component_access(&self) -> &TypeAccess<TypeId> {
|
||||
&self.component_access
|
||||
fn archetype_component_access(&self) -> &Access<ArchetypeComponentId> {
|
||||
self.internal_system.archetype_component_access()
|
||||
}
|
||||
|
||||
fn resource_access(&self) -> &TypeAccess<TypeId> {
|
||||
&self.resource_access
|
||||
fn component_access(&self) -> &Access<ComponentId> {
|
||||
self.internal_system.component_access()
|
||||
}
|
||||
|
||||
fn is_non_send(&self) -> bool {
|
||||
false
|
||||
fn is_send(&self) -> bool {
|
||||
self.internal_system.is_send()
|
||||
}
|
||||
|
||||
unsafe fn run_unsafe(
|
||||
&mut self,
|
||||
_input: Self::In,
|
||||
_world: &bevy_ecs::World,
|
||||
resources: &bevy_ecs::Resources,
|
||||
) -> Option<Self::Out> {
|
||||
let time = resources.get::<Time>().unwrap();
|
||||
let result = self.update(&time);
|
||||
if let Some(ref label) = self.label {
|
||||
let mut fixed_timesteps = resources.get_mut::<FixedTimesteps>().unwrap();
|
||||
let state = fixed_timesteps.fixed_timesteps.get_mut(label).unwrap();
|
||||
state.step = self.step;
|
||||
state.accumulator = self.accumulator;
|
||||
}
|
||||
|
||||
Some(result)
|
||||
unsafe fn run_unsafe(&mut self, _input: Self::In, world: &World) -> Self::Out {
|
||||
// SAFE: this system inherits the internal system's component access and archetype component access,
|
||||
// which means the caller has ensured running the internal system is safe
|
||||
self.internal_system.run_unsafe((), world)
|
||||
}
|
||||
|
||||
fn apply_buffers(
|
||||
&mut self,
|
||||
_world: &mut bevy_ecs::World,
|
||||
_resources: &mut bevy_ecs::Resources,
|
||||
) {
|
||||
fn apply_buffers(&mut self, world: &mut World) {
|
||||
self.internal_system.apply_buffers(world)
|
||||
}
|
||||
|
||||
fn initialize(&mut self, _world: &mut bevy_ecs::World, resources: &mut bevy_ecs::Resources) {
|
||||
self.resource_access.add_read(TypeId::of::<Time>());
|
||||
if let Some(ref label) = self.label {
|
||||
let mut fixed_timesteps = resources.get_mut::<FixedTimesteps>().unwrap();
|
||||
fn initialize(&mut self, world: &mut World) {
|
||||
self.internal_system = Box::new(
|
||||
Self::prepare_system
|
||||
.system()
|
||||
.config(|c| c.0 = Some(self.state.clone())),
|
||||
);
|
||||
self.internal_system.initialize(world);
|
||||
if let Some(ref label) = self.state.label {
|
||||
let mut fixed_timesteps = world.get_resource_mut::<FixedTimesteps>().unwrap();
|
||||
fixed_timesteps.fixed_timesteps.insert(
|
||||
label.clone(),
|
||||
FixedTimestepState {
|
||||
accumulator: 0.0,
|
||||
step: self.step,
|
||||
step: self.state.step,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use bevy_ecs::ResMut;
|
||||
use bevy_ecs::system::ResMut;
|
||||
use bevy_utils::{Duration, Instant};
|
||||
|
||||
/// Tracks elapsed time since the last update and since the App has started
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use bevy_reflect::{Reflect, ReflectComponent};
|
||||
use bevy_ecs::reflect::ReflectComponent;
|
||||
use bevy_reflect::Reflect;
|
||||
use bevy_utils::Duration;
|
||||
|
||||
/// Tracks elapsed time. Enters the finished state once `duration` is reached.
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
use bevy_app::{AppBuilder, Plugin};
|
||||
use bevy_ecs::{
|
||||
system::{IntoExclusiveSystem, IntoSystem, ResMut},
|
||||
world::World,
|
||||
};
|
||||
|
||||
use crate::{Diagnostic, DiagnosticId, Diagnostics};
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_ecs::{IntoExclusiveSystem, IntoSystem, ResMut, Resources, World};
|
||||
|
||||
/// Adds "entity count" diagnostic to an App
|
||||
#[derive(Default)]
|
||||
|
@ -21,9 +25,10 @@ impl EntityCountDiagnosticsPlugin {
|
|||
diagnostics.add(Diagnostic::new(Self::ENTITY_COUNT, "entity_count", 20));
|
||||
}
|
||||
|
||||
pub fn diagnostic_system(world: &mut World, resources: &mut Resources) {
|
||||
if let Some(mut diagnostics) = resources.get_mut::<Diagnostics>() {
|
||||
diagnostics.add_measurement(Self::ENTITY_COUNT, world.entity_count() as f64);
|
||||
pub fn diagnostic_system(world: &mut World) {
|
||||
let entity_count = world.entities().len();
|
||||
if let Some(mut diagnostics) = world.get_resource_mut::<Diagnostics>() {
|
||||
diagnostics.add_measurement(Self::ENTITY_COUNT, entity_count as f64);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{Diagnostic, DiagnosticId, Diagnostics};
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_core::Time;
|
||||
use bevy_ecs::{IntoSystem, Res, ResMut};
|
||||
use bevy_ecs::system::{IntoSystem, Res, ResMut};
|
||||
|
||||
/// Adds "frame time" diagnostic to an App, specifically "frame time", "fps" and "frame count"
|
||||
#[derive(Default)]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use super::{Diagnostic, DiagnosticId, Diagnostics};
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_core::{Time, Timer};
|
||||
use bevy_ecs::{IntoSystem, Res, ResMut};
|
||||
use bevy_ecs::system::{IntoSystem, Res, ResMut};
|
||||
use bevy_log::{debug, info};
|
||||
use bevy_utils::Duration;
|
||||
|
||||
|
|
|
@ -9,24 +9,26 @@ authors = [
|
|||
description = "Bevy Engine's entity component system"
|
||||
homepage = "https://bevyengine.org"
|
||||
repository = "https://github.com/bevyengine/bevy"
|
||||
license = "Apache-2.0"
|
||||
license = "MIT"
|
||||
keywords = ["ecs", "game", "bevy"]
|
||||
categories = ["game-engines", "data-structures"]
|
||||
|
||||
[features]
|
||||
trace = []
|
||||
default = ["bevy_reflect"]
|
||||
|
||||
[dependencies]
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.4.0", optional = true }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.4.0" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.4.0" }
|
||||
bevy_ecs_macros = { path = "macros", version = "0.4.0" }
|
||||
|
||||
async-channel = "1.4"
|
||||
bitflags = "1.2"
|
||||
fixedbitset = "0.3"
|
||||
fxhash = "0.2"
|
||||
async-channel = "1.4.2"
|
||||
rand = "0.8.0"
|
||||
serde = "1.0"
|
||||
thiserror = "1.0"
|
||||
fixedbitset = "0.3.1"
|
||||
bitflags = "1.2.1"
|
||||
downcast-rs = "1.2.0"
|
||||
parking_lot = "0.11.0"
|
||||
lazy_static = { version = "1.4.0" }
|
||||
downcast-rs = "1.2"
|
||||
parking_lot = "0.11"
|
||||
rand = "0.7"
|
||||
serde = "1"
|
||||
|
|
|
@ -1,202 +0,0 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -1,202 +0,0 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -1,7 +0,0 @@
|
|||
# Bevy ECS
|
||||
|
||||
Bevy Engine's entity component system
|
||||
|
||||
## Licensing
|
||||
|
||||
Bevy ECS has its roots in hecs, which is licensed as Apache 2.0. All original hecs code is licensed under Apache 2.0. All added/modified code is dual licensed under MIT and Apache 2.0. Files with an Apache 2.0 license header (with Google LLC as the copyright holder) were from the "original hecs" codebase. Files without the header were created by Bevy contributors.
|
|
@ -2,9 +2,12 @@
|
|||
name = "bevy_ecs_macros"
|
||||
version = "0.4.0"
|
||||
description = "Bevy ECS Macros"
|
||||
authors = ["Benjamin Saunders <ben.e.saunders@gmail.com>"]
|
||||
authors = [
|
||||
"Bevy Contributors <bevyengine@gmail.com>",
|
||||
"Carter Anderson <mcanders1@gmail.com>",
|
||||
]
|
||||
edition = "2018"
|
||||
license = "Apache-2.0"
|
||||
license = "MIT"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
|
|
@ -1,258 +1,172 @@
|
|||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// modified by Bevy contributors
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use find_crate::{Dependencies, Manifest};
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||
use quote::quote;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{
|
||||
parse::ParseStream, parse_macro_input, punctuated::Punctuated, Data, DataStruct, DeriveInput,
|
||||
Error, Field, Fields, GenericParam, Ident, Index, Lifetime, Path, Result, Token,
|
||||
parse::{Parse, ParseStream},
|
||||
parse_macro_input,
|
||||
punctuated::Punctuated,
|
||||
token::Comma,
|
||||
Data, DataStruct, DeriveInput, Field, Fields, GenericParam, Ident, Index, Lifetime, LitInt,
|
||||
Path, Result, Token,
|
||||
};
|
||||
|
||||
/// Implement `Bundle` for a monomorphic struct
|
||||
///
|
||||
/// Using derived `Bundle` impls improves spawn performance and can be convenient when combined with
|
||||
/// other derives like `serde::Deserialize`.
|
||||
#[proc_macro_derive(Bundle)]
|
||||
pub fn derive_bundle(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
match derive_bundle_(input) {
|
||||
Ok(ts) => ts,
|
||||
Err(e) => e.to_compile_error(),
|
||||
}
|
||||
.into()
|
||||
struct AllTuples {
|
||||
macro_ident: Ident,
|
||||
start: usize,
|
||||
end: usize,
|
||||
idents: Vec<Ident>,
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn derive_bundle_(input: DeriveInput) -> Result<TokenStream2> {
|
||||
let ident = input.ident;
|
||||
let data = match input.data {
|
||||
syn::Data::Struct(s) => s,
|
||||
_ => {
|
||||
return Err(Error::new_spanned(
|
||||
ident,
|
||||
"derive(Bundle) does not support enums or unions",
|
||||
))
|
||||
impl Parse for AllTuples {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let macro_ident = input.parse::<Ident>()?;
|
||||
input.parse::<Comma>()?;
|
||||
let start = input.parse::<LitInt>()?.base10_parse()?;
|
||||
input.parse::<Comma>()?;
|
||||
let end = input.parse::<LitInt>()?.base10_parse()?;
|
||||
input.parse::<Comma>()?;
|
||||
let mut idents = Vec::new();
|
||||
idents.push(input.parse::<Ident>()?);
|
||||
while input.parse::<Comma>().is_ok() {
|
||||
idents.push(input.parse::<Ident>()?);
|
||||
}
|
||||
};
|
||||
let (tys, field_members) = struct_fields(&data.fields);
|
||||
|
||||
let ecs_path = bevy_ecs_path();
|
||||
let field_idents = member_as_idents(&field_members);
|
||||
let generics = add_additional_bounds_to_generic_params(&ecs_path, input.generics);
|
||||
|
||||
let dyn_bundle_code =
|
||||
gen_dynamic_bundle_impl(&ecs_path, &ident, &generics, &field_members, &tys);
|
||||
let bundle_code = if tys.is_empty() {
|
||||
gen_unit_struct_bundle_impl(&ecs_path, ident, &generics)
|
||||
} else {
|
||||
gen_bundle_impl(
|
||||
&ecs_path,
|
||||
&ident,
|
||||
&generics,
|
||||
&field_members,
|
||||
&field_idents,
|
||||
&tys,
|
||||
)
|
||||
};
|
||||
let mut ts = dyn_bundle_code;
|
||||
ts.extend(bundle_code);
|
||||
Ok(ts)
|
||||
}
|
||||
|
||||
fn gen_dynamic_bundle_impl(
|
||||
ecs_path: &syn::Path,
|
||||
ident: &syn::Ident,
|
||||
generics: &syn::Generics,
|
||||
field_members: &[syn::Member],
|
||||
tys: &[&syn::Type],
|
||||
) -> TokenStream2 {
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
quote! {
|
||||
impl #impl_generics ::#ecs_path::DynamicBundle for #ident #ty_generics #where_clause {
|
||||
fn with_ids<__hecs__T>(&self, f: impl ::std::ops::FnOnce(&[::std::any::TypeId]) -> __hecs__T) -> __hecs__T {
|
||||
<Self as ::#ecs_path::Bundle>::with_static_ids(f)
|
||||
}
|
||||
|
||||
fn type_info(&self) -> ::std::vec::Vec<::#ecs_path::TypeInfo> {
|
||||
<Self as ::#ecs_path::Bundle>::static_type_info()
|
||||
}
|
||||
|
||||
#[allow(clippy::forget_copy)]
|
||||
unsafe fn put(mut self, mut f: impl ::std::ops::FnMut(*mut u8, ::std::any::TypeId, usize) -> bool) {
|
||||
#(
|
||||
if f((&mut self.#field_members as *mut #tys).cast::<u8>(), ::std::any::TypeId::of::<#tys>(), ::std::mem::size_of::<#tys>()) {
|
||||
#[allow(clippy::forget_copy)]
|
||||
::std::mem::forget(self.#field_members);
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_bundle_impl(
|
||||
ecs_path: &syn::Path,
|
||||
ident: &syn::Ident,
|
||||
generics: &syn::Generics,
|
||||
field_members: &[syn::Member],
|
||||
field_idents: &[Cow<syn::Ident>],
|
||||
tys: &[&syn::Type],
|
||||
) -> TokenStream2 {
|
||||
let num_tys = tys.len();
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
let with_static_ids_inner = quote! {
|
||||
{
|
||||
let mut tys = [#((::std::mem::align_of::<#tys>(), ::std::any::TypeId::of::<#tys>())),*];
|
||||
tys.sort_unstable_by(|x, y| {
|
||||
::std::cmp::Ord::cmp(&x.0, &y.0)
|
||||
.reverse()
|
||||
.then(::std::cmp::Ord::cmp(&x.1, &y.1))
|
||||
});
|
||||
let mut ids = [::std::any::TypeId::of::<()>(); #num_tys];
|
||||
for (id, info) in ::std::iter::Iterator::zip(ids.iter_mut(), tys.iter()) {
|
||||
*id = info.1;
|
||||
}
|
||||
ids
|
||||
}
|
||||
};
|
||||
let with_static_ids_body = if generics.params.is_empty() {
|
||||
quote! {
|
||||
::#ecs_path::lazy_static::lazy_static! {
|
||||
static ref ELEMENTS: [::std::any::TypeId; #num_tys] = {
|
||||
#with_static_ids_inner
|
||||
};
|
||||
}
|
||||
f(&*ELEMENTS)
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
f(&#with_static_ids_inner)
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
impl #impl_generics ::#ecs_path::Bundle for #ident #ty_generics #where_clause {
|
||||
#[allow(non_camel_case_types)]
|
||||
fn with_static_ids<__hecs__T>(f: impl ::std::ops::FnOnce(&[::std::any::TypeId]) -> __hecs__T) -> __hecs__T {
|
||||
#with_static_ids_body
|
||||
}
|
||||
|
||||
fn static_type_info() -> ::std::vec::Vec<::#ecs_path::TypeInfo> {
|
||||
let mut info = ::std::vec![#(::#ecs_path::TypeInfo::of::<#tys>()),*];
|
||||
info.sort_unstable();
|
||||
info
|
||||
}
|
||||
|
||||
unsafe fn get(
|
||||
mut f: impl ::std::ops::FnMut(::std::any::TypeId, usize) -> ::std::option::Option<::std::ptr::NonNull<u8>>,
|
||||
) -> ::std::result::Result<Self, ::#ecs_path::MissingComponent> {
|
||||
#(
|
||||
let #field_idents = f(::std::any::TypeId::of::<#tys>(), ::std::mem::size_of::<#tys>())
|
||||
.ok_or_else(::#ecs_path::MissingComponent::new::<#tys>)?
|
||||
.cast::<#tys>()
|
||||
.as_ptr();
|
||||
)*
|
||||
::std::result::Result::Ok(Self { #( #field_members: #field_idents.read(), )* })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// no reason to generate a static for unit structs
|
||||
fn gen_unit_struct_bundle_impl(
|
||||
ecs_path: &syn::Path,
|
||||
ident: syn::Ident,
|
||||
generics: &syn::Generics,
|
||||
) -> TokenStream2 {
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
quote! {
|
||||
impl #impl_generics ::#ecs_path::Bundle for #ident #ty_generics #where_clause {
|
||||
#[allow(non_camel_case_types)]
|
||||
fn with_static_ids<__hecs__T>(f: impl ::std::ops::FnOnce(&[::std::any::TypeId]) -> __hecs__T) -> __hecs__T { f(&[]) }
|
||||
fn static_type_info() -> ::std::vec::Vec<::#ecs_path::TypeInfo> { ::std::vec::Vec::new() }
|
||||
|
||||
unsafe fn get(
|
||||
f: impl ::std::ops::FnMut(::std::any::TypeId, usize) -> ::std::option::Option<::std::ptr::NonNull<u8>>,
|
||||
) -> Result<Self, ::#ecs_path::MissingComponent> {
|
||||
Ok(Self {/* for some reason this works for all unit struct variations */})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_component_trait_bound(ecs_path: &syn::Path) -> syn::TraitBound {
|
||||
syn::TraitBound {
|
||||
paren_token: None,
|
||||
modifier: syn::TraitBoundModifier::None,
|
||||
lifetimes: None,
|
||||
path: syn::parse_quote!(::#ecs_path::Component),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_additional_bounds_to_generic_params(
|
||||
ecs: &syn::Path,
|
||||
mut generics: syn::Generics,
|
||||
) -> syn::Generics {
|
||||
generics.type_params_mut().for_each(|tp| {
|
||||
tp.bounds
|
||||
.push(syn::TypeParamBound::Trait(make_component_trait_bound(ecs)))
|
||||
});
|
||||
generics
|
||||
}
|
||||
|
||||
fn struct_fields(fields: &syn::Fields) -> (Vec<&syn::Type>, Vec<syn::Member>) {
|
||||
match fields {
|
||||
syn::Fields::Named(ref fields) => fields
|
||||
.named
|
||||
.iter()
|
||||
.map(|f| (&f.ty, syn::Member::Named(f.ident.clone().unwrap())))
|
||||
.unzip(),
|
||||
syn::Fields::Unnamed(ref fields) => fields
|
||||
.unnamed
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, f)| {
|
||||
(
|
||||
&f.ty,
|
||||
syn::Member::Unnamed(syn::Index {
|
||||
index: i as u32,
|
||||
span: Span::call_site(),
|
||||
}),
|
||||
)
|
||||
})
|
||||
.unzip(),
|
||||
syn::Fields::Unit => (Vec::new(), Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
fn member_as_idents(members: &[syn::Member]) -> Vec<Cow<'_, syn::Ident>> {
|
||||
members
|
||||
.iter()
|
||||
.map(|member| match member {
|
||||
syn::Member::Named(ident) => Cow::Borrowed(ident),
|
||||
&syn::Member::Unnamed(syn::Index { index, span }) => {
|
||||
Cow::Owned(syn::Ident::new(&format!("tuple_field_{}", index), span))
|
||||
}
|
||||
Ok(AllTuples {
|
||||
macro_ident,
|
||||
start,
|
||||
end,
|
||||
idents,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn all_tuples(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as AllTuples);
|
||||
let len = input.end - input.start;
|
||||
let mut ident_tuples = Vec::with_capacity(len);
|
||||
for i in input.start..input.end {
|
||||
let idents = input
|
||||
.idents
|
||||
.iter()
|
||||
.map(|ident| format_ident!("{}{}", ident, i));
|
||||
if input.idents.len() < 2 {
|
||||
ident_tuples.push(quote! {
|
||||
#(#idents)*
|
||||
});
|
||||
} else {
|
||||
ident_tuples.push(quote! {
|
||||
(#(#idents),*)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let macro_ident = &input.macro_ident;
|
||||
let invocations = (input.start..input.end).map(|i| {
|
||||
let ident_tuples = &ident_tuples[0..i];
|
||||
quote! {
|
||||
#macro_ident!(#(#ident_tuples),*);
|
||||
}
|
||||
});
|
||||
TokenStream::from(quote! {
|
||||
#(
|
||||
#invocations
|
||||
)*
|
||||
})
|
||||
}
|
||||
|
||||
static BUNDLE_ATTRIBUTE_NAME: &str = "bundle";
|
||||
|
||||
#[proc_macro_derive(Bundle, attributes(bundle))]
|
||||
pub fn derive_bundle(input: TokenStream) -> TokenStream {
|
||||
let ast = parse_macro_input!(input as DeriveInput);
|
||||
let ecs_path = bevy_ecs_path();
|
||||
|
||||
let named_fields = match &ast.data {
|
||||
Data::Struct(DataStruct {
|
||||
fields: Fields::Named(fields),
|
||||
..
|
||||
}) => &fields.named,
|
||||
_ => panic!("Expected a struct with named fields."),
|
||||
};
|
||||
|
||||
let is_bundle = named_fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
field
|
||||
.attrs
|
||||
.iter()
|
||||
.any(|a| *a.path.get_ident().as_ref().unwrap() == BUNDLE_ATTRIBUTE_NAME)
|
||||
})
|
||||
.collect::<Vec<bool>>();
|
||||
let field = named_fields
|
||||
.iter()
|
||||
.map(|field| field.ident.as_ref().unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
let field_type = named_fields
|
||||
.iter()
|
||||
.map(|field| &field.ty)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut field_type_infos = Vec::new();
|
||||
let mut field_get_components = Vec::new();
|
||||
let mut field_from_components = Vec::new();
|
||||
for ((field_type, is_bundle), field) in
|
||||
field_type.iter().zip(is_bundle.iter()).zip(field.iter())
|
||||
{
|
||||
if *is_bundle {
|
||||
field_type_infos.push(quote! {
|
||||
type_info.extend(#field_type::type_info());
|
||||
});
|
||||
field_get_components.push(quote! {
|
||||
self.#field.get_components(&mut func);
|
||||
});
|
||||
field_from_components.push(quote! {
|
||||
#field: #field_type::from_components(&mut func),
|
||||
});
|
||||
} else {
|
||||
field_type_infos.push(quote! {
|
||||
type_info.push(#ecs_path::component::TypeInfo::of::<#field_type>());
|
||||
});
|
||||
field_get_components.push(quote! {
|
||||
func((&mut self.#field as *mut #field_type).cast::<u8>());
|
||||
std::mem::forget(self.#field);
|
||||
});
|
||||
field_from_components.push(quote! {
|
||||
#field: func().cast::<#field_type>().read(),
|
||||
});
|
||||
}
|
||||
}
|
||||
let field_len = field.len();
|
||||
let generics = ast.generics;
|
||||
let (impl_generics, ty_generics, _where_clause) = generics.split_for_impl();
|
||||
let struct_name = &ast.ident;
|
||||
|
||||
TokenStream::from(quote! {
|
||||
/// SAFE: TypeInfo is returned in field-definition-order. [from_components] and [get_components] use field-definition-order
|
||||
unsafe impl #impl_generics #ecs_path::bundle::Bundle for #struct_name#ty_generics {
|
||||
fn type_info() -> Vec<#ecs_path::component::TypeInfo> {
|
||||
let mut type_info = Vec::with_capacity(#field_len);
|
||||
#(#field_type_infos)*
|
||||
type_info
|
||||
}
|
||||
|
||||
#[allow(unused_variables, unused_mut, non_snake_case)]
|
||||
unsafe fn from_components(mut func: impl FnMut() -> *mut u8) -> Self {
|
||||
Self {
|
||||
#(#field_from_components)*
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables, unused_mut, forget_copy, forget_ref)]
|
||||
fn get_components(mut self, mut func: impl FnMut(*mut u8)) {
|
||||
#(#field_get_components)*
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn get_idents(fmt_string: fn(usize) -> String, count: usize) -> Vec<Ident> {
|
||||
|
@ -278,19 +192,19 @@ pub fn impl_query_set(_input: TokenStream) -> TokenStream {
|
|||
let mut query_fn_muts = Vec::new();
|
||||
for i in 0..max_queries {
|
||||
let query = &queries[i];
|
||||
let lifetime = &lifetimes[i];
|
||||
let filter = &filters[i];
|
||||
let lifetime = &lifetimes[i];
|
||||
let fn_name = Ident::new(&format!("q{}", i), Span::call_site());
|
||||
let fn_name_mut = Ident::new(&format!("q{}_mut", i), Span::call_site());
|
||||
let index = Index::from(i);
|
||||
query_fns.push(quote! {
|
||||
pub fn #fn_name(&self) -> &Query<#lifetime, #query, #filter> {
|
||||
&self.value.#index
|
||||
&self.0.#index
|
||||
}
|
||||
});
|
||||
query_fn_muts.push(quote! {
|
||||
pub fn #fn_name_mut(&mut self) -> &mut Query<#lifetime, #query, #filter> {
|
||||
&mut self.value.#index
|
||||
&mut self.0.#index
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -302,26 +216,71 @@ pub fn impl_query_set(_input: TokenStream) -> TokenStream {
|
|||
let query_fn = &query_fns[0..query_count];
|
||||
let query_fn_mut = &query_fn_muts[0..query_count];
|
||||
tokens.extend(TokenStream::from(quote! {
|
||||
impl<#(#lifetime,)* #(#query: WorldQuery,)* #(#filter: QueryFilter,)*> QueryTuple for (#(Query<#lifetime, #query, #filter>,)*) {
|
||||
unsafe fn new(world: &World, component_access: &TypeAccess<ArchetypeComponent>) -> Self {
|
||||
(
|
||||
#(
|
||||
Query::<#query, #filter>::new(
|
||||
std::mem::transmute(world),
|
||||
std::mem::transmute(component_access),
|
||||
),
|
||||
)*
|
||||
)
|
||||
impl<#(#lifetime,)* #(#query: WorldQuery + 'static,)* #(#filter: WorldQuery + 'static,)*> SystemParam for QuerySet<(#(Query<#lifetime, #query, #filter>,)*)>
|
||||
where #(#filter::Fetch: FilterFetch,)*
|
||||
{
|
||||
type Fetch = QuerySetState<(#(QueryState<#query, #filter>,)*)>;
|
||||
}
|
||||
|
||||
// SAFE: Relevant query ComponentId and ArchetypeComponentId access is applied to SystemState. If any QueryState conflicts
|
||||
// with any prior access, a panic will occur.
|
||||
unsafe impl<#(#query: WorldQuery + 'static,)* #(#filter: WorldQuery + 'static,)*> SystemParamState for QuerySetState<(#(QueryState<#query, #filter>,)*)>
|
||||
where #(#filter::Fetch: FilterFetch,)*
|
||||
{
|
||||
type Config = ();
|
||||
fn init(world: &mut World, system_state: &mut SystemState, config: Self::Config) -> Self {
|
||||
#(
|
||||
let mut #query = QueryState::<#query, #filter>::new(world);
|
||||
assert_component_access_compatibility(
|
||||
&system_state.name,
|
||||
std::any::type_name::<#query>(),
|
||||
std::any::type_name::<#filter>(),
|
||||
&system_state.component_access_set,
|
||||
&#query.component_access,
|
||||
world,
|
||||
);
|
||||
)*
|
||||
#(
|
||||
system_state
|
||||
.component_access_set
|
||||
.add(#query.component_access.clone());
|
||||
system_state
|
||||
.archetype_component_access
|
||||
.extend(&#query.archetype_component_access);
|
||||
)*
|
||||
QuerySetState((#(#query,)*))
|
||||
}
|
||||
|
||||
fn get_accesses() -> Vec<QueryAccess> {
|
||||
vec![
|
||||
#(QueryAccess::union(vec![<#query::Fetch as Fetch>::access(), #filter::access()]),)*
|
||||
]
|
||||
fn new_archetype(&mut self, archetype: &Archetype, system_state: &mut SystemState) {
|
||||
let (#(#query,)*) = &mut self.0;
|
||||
#(
|
||||
#query.new_archetype(archetype);
|
||||
system_state
|
||||
.archetype_component_access
|
||||
.extend(&#query.archetype_component_access);
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
impl<#(#lifetime,)* #(#query: WorldQuery,)* #(#filter: QueryFilter,)*> QuerySet<(#(Query<#lifetime, #query, #filter>,)*)> {
|
||||
impl<'a, #(#query: WorldQuery + 'static,)* #(#filter: WorldQuery + 'static,)*> SystemParamFetch<'a> for QuerySetState<(#(QueryState<#query, #filter>,)*)>
|
||||
where #(#filter::Fetch: FilterFetch,)*
|
||||
{
|
||||
type Item = QuerySet<(#(Query<'a, #query, #filter>,)*)>;
|
||||
|
||||
#[inline]
|
||||
unsafe fn get_param(
|
||||
state: &'a mut Self,
|
||||
_system_state: &'a SystemState,
|
||||
world: &'a World,
|
||||
) -> Self::Item {
|
||||
let (#(#query,)*) = &state.0;
|
||||
QuerySet((#(Query::new(world, #query),)*))
|
||||
}
|
||||
}
|
||||
|
||||
impl<#(#lifetime,)* #(#query: WorldQuery,)* #(#filter: WorldQuery,)*> QuerySet<(#(Query<#lifetime, #query, #filter>,)*)>
|
||||
where #(#filter::Fetch: FilterFetch,)*
|
||||
{
|
||||
#(#query_fn)*
|
||||
#(#query_fn_mut)*
|
||||
}
|
||||
|
@ -349,8 +308,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
|
|||
}) => &fields.named,
|
||||
_ => panic!("Expected a struct with named fields."),
|
||||
};
|
||||
|
||||
let ecs_path = bevy_ecs_path();
|
||||
let path = bevy_ecs_path();
|
||||
|
||||
let field_attributes = fields
|
||||
.iter()
|
||||
|
@ -378,16 +336,18 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
|
|||
})
|
||||
.collect::<Vec<(&Field, SystemParamFieldAttributes)>>();
|
||||
let mut fields = Vec::new();
|
||||
let mut field_indices = Vec::new();
|
||||
let mut field_types = Vec::new();
|
||||
let mut ignored_fields = Vec::new();
|
||||
let mut ignored_field_types = Vec::new();
|
||||
for (field, attrs) in field_attributes.iter() {
|
||||
for (i, (field, attrs)) in field_attributes.iter().enumerate() {
|
||||
if attrs.ignore {
|
||||
ignored_fields.push(field.ident.as_ref().unwrap());
|
||||
ignored_field_types.push(&field.ty);
|
||||
} else {
|
||||
fields.push(field.ident.as_ref().unwrap());
|
||||
field_types.push(&field.ty);
|
||||
field_indices.push(Index::from(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -400,19 +360,6 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
|
|||
.filter(|g| matches!(g, GenericParam::Type(_)))
|
||||
.collect();
|
||||
|
||||
let phantoms = lifetimeless_generics
|
||||
.iter()
|
||||
.map(|g| {
|
||||
let g = match g {
|
||||
GenericParam::Type(g) => &g.ident,
|
||||
_ => panic!(),
|
||||
};
|
||||
quote! { ::std::marker::PhantomData::<#g>, }
|
||||
})
|
||||
.fold(quote!(), |old, new| {
|
||||
quote! { #old #new }
|
||||
});
|
||||
|
||||
let mut punctuated_generics = Punctuated::<_, Token![,]>::new();
|
||||
punctuated_generics.extend(lifetimeless_generics.iter());
|
||||
|
||||
|
@ -423,29 +370,43 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
|
|||
}));
|
||||
|
||||
let struct_name = &ast.ident;
|
||||
let fetch_struct_name = Ident::new(&format!("Fetch{}", struct_name), Span::call_site());
|
||||
let fetch_struct_name = Ident::new(&format!("{}State", struct_name), Span::call_site());
|
||||
|
||||
TokenStream::from(quote! {
|
||||
pub struct #fetch_struct_name<#punctuated_generics>(#phantoms);
|
||||
impl #impl_generics #ecs_path::SystemParam for #struct_name#ty_generics #where_clause {
|
||||
type Fetch = #fetch_struct_name <#punctuated_generic_idents>;
|
||||
impl #impl_generics #path::system::SystemParam for #struct_name#ty_generics #where_clause {
|
||||
type Fetch = #fetch_struct_name <(#(<#field_types as SystemParam>::Fetch,)*), #punctuated_generic_idents>;
|
||||
}
|
||||
|
||||
impl #impl_generics #ecs_path::FetchSystemParam<'a> for #fetch_struct_name<#punctuated_generic_idents> {
|
||||
type Item = #struct_name#ty_generics;
|
||||
fn init(system_state: &mut #ecs_path::SystemState, world: &#ecs_path::World, resources: &mut #ecs_path::Resources) {
|
||||
#(<<#field_types as #ecs_path::SystemParam>::Fetch as #ecs_path::FetchSystemParam>::init(system_state, world, resources);)*
|
||||
pub struct #fetch_struct_name<TSystemParamState, #punctuated_generic_idents> {
|
||||
state: TSystemParamState,
|
||||
marker: std::marker::PhantomData<(#punctuated_generic_idents)>
|
||||
}
|
||||
|
||||
unsafe impl<TSystemParamState: #path::system::SystemParamState, #punctuated_generics> #path::system::SystemParamState for #fetch_struct_name<TSystemParamState, #punctuated_generic_idents> {
|
||||
type Config = TSystemParamState::Config;
|
||||
fn init(world: &mut #path::world::World, system_state: &mut #path::system::SystemState, config: Self::Config) -> Self {
|
||||
Self {
|
||||
state: TSystemParamState::init(world, system_state, config),
|
||||
marker: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn new_archetype(&mut self, archetype: &#path::archetype::Archetype, system_state: &mut #path::system::SystemState) {
|
||||
self.state.new_archetype(archetype, system_state)
|
||||
}
|
||||
}
|
||||
|
||||
impl #impl_generics #path::system::SystemParamFetch<'a> for #fetch_struct_name <(#(<#field_types as SystemParam>::Fetch,)*), #punctuated_generic_idents> {
|
||||
type Item = #struct_name#ty_generics;
|
||||
unsafe fn get_param(
|
||||
system_state: &'a #ecs_path::SystemState,
|
||||
world: &'a #ecs_path::World,
|
||||
resources: &'a #ecs_path::Resources,
|
||||
) -> Option<Self::Item> {
|
||||
Some(#struct_name {
|
||||
#(#fields: <<#field_types as #ecs_path::SystemParam>::Fetch as #ecs_path::FetchSystemParam>::get_param(system_state, world, resources)?,)*
|
||||
state: &'a mut Self,
|
||||
system_state: &'a #path::system::SystemState,
|
||||
world: &'a #path::world::World,
|
||||
) -> Self::Item {
|
||||
#struct_name {
|
||||
#(#fields: <<#field_types as SystemParam>::Fetch as #path::system::SystemParamFetch>::get_param(&mut state.state.#field_indices, system_state, world),)*
|
||||
#(#ignored_fields: <#ignored_field_types>::default(),)*
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -474,8 +435,8 @@ fn derive_label(input: DeriveInput, label_type: Ident) -> TokenStream2 {
|
|||
let ecs_path: Path = bevy_ecs_path();
|
||||
|
||||
quote! {
|
||||
impl #ecs_path::#label_type for #ident {
|
||||
fn dyn_clone(&self) -> Box<dyn #ecs_path::#label_type> {
|
||||
impl #ecs_path::schedule::#label_type for #ident {
|
||||
fn dyn_clone(&self) -> Box<dyn #ecs_path::schedule::#label_type> {
|
||||
Box::new(Clone::clone(self))
|
||||
}
|
||||
}
|
||||
|
|
522
crates/bevy_ecs/src/archetype.rs
Normal file
522
crates/bevy_ecs/src/archetype.rs
Normal file
|
@ -0,0 +1,522 @@
|
|||
use crate::{
|
||||
bundle::BundleId,
|
||||
component::{ComponentFlags, ComponentId, StorageType},
|
||||
entity::{Entity, EntityLocation},
|
||||
storage::{Column, SparseArray, SparseSet, SparseSetIndex, TableId},
|
||||
};
|
||||
use std::{borrow::Cow, collections::HashMap, hash::Hash};
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct ArchetypeId(usize);
|
||||
|
||||
impl ArchetypeId {
|
||||
#[inline]
|
||||
pub const fn new(index: usize) -> Self {
|
||||
ArchetypeId(index)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn empty() -> ArchetypeId {
|
||||
ArchetypeId(0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn resource() -> ArchetypeId {
|
||||
ArchetypeId(1)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn index(self) -> usize {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FromBundle {
|
||||
pub archetype_id: ArchetypeId,
|
||||
pub bundle_flags: Vec<ComponentFlags>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Edges {
|
||||
pub add_bundle: SparseArray<BundleId, ArchetypeId>,
|
||||
pub remove_bundle: SparseArray<BundleId, Option<ArchetypeId>>,
|
||||
pub remove_bundle_intersection: SparseArray<BundleId, Option<ArchetypeId>>,
|
||||
pub from_bundle: SparseArray<BundleId, FromBundle>,
|
||||
}
|
||||
|
||||
impl Edges {
|
||||
#[inline]
|
||||
pub fn get_add_bundle(&self, bundle_id: BundleId) -> Option<ArchetypeId> {
|
||||
self.add_bundle.get(bundle_id).cloned()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_add_bundle(&mut self, bundle_id: BundleId, archetype_id: ArchetypeId) {
|
||||
self.add_bundle.insert(bundle_id, archetype_id);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_from_bundle(&self, bundle_id: BundleId) -> Option<&FromBundle> {
|
||||
self.from_bundle.get(bundle_id)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_from_bundle(
|
||||
&mut self,
|
||||
bundle_id: BundleId,
|
||||
archetype_id: ArchetypeId,
|
||||
bundle_flags: Vec<ComponentFlags>,
|
||||
) {
|
||||
self.from_bundle.insert(
|
||||
bundle_id,
|
||||
FromBundle {
|
||||
archetype_id,
|
||||
bundle_flags,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_remove_bundle(&self, bundle_id: BundleId) -> Option<Option<ArchetypeId>> {
|
||||
self.remove_bundle.get(bundle_id).cloned()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_remove_bundle(&mut self, bundle_id: BundleId, archetype_id: Option<ArchetypeId>) {
|
||||
self.remove_bundle.insert(bundle_id, archetype_id);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_remove_bundle_intersection(
|
||||
&self,
|
||||
bundle_id: BundleId,
|
||||
) -> Option<Option<ArchetypeId>> {
|
||||
self.remove_bundle_intersection.get(bundle_id).cloned()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_remove_bundle_intersection(
|
||||
&mut self,
|
||||
bundle_id: BundleId,
|
||||
archetype_id: Option<ArchetypeId>,
|
||||
) {
|
||||
self.remove_bundle_intersection
|
||||
.insert(bundle_id, archetype_id);
|
||||
}
|
||||
}
|
||||
|
||||
struct TableInfo {
|
||||
id: TableId,
|
||||
entity_rows: Vec<usize>,
|
||||
}
|
||||
|
||||
pub(crate) struct ArchetypeSwapRemoveResult {
|
||||
pub swapped_entity: Option<Entity>,
|
||||
pub table_row: usize,
|
||||
}
|
||||
|
||||
pub(crate) struct ArchetypeComponentInfo {
|
||||
pub(crate) storage_type: StorageType,
|
||||
pub(crate) archetype_component_id: ArchetypeComponentId,
|
||||
}
|
||||
|
||||
pub struct Archetype {
|
||||
id: ArchetypeId,
|
||||
entities: Vec<Entity>,
|
||||
edges: Edges,
|
||||
table_info: TableInfo,
|
||||
table_components: Cow<'static, [ComponentId]>,
|
||||
sparse_set_components: Cow<'static, [ComponentId]>,
|
||||
pub(crate) unique_components: SparseSet<ComponentId, Column>,
|
||||
pub(crate) components: SparseSet<ComponentId, ArchetypeComponentInfo>,
|
||||
}
|
||||
|
||||
impl Archetype {
|
||||
pub fn new(
|
||||
id: ArchetypeId,
|
||||
table_id: TableId,
|
||||
table_components: Cow<'static, [ComponentId]>,
|
||||
sparse_set_components: Cow<'static, [ComponentId]>,
|
||||
table_archetype_components: Vec<ArchetypeComponentId>,
|
||||
sparse_set_archetype_components: Vec<ArchetypeComponentId>,
|
||||
) -> Self {
|
||||
let mut components =
|
||||
SparseSet::with_capacity(table_components.len() + sparse_set_components.len());
|
||||
for (component_id, archetype_component_id) in
|
||||
table_components.iter().zip(table_archetype_components)
|
||||
{
|
||||
components.insert(
|
||||
*component_id,
|
||||
ArchetypeComponentInfo {
|
||||
storage_type: StorageType::Table,
|
||||
archetype_component_id,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
for (component_id, archetype_component_id) in sparse_set_components
|
||||
.iter()
|
||||
.zip(sparse_set_archetype_components)
|
||||
{
|
||||
components.insert(
|
||||
*component_id,
|
||||
ArchetypeComponentInfo {
|
||||
storage_type: StorageType::SparseSet,
|
||||
archetype_component_id,
|
||||
},
|
||||
);
|
||||
}
|
||||
Self {
|
||||
id,
|
||||
table_info: TableInfo {
|
||||
id: table_id,
|
||||
entity_rows: Default::default(),
|
||||
},
|
||||
components,
|
||||
table_components,
|
||||
sparse_set_components,
|
||||
unique_components: SparseSet::new(),
|
||||
entities: Default::default(),
|
||||
edges: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> ArchetypeId {
|
||||
self.id
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn table_id(&self) -> TableId {
|
||||
self.table_info.id
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn entities(&self) -> &[Entity] {
|
||||
&self.entities
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn entity_table_rows(&self) -> &[usize] {
|
||||
&self.table_info.entity_rows
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn table_components(&self) -> &[ComponentId] {
|
||||
&self.table_components
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sparse_set_components(&self) -> &[ComponentId] {
|
||||
&self.sparse_set_components
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn unique_components(&self) -> &SparseSet<ComponentId, Column> {
|
||||
&self.unique_components
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn unique_components_mut(&mut self) -> &mut SparseSet<ComponentId, Column> {
|
||||
&mut self.unique_components
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn components(&self) -> impl Iterator<Item = ComponentId> + '_ {
|
||||
self.components.indices()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn edges(&self) -> &Edges {
|
||||
&self.edges
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn edges_mut(&mut self) -> &mut Edges {
|
||||
&mut self.edges
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn entity_table_row(&self, index: usize) -> usize {
|
||||
self.table_info.entity_rows[index]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_entity_table_row(&mut self, index: usize, table_row: usize) {
|
||||
self.table_info.entity_rows[index] = table_row;
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// valid component values must be immediately written to the relevant storages
|
||||
/// `table_row` must be valid
|
||||
pub unsafe fn allocate(&mut self, entity: Entity, table_row: usize) -> EntityLocation {
|
||||
self.entities.push(entity);
|
||||
self.table_info.entity_rows.push(table_row);
|
||||
|
||||
EntityLocation {
|
||||
archetype_id: self.id,
|
||||
index: self.entities.len() - 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reserve(&mut self, additional: usize) {
|
||||
self.entities.reserve(additional);
|
||||
self.table_info.entity_rows.reserve(additional);
|
||||
}
|
||||
|
||||
/// Removes the entity at `index` by swapping it out. Returns the table row the entity is stored in.
|
||||
pub(crate) fn swap_remove(&mut self, index: usize) -> ArchetypeSwapRemoveResult {
|
||||
let is_last = index == self.entities.len() - 1;
|
||||
self.entities.swap_remove(index);
|
||||
ArchetypeSwapRemoveResult {
|
||||
swapped_entity: if is_last {
|
||||
None
|
||||
} else {
|
||||
Some(self.entities[index])
|
||||
},
|
||||
table_row: self.table_info.entity_rows.swap_remove(index),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.entities.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.entities.is_empty()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn contains(&self, component_id: ComponentId) -> bool {
|
||||
self.components.contains(component_id)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_storage_type(&self, component_id: ComponentId) -> Option<StorageType> {
|
||||
self.components
|
||||
.get(component_id)
|
||||
.map(|info| info.storage_type)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_archetype_component_id(
|
||||
&self,
|
||||
component_id: ComponentId,
|
||||
) -> Option<ArchetypeComponentId> {
|
||||
self.components
|
||||
.get(component_id)
|
||||
.map(|info| info.archetype_component_id)
|
||||
}
|
||||
}
|
||||
|
||||
/// A generational id that changes every time the set of archetypes changes
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct ArchetypeGeneration(usize);
|
||||
|
||||
impl ArchetypeGeneration {
|
||||
#[inline]
|
||||
pub fn new(generation: usize) -> Self {
|
||||
ArchetypeGeneration(generation)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn value(self) -> usize {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Hash, PartialEq, Eq)]
|
||||
pub struct ArchetypeIdentity {
|
||||
table_components: Cow<'static, [ComponentId]>,
|
||||
sparse_set_components: Cow<'static, [ComponentId]>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct ArchetypeComponentId(usize);
|
||||
|
||||
impl ArchetypeComponentId {
|
||||
#[inline]
|
||||
pub const fn new(index: usize) -> Self {
|
||||
Self(index)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn index(self) -> usize {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl SparseSetIndex for ArchetypeComponentId {
|
||||
#[inline]
|
||||
fn sparse_set_index(&self) -> usize {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn get_sparse_set_index(value: usize) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Archetypes {
|
||||
pub(crate) archetypes: Vec<Archetype>,
|
||||
pub(crate) archetype_component_count: usize,
|
||||
archetype_ids: HashMap<ArchetypeIdentity, ArchetypeId>,
|
||||
}
|
||||
|
||||
impl Default for Archetypes {
|
||||
fn default() -> Self {
|
||||
let mut archetypes = Archetypes {
|
||||
archetypes: Vec::new(),
|
||||
archetype_ids: Default::default(),
|
||||
archetype_component_count: 0,
|
||||
};
|
||||
archetypes.get_id_or_insert(TableId::empty(), Vec::new(), Vec::new());
|
||||
|
||||
// adds the resource archetype. it is "special" in that it is inaccessible via a "hash", which prevents entities from
|
||||
// being added to it
|
||||
archetypes.archetypes.push(Archetype::new(
|
||||
ArchetypeId::resource(),
|
||||
TableId::empty(),
|
||||
Cow::Owned(Vec::new()),
|
||||
Cow::Owned(Vec::new()),
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
));
|
||||
archetypes
|
||||
}
|
||||
}
|
||||
|
||||
impl Archetypes {
|
||||
#[inline]
|
||||
pub fn generation(&self) -> ArchetypeGeneration {
|
||||
ArchetypeGeneration(self.archetypes.len())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.archetypes.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn empty(&self) -> &Archetype {
|
||||
// SAFE: empty archetype always exists
|
||||
unsafe { self.archetypes.get_unchecked(ArchetypeId::empty().index()) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn empty_mut(&mut self) -> &mut Archetype {
|
||||
// SAFE: empty archetype always exists
|
||||
unsafe {
|
||||
self.archetypes
|
||||
.get_unchecked_mut(ArchetypeId::empty().index())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn resource(&self) -> &Archetype {
|
||||
// SAFE: resource archetype always exists
|
||||
unsafe {
|
||||
self.archetypes
|
||||
.get_unchecked(ArchetypeId::resource().index())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn resource_mut(&mut self) -> &mut Archetype {
|
||||
// SAFE: resource archetype always exists
|
||||
unsafe {
|
||||
self.archetypes
|
||||
.get_unchecked_mut(ArchetypeId::resource().index())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.archetypes.is_empty()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get(&self, id: ArchetypeId) -> Option<&Archetype> {
|
||||
self.archetypes.get(id.index())
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `id` must be valid
|
||||
#[inline]
|
||||
pub unsafe fn get_unchecked(&self, id: ArchetypeId) -> &Archetype {
|
||||
debug_assert!(id.index() < self.archetypes.len());
|
||||
self.archetypes.get_unchecked(id.index())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_mut(&mut self, id: ArchetypeId) -> Option<&mut Archetype> {
|
||||
self.archetypes.get_mut(id.index())
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `id` must be valid
|
||||
#[inline]
|
||||
pub unsafe fn get_unchecked_mut(&mut self, id: ArchetypeId) -> &mut Archetype {
|
||||
debug_assert!(id.index() < self.archetypes.len());
|
||||
self.archetypes.get_unchecked_mut(id.index())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn iter(&self) -> impl Iterator<Item = &Archetype> {
|
||||
self.archetypes.iter()
|
||||
}
|
||||
|
||||
/// Gets the archetype id matching the given inputs or inserts a new one if it doesn't exist.
|
||||
/// `table_components` and `sparse_set_components` must be sorted
|
||||
/// # Safety
|
||||
/// TableId must exist in tables
|
||||
pub(crate) fn get_id_or_insert(
|
||||
&mut self,
|
||||
table_id: TableId,
|
||||
table_components: Vec<ComponentId>,
|
||||
sparse_set_components: Vec<ComponentId>,
|
||||
) -> ArchetypeId {
|
||||
let table_components = Cow::from(table_components);
|
||||
let sparse_set_components = Cow::from(sparse_set_components);
|
||||
let archetype_identity = ArchetypeIdentity {
|
||||
sparse_set_components: sparse_set_components.clone(),
|
||||
table_components: table_components.clone(),
|
||||
};
|
||||
|
||||
let archetypes = &mut self.archetypes;
|
||||
let archetype_component_count = &mut self.archetype_component_count;
|
||||
let mut next_archetype_component_id = move || {
|
||||
let id = ArchetypeComponentId(*archetype_component_count);
|
||||
*archetype_component_count += 1;
|
||||
id
|
||||
};
|
||||
*self
|
||||
.archetype_ids
|
||||
.entry(archetype_identity)
|
||||
.or_insert_with(move || {
|
||||
let id = ArchetypeId(archetypes.len());
|
||||
let table_archetype_components = (0..table_components.len())
|
||||
.map(|_| next_archetype_component_id())
|
||||
.collect();
|
||||
let sparse_set_archetype_components = (0..sparse_set_components.len())
|
||||
.map(|_| next_archetype_component_id())
|
||||
.collect();
|
||||
archetypes.push(Archetype::new(
|
||||
id,
|
||||
table_id,
|
||||
table_components,
|
||||
sparse_set_components,
|
||||
table_archetype_components,
|
||||
sparse_set_archetype_components,
|
||||
));
|
||||
id
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn archetype_components_len(&self) -> usize {
|
||||
self.archetype_component_count
|
||||
}
|
||||
}
|
207
crates/bevy_ecs/src/bundle.rs
Normal file
207
crates/bevy_ecs/src/bundle.rs
Normal file
|
@ -0,0 +1,207 @@
|
|||
pub use bevy_ecs_macros::Bundle;
|
||||
|
||||
use crate::{
|
||||
component::{Component, ComponentFlags, ComponentId, Components, StorageType, TypeInfo},
|
||||
entity::Entity,
|
||||
storage::{SparseSetIndex, SparseSets, Table},
|
||||
};
|
||||
use bevy_ecs_macros::all_tuples;
|
||||
use std::{any::TypeId, collections::HashMap};
|
||||
|
||||
/// An ordered collection of components
|
||||
///
|
||||
/// See [Bundle]
|
||||
/// # Safety
|
||||
/// [Bundle::type_info] must return the TypeInfo for each component type in the bundle, in the _exact_
|
||||
/// order that [Bundle::get_components] is called.
|
||||
/// [Bundle::from_components] must call `func` exactly once for each [TypeInfo] returned by [Bundle::type_info]
|
||||
pub unsafe trait Bundle: Send + Sync + 'static {
|
||||
/// Gets this [Bundle]'s components type info, in the order of this bundle's Components
|
||||
fn type_info() -> Vec<TypeInfo>;
|
||||
|
||||
/// Calls `func`, which should return data for each component in the bundle, in the order of this bundle's Components
|
||||
/// # Safety
|
||||
/// Caller must return data for each component in the bundle, in the order of this bundle's Components
|
||||
unsafe fn from_components(func: impl FnMut() -> *mut u8) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Calls `func` on each value, in the order of this bundle's Components. This will "mem::forget" the bundle
|
||||
/// fields, so callers are responsible for dropping the fields if that is desirable.
|
||||
fn get_components(self, func: impl FnMut(*mut u8));
|
||||
}
|
||||
|
||||
macro_rules! tuple_impl {
|
||||
($($name: ident),*) => {
|
||||
/// SAFE: TypeInfo is returned in tuple-order. [Bundle::from_components] and [Bundle::get_components] use tuple-order
|
||||
unsafe impl<$($name: Component),*> Bundle for ($($name,)*) {
|
||||
fn type_info() -> Vec<TypeInfo> {
|
||||
vec![$(TypeInfo::of::<$name>()),*]
|
||||
}
|
||||
|
||||
#[allow(unused_variables, unused_mut)]
|
||||
unsafe fn from_components(mut func: impl FnMut() -> *mut u8) -> Self {
|
||||
#[allow(non_snake_case)]
|
||||
let ($(mut $name,)*) = (
|
||||
$(func().cast::<$name>(),)*
|
||||
);
|
||||
($($name.read(),)*)
|
||||
}
|
||||
|
||||
#[allow(unused_variables, unused_mut)]
|
||||
fn get_components(self, mut func: impl FnMut(*mut u8)) {
|
||||
#[allow(non_snake_case)]
|
||||
let ($(mut $name,)*) = self;
|
||||
$(
|
||||
func((&mut $name as *mut $name).cast::<u8>());
|
||||
std::mem::forget($name);
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
all_tuples!(tuple_impl, 0, 15, C);
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BundleId(usize);
|
||||
|
||||
impl BundleId {
|
||||
#[inline]
|
||||
pub fn index(self) -> usize {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl SparseSetIndex for BundleId {
|
||||
#[inline]
|
||||
fn sparse_set_index(&self) -> usize {
|
||||
self.index()
|
||||
}
|
||||
|
||||
fn get_sparse_set_index(value: usize) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BundleInfo {
|
||||
pub(crate) id: BundleId,
|
||||
pub(crate) component_ids: Vec<ComponentId>,
|
||||
pub(crate) storage_types: Vec<StorageType>,
|
||||
}
|
||||
|
||||
impl BundleInfo {
|
||||
/// # Safety
|
||||
/// table row must exist, entity must be valid
|
||||
#[inline]
|
||||
pub(crate) unsafe fn write_components<T: Bundle>(
|
||||
&self,
|
||||
sparse_sets: &mut SparseSets,
|
||||
entity: Entity,
|
||||
table: &Table,
|
||||
table_row: usize,
|
||||
bundle_flags: &[ComponentFlags],
|
||||
bundle: T,
|
||||
) {
|
||||
// NOTE: get_components calls this closure on each component in "bundle order". bundle_info.component_ids are also in "bundle order"
|
||||
let mut bundle_component = 0;
|
||||
bundle.get_components(|component_ptr| {
|
||||
// SAFE: component_id was initialized by get_dynamic_bundle_info
|
||||
let component_id = *self.component_ids.get_unchecked(bundle_component);
|
||||
let flags = *bundle_flags.get_unchecked(bundle_component);
|
||||
match self.storage_types[bundle_component] {
|
||||
StorageType::Table => {
|
||||
let column = table.get_column(component_id).unwrap();
|
||||
column.set_unchecked(table_row, component_ptr);
|
||||
column.get_flags_unchecked_mut(table_row).insert(flags);
|
||||
}
|
||||
StorageType::SparseSet => {
|
||||
let sparse_set = sparse_sets.get_mut(component_id).unwrap();
|
||||
sparse_set.insert(entity, component_ptr, flags);
|
||||
}
|
||||
}
|
||||
bundle_component += 1;
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> BundleId {
|
||||
self.id
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn components(&self) -> &[ComponentId] {
|
||||
&self.component_ids
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn storage_types(&self) -> &[StorageType] {
|
||||
&self.storage_types
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Bundles {
|
||||
bundle_infos: Vec<BundleInfo>,
|
||||
bundle_ids: HashMap<TypeId, BundleId>,
|
||||
}
|
||||
|
||||
impl Bundles {
|
||||
#[inline]
|
||||
pub fn get(&self, bundle_id: BundleId) -> Option<&BundleInfo> {
|
||||
self.bundle_infos.get(bundle_id.index())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_id(&self, type_id: TypeId) -> Option<BundleId> {
|
||||
self.bundle_ids.get(&type_id).cloned()
|
||||
}
|
||||
|
||||
pub(crate) fn init_info<'a, T: Bundle>(
|
||||
&'a mut self,
|
||||
components: &mut Components,
|
||||
) -> &'a BundleInfo {
|
||||
let bundle_infos = &mut self.bundle_infos;
|
||||
let id = self.bundle_ids.entry(TypeId::of::<T>()).or_insert_with(|| {
|
||||
let type_info = T::type_info();
|
||||
let id = BundleId(bundle_infos.len());
|
||||
let bundle_info =
|
||||
initialize_bundle(std::any::type_name::<T>(), &type_info, id, components);
|
||||
bundle_infos.push(bundle_info);
|
||||
id
|
||||
});
|
||||
// SAFE: index either exists, or was initialized
|
||||
unsafe { self.bundle_infos.get_unchecked(id.0) }
|
||||
}
|
||||
}
|
||||
|
||||
fn initialize_bundle(
|
||||
bundle_type_name: &'static str,
|
||||
type_info: &[TypeInfo],
|
||||
id: BundleId,
|
||||
components: &mut Components,
|
||||
) -> BundleInfo {
|
||||
let mut component_ids = Vec::new();
|
||||
let mut storage_types = Vec::new();
|
||||
|
||||
for type_info in type_info {
|
||||
let component_id = components.get_or_insert_with(type_info.type_id(), || type_info.clone());
|
||||
// SAFE: get_with_type_info ensures info was created
|
||||
let info = unsafe { components.get_info_unchecked(component_id) };
|
||||
component_ids.push(component_id);
|
||||
storage_types.push(info.storage_type());
|
||||
}
|
||||
|
||||
let mut deduped = component_ids.clone();
|
||||
deduped.sort();
|
||||
deduped.dedup();
|
||||
if deduped.len() != component_ids.len() {
|
||||
panic!("Bundle {} has duplicate components", bundle_type_name);
|
||||
}
|
||||
|
||||
BundleInfo {
|
||||
id,
|
||||
component_ids,
|
||||
storage_types,
|
||||
}
|
||||
}
|
296
crates/bevy_ecs/src/component/mod.rs
Normal file
296
crates/bevy_ecs/src/component/mod.rs
Normal file
|
@ -0,0 +1,296 @@
|
|||
mod type_info;
|
||||
|
||||
pub use type_info::*;
|
||||
|
||||
use crate::storage::SparseSetIndex;
|
||||
use bitflags::bitflags;
|
||||
use std::{
|
||||
alloc::Layout,
|
||||
any::{Any, TypeId},
|
||||
collections::hash_map::Entry,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
pub trait Component: Send + Sync + 'static {}
|
||||
impl<T: Send + Sync + 'static> Component for T {}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum StorageType {
|
||||
Table,
|
||||
SparseSet,
|
||||
}
|
||||
|
||||
impl Default for StorageType {
|
||||
fn default() -> Self {
|
||||
StorageType::Table
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ComponentInfo {
|
||||
name: String,
|
||||
id: ComponentId,
|
||||
type_id: Option<TypeId>,
|
||||
// SAFETY: This must remain private. It must only be set to "true" if this component is actually Send + Sync
|
||||
is_send_and_sync: bool,
|
||||
layout: Layout,
|
||||
drop: unsafe fn(*mut u8),
|
||||
storage_type: StorageType,
|
||||
}
|
||||
|
||||
impl ComponentInfo {
|
||||
#[inline]
|
||||
pub fn id(&self) -> ComponentId {
|
||||
self.id
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn type_id(&self) -> Option<TypeId> {
|
||||
self.type_id
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn layout(&self) -> Layout {
|
||||
self.layout
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn drop(&self) -> unsafe fn(*mut u8) {
|
||||
self.drop
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn storage_type(&self) -> StorageType {
|
||||
self.storage_type
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_send_and_sync(&self) -> bool {
|
||||
self.is_send_and_sync
|
||||
}
|
||||
|
||||
fn new(id: ComponentId, descriptor: ComponentDescriptor) -> Self {
|
||||
ComponentInfo {
|
||||
id,
|
||||
name: descriptor.name,
|
||||
storage_type: descriptor.storage_type,
|
||||
type_id: descriptor.type_id,
|
||||
is_send_and_sync: descriptor.is_send_and_sync,
|
||||
drop: descriptor.drop,
|
||||
layout: descriptor.layout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub struct ComponentId(usize);
|
||||
|
||||
impl ComponentId {
|
||||
#[inline]
|
||||
pub const fn new(index: usize) -> ComponentId {
|
||||
ComponentId(index)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn index(self) -> usize {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl SparseSetIndex for ComponentId {
|
||||
#[inline]
|
||||
fn sparse_set_index(&self) -> usize {
|
||||
self.index()
|
||||
}
|
||||
|
||||
fn get_sparse_set_index(value: usize) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ComponentDescriptor {
|
||||
name: String,
|
||||
storage_type: StorageType,
|
||||
// SAFETY: This must remain private. It must only be set to "true" if this component is actually Send + Sync
|
||||
is_send_and_sync: bool,
|
||||
type_id: Option<TypeId>,
|
||||
layout: Layout,
|
||||
drop: unsafe fn(*mut u8),
|
||||
}
|
||||
|
||||
impl ComponentDescriptor {
|
||||
pub fn new<T: Component>(storage_type: StorageType) -> Self {
|
||||
Self {
|
||||
name: std::any::type_name::<T>().to_string(),
|
||||
storage_type,
|
||||
is_send_and_sync: true,
|
||||
type_id: Some(TypeId::of::<T>()),
|
||||
layout: Layout::new::<T>(),
|
||||
drop: TypeInfo::drop_ptr::<T>,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn storage_type(&self) -> StorageType {
|
||||
self.storage_type
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn type_id(&self) -> Option<TypeId> {
|
||||
self.type_id
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TypeInfo> for ComponentDescriptor {
|
||||
fn from(type_info: TypeInfo) -> Self {
|
||||
Self {
|
||||
name: type_info.type_name().to_string(),
|
||||
storage_type: StorageType::default(),
|
||||
is_send_and_sync: type_info.is_send_and_sync(),
|
||||
type_id: Some(type_info.type_id()),
|
||||
drop: type_info.drop(),
|
||||
layout: type_info.layout(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Components {
|
||||
components: Vec<ComponentInfo>,
|
||||
indices: std::collections::HashMap<TypeId, usize, fxhash::FxBuildHasher>,
|
||||
resource_indices: std::collections::HashMap<TypeId, usize, fxhash::FxBuildHasher>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ComponentsError {
|
||||
#[error("A component of type {0:?} already exists")]
|
||||
ComponentAlreadyExists(TypeId),
|
||||
}
|
||||
|
||||
impl Components {
|
||||
pub(crate) fn add(
|
||||
&mut self,
|
||||
descriptor: ComponentDescriptor,
|
||||
) -> Result<ComponentId, ComponentsError> {
|
||||
let index = self.components.len();
|
||||
if let Some(type_id) = descriptor.type_id {
|
||||
let index_entry = self.indices.entry(type_id);
|
||||
if let Entry::Occupied(_) = index_entry {
|
||||
return Err(ComponentsError::ComponentAlreadyExists(type_id));
|
||||
}
|
||||
self.indices.insert(type_id, index);
|
||||
}
|
||||
self.components
|
||||
.push(ComponentInfo::new(ComponentId(index), descriptor));
|
||||
|
||||
Ok(ComponentId(index))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_or_insert_id<T: Component>(&mut self) -> ComponentId {
|
||||
self.get_or_insert_with(TypeId::of::<T>(), TypeInfo::of::<T>)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_or_insert_info<T: Component>(&mut self) -> &ComponentInfo {
|
||||
let id = self.get_or_insert_id::<T>();
|
||||
// SAFE: component_info with the given `id` initialized above
|
||||
unsafe { self.get_info_unchecked(id) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.components.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.components.len() == 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_info(&self, id: ComponentId) -> Option<&ComponentInfo> {
|
||||
self.components.get(id.0)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `id` must be a valid [ComponentId]
|
||||
#[inline]
|
||||
pub unsafe fn get_info_unchecked(&self, id: ComponentId) -> &ComponentInfo {
|
||||
debug_assert!(id.index() < self.components.len());
|
||||
self.components.get_unchecked(id.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_id(&self, type_id: TypeId) -> Option<ComponentId> {
|
||||
self.indices.get(&type_id).map(|index| ComponentId(*index))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_resource_id(&self, type_id: TypeId) -> Option<ComponentId> {
|
||||
self.resource_indices
|
||||
.get(&type_id)
|
||||
.map(|index| ComponentId(*index))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_or_insert_resource_id<T: Component>(&mut self) -> ComponentId {
|
||||
self.get_or_insert_resource_with(TypeId::of::<T>(), TypeInfo::of::<T>)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_or_insert_non_send_resource_id<T: Any>(&mut self) -> ComponentId {
|
||||
self.get_or_insert_resource_with(TypeId::of::<T>(), TypeInfo::of_non_send_and_sync::<T>)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_or_insert_resource_with(
|
||||
&mut self,
|
||||
type_id: TypeId,
|
||||
func: impl FnOnce() -> TypeInfo,
|
||||
) -> ComponentId {
|
||||
let components = &mut self.components;
|
||||
let index = self.resource_indices.entry(type_id).or_insert_with(|| {
|
||||
let type_info = func();
|
||||
let index = components.len();
|
||||
components.push(ComponentInfo::new(ComponentId(index), type_info.into()));
|
||||
index
|
||||
});
|
||||
|
||||
ComponentId(*index)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn get_or_insert_with(
|
||||
&mut self,
|
||||
type_id: TypeId,
|
||||
func: impl FnOnce() -> TypeInfo,
|
||||
) -> ComponentId {
|
||||
let components = &mut self.components;
|
||||
let index = self.indices.entry(type_id).or_insert_with(|| {
|
||||
let type_info = func();
|
||||
let index = components.len();
|
||||
components.push(ComponentInfo::new(ComponentId(index), type_info.into()));
|
||||
index
|
||||
});
|
||||
|
||||
ComponentId(*index)
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct ComponentFlags: u8 {
|
||||
const ADDED = 1;
|
||||
const MUTATED = 2;
|
||||
}
|
||||
}
|
63
crates/bevy_ecs/src/component/type_info.rs
Normal file
63
crates/bevy_ecs/src/component/type_info.rs
Normal file
|
@ -0,0 +1,63 @@
|
|||
use std::{alloc::Layout, any::TypeId};
|
||||
|
||||
/// Metadata required to store a component
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct TypeInfo {
|
||||
type_id: TypeId,
|
||||
layout: Layout,
|
||||
drop: unsafe fn(*mut u8),
|
||||
type_name: &'static str,
|
||||
is_send_and_sync: bool,
|
||||
}
|
||||
|
||||
impl TypeInfo {
|
||||
/// Metadata for `T`
|
||||
pub fn of<T: Send + Sync + 'static>() -> Self {
|
||||
Self {
|
||||
type_id: TypeId::of::<T>(),
|
||||
layout: Layout::new::<T>(),
|
||||
is_send_and_sync: true,
|
||||
drop: Self::drop_ptr::<T>,
|
||||
type_name: core::any::type_name::<T>(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn of_non_send_and_sync<T: 'static>() -> Self {
|
||||
Self {
|
||||
type_id: TypeId::of::<T>(),
|
||||
layout: Layout::new::<T>(),
|
||||
is_send_and_sync: false,
|
||||
drop: Self::drop_ptr::<T>,
|
||||
type_name: core::any::type_name::<T>(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn type_id(&self) -> TypeId {
|
||||
self.type_id
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn layout(&self) -> Layout {
|
||||
self.layout
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn drop(&self) -> unsafe fn(*mut u8) {
|
||||
self.drop
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_send_and_sync(&self) -> bool {
|
||||
self.is_send_and_sync
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn type_name(&self) -> &'static str {
|
||||
self.type_name
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn drop_ptr<T>(x: *mut u8) {
|
||||
x.cast::<T>().drop_in_place()
|
||||
}
|
||||
}
|
|
@ -1,472 +0,0 @@
|
|||
use bevy_utils::HashSet;
|
||||
use fixedbitset::FixedBitSet;
|
||||
use std::{any::TypeId, boxed::Box, hash::Hash, vec::Vec};
|
||||
|
||||
use super::{Archetype, World};
|
||||
|
||||
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
enum ArchetypeAccess {
|
||||
None,
|
||||
Read,
|
||||
Write,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct ArchetypeComponent {
|
||||
pub archetype_index: u32,
|
||||
pub component: TypeId,
|
||||
}
|
||||
|
||||
impl ArchetypeComponent {
|
||||
#[inline]
|
||||
pub fn new<T: 'static>(archetype_index: u32) -> Self {
|
||||
ArchetypeComponent {
|
||||
archetype_index,
|
||||
component: TypeId::of::<T>(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new_ty(archetype_index: u32, component: TypeId) -> Self {
|
||||
ArchetypeComponent {
|
||||
archetype_index,
|
||||
component,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum QueryAccess {
|
||||
None,
|
||||
Read(TypeId, &'static str),
|
||||
Write(TypeId, &'static str),
|
||||
Optional(Box<QueryAccess>),
|
||||
With(TypeId, Box<QueryAccess>),
|
||||
Without(TypeId, Box<QueryAccess>),
|
||||
Union(Vec<QueryAccess>),
|
||||
}
|
||||
|
||||
impl QueryAccess {
|
||||
pub fn read<T: 'static>() -> QueryAccess {
|
||||
QueryAccess::Read(TypeId::of::<T>(), std::any::type_name::<T>())
|
||||
}
|
||||
|
||||
pub fn write<T: 'static>() -> QueryAccess {
|
||||
QueryAccess::Write(TypeId::of::<T>(), std::any::type_name::<T>())
|
||||
}
|
||||
|
||||
pub fn with<T: 'static>(access: QueryAccess) -> QueryAccess {
|
||||
QueryAccess::With(TypeId::of::<T>(), Box::new(access))
|
||||
}
|
||||
|
||||
pub fn without<T: 'static>(access: QueryAccess) -> QueryAccess {
|
||||
QueryAccess::Without(TypeId::of::<T>(), Box::new(access))
|
||||
}
|
||||
|
||||
pub fn optional(access: QueryAccess) -> QueryAccess {
|
||||
QueryAccess::Optional(Box::new(access))
|
||||
}
|
||||
|
||||
pub fn union(accesses: Vec<QueryAccess>) -> QueryAccess {
|
||||
QueryAccess::Union(accesses)
|
||||
}
|
||||
|
||||
pub fn get_world_archetype_access(
|
||||
&self,
|
||||
world: &World,
|
||||
mut type_access: Option<&mut TypeAccess<ArchetypeComponent>>,
|
||||
) {
|
||||
let archetypes = world.archetypes();
|
||||
for (i, archetype) in archetypes.enumerate() {
|
||||
let type_access = type_access.as_deref_mut();
|
||||
let _ = self.get_access(archetype, i as u32, type_access);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_component_access(&self, type_access: &mut TypeAccess<TypeId>) {
|
||||
match self {
|
||||
QueryAccess::None => {}
|
||||
QueryAccess::Read(ty, _) => type_access.add_read(*ty),
|
||||
QueryAccess::Write(ty, _) => type_access.add_write(*ty),
|
||||
QueryAccess::Optional(access) => access.get_component_access(type_access),
|
||||
QueryAccess::With(_, access) => access.get_component_access(type_access),
|
||||
QueryAccess::Without(_, access) => access.get_component_access(type_access),
|
||||
QueryAccess::Union(accesses) => {
|
||||
for access in accesses {
|
||||
access.get_component_access(type_access);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_type_name(&self, type_id: TypeId) -> Option<&'static str> {
|
||||
match self {
|
||||
QueryAccess::None => None,
|
||||
QueryAccess::Read(current_type_id, name) => {
|
||||
if type_id == *current_type_id {
|
||||
Some(*name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
QueryAccess::Write(current_type_id, name) => {
|
||||
if type_id == *current_type_id {
|
||||
Some(*name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
QueryAccess::Optional(query_access) => query_access.get_type_name(type_id),
|
||||
QueryAccess::With(_, query_access) => query_access.get_type_name(type_id),
|
||||
QueryAccess::Without(_, query_access) => query_access.get_type_name(type_id),
|
||||
QueryAccess::Union(query_accesses) => {
|
||||
for query_access in query_accesses.iter() {
|
||||
if let Some(name) = query_access.get_type_name(type_id) {
|
||||
return Some(name);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns how this [QueryAccess] accesses the given `archetype`.
|
||||
/// If `type_access` is set, it will populate type access with the types this query reads/writes
|
||||
fn get_access(
|
||||
&self,
|
||||
archetype: &Archetype,
|
||||
archetype_index: u32,
|
||||
type_access: Option<&mut TypeAccess<ArchetypeComponent>>,
|
||||
) -> Option<ArchetypeAccess> {
|
||||
match self {
|
||||
QueryAccess::None => Some(ArchetypeAccess::None),
|
||||
QueryAccess::Read(ty, _) => {
|
||||
if archetype.has_type(*ty) {
|
||||
if let Some(type_access) = type_access {
|
||||
type_access.add_read(ArchetypeComponent::new_ty(archetype_index, *ty));
|
||||
}
|
||||
Some(ArchetypeAccess::Read)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
QueryAccess::Write(ty, _) => {
|
||||
if archetype.has_type(*ty) {
|
||||
if let Some(type_access) = type_access {
|
||||
type_access.add_write(ArchetypeComponent::new_ty(archetype_index, *ty));
|
||||
}
|
||||
Some(ArchetypeAccess::Write)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
QueryAccess::Optional(query_access) => {
|
||||
if let Some(access) = query_access.get_access(archetype, archetype_index, None) {
|
||||
// only re-run get_archetype_access if we need to set type_access
|
||||
if type_access.is_some() {
|
||||
query_access.get_access(archetype, archetype_index, type_access)
|
||||
} else {
|
||||
Some(access)
|
||||
}
|
||||
} else {
|
||||
Some(ArchetypeAccess::Read)
|
||||
}
|
||||
}
|
||||
QueryAccess::With(ty, query_access) => {
|
||||
if archetype.has_type(*ty) {
|
||||
query_access.get_access(archetype, archetype_index, type_access)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
QueryAccess::Without(ty, query_access) => {
|
||||
if !archetype.has_type(*ty) {
|
||||
query_access.get_access(archetype, archetype_index, type_access)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
QueryAccess::Union(query_accesses) => {
|
||||
let mut result = None;
|
||||
for query_access in query_accesses {
|
||||
if let Some(access) = query_access.get_access(archetype, archetype_index, None)
|
||||
{
|
||||
result = Some(result.unwrap_or(ArchetypeAccess::Read).max(access));
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
// only set the type access if there is a full match
|
||||
if let Some(type_access) = type_access {
|
||||
if result.is_some() {
|
||||
for query_access in query_accesses {
|
||||
query_access.get_access(archetype, archetype_index, Some(type_access));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides information about the types a [System](crate::System) reads and writes
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
pub struct TypeAccess<T: Hash + Eq + PartialEq> {
|
||||
reads_all: bool,
|
||||
reads_and_writes: HashSet<T>,
|
||||
writes: HashSet<T>,
|
||||
}
|
||||
|
||||
impl<T: Hash + Eq + PartialEq> Default for TypeAccess<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
reads_all: false,
|
||||
reads_and_writes: Default::default(),
|
||||
writes: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Hash + Eq + PartialEq + Copy> TypeAccess<T> {
|
||||
pub fn new(reads: Vec<T>, writes: Vec<T>) -> Self {
|
||||
let mut type_access = TypeAccess::default();
|
||||
for write in writes {
|
||||
type_access.add_write(write);
|
||||
}
|
||||
for read in reads {
|
||||
type_access.add_read(read);
|
||||
}
|
||||
type_access
|
||||
}
|
||||
|
||||
pub fn is_compatible(&self, other: &TypeAccess<T>) -> bool {
|
||||
if self.reads_all {
|
||||
other.writes.is_empty()
|
||||
} else if other.reads_all {
|
||||
self.writes.is_empty()
|
||||
} else {
|
||||
self.writes.is_disjoint(&other.reads_and_writes)
|
||||
&& self.reads_and_writes.is_disjoint(&other.writes)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_conflict<'a>(&'a self, other: &'a TypeAccess<T>) -> Option<&'a T> {
|
||||
if self.reads_all {
|
||||
other.writes.iter().next()
|
||||
} else if other.reads_all {
|
||||
self.writes.iter().next()
|
||||
} else {
|
||||
match self.writes.intersection(&other.reads_and_writes).next() {
|
||||
Some(element) => Some(element),
|
||||
None => other.writes.intersection(&self.reads_and_writes).next(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extend(&mut self, other: &TypeAccess<T>) {
|
||||
self.reads_all = self.reads_all || other.reads_all;
|
||||
self.writes.extend(&other.writes);
|
||||
self.reads_and_writes.extend(&other.reads_and_writes);
|
||||
}
|
||||
|
||||
pub fn add_read(&mut self, ty: T) {
|
||||
self.reads_and_writes.insert(ty);
|
||||
}
|
||||
|
||||
pub fn add_write(&mut self, ty: T) {
|
||||
self.reads_and_writes.insert(ty);
|
||||
self.writes.insert(ty);
|
||||
}
|
||||
|
||||
pub fn read_all(&mut self) {
|
||||
self.reads_all = true;
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.reads_all = false;
|
||||
self.reads_and_writes.clear();
|
||||
self.writes.clear();
|
||||
}
|
||||
|
||||
pub fn is_read_or_write(&self, ty: &T) -> bool {
|
||||
self.reads_all || self.reads_and_writes.contains(ty)
|
||||
}
|
||||
|
||||
pub fn is_write(&self, ty: &T) -> bool {
|
||||
self.writes.contains(ty)
|
||||
}
|
||||
|
||||
pub fn reads_all(&self) -> bool {
|
||||
self.reads_all
|
||||
}
|
||||
|
||||
/// Returns an iterator of distinct accessed types if only some types are accessed.
|
||||
pub fn all_distinct_types(&self) -> Option<impl Iterator<Item = &T>> {
|
||||
if !self.reads_all {
|
||||
return Some(self.reads_and_writes.iter());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn condense(&self, all_types: &[T]) -> CondensedTypeAccess {
|
||||
if self.reads_all {
|
||||
let mut writes = FixedBitSet::with_capacity(all_types.len());
|
||||
for (index, access_type) in all_types.iter().enumerate() {
|
||||
if self.writes.contains(access_type) {
|
||||
writes.insert(index);
|
||||
}
|
||||
}
|
||||
CondensedTypeAccess {
|
||||
reads_all: true,
|
||||
reads_and_writes: Default::default(),
|
||||
writes,
|
||||
}
|
||||
} else {
|
||||
let mut reads_and_writes = FixedBitSet::with_capacity(all_types.len());
|
||||
let mut writes = FixedBitSet::with_capacity(all_types.len());
|
||||
for (index, access_type) in all_types.iter().enumerate() {
|
||||
if self.writes.contains(access_type) {
|
||||
reads_and_writes.insert(index);
|
||||
writes.insert(index);
|
||||
} else if self.reads_and_writes.contains(access_type) {
|
||||
reads_and_writes.insert(index);
|
||||
}
|
||||
}
|
||||
CondensedTypeAccess {
|
||||
reads_all: false,
|
||||
reads_and_writes,
|
||||
writes,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: consider making it typed, to enable compiler helping with bug hunting?
|
||||
#[derive(Default, Debug, Eq, PartialEq, Clone)]
|
||||
pub struct CondensedTypeAccess {
|
||||
reads_all: bool,
|
||||
reads_and_writes: FixedBitSet,
|
||||
writes: FixedBitSet,
|
||||
}
|
||||
|
||||
impl CondensedTypeAccess {
|
||||
pub fn grow(&mut self, bits: usize) {
|
||||
self.reads_and_writes.grow(bits);
|
||||
self.writes.grow(bits);
|
||||
}
|
||||
|
||||
pub fn reads_all(&self) -> bool {
|
||||
self.reads_all
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.reads_all = false;
|
||||
self.reads_and_writes.clear();
|
||||
self.writes.clear();
|
||||
}
|
||||
|
||||
pub fn extend(&mut self, other: &CondensedTypeAccess) {
|
||||
self.reads_all = self.reads_all || other.reads_all;
|
||||
self.reads_and_writes.union_with(&other.reads_and_writes);
|
||||
self.writes.union_with(&other.writes);
|
||||
}
|
||||
|
||||
pub fn is_compatible(&self, other: &CondensedTypeAccess) -> bool {
|
||||
if self.reads_all {
|
||||
0 == other.writes.count_ones(..)
|
||||
} else if other.reads_all {
|
||||
0 == self.writes.count_ones(..)
|
||||
} else {
|
||||
self.writes.is_disjoint(&other.reads_and_writes)
|
||||
&& self.reads_and_writes.is_disjoint(&other.writes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ArchetypeComponent, TypeAccess};
|
||||
use crate::{core::World, Entity, Fetch, QueryAccess, WorldQuery};
|
||||
use std::vec;
|
||||
|
||||
struct A;
|
||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||
struct B;
|
||||
struct C;
|
||||
|
||||
#[test]
|
||||
fn query_type_access() {
|
||||
let mut world = World::default();
|
||||
let e1 = world.spawn((A,));
|
||||
let e2 = world.spawn((A, B));
|
||||
let e3 = world.spawn((A, B, C));
|
||||
|
||||
let e1_archetype = world.get_entity_location(e1).unwrap().archetype;
|
||||
let e2_archetype = world.get_entity_location(e2).unwrap().archetype;
|
||||
let e3_archetype = world.get_entity_location(e3).unwrap().archetype;
|
||||
|
||||
let e1_a = ArchetypeComponent::new::<A>(e1_archetype);
|
||||
let e2_a = ArchetypeComponent::new::<A>(e2_archetype);
|
||||
let e2_b = ArchetypeComponent::new::<B>(e2_archetype);
|
||||
let e3_a = ArchetypeComponent::new::<A>(e3_archetype);
|
||||
let e3_b = ArchetypeComponent::new::<B>(e3_archetype);
|
||||
let e3_c = ArchetypeComponent::new::<C>(e3_archetype);
|
||||
|
||||
let mut a_type_access = TypeAccess::default();
|
||||
<(&A,) as WorldQuery>::Fetch::access()
|
||||
.get_world_archetype_access(&world, Some(&mut a_type_access));
|
||||
|
||||
assert_eq!(
|
||||
a_type_access,
|
||||
TypeAccess::new(vec![e1_a, e2_a, e3_a], vec![])
|
||||
);
|
||||
|
||||
let mut a_b_type_access = TypeAccess::default();
|
||||
<(&A, &B) as WorldQuery>::Fetch::access()
|
||||
.get_world_archetype_access(&world, Some(&mut a_b_type_access));
|
||||
|
||||
assert_eq!(
|
||||
a_b_type_access,
|
||||
TypeAccess::new(vec![e2_a, e2_b, e3_a, e3_b], vec![])
|
||||
);
|
||||
|
||||
let mut a_bmut_type_access = TypeAccess::default();
|
||||
<(&A, &mut B) as WorldQuery>::Fetch::access()
|
||||
.get_world_archetype_access(&world, Some(&mut a_bmut_type_access));
|
||||
|
||||
assert_eq!(
|
||||
a_bmut_type_access,
|
||||
TypeAccess::new(vec![e2_a, e3_a], vec![e2_b, e3_b])
|
||||
);
|
||||
|
||||
let mut a_option_bmut_type_access = TypeAccess::default();
|
||||
<(Entity, &A, Option<&mut B>) as WorldQuery>::Fetch::access()
|
||||
.get_world_archetype_access(&world, Some(&mut a_option_bmut_type_access));
|
||||
|
||||
assert_eq!(
|
||||
a_option_bmut_type_access,
|
||||
TypeAccess::new(vec![e1_a, e2_a, e3_a], vec![e2_b, e3_b])
|
||||
);
|
||||
|
||||
let mut a_with_b_type_access = TypeAccess::default();
|
||||
QueryAccess::with::<B>(<&A as WorldQuery>::Fetch::access())
|
||||
.get_world_archetype_access(&world, Some(&mut a_with_b_type_access));
|
||||
|
||||
assert_eq!(
|
||||
a_with_b_type_access,
|
||||
TypeAccess::new(vec![e2_a, e3_a], vec![])
|
||||
);
|
||||
|
||||
let mut a_with_b_option_c_type_access = TypeAccess::default();
|
||||
QueryAccess::with::<B>(<(&A, Option<&mut C>) as WorldQuery>::Fetch::access())
|
||||
.get_world_archetype_access(&world, Some(&mut a_with_b_option_c_type_access));
|
||||
|
||||
assert_eq!(
|
||||
a_with_b_option_c_type_access,
|
||||
TypeAccess::new(vec![e2_a, e3_a], vec![e3_c])
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,557 +0,0 @@
|
|||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// modified by Bevy contributors
|
||||
|
||||
use crate::{AtomicBorrow, Component, Entity};
|
||||
use bitflags::bitflags;
|
||||
use std::{
|
||||
alloc::{alloc, dealloc, Layout},
|
||||
any::{type_name, TypeId},
|
||||
cell::UnsafeCell,
|
||||
collections::HashMap,
|
||||
mem,
|
||||
ptr::{self, NonNull},
|
||||
};
|
||||
|
||||
/// A collection of entities having the same component types
|
||||
///
|
||||
/// Accessing `Archetype`s is only required for complex dynamic scheduling. To manipulate entities,
|
||||
/// go through the `World`.
|
||||
#[derive(Debug)]
|
||||
pub struct Archetype {
|
||||
types: Vec<TypeInfo>,
|
||||
state: TypeIdMap<TypeState>,
|
||||
len: usize,
|
||||
entities: Vec<Entity>,
|
||||
// UnsafeCell allows unique references into `data` to be constructed while shared references
|
||||
// containing the `Archetype` exist
|
||||
data: UnsafeCell<NonNull<u8>>,
|
||||
data_size: usize,
|
||||
grow_size: usize,
|
||||
}
|
||||
|
||||
impl Archetype {
|
||||
fn assert_type_info(types: &[TypeInfo]) {
|
||||
types.windows(2).for_each(|x| match x[0].cmp(&x[1]) {
|
||||
core::cmp::Ordering::Less => (),
|
||||
#[cfg(debug_assertions)]
|
||||
core::cmp::Ordering::Equal => panic!(
|
||||
"attempted to allocate entity with duplicate {} components; \
|
||||
each type must occur at most once!",
|
||||
x[0].type_name
|
||||
),
|
||||
#[cfg(not(debug_assertions))]
|
||||
core::cmp::Ordering::Equal => panic!(
|
||||
"attempted to allocate entity with duplicate components; \
|
||||
each type must occur at most once!"
|
||||
),
|
||||
core::cmp::Ordering::Greater => panic!("Type info is unsorted."),
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub fn new(types: Vec<TypeInfo>) -> Self {
|
||||
Self::with_grow(types, 64)
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub fn with_grow(types: Vec<TypeInfo>, grow_size: usize) -> Self {
|
||||
Self::assert_type_info(&types);
|
||||
let mut state = HashMap::with_capacity_and_hasher(types.len(), Default::default());
|
||||
for ty in &types {
|
||||
state.insert(ty.id, TypeState::new());
|
||||
}
|
||||
Self {
|
||||
state,
|
||||
types,
|
||||
entities: Vec::new(),
|
||||
len: 0,
|
||||
data: UnsafeCell::new(NonNull::dangling()),
|
||||
data_size: 0,
|
||||
grow_size,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn clear(&mut self) {
|
||||
for ty in &self.types {
|
||||
for index in 0..self.len {
|
||||
unsafe {
|
||||
let removed = self
|
||||
.get_dynamic(ty.id, ty.layout.size(), index)
|
||||
.unwrap()
|
||||
.as_ptr();
|
||||
(ty.drop)(removed);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.len = 0;
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[inline]
|
||||
pub fn has<T: Component>(&self) -> bool {
|
||||
self.has_dynamic(TypeId::of::<T>())
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[inline]
|
||||
pub fn has_type(&self, ty: TypeId) -> bool {
|
||||
self.has_dynamic(ty)
|
||||
}
|
||||
|
||||
pub(crate) fn has_dynamic(&self, id: TypeId) -> bool {
|
||||
self.state.contains_key(&id)
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[inline]
|
||||
pub fn get<T: Component>(&self) -> Option<NonNull<T>> {
|
||||
let state = self.state.get(&TypeId::of::<T>())?;
|
||||
Some(unsafe {
|
||||
NonNull::new_unchecked(
|
||||
(*self.data.get()).as_ptr().add(state.offset).cast::<T>() as *mut T
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[inline]
|
||||
pub fn get_with_type_state<T: Component>(&self) -> Option<(NonNull<T>, &TypeState)> {
|
||||
let state = self.state.get(&TypeId::of::<T>())?;
|
||||
Some(unsafe {
|
||||
(
|
||||
NonNull::new_unchecked(
|
||||
(*self.data.get()).as_ptr().add(state.offset).cast::<T>() as *mut T
|
||||
),
|
||||
state,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub fn get_type_state(&self, ty: TypeId) -> Option<&TypeState> {
|
||||
self.state.get(&ty)
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub fn get_type_state_mut(&mut self, ty: TypeId) -> Option<&mut TypeState> {
|
||||
self.state.get_mut(&ty)
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[inline]
|
||||
pub fn borrow<T: Component>(&self) {
|
||||
if self
|
||||
.state
|
||||
.get(&TypeId::of::<T>())
|
||||
.map_or(false, |x| !x.borrow.borrow())
|
||||
{
|
||||
panic!("{} already borrowed uniquely.", type_name::<T>());
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[inline]
|
||||
pub fn borrow_mut<T: Component>(&self) {
|
||||
if self
|
||||
.state
|
||||
.get(&TypeId::of::<T>())
|
||||
.map_or(false, |x| !x.borrow.borrow_mut())
|
||||
{
|
||||
panic!("{} already borrowed.", type_name::<T>());
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[inline]
|
||||
pub fn release<T: Component>(&self) {
|
||||
if let Some(x) = self.state.get(&TypeId::of::<T>()) {
|
||||
x.borrow.release();
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[inline]
|
||||
pub fn release_mut<T: Component>(&self) {
|
||||
if let Some(x) = self.state.get(&TypeId::of::<T>()) {
|
||||
x.borrow.release_mut();
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len == 0
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub fn iter_entities(&self) -> impl Iterator<Item = &Entity> {
|
||||
self.entities.iter().take(self.len)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn entities(&self) -> NonNull<Entity> {
|
||||
unsafe { NonNull::new_unchecked(self.entities.as_ptr() as *mut _) }
|
||||
}
|
||||
|
||||
pub(crate) fn get_entity(&self, index: usize) -> Entity {
|
||||
self.entities[index]
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub fn types(&self) -> &[TypeInfo] {
|
||||
&self.types
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `index` must be in-bounds
|
||||
pub unsafe fn get_dynamic(&self, ty: TypeId, size: usize, index: usize) -> Option<NonNull<u8>> {
|
||||
debug_assert!(index < self.len);
|
||||
Some(NonNull::new_unchecked(
|
||||
(*self.data.get())
|
||||
.as_ptr()
|
||||
.add(self.state.get(&ty)?.offset + size * index)
|
||||
.cast::<u8>(),
|
||||
))
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// Every type must be written immediately after this call
|
||||
pub unsafe fn allocate(&mut self, id: Entity) -> usize {
|
||||
if self.len == self.entities.len() {
|
||||
self.grow(self.len.max(self.grow_size));
|
||||
}
|
||||
|
||||
self.entities[self.len] = id;
|
||||
self.len += 1;
|
||||
self.len - 1
|
||||
}
|
||||
|
||||
pub(crate) fn reserve(&mut self, additional: usize) {
|
||||
if additional > (self.capacity() - self.len()) {
|
||||
self.grow(additional - (self.capacity() - self.len()));
|
||||
}
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
self.entities.len()
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub fn clear_trackers(&mut self) {
|
||||
for type_state in self.state.values_mut() {
|
||||
type_state.clear_trackers();
|
||||
}
|
||||
}
|
||||
|
||||
fn grow(&mut self, increment: usize) {
|
||||
unsafe {
|
||||
let old_count = self.len;
|
||||
let new_capacity = self.capacity() + increment;
|
||||
self.entities.resize(
|
||||
new_capacity,
|
||||
Entity {
|
||||
id: u32::MAX,
|
||||
generation: u32::MAX,
|
||||
},
|
||||
);
|
||||
|
||||
for type_state in self.state.values_mut() {
|
||||
type_state
|
||||
.component_flags
|
||||
.resize_with(new_capacity, ComponentFlags::empty);
|
||||
}
|
||||
|
||||
let old_data_size = mem::replace(&mut self.data_size, 0);
|
||||
let mut old_offsets = Vec::with_capacity(self.types.len());
|
||||
for ty in &self.types {
|
||||
self.data_size = align(self.data_size, ty.layout.align());
|
||||
let ty_state = self.state.get_mut(&ty.id).unwrap();
|
||||
old_offsets.push(ty_state.offset);
|
||||
ty_state.offset = self.data_size;
|
||||
self.data_size += ty.layout.size() * new_capacity;
|
||||
}
|
||||
let new_data = if self.data_size == 0 {
|
||||
NonNull::dangling()
|
||||
} else {
|
||||
NonNull::new(alloc(
|
||||
Layout::from_size_align(
|
||||
self.data_size,
|
||||
self.types.first().map_or(1, |x| x.layout.align()),
|
||||
)
|
||||
.unwrap(),
|
||||
))
|
||||
.unwrap()
|
||||
};
|
||||
if old_data_size != 0 {
|
||||
for (i, ty) in self.types.iter().enumerate() {
|
||||
let old_off = old_offsets[i];
|
||||
let new_off = self.state.get(&ty.id).unwrap().offset;
|
||||
ptr::copy_nonoverlapping(
|
||||
(*self.data.get()).as_ptr().add(old_off),
|
||||
new_data.as_ptr().add(new_off),
|
||||
ty.layout.size() * old_count,
|
||||
);
|
||||
}
|
||||
dealloc(
|
||||
(*self.data.get()).as_ptr().cast(),
|
||||
Layout::from_size_align_unchecked(
|
||||
old_data_size,
|
||||
self.types.first().map_or(1, |x| x.layout.align()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
self.data = UnsafeCell::new(new_data);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the ID of the entity moved into `index`, if any
|
||||
pub(crate) unsafe fn remove(&mut self, index: usize) -> Option<Entity> {
|
||||
let last = self.len - 1;
|
||||
for ty in &self.types {
|
||||
let removed = self
|
||||
.get_dynamic(ty.id, ty.layout.size(), index)
|
||||
.unwrap()
|
||||
.as_ptr();
|
||||
(ty.drop)(removed);
|
||||
if index != last {
|
||||
// TODO: copy component tracker state here
|
||||
ptr::copy_nonoverlapping(
|
||||
self.get_dynamic(ty.id, ty.layout.size(), last)
|
||||
.unwrap()
|
||||
.as_ptr(),
|
||||
removed,
|
||||
ty.layout.size(),
|
||||
);
|
||||
|
||||
let type_state = self.state.get_mut(&ty.id).unwrap();
|
||||
type_state.component_flags[index] = type_state.component_flags[last];
|
||||
}
|
||||
}
|
||||
self.len = last;
|
||||
if index != last {
|
||||
self.entities[index] = self.entities[last];
|
||||
Some(self.entities[last])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the ID of the entity moved into `index`, if any
|
||||
pub(crate) unsafe fn move_to(
|
||||
&mut self,
|
||||
index: usize,
|
||||
mut f: impl FnMut(*mut u8, TypeId, usize, ComponentFlags),
|
||||
) -> Option<Entity> {
|
||||
let last = self.len - 1;
|
||||
for ty in &self.types {
|
||||
let moved = self
|
||||
.get_dynamic(ty.id, ty.layout.size(), index)
|
||||
.unwrap()
|
||||
.as_ptr();
|
||||
let type_state = self.state.get(&ty.id).unwrap();
|
||||
let flags = type_state.component_flags[index];
|
||||
f(moved, ty.id(), ty.layout().size(), flags);
|
||||
if index != last {
|
||||
ptr::copy_nonoverlapping(
|
||||
self.get_dynamic(ty.id, ty.layout.size(), last)
|
||||
.unwrap()
|
||||
.as_ptr(),
|
||||
moved,
|
||||
ty.layout.size(),
|
||||
);
|
||||
let type_state = self.state.get_mut(&ty.id).unwrap();
|
||||
type_state.component_flags[index] = type_state.component_flags[last];
|
||||
}
|
||||
}
|
||||
self.len -= 1;
|
||||
if index != last {
|
||||
self.entities[index] = self.entities[last];
|
||||
Some(self.entities[last])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// - `component` must point to valid memory
|
||||
/// - the component `ty`pe must be registered
|
||||
/// - `index` must be in-bound
|
||||
/// - `size` must be the size of the component
|
||||
/// - the storage array must be big enough
|
||||
pub unsafe fn put_dynamic(
|
||||
&mut self,
|
||||
component: *mut u8,
|
||||
ty: TypeId,
|
||||
size: usize,
|
||||
index: usize,
|
||||
flags: ComponentFlags,
|
||||
) {
|
||||
let state = self.state.get_mut(&ty).unwrap();
|
||||
state.component_flags[index] = flags;
|
||||
let ptr = (*self.data.get())
|
||||
.as_ptr()
|
||||
.add(state.offset + size * index)
|
||||
.cast::<u8>();
|
||||
ptr::copy_nonoverlapping(component, ptr, size);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Archetype {
|
||||
fn drop(&mut self) {
|
||||
self.clear();
|
||||
if self.data_size != 0 {
|
||||
unsafe {
|
||||
dealloc(
|
||||
(*self.data.get()).as_ptr().cast(),
|
||||
Layout::from_size_align_unchecked(
|
||||
self.data_size,
|
||||
self.types.first().map_or(1, |x| x.layout.align()),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata about a type stored in an archetype
|
||||
#[derive(Debug)]
|
||||
pub struct TypeState {
|
||||
offset: usize,
|
||||
borrow: AtomicBorrow,
|
||||
component_flags: Vec<ComponentFlags>,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct ComponentFlags: u8 {
|
||||
const ADDED = 1;
|
||||
const MUTATED = 2;
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeState {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
offset: 0,
|
||||
borrow: AtomicBorrow::new(),
|
||||
component_flags: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_trackers(&mut self) {
|
||||
for flags in self.component_flags.iter_mut() {
|
||||
*flags = ComponentFlags::empty();
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[inline]
|
||||
pub fn component_flags(&self) -> NonNull<ComponentFlags> {
|
||||
unsafe { NonNull::new_unchecked(self.component_flags.as_ptr() as *mut ComponentFlags) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata required to store a component
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct TypeInfo {
|
||||
id: TypeId,
|
||||
layout: Layout,
|
||||
drop: unsafe fn(*mut u8),
|
||||
type_name: &'static str,
|
||||
}
|
||||
|
||||
impl TypeInfo {
|
||||
/// Metadata for `T`
|
||||
pub fn of<T: 'static>() -> Self {
|
||||
unsafe fn drop_ptr<T>(x: *mut u8) {
|
||||
x.cast::<T>().drop_in_place()
|
||||
}
|
||||
|
||||
Self {
|
||||
id: TypeId::of::<T>(),
|
||||
layout: Layout::new::<T>(),
|
||||
drop: drop_ptr::<T>,
|
||||
type_name: core::any::type_name::<T>(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[inline]
|
||||
pub fn id(&self) -> TypeId {
|
||||
self.id
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[inline]
|
||||
pub fn layout(&self) -> Layout {
|
||||
self.layout
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn drop(&self, data: *mut u8) {
|
||||
(self.drop)(data)
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[inline]
|
||||
pub fn type_name(&self) -> &'static str {
|
||||
self.type_name
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for TypeInfo {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for TypeInfo {
|
||||
/// Order by alignment, descending. Ties broken with TypeId.
|
||||
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
|
||||
self.layout
|
||||
.align()
|
||||
.cmp(&other.layout.align())
|
||||
.reverse()
|
||||
.then_with(|| self.id.cmp(&other.id))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for TypeInfo {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for TypeInfo {}
|
||||
|
||||
fn align(x: usize, alignment: usize) -> usize {
|
||||
debug_assert!(alignment.is_power_of_two());
|
||||
(x + alignment - 1) & (!alignment + 1)
|
||||
}
|
||||
|
||||
/// A hasher optimized for hashing a single TypeId.
|
||||
///
|
||||
/// We don't use RandomState from std or Random state from Ahash
|
||||
/// because fxhash is [proved to be faster](https://github.com/bevyengine/bevy/pull/1119#issuecomment-751361215)
|
||||
/// and we don't need Hash Dos attack protection here
|
||||
/// since TypeIds generated during compilation and there is no reason to user attack himself.
|
||||
pub(crate) type TypeIdMap<V> = HashMap<TypeId, V, fxhash::FxBuildHasher>;
|
|
@ -1,222 +0,0 @@
|
|||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// modified by Bevy contributors
|
||||
|
||||
use crate::{Archetype, Component, ComponentFlags, MissingComponent};
|
||||
use core::{
|
||||
fmt::Debug,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
|
||||
/// Atomically enforces Rust-style borrow checking at runtime
|
||||
#[derive(Debug)]
|
||||
pub struct AtomicBorrow(AtomicUsize);
|
||||
|
||||
impl AtomicBorrow {
|
||||
/// Creates a new AtomicBorrow
|
||||
pub const fn new() -> Self {
|
||||
Self(AtomicUsize::new(0))
|
||||
}
|
||||
|
||||
/// Starts a new immutable borrow. This can be called any number of times
|
||||
pub fn borrow(&self) -> bool {
|
||||
let value = self.0.fetch_add(1, Ordering::Acquire).wrapping_add(1);
|
||||
if value == 0 {
|
||||
// Wrapped, this borrow is invalid!
|
||||
core::panic!()
|
||||
}
|
||||
if value & UNIQUE_BIT != 0 {
|
||||
self.0.fetch_sub(1, Ordering::Release);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts a new mutable borrow. This must be unique. It cannot be done in parallel with other borrows or borrow_muts
|
||||
pub fn borrow_mut(&self) -> bool {
|
||||
self.0
|
||||
.compare_exchange(0, UNIQUE_BIT, Ordering::Acquire, Ordering::Relaxed)
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
/// Release an immutable borrow.
|
||||
pub fn release(&self) {
|
||||
let value = self.0.fetch_sub(1, Ordering::Release);
|
||||
debug_assert!(value != 0, "unbalanced release");
|
||||
debug_assert!(value & UNIQUE_BIT == 0, "shared release of unique borrow");
|
||||
}
|
||||
|
||||
/// Release a mutable borrow.
|
||||
pub fn release_mut(&self) {
|
||||
let value = self.0.fetch_and(!UNIQUE_BIT, Ordering::Release);
|
||||
debug_assert_ne!(value & UNIQUE_BIT, 0, "unique release of shared borrow");
|
||||
}
|
||||
}
|
||||
|
||||
const UNIQUE_BIT: usize = !(usize::max_value() >> 1);
|
||||
|
||||
/// Shared borrow of an entity's component
|
||||
#[derive(Clone)]
|
||||
pub struct Ref<'a, T: Component> {
|
||||
archetype: &'a Archetype,
|
||||
target: &'a T,
|
||||
}
|
||||
|
||||
impl<'a, T: Component> Ref<'a, T> {
|
||||
/// Creates a new entity component borrow
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - the index of the component must be valid
|
||||
pub unsafe fn new(archetype: &'a Archetype, index: usize) -> Result<Self, MissingComponent> {
|
||||
let target = archetype
|
||||
.get::<T>()
|
||||
.ok_or_else(MissingComponent::new::<T>)?;
|
||||
archetype.borrow::<T>();
|
||||
Ok(Self {
|
||||
archetype,
|
||||
target: &*target.as_ptr().add(index as usize),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Component> Send for Ref<'_, T> {}
|
||||
unsafe impl<T: Component> Sync for Ref<'_, T> {}
|
||||
|
||||
impl<'a, T: Component> Drop for Ref<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
self.archetype.release::<T>();
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Component> Deref for Ref<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
self.target
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Component> Debug for Ref<'a, T>
|
||||
where
|
||||
T: Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
self.deref().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Unique borrow of an entity's component
|
||||
pub struct RefMut<'a, T: Component> {
|
||||
archetype: &'a Archetype,
|
||||
target: &'a mut T,
|
||||
flags: &'a mut ComponentFlags,
|
||||
}
|
||||
|
||||
impl<'a, T: Component> RefMut<'a, T> {
|
||||
/// Creates a new entity component mutable borrow
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - the index of the component must be valid
|
||||
pub unsafe fn new(archetype: &'a Archetype, index: usize) -> Result<Self, MissingComponent> {
|
||||
let (target, type_state) = archetype
|
||||
.get_with_type_state::<T>()
|
||||
.ok_or_else(MissingComponent::new::<T>)?;
|
||||
archetype.borrow_mut::<T>();
|
||||
Ok(Self {
|
||||
archetype,
|
||||
target: &mut *target.as_ptr().add(index),
|
||||
flags: &mut *type_state.component_flags().as_ptr().add(index),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Component> Send for RefMut<'_, T> {}
|
||||
unsafe impl<T: Component> Sync for RefMut<'_, T> {}
|
||||
|
||||
impl<'a, T: Component> Drop for RefMut<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
self.archetype.release_mut::<T>();
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Component> Deref for RefMut<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
self.target
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Component> DerefMut for RefMut<'a, T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
self.flags.insert(ComponentFlags::MUTATED);
|
||||
self.target
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Component> Debug for RefMut<'a, T>
|
||||
where
|
||||
T: Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
self.deref().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle to an entity with any component types
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct EntityRef<'a> {
|
||||
archetype: Option<&'a Archetype>,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl<'a> EntityRef<'a> {
|
||||
/// Construct a `Ref` for an entity with no components
|
||||
pub(crate) fn empty() -> Self {
|
||||
Self {
|
||||
archetype: None,
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn new(archetype: &'a Archetype, index: usize) -> Self {
|
||||
Self {
|
||||
archetype: Some(archetype),
|
||||
index,
|
||||
}
|
||||
}
|
||||
|
||||
/// Borrow the component of type `T`, if it exists
|
||||
///
|
||||
/// Panics if the component is already uniquely borrowed from another entity with the same
|
||||
/// components.
|
||||
pub fn get<T: Component>(&self) -> Option<Ref<'a, T>> {
|
||||
Some(unsafe { Ref::new(self.archetype?, self.index).ok()? })
|
||||
}
|
||||
|
||||
/// Uniquely borrow the component of type `T`, if it exists
|
||||
///
|
||||
/// Panics if the component is already borrowed from another entity with the same components.
|
||||
pub fn get_mut<T: Component>(&self) -> Option<RefMut<'a, T>> {
|
||||
Some(unsafe { RefMut::new(self.archetype?, self.index).ok()? })
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'a> Send for EntityRef<'a> {}
|
||||
unsafe impl<'a> Sync for EntityRef<'a> {}
|
|
@ -1,151 +0,0 @@
|
|||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// modified by Bevy contributors
|
||||
|
||||
use crate::{Component, TypeInfo};
|
||||
use std::{
|
||||
any::{type_name, TypeId},
|
||||
fmt, mem,
|
||||
ptr::NonNull,
|
||||
};
|
||||
|
||||
/// A dynamically typed collection of components
|
||||
///
|
||||
/// See [Bundle]
|
||||
pub trait DynamicBundle {
|
||||
/// Invoke a callback on the fields' type IDs, sorted by descending alignment then id
|
||||
#[doc(hidden)]
|
||||
fn with_ids<T>(&self, f: impl FnOnce(&[TypeId]) -> T) -> T;
|
||||
/// Obtain the fields' TypeInfos, sorted by descending alignment then id
|
||||
#[doc(hidden)]
|
||||
fn type_info(&self) -> Vec<TypeInfo>;
|
||||
/// Allow a callback to move all components out of the bundle
|
||||
///
|
||||
/// Must invoke `f` only with a valid pointer, its type, and the pointee's size. A `false`
|
||||
/// return value indicates that the value was not moved and should be dropped.
|
||||
#[doc(hidden)]
|
||||
unsafe fn put(self, f: impl FnMut(*mut u8, TypeId, usize) -> bool);
|
||||
}
|
||||
|
||||
/// A statically typed collection of components
|
||||
///
|
||||
/// See [DynamicBundle]
|
||||
pub trait Bundle: DynamicBundle {
|
||||
#[doc(hidden)]
|
||||
fn with_static_ids<T>(f: impl FnOnce(&[TypeId]) -> T) -> T;
|
||||
|
||||
/// Obtain the fields' TypeInfos, sorted by descending alignment then id
|
||||
#[doc(hidden)]
|
||||
fn static_type_info() -> Vec<TypeInfo>;
|
||||
|
||||
/// Construct `Self` by moving components out of pointers fetched by `f`
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `f` must produce pointers to the expected fields. The implementation must not read from any
|
||||
/// pointers if any call to `f` returns `None`.
|
||||
#[doc(hidden)]
|
||||
unsafe fn get(
|
||||
f: impl FnMut(TypeId, usize) -> Option<NonNull<u8>>,
|
||||
) -> Result<Self, MissingComponent>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
/// Error indicating that an entity did not have a required component
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct MissingComponent(&'static str);
|
||||
|
||||
impl MissingComponent {
|
||||
/// Construct an error representing a missing `T`
|
||||
pub fn new<T: Component>() -> Self {
|
||||
Self(type_name::<T>())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MissingComponent {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "missing {} component", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for MissingComponent {}
|
||||
|
||||
macro_rules! tuple_impl {
|
||||
($($name: ident),*) => {
|
||||
impl<$($name: Component),*> DynamicBundle for ($($name,)*) {
|
||||
fn with_ids<T>(&self, f: impl FnOnce(&[TypeId]) -> T) -> T {
|
||||
Self::with_static_ids(f)
|
||||
}
|
||||
|
||||
fn type_info(&self) -> Vec<TypeInfo> {
|
||||
Self::static_type_info()
|
||||
}
|
||||
|
||||
#[allow(unused_variables, unused_mut)]
|
||||
unsafe fn put(self, mut f: impl FnMut(*mut u8, TypeId, usize) -> bool) {
|
||||
#[allow(non_snake_case)]
|
||||
let ($(mut $name,)*) = self;
|
||||
$(
|
||||
if f(
|
||||
(&mut $name as *mut $name).cast::<u8>(),
|
||||
TypeId::of::<$name>(),
|
||||
mem::size_of::<$name>()
|
||||
) {
|
||||
mem::forget($name)
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
impl<$($name: Component),*> Bundle for ($($name,)*) {
|
||||
fn with_static_ids<T>(f: impl FnOnce(&[TypeId]) -> T) -> T {
|
||||
const N: usize = count!($($name),*);
|
||||
let mut xs: [(usize, TypeId); N] = [$((mem::align_of::<$name>(), TypeId::of::<$name>())),*];
|
||||
xs.sort_unstable_by(|x, y| x.0.cmp(&y.0).reverse().then(x.1.cmp(&y.1)));
|
||||
let mut ids = [TypeId::of::<()>(); N];
|
||||
for (slot, &(_, id)) in ids.iter_mut().zip(xs.iter()) {
|
||||
*slot = id;
|
||||
}
|
||||
f(&ids)
|
||||
}
|
||||
|
||||
fn static_type_info() -> Vec<TypeInfo> {
|
||||
let mut xs = vec![$(TypeInfo::of::<$name>()),*];
|
||||
xs.sort_unstable();
|
||||
xs
|
||||
}
|
||||
|
||||
#[allow(unused_variables, unused_mut)]
|
||||
unsafe fn get(mut f: impl FnMut(TypeId, usize) -> Option<NonNull<u8>>) -> Result<Self, MissingComponent> {
|
||||
#[allow(non_snake_case)]
|
||||
let ($(mut $name,)*) = ($(
|
||||
f(TypeId::of::<$name>(), mem::size_of::<$name>()).ok_or_else(MissingComponent::new::<$name>)?
|
||||
.as_ptr()
|
||||
.cast::<$name>(),)*
|
||||
);
|
||||
Ok(($($name.read(),)*))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! count {
|
||||
() => { 0 };
|
||||
($x: ident $(, $rest: ident)*) => { 1 + count!($($rest),*) };
|
||||
}
|
||||
|
||||
smaller_tuples_too!(tuple_impl, O, N, M, L, K, J, I, H, G, F, E, D, C, B, A);
|
|
@ -1,386 +0,0 @@
|
|||
use std::{
|
||||
convert::TryFrom,
|
||||
fmt, mem,
|
||||
sync::atomic::{AtomicU32, Ordering},
|
||||
};
|
||||
|
||||
/// Lightweight unique ID of an entity
|
||||
///
|
||||
/// Obtained from `World::spawn`. Can be stored to refer to an entity in the future.
|
||||
#[derive(Clone, Copy, Hash, Eq, Ord, PartialEq, PartialOrd)]
|
||||
pub struct Entity {
|
||||
pub(crate) generation: u32,
|
||||
pub(crate) id: u32,
|
||||
}
|
||||
|
||||
impl Entity {
|
||||
/// Creates a new entity reference with a generation of 0
|
||||
pub fn new(id: u32) -> Entity {
|
||||
Entity { id, generation: 0 }
|
||||
}
|
||||
|
||||
/// Convert to a form convenient for passing outside of rust
|
||||
///
|
||||
/// Only useful for identifying entities within the same instance of an application. Do not use
|
||||
/// for serialization between runs.
|
||||
///
|
||||
/// No particular structure is guaranteed for the returned bits.
|
||||
pub fn to_bits(self) -> u64 {
|
||||
u64::from(self.generation) << 32 | u64::from(self.id)
|
||||
}
|
||||
|
||||
/// Reconstruct an `Entity` previously destructured with `to_bits`
|
||||
///
|
||||
/// Only useful when applied to results from `to_bits` in the same instance of an application.
|
||||
pub fn from_bits(bits: u64) -> Self {
|
||||
Self {
|
||||
generation: (bits >> 32) as u32,
|
||||
id: bits as u32,
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract a transiently unique identifier
|
||||
///
|
||||
/// No two simultaneously-live entities share the same ID, but dead entities' IDs may collide
|
||||
/// with both live and dead entities. Useful for compactly representing entities within a
|
||||
/// specific snapshot of the world, such as when serializing.
|
||||
pub fn id(self) -> u32 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Entity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}v{}", self.id, self.generation)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct Entities {
|
||||
pub meta: Vec<EntityMeta>,
|
||||
// Reserved entities outside the range of `meta`, having implicit generation 0, archetype 0, and
|
||||
// undefined index. Calling `flush` converts these to real entities, which can have a fully
|
||||
// defined location.
|
||||
pending: AtomicU32,
|
||||
// Unused entity IDs below `meta.len()`
|
||||
free: Vec<u32>,
|
||||
free_cursor: AtomicU32,
|
||||
// Reserved IDs within `meta.len()` with implicit archetype 0 and undefined index. Should be
|
||||
// consumed and used to initialize locations to produce real entities after calling `flush`.
|
||||
reserved: Box<[AtomicU32]>,
|
||||
reserved_cursor: AtomicU32,
|
||||
}
|
||||
|
||||
impl Entities {
|
||||
/// Reserve an entity ID concurrently
|
||||
///
|
||||
/// Storage for entity generation and location is lazily allocated by calling `flush`. Locations
|
||||
/// can be determined by the return value of `flush` and by iterating through the `reserved`
|
||||
/// accessors, and should all be written immediately after flushing.
|
||||
pub fn reserve_entity(&self) -> Entity {
|
||||
loop {
|
||||
let index = self.free_cursor.load(Ordering::Relaxed);
|
||||
return match index.checked_sub(1) {
|
||||
// The freelist is empty, so increment `pending` to arrange for a new entity with a
|
||||
// predictable ID to be allocated on the next `flush` call
|
||||
None => {
|
||||
let n = self.pending.fetch_add(1, Ordering::Relaxed);
|
||||
Entity {
|
||||
generation: 0,
|
||||
id: u32::try_from(self.meta.len())
|
||||
.ok()
|
||||
.and_then(|x| x.checked_add(n))
|
||||
.expect("Too many entities."),
|
||||
}
|
||||
}
|
||||
// The freelist has entities in it, so move the last entry to the reserved list, to
|
||||
// be consumed by the caller as part of a higher-level flush.
|
||||
Some(next) => {
|
||||
// We don't care about memory ordering here so long as we get our slot.
|
||||
if self
|
||||
.free_cursor
|
||||
.compare_exchange_weak(index, next, Ordering::Relaxed, Ordering::Relaxed)
|
||||
.is_err()
|
||||
{
|
||||
// Another thread already consumed this slot, start over.
|
||||
continue;
|
||||
}
|
||||
let id = self.free[next as usize];
|
||||
let reservation = self.reserved_cursor.fetch_add(1, Ordering::Relaxed);
|
||||
self.reserved[reservation as usize].store(id, Ordering::Relaxed);
|
||||
Entity {
|
||||
generation: self.meta[id as usize].generation,
|
||||
id,
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocate an entity ID directly
|
||||
///
|
||||
/// Location should be written immediately.
|
||||
pub fn alloc(&mut self) -> Entity {
|
||||
debug_assert_eq!(
|
||||
self.pending.load(Ordering::Relaxed),
|
||||
0,
|
||||
"allocator must be flushed before potentially growing"
|
||||
);
|
||||
let index = self.free_cursor.load(Ordering::Relaxed);
|
||||
match index.checked_sub(1) {
|
||||
None => {
|
||||
self.grow(0);
|
||||
let cursor = self.free_cursor.fetch_sub(1, Ordering::Relaxed);
|
||||
let id = self.free[(cursor - 1) as usize];
|
||||
Entity {
|
||||
generation: self.meta[id as usize].generation,
|
||||
id,
|
||||
}
|
||||
}
|
||||
Some(next) => {
|
||||
// Not racey due to &mut self
|
||||
self.free_cursor.store(next, Ordering::Relaxed);
|
||||
let id = self.free[next as usize];
|
||||
Entity {
|
||||
generation: self.meta[id as usize].generation,
|
||||
id,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Destroy an entity, allowing it to be reused
|
||||
///
|
||||
/// Must not be called on reserved entities prior to `flush`.
|
||||
pub fn free(&mut self, entity: Entity) -> Result<Location, NoSuchEntity> {
|
||||
let meta = &mut self.meta[entity.id as usize];
|
||||
if meta.generation != entity.generation {
|
||||
return Err(NoSuchEntity);
|
||||
}
|
||||
meta.generation += 1;
|
||||
let loc = mem::replace(
|
||||
&mut meta.location,
|
||||
Location {
|
||||
archetype: 0,
|
||||
// Guard against bugs in reservation handling
|
||||
index: usize::max_value(),
|
||||
},
|
||||
);
|
||||
let index = self.free_cursor.fetch_add(1, Ordering::Relaxed); // Not racey due to &mut self
|
||||
self.free[index as usize] = entity.id;
|
||||
debug_assert!(
|
||||
loc.index != usize::max_value(),
|
||||
"free called on reserved entity without flush"
|
||||
);
|
||||
Ok(loc)
|
||||
}
|
||||
|
||||
/// Ensure `n` at least allocations can succeed without reallocating
|
||||
pub fn reserve(&mut self, additional: u32) {
|
||||
debug_assert_eq!(
|
||||
self.pending.load(Ordering::Relaxed),
|
||||
0,
|
||||
"allocator must be flushed before potentially growing"
|
||||
);
|
||||
let free = self.free_cursor.load(Ordering::Relaxed);
|
||||
if additional > free {
|
||||
self.grow(additional - free);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains(&self, entity: Entity) -> bool {
|
||||
if entity.id >= self.meta.len() as u32 {
|
||||
return true;
|
||||
}
|
||||
self.meta[entity.id as usize].generation == entity.generation
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
// Not racey due to &mut self
|
||||
self.free_cursor
|
||||
.store(self.meta.len() as u32, Ordering::Relaxed);
|
||||
for (i, x) in self.free.iter_mut().enumerate() {
|
||||
*x = i as u32;
|
||||
}
|
||||
self.pending.store(0, Ordering::Relaxed);
|
||||
self.reserved_cursor.store(0, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Access the location storage of an entity
|
||||
///
|
||||
/// Must not be called on pending entities.
|
||||
pub fn get_mut(&mut self, entity: Entity) -> Result<&mut Location, NoSuchEntity> {
|
||||
let meta = &mut self.meta[entity.id as usize];
|
||||
if meta.generation == entity.generation {
|
||||
Ok(&mut meta.location)
|
||||
} else {
|
||||
Err(NoSuchEntity)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `Ok(Location { archetype: 0, index: undefined })` for pending entities
|
||||
pub fn get(&self, entity: Entity) -> Result<Location, NoSuchEntity> {
|
||||
if self.meta.len() <= entity.id as usize {
|
||||
return Ok(Location {
|
||||
archetype: 0,
|
||||
index: usize::max_value(),
|
||||
});
|
||||
}
|
||||
let meta = &self.meta[entity.id as usize];
|
||||
if meta.generation != entity.generation {
|
||||
return Err(NoSuchEntity);
|
||||
}
|
||||
if meta.location.archetype == 0 {
|
||||
return Ok(Location {
|
||||
archetype: 0,
|
||||
index: usize::max_value(),
|
||||
});
|
||||
}
|
||||
Ok(meta.location)
|
||||
}
|
||||
|
||||
/// Allocate space for and enumerate pending entities
|
||||
#[allow(clippy::reversed_empty_ranges)]
|
||||
pub fn flush(&mut self) -> impl Iterator<Item = u32> {
|
||||
let pending = self.pending.load(Ordering::Relaxed); // Not racey due to &mut self
|
||||
if pending != 0 {
|
||||
let first = self.meta.len() as u32;
|
||||
self.grow(0);
|
||||
first..(first + pending)
|
||||
} else {
|
||||
0..0
|
||||
}
|
||||
}
|
||||
|
||||
/// Number of freed entities in `self.meta`
|
||||
pub fn freed_len(&self) -> u32 {
|
||||
self.free_cursor.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Number of reserved entities outside of `self.meta`
|
||||
pub fn pending_len(&self) -> u32 {
|
||||
self.pending.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
// The following three methods allow iteration over `reserved` simultaneous to location
|
||||
// writes. This is a lazy hack, but we only use it in `World::flush` so the complexity and unsafety
|
||||
// involved in producing an `impl Iterator<Item=(u32, &mut Location)>` isn't a clear win.
|
||||
/// Number of reserved entities in `self.meta`
|
||||
pub fn reserved_len(&self) -> u32 {
|
||||
self.reserved_cursor.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn reserved(&self, i: u32) -> u32 {
|
||||
debug_assert!(i < self.reserved_len());
|
||||
self.reserved[i as usize].load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn clear_reserved(&mut self) {
|
||||
self.reserved_cursor.store(0, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Expand storage and mark all but the first `pending` of the new slots as free
|
||||
fn grow(&mut self, increment: u32) {
|
||||
let pending = self.pending.swap(0, Ordering::Relaxed);
|
||||
let new_len = (self.meta.len() + pending as usize + increment as usize)
|
||||
.max(self.meta.len() * 2)
|
||||
.max(1024);
|
||||
let mut new_meta = Vec::with_capacity(new_len);
|
||||
new_meta.extend_from_slice(&self.meta);
|
||||
new_meta.resize(
|
||||
new_len,
|
||||
EntityMeta {
|
||||
generation: 0,
|
||||
location: Location {
|
||||
archetype: 0,
|
||||
index: usize::max_value(), // dummy value, to be filled in
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
let free_cursor = self.free_cursor.load(Ordering::Relaxed); // Not racey due to &mut self
|
||||
let mut new_free = Vec::with_capacity(new_len);
|
||||
new_free.extend_from_slice(&self.free[0..free_cursor as usize]);
|
||||
// Add freshly allocated trailing free slots
|
||||
new_free.extend(((self.meta.len() as u32 + pending)..new_len as u32).rev());
|
||||
debug_assert!(new_free.len() <= new_len);
|
||||
self.free_cursor
|
||||
.store(new_free.len() as u32, Ordering::Relaxed); // Not racey due to &mut self
|
||||
|
||||
// Zero-fill
|
||||
new_free.resize(new_len, 0);
|
||||
|
||||
self.meta = new_meta;
|
||||
self.free = new_free;
|
||||
let mut new_reserved = Vec::with_capacity(new_len);
|
||||
// Not racey due to &mut self
|
||||
let reserved_cursor = self.reserved_cursor.load(Ordering::Relaxed);
|
||||
for x in &self.reserved[..reserved_cursor as usize] {
|
||||
new_reserved.push(AtomicU32::new(x.load(Ordering::Relaxed)));
|
||||
}
|
||||
new_reserved.resize_with(new_len, || AtomicU32::new(0));
|
||||
self.reserved = new_reserved.into();
|
||||
}
|
||||
|
||||
pub fn get_reserver(&self) -> EntityReserver {
|
||||
// SAFE: reservers use atomics for anything write-related
|
||||
let entities: &'static Entities = unsafe { mem::transmute(self) };
|
||||
EntityReserver { entities }
|
||||
}
|
||||
}
|
||||
|
||||
/// Reserves entities in a way that is usable in multi-threaded contexts.
|
||||
#[derive(Debug)]
|
||||
pub struct EntityReserver {
|
||||
entities: &'static Entities,
|
||||
}
|
||||
|
||||
impl EntityReserver {
|
||||
/// Reserves an entity
|
||||
pub fn reserve_entity(&self) -> Entity {
|
||||
self.entities.reserve_entity()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) struct EntityMeta {
|
||||
pub generation: u32,
|
||||
pub location: Location,
|
||||
}
|
||||
|
||||
/// A location of an entity in an archetype
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Location {
|
||||
/// The archetype index
|
||||
pub archetype: u32,
|
||||
|
||||
/// The index of the entity in the archetype
|
||||
pub index: usize,
|
||||
}
|
||||
|
||||
/// Error indicating that no entity with a particular ID exists
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct NoSuchEntity;
|
||||
|
||||
impl fmt::Display for NoSuchEntity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.pad("no such entity")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl Error for NoSuchEntity {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn entity_bits_roundtrip() {
|
||||
let e = Entity {
|
||||
generation: 0xDEADBEEF,
|
||||
id: 0xBAADF00D,
|
||||
};
|
||||
assert_eq!(Entity::from_bits(e.to_bits()), e);
|
||||
}
|
||||
}
|
|
@ -1,188 +0,0 @@
|
|||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// modified by Bevy contributors
|
||||
|
||||
use bevy_utils::HashSet;
|
||||
use std::{
|
||||
alloc::{alloc, dealloc, Layout},
|
||||
any::TypeId,
|
||||
mem::{self, MaybeUninit},
|
||||
ptr,
|
||||
};
|
||||
|
||||
use crate::{Component, DynamicBundle, TypeInfo};
|
||||
|
||||
/// Helper for incrementally constructing a bundle of components with dynamic component types
|
||||
///
|
||||
/// Prefer reusing the same builder over creating new ones repeatedly.
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::*;
|
||||
/// let mut world = World::new();
|
||||
/// let mut builder = EntityBuilder::new();
|
||||
/// builder.add(123).add("abc");
|
||||
/// let e = world.spawn(builder.build()); // builder can now be reused
|
||||
/// assert_eq!(*world.get::<i32>(e).unwrap(), 123);
|
||||
/// assert_eq!(*world.get::<&str>(e).unwrap(), "abc");
|
||||
/// ```
|
||||
pub struct EntityBuilder {
|
||||
storage: Box<[MaybeUninit<u8>]>,
|
||||
cursor: usize,
|
||||
info: Vec<(TypeInfo, usize)>,
|
||||
ids: Vec<TypeId>,
|
||||
id_set: HashSet<TypeId>,
|
||||
}
|
||||
|
||||
impl EntityBuilder {
|
||||
/// Create a builder representing an entity with no components
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
cursor: 0,
|
||||
storage: Box::new([]),
|
||||
info: Vec::new(),
|
||||
ids: Vec::new(),
|
||||
id_set: HashSet::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add `component` to the entity
|
||||
pub fn add<T: Component>(&mut self, component: T) -> &mut Self {
|
||||
if !self.id_set.insert(TypeId::of::<T>()) {
|
||||
return self;
|
||||
}
|
||||
let end = self.cursor + mem::size_of::<T>();
|
||||
if end > self.storage.len() {
|
||||
self.grow(end);
|
||||
}
|
||||
if mem::size_of::<T>() != 0 {
|
||||
unsafe {
|
||||
self.storage
|
||||
.as_mut_ptr()
|
||||
.add(self.cursor)
|
||||
.cast::<T>()
|
||||
.write_unaligned(component);
|
||||
}
|
||||
}
|
||||
self.info.push((TypeInfo::of::<T>(), self.cursor));
|
||||
self.cursor += mem::size_of::<T>();
|
||||
self
|
||||
}
|
||||
|
||||
fn grow(&mut self, min_size: usize) {
|
||||
let new_len = min_size.next_power_of_two().max(64);
|
||||
let mut new_storage = vec![MaybeUninit::uninit(); new_len].into_boxed_slice();
|
||||
new_storage[..self.cursor].copy_from_slice(&self.storage[..self.cursor]);
|
||||
self.storage = new_storage;
|
||||
}
|
||||
|
||||
/// Construct a `Bundle` suitable for spawning
|
||||
pub fn build(&mut self) -> BuiltEntity<'_> {
|
||||
self.info.sort_unstable_by_key(|x| x.0);
|
||||
self.ids.extend(self.info.iter().map(|x| x.0.id()));
|
||||
BuiltEntity { builder: self }
|
||||
}
|
||||
|
||||
/// Drop previously `add`ed components
|
||||
///
|
||||
/// The builder is cleared implicitly when an entity is built, so this doesn't usually need to
|
||||
/// be called.
|
||||
pub fn clear(&mut self) {
|
||||
self.ids.clear();
|
||||
self.id_set.clear();
|
||||
self.cursor = 0;
|
||||
let max_size = self
|
||||
.info
|
||||
.iter()
|
||||
.map(|x| x.0.layout().size())
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
let max_align = self
|
||||
.info
|
||||
.iter()
|
||||
.map(|x| x.0.layout().align())
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
unsafe {
|
||||
// Suitably aligned storage for drop
|
||||
let tmp = if max_size > 0 {
|
||||
alloc(Layout::from_size_align(max_size, max_align).unwrap()).cast()
|
||||
} else {
|
||||
max_align as *mut _
|
||||
};
|
||||
for (ty, offset) in self.info.drain(..) {
|
||||
ptr::copy_nonoverlapping(
|
||||
self.storage[offset..offset + ty.layout().size()]
|
||||
.as_ptr()
|
||||
.cast(),
|
||||
tmp,
|
||||
ty.layout().size(),
|
||||
);
|
||||
ty.drop(tmp);
|
||||
}
|
||||
if max_size > 0 {
|
||||
dealloc(tmp, Layout::from_size_align(max_size, max_align).unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for EntityBuilder {}
|
||||
unsafe impl Sync for EntityBuilder {}
|
||||
|
||||
impl Drop for EntityBuilder {
|
||||
fn drop(&mut self) {
|
||||
// Ensure buffered components aren't leaked
|
||||
self.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EntityBuilder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// The output of an `EntityBuilder`, suitable for passing to `World::spawn` or `World::insert`
|
||||
pub struct BuiltEntity<'a> {
|
||||
builder: &'a mut EntityBuilder,
|
||||
}
|
||||
|
||||
impl DynamicBundle for BuiltEntity<'_> {
|
||||
fn with_ids<T>(&self, f: impl FnOnce(&[TypeId]) -> T) -> T {
|
||||
f(&self.builder.ids)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
fn type_info(&self) -> Vec<TypeInfo> {
|
||||
self.builder.info.iter().map(|x| x.0).collect()
|
||||
}
|
||||
|
||||
unsafe fn put(self, mut f: impl FnMut(*mut u8, TypeId, usize) -> bool) {
|
||||
for (ty, offset) in self.builder.info.drain(..) {
|
||||
let ptr = self.builder.storage.as_mut_ptr().add(offset).cast();
|
||||
if !f(ptr, ty.id(), ty.layout().size()) {
|
||||
ty.drop(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for BuiltEntity<'_> {
|
||||
fn drop(&mut self) {
|
||||
// Ensures components aren't leaked if `store` was never called, and prepares the builder
|
||||
// for reuse.
|
||||
self.builder.clear();
|
||||
}
|
||||
}
|
|
@ -1,272 +0,0 @@
|
|||
use crate::{core::ComponentFlags, Archetype, Bundle, Component, QueryAccess};
|
||||
use std::{any::TypeId, marker::PhantomData, ptr::NonNull};
|
||||
|
||||
pub trait QueryFilter: Sized {
|
||||
type EntityFilter: EntityFilter;
|
||||
fn access() -> QueryAccess;
|
||||
fn get_entity_filter(archetype: &Archetype) -> Option<Self::EntityFilter>;
|
||||
}
|
||||
|
||||
pub trait EntityFilter: Sized {
|
||||
const DANGLING: Self;
|
||||
|
||||
/// # Safety
|
||||
/// This might access archetype data in an unsafe manner. In general filters should be read-only and they should only access
|
||||
/// the data they have claimed in `access()`.
|
||||
unsafe fn matches_entity(&self, _offset: usize) -> bool;
|
||||
}
|
||||
|
||||
pub struct AnyEntityFilter;
|
||||
|
||||
impl EntityFilter for AnyEntityFilter {
|
||||
const DANGLING: Self = AnyEntityFilter;
|
||||
|
||||
#[inline]
|
||||
unsafe fn matches_entity(&self, _offset: usize) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Or<T>(pub T);
|
||||
|
||||
/// Query transformer that retrieves components of type `T` that have been mutated since the start of the frame.
|
||||
/// Added components do not count as mutated.
|
||||
pub struct Mutated<T>(NonNull<ComponentFlags>, PhantomData<T>);
|
||||
|
||||
/// Query transformer that retrieves components of type `T` that have been added since the start of the frame.
|
||||
pub struct Added<T>(NonNull<ComponentFlags>, PhantomData<T>);
|
||||
|
||||
/// Query transformer that retrieves components of type `T` that have either been mutated or added since the start of the frame.
|
||||
pub struct Changed<T>(NonNull<ComponentFlags>, PhantomData<T>);
|
||||
|
||||
impl QueryFilter for () {
|
||||
type EntityFilter = AnyEntityFilter;
|
||||
|
||||
fn access() -> QueryAccess {
|
||||
QueryAccess::None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_entity_filter(_archetype: &Archetype) -> Option<Self::EntityFilter> {
|
||||
Some(AnyEntityFilter)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> QueryFilter for Added<T> {
|
||||
type EntityFilter = Self;
|
||||
|
||||
fn access() -> QueryAccess {
|
||||
QueryAccess::read::<T>()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_entity_filter(archetype: &Archetype) -> Option<Self::EntityFilter> {
|
||||
archetype
|
||||
.get_type_state(TypeId::of::<T>())
|
||||
.map(|state| Added(state.component_flags(), Default::default()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> EntityFilter for Added<T> {
|
||||
const DANGLING: Self = Added(NonNull::dangling(), PhantomData::<T>);
|
||||
|
||||
#[inline]
|
||||
unsafe fn matches_entity(&self, offset: usize) -> bool {
|
||||
(*self.0.as_ptr().add(offset)).contains(ComponentFlags::ADDED)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> QueryFilter for Mutated<T> {
|
||||
type EntityFilter = Self;
|
||||
|
||||
fn access() -> QueryAccess {
|
||||
QueryAccess::read::<T>()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_entity_filter(archetype: &Archetype) -> Option<Self::EntityFilter> {
|
||||
archetype
|
||||
.get_type_state(TypeId::of::<T>())
|
||||
.map(|state| Mutated(state.component_flags(), Default::default()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> EntityFilter for Mutated<T> {
|
||||
const DANGLING: Self = Mutated(NonNull::dangling(), PhantomData::<T>);
|
||||
|
||||
unsafe fn matches_entity(&self, offset: usize) -> bool {
|
||||
(*self.0.as_ptr().add(offset)).contains(ComponentFlags::MUTATED)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> QueryFilter for Changed<T> {
|
||||
type EntityFilter = Self;
|
||||
|
||||
fn access() -> QueryAccess {
|
||||
QueryAccess::read::<T>()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_entity_filter(archetype: &Archetype) -> Option<Self::EntityFilter> {
|
||||
archetype
|
||||
.get_type_state(TypeId::of::<T>())
|
||||
.map(|state| Changed(state.component_flags(), Default::default()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> EntityFilter for Changed<T> {
|
||||
const DANGLING: Self = Changed(NonNull::dangling(), PhantomData::<T>);
|
||||
|
||||
#[inline]
|
||||
unsafe fn matches_entity(&self, offset: usize) -> bool {
|
||||
let flags = *self.0.as_ptr().add(offset);
|
||||
flags.contains(ComponentFlags::ADDED) || flags.contains(ComponentFlags::MUTATED)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Without<T>(PhantomData<T>);
|
||||
|
||||
impl<T: Component> QueryFilter for Without<T> {
|
||||
type EntityFilter = AnyEntityFilter;
|
||||
|
||||
fn access() -> QueryAccess {
|
||||
QueryAccess::without::<T>(QueryAccess::None)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_entity_filter(archetype: &Archetype) -> Option<Self::EntityFilter> {
|
||||
if archetype.has_type(TypeId::of::<T>()) {
|
||||
None
|
||||
} else {
|
||||
Some(AnyEntityFilter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct With<T>(PhantomData<T>);
|
||||
|
||||
impl<T: Component> QueryFilter for With<T> {
|
||||
type EntityFilter = AnyEntityFilter;
|
||||
|
||||
fn access() -> QueryAccess {
|
||||
QueryAccess::with::<T>(QueryAccess::None)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_entity_filter(archetype: &Archetype) -> Option<Self::EntityFilter> {
|
||||
if archetype.has_type(TypeId::of::<T>()) {
|
||||
Some(AnyEntityFilter)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WithType<T: Bundle>(PhantomData<T>);
|
||||
|
||||
impl<T: Bundle> QueryFilter for WithType<T> {
|
||||
type EntityFilter = AnyEntityFilter;
|
||||
|
||||
fn access() -> QueryAccess {
|
||||
QueryAccess::union(
|
||||
T::static_type_info()
|
||||
.iter()
|
||||
.map(|info| QueryAccess::With(info.id(), Box::new(QueryAccess::None)))
|
||||
.collect::<Vec<QueryAccess>>(),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_entity_filter(archetype: &Archetype) -> Option<Self::EntityFilter> {
|
||||
if T::static_type_info()
|
||||
.iter()
|
||||
.all(|info| archetype.has_type(info.id()))
|
||||
{
|
||||
Some(AnyEntityFilter)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_query_filter_tuple {
|
||||
($($filter: ident),*) => {
|
||||
#[allow(unused_variables)]
|
||||
#[allow(non_snake_case)]
|
||||
impl<$($filter: QueryFilter),*> QueryFilter for ($($filter,)*) {
|
||||
type EntityFilter = ($($filter::EntityFilter,)*);
|
||||
|
||||
fn access() -> QueryAccess {
|
||||
QueryAccess::union(vec![
|
||||
$($filter::access(),)+
|
||||
])
|
||||
}
|
||||
|
||||
fn get_entity_filter(archetype: &Archetype) -> Option<Self::EntityFilter> {
|
||||
Some(($($filter::get_entity_filter(archetype)?,)*))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[allow(non_snake_case)]
|
||||
impl<$($filter: EntityFilter),*> EntityFilter for ($($filter,)*) {
|
||||
const DANGLING: Self = ($($filter::DANGLING,)*);
|
||||
unsafe fn matches_entity(&self, offset: usize) -> bool {
|
||||
let ($($filter,)*) = self;
|
||||
true $(&& $filter.matches_entity(offset))*
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[allow(non_snake_case)]
|
||||
impl<$($filter: QueryFilter),*> QueryFilter for Or<($($filter,)*)> {
|
||||
type EntityFilter = Or<($(Option<<$filter as QueryFilter>::EntityFilter>,)*)>;
|
||||
fn access() -> QueryAccess {
|
||||
QueryAccess::union(vec![
|
||||
$(QueryAccess::Optional(Box::new($filter::access())),)+
|
||||
])
|
||||
}
|
||||
|
||||
fn get_entity_filter(archetype: &Archetype) -> Option<Self::EntityFilter> {
|
||||
let mut matches_something = false;
|
||||
$(
|
||||
let $filter = $filter::get_entity_filter(archetype);
|
||||
matches_something = matches_something || $filter.is_some();
|
||||
)*
|
||||
if matches_something {
|
||||
Some(Or(($($filter,)*)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#[allow(unused_variables)]
|
||||
#[allow(non_snake_case)]
|
||||
impl<$($filter: EntityFilter),*> EntityFilter for Or<($(Option<$filter>,)*)> {
|
||||
const DANGLING: Self = Or(($(Some($filter::DANGLING),)*));
|
||||
unsafe fn matches_entity(&self, offset: usize) -> bool {
|
||||
let Or(($($filter,)*)) = self;
|
||||
false $(|| $filter.as_ref().map_or(false, |filter|filter.matches_entity(offset)))*
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_query_filter_tuple!(A);
|
||||
impl_query_filter_tuple!(A, B);
|
||||
impl_query_filter_tuple!(A, B, C);
|
||||
impl_query_filter_tuple!(A, B, C, D);
|
||||
impl_query_filter_tuple!(A, B, C, D, E);
|
||||
impl_query_filter_tuple!(A, B, C, D, E, F);
|
||||
impl_query_filter_tuple!(A, B, C, D, E, F, G);
|
||||
impl_query_filter_tuple!(A, B, C, D, E, F, G, H);
|
||||
impl_query_filter_tuple!(A, B, C, D, E, F, G, H, I);
|
||||
impl_query_filter_tuple!(A, B, C, D, E, F, G, H, I, J);
|
||||
impl_query_filter_tuple!(A, B, C, D, E, F, G, H, I, J, K);
|
||||
impl_query_filter_tuple!(A, B, C, D, E, F, G, H, I, J, K, L);
|
||||
impl_query_filter_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M);
|
||||
impl_query_filter_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
|
||||
impl_query_filter_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
|
||||
impl_query_filter_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
|
|
@ -1,64 +0,0 @@
|
|||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// modified by Bevy contributors
|
||||
|
||||
/// Imagine macro parameters, but more like those Russian dolls.
|
||||
///
|
||||
/// Calls m!(A, B, C), m!(A, B), m!(B), and m!() for i.e. (m, A, B, C)
|
||||
/// where m is any macro, for any number of parameters.
|
||||
#[macro_export]
|
||||
macro_rules! smaller_tuples_too {
|
||||
($m: ident, $ty: ident) => {
|
||||
$m!{$ty}
|
||||
$m!{}
|
||||
};
|
||||
($m: ident, $ty: ident, $($tt: ident),*) => {
|
||||
$m!{$ty, $($tt),*}
|
||||
smaller_tuples_too!{$m, $($tt),*}
|
||||
};
|
||||
}
|
||||
|
||||
mod access;
|
||||
mod archetype;
|
||||
mod borrow;
|
||||
mod bundle;
|
||||
mod entities;
|
||||
mod entity_builder;
|
||||
mod entity_map;
|
||||
mod filter;
|
||||
mod query;
|
||||
mod serde;
|
||||
mod world;
|
||||
mod world_builder;
|
||||
|
||||
pub use access::{ArchetypeComponent, CondensedTypeAccess, QueryAccess, TypeAccess};
|
||||
pub use archetype::{Archetype, ComponentFlags, TypeState};
|
||||
pub use borrow::{AtomicBorrow, Ref, RefMut};
|
||||
pub use bundle::{Bundle, DynamicBundle, MissingComponent};
|
||||
pub use entities::{Entity, EntityReserver, Location, NoSuchEntity};
|
||||
pub use entity_builder::{BuiltEntity, EntityBuilder};
|
||||
pub use entity_map::*;
|
||||
pub use filter::{Added, Changed, EntityFilter, Mutated, Or, QueryFilter, With, Without};
|
||||
pub use query::{Batch, BatchedIter, Flags, Mut, QueryIter, ReadOnlyFetch, WorldQuery};
|
||||
pub use world::{ArchetypesGeneration, Component, ComponentError, SpawnBatchIter, World};
|
||||
pub use world_builder::*;
|
||||
|
||||
// Unstable implementation details needed by the macros
|
||||
#[doc(hidden)]
|
||||
pub use archetype::TypeInfo;
|
||||
#[doc(hidden)]
|
||||
pub use bevy_utils;
|
||||
#[doc(hidden)]
|
||||
pub use query::Fetch;
|
|
@ -1,775 +0,0 @@
|
|||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// modified by Bevy contributors
|
||||
|
||||
use super::{Archetype, Component, Entity, MissingComponent, QueryAccess, QueryFilter};
|
||||
use crate::{ComponentFlags, EntityFilter};
|
||||
use std::{
|
||||
marker::PhantomData,
|
||||
ops::{Deref, DerefMut},
|
||||
ptr::NonNull,
|
||||
vec,
|
||||
};
|
||||
|
||||
/// A collection of component types to fetch from a `World`
|
||||
pub trait WorldQuery {
|
||||
#[doc(hidden)]
|
||||
type Fetch: for<'a> Fetch<'a>;
|
||||
}
|
||||
|
||||
/// A fetch that is read only. This should only be implemented for read-only fetches.
|
||||
pub unsafe trait ReadOnlyFetch {}
|
||||
|
||||
/// Streaming iterators over contiguous homogeneous ranges of components
|
||||
pub trait Fetch<'a>: Sized {
|
||||
/// Type of value to be fetched
|
||||
type Item;
|
||||
|
||||
/// A value on which `get` may never be called
|
||||
#[allow(clippy::declare_interior_mutable_const)] // no const fn in traits
|
||||
const DANGLING: Self;
|
||||
|
||||
/// How this query will access `archetype`, if at all
|
||||
fn access() -> QueryAccess;
|
||||
|
||||
/// Construct a `Fetch` for `archetype` if it should be traversed
|
||||
///
|
||||
/// # Safety
|
||||
/// `offset` must be in bounds of `archetype`
|
||||
unsafe fn get(archetype: &'a Archetype, offset: usize) -> Option<Self>;
|
||||
|
||||
/// Access the `n`th item in this archetype without bounds checking
|
||||
///
|
||||
/// # Safety
|
||||
/// - Must only be called after `borrow`
|
||||
/// - `release` must not be called while `'a` is still live
|
||||
/// - Bounds-checking must be performed externally
|
||||
/// - Any resulting borrows must be legal (e.g. no &mut to something another iterator might access)
|
||||
unsafe fn fetch(&self, n: usize) -> Self::Item;
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct EntityFetch(NonNull<Entity>);
|
||||
unsafe impl ReadOnlyFetch for EntityFetch {}
|
||||
|
||||
impl WorldQuery for Entity {
|
||||
type Fetch = EntityFetch;
|
||||
}
|
||||
|
||||
impl<'a> Fetch<'a> for EntityFetch {
|
||||
type Item = Entity;
|
||||
|
||||
const DANGLING: Self = Self(NonNull::dangling());
|
||||
|
||||
#[inline]
|
||||
unsafe fn get(archetype: &'a Archetype, offset: usize) -> Option<Self> {
|
||||
Some(EntityFetch(NonNull::new_unchecked(
|
||||
archetype.entities().as_ptr().add(offset),
|
||||
)))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn fetch(&self, n: usize) -> Self::Item {
|
||||
*self.0.as_ptr().add(n)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn access() -> QueryAccess {
|
||||
QueryAccess::None
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Component> WorldQuery for &'a T {
|
||||
type Fetch = FetchRead<T>;
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct FetchRead<T>(NonNull<T>);
|
||||
|
||||
unsafe impl<T> ReadOnlyFetch for FetchRead<T> {}
|
||||
|
||||
impl<'a, T: Component> Fetch<'a> for FetchRead<T> {
|
||||
type Item = &'a T;
|
||||
|
||||
const DANGLING: Self = Self(NonNull::dangling());
|
||||
|
||||
unsafe fn get(archetype: &'a Archetype, offset: usize) -> Option<Self> {
|
||||
archetype
|
||||
.get::<T>()
|
||||
.map(|x| Self(NonNull::new_unchecked(x.as_ptr().add(offset))))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn fetch(&self, n: usize) -> &'a T {
|
||||
&*self.0.as_ptr().add(n)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn access() -> QueryAccess {
|
||||
QueryAccess::read::<T>()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Component> WorldQuery for &'a mut T {
|
||||
type Fetch = FetchMut<T>;
|
||||
}
|
||||
|
||||
impl<T: WorldQuery> WorldQuery for Option<T> {
|
||||
type Fetch = TryFetch<T::Fetch>;
|
||||
}
|
||||
|
||||
/// Flags on component `T` that happened since the start of the frame.
|
||||
#[derive(Clone)]
|
||||
pub struct Flags<T: Component> {
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
with: bool,
|
||||
added: bool,
|
||||
mutated: bool,
|
||||
}
|
||||
impl<T: Component> std::fmt::Debug for Flags<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Flags")
|
||||
.field("with", &self.with)
|
||||
.field("added", &self.added)
|
||||
.field("mutated", &self.mutated)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> Flags<T> {
|
||||
/// Does the entity have this component
|
||||
pub fn with(&self) -> bool {
|
||||
self.with
|
||||
}
|
||||
|
||||
/// Has this component been added since the start of the frame.
|
||||
pub fn added(&self) -> bool {
|
||||
self.added
|
||||
}
|
||||
|
||||
/// Has this component been mutated since the start of the frame.
|
||||
pub fn mutated(&self) -> bool {
|
||||
self.mutated
|
||||
}
|
||||
|
||||
/// Has this component been either mutated or added since the start of the frame.
|
||||
pub fn changed(&self) -> bool {
|
||||
self.added || self.mutated
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> WorldQuery for Flags<T> {
|
||||
type Fetch = FlagsFetch<T>;
|
||||
}
|
||||
|
||||
/// Unique borrow of an entity's component
|
||||
pub struct Mut<'a, T: Component> {
|
||||
pub(crate) value: &'a mut T,
|
||||
pub(crate) flags: &'a mut ComponentFlags,
|
||||
}
|
||||
|
||||
impl<'a, T: Component> Mut<'a, T> {
|
||||
/// Creates a new mutable reference to a component. This is unsafe because the index bounds are not checked.
|
||||
///
|
||||
/// # Safety
|
||||
/// This doesn't check the bounds of index in archetype
|
||||
pub unsafe fn new(archetype: &'a Archetype, index: usize) -> Result<Self, MissingComponent> {
|
||||
let (target, type_state) = archetype
|
||||
.get_with_type_state::<T>()
|
||||
.ok_or_else(MissingComponent::new::<T>)?;
|
||||
Ok(Self {
|
||||
value: &mut *target.as_ptr().add(index),
|
||||
flags: &mut *type_state.component_flags().as_ptr().add(index),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Component> Send for Mut<'_, T> {}
|
||||
unsafe impl<T: Component> Sync for Mut<'_, T> {}
|
||||
|
||||
impl<'a, T: Component> Deref for Mut<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &T {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Component> DerefMut for Mut<'a, T> {
|
||||
#[inline]
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
self.flags.insert(ComponentFlags::MUTATED);
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Component + core::fmt::Debug> core::fmt::Debug for Mut<'a, T> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
self.value.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Component> WorldQuery for Mut<'a, T> {
|
||||
type Fetch = FetchMut<T>;
|
||||
}
|
||||
#[doc(hidden)]
|
||||
pub struct FetchMut<T>(NonNull<T>, NonNull<ComponentFlags>);
|
||||
|
||||
impl<'a, T: Component> Fetch<'a> for FetchMut<T> {
|
||||
type Item = Mut<'a, T>;
|
||||
|
||||
const DANGLING: Self = Self(NonNull::dangling(), NonNull::dangling());
|
||||
|
||||
unsafe fn get(archetype: &'a Archetype, offset: usize) -> Option<Self> {
|
||||
archetype
|
||||
.get_with_type_state::<T>()
|
||||
.map(|(components, type_state)| {
|
||||
Self(
|
||||
NonNull::new_unchecked(components.as_ptr().add(offset)),
|
||||
NonNull::new_unchecked(type_state.component_flags().as_ptr().add(offset)),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn fetch(&self, n: usize) -> Mut<'a, T> {
|
||||
Mut {
|
||||
value: &mut *self.0.as_ptr().add(n),
|
||||
flags: &mut *self.1.as_ptr().add(n),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn access() -> QueryAccess {
|
||||
QueryAccess::write::<T>()
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct TryFetch<T>(Option<T>);
|
||||
unsafe impl<T> ReadOnlyFetch for TryFetch<T> where T: ReadOnlyFetch {}
|
||||
|
||||
impl<'a, T: Fetch<'a>> Fetch<'a> for TryFetch<T> {
|
||||
type Item = Option<T::Item>;
|
||||
|
||||
const DANGLING: Self = Self(None);
|
||||
|
||||
#[inline]
|
||||
fn access() -> QueryAccess {
|
||||
QueryAccess::optional(T::access())
|
||||
}
|
||||
|
||||
unsafe fn get(archetype: &'a Archetype, offset: usize) -> Option<Self> {
|
||||
Some(Self(T::get(archetype, offset)))
|
||||
}
|
||||
|
||||
unsafe fn fetch(&self, n: usize) -> Option<T::Item> {
|
||||
Some(self.0.as_ref()?.fetch(n))
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct FlagsFetch<T>(Option<NonNull<ComponentFlags>>, PhantomData<T>);
|
||||
unsafe impl<T> ReadOnlyFetch for FlagsFetch<T> {}
|
||||
|
||||
impl<'a, T: Component> Fetch<'a> for FlagsFetch<T> {
|
||||
type Item = Flags<T>;
|
||||
|
||||
const DANGLING: Self = Self(None, PhantomData::<T>);
|
||||
|
||||
#[inline]
|
||||
fn access() -> QueryAccess {
|
||||
QueryAccess::read::<T>()
|
||||
}
|
||||
|
||||
unsafe fn get(archetype: &'a Archetype, offset: usize) -> Option<Self> {
|
||||
Some(Self(
|
||||
archetype
|
||||
.get_type_state(std::any::TypeId::of::<T>())
|
||||
.map(|type_state| {
|
||||
NonNull::new_unchecked(type_state.component_flags().as_ptr().add(offset))
|
||||
}),
|
||||
PhantomData::<T>,
|
||||
))
|
||||
}
|
||||
|
||||
unsafe fn fetch(&self, n: usize) -> Self::Item {
|
||||
if let Some(flags) = self.0.as_ref() {
|
||||
let flags = *flags.as_ptr().add(n);
|
||||
Self::Item {
|
||||
_marker: PhantomData::<T>,
|
||||
with: true,
|
||||
added: flags.contains(ComponentFlags::ADDED),
|
||||
mutated: flags.contains(ComponentFlags::MUTATED),
|
||||
}
|
||||
} else {
|
||||
Self::Item {
|
||||
_marker: PhantomData::<T>,
|
||||
with: false,
|
||||
added: false,
|
||||
mutated: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ChunkInfo<Q: WorldQuery, F: QueryFilter> {
|
||||
fetch: Q::Fetch,
|
||||
filter: F::EntityFilter,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
/// Iterator over the set of entities with the components in `Q`
|
||||
pub struct QueryIter<'w, Q: WorldQuery, F: QueryFilter> {
|
||||
archetypes: &'w [Archetype],
|
||||
archetype_index: usize,
|
||||
chunk_info: ChunkInfo<Q, F>,
|
||||
chunk_position: usize,
|
||||
}
|
||||
|
||||
impl<'w, Q: WorldQuery, F: QueryFilter> QueryIter<'w, Q, F> {
|
||||
const EMPTY: ChunkInfo<Q, F> = ChunkInfo {
|
||||
fetch: Q::Fetch::DANGLING,
|
||||
len: 0,
|
||||
filter: F::EntityFilter::DANGLING,
|
||||
};
|
||||
|
||||
/// Creates a new QueryIter
|
||||
#[inline]
|
||||
pub(crate) fn new(archetypes: &'w [Archetype]) -> Self {
|
||||
Self {
|
||||
archetypes,
|
||||
archetype_index: 0,
|
||||
chunk_info: Self::EMPTY,
|
||||
chunk_position: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w, Q: WorldQuery, F: QueryFilter> Iterator for QueryIter<'w, Q, F> {
|
||||
type Item = <Q::Fetch as Fetch<'w>>::Item;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
unsafe {
|
||||
loop {
|
||||
if self.chunk_position == self.chunk_info.len {
|
||||
let archetype = self.archetypes.get(self.archetype_index)?;
|
||||
self.archetype_index += 1;
|
||||
self.chunk_position = 0;
|
||||
self.chunk_info = Q::Fetch::get(archetype, 0)
|
||||
.and_then(|fetch| {
|
||||
Some(ChunkInfo {
|
||||
fetch,
|
||||
len: archetype.len(),
|
||||
filter: F::get_entity_filter(archetype)?,
|
||||
})
|
||||
})
|
||||
.unwrap_or(Self::EMPTY);
|
||||
continue;
|
||||
}
|
||||
|
||||
if !self
|
||||
.chunk_info
|
||||
.filter
|
||||
.matches_entity(self.chunk_position as usize)
|
||||
{
|
||||
self.chunk_position += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
let item = Some(self.chunk_info.fetch.fetch(self.chunk_position as usize));
|
||||
self.chunk_position += 1;
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if the Fetch is an UnfilteredFetch, then we can cheaply compute the length of the query by getting
|
||||
// the length of each matching archetype
|
||||
impl<'w, Q: WorldQuery> ExactSizeIterator for QueryIter<'w, Q, ()> {
|
||||
fn len(&self) -> usize {
|
||||
self.archetypes
|
||||
.iter()
|
||||
.filter(|&archetype| unsafe { Q::Fetch::get(archetype, 0).is_some() })
|
||||
.map(|x| x.len())
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
|
||||
struct ChunkIter<Q: WorldQuery, F: QueryFilter> {
|
||||
fetch: Q::Fetch,
|
||||
filter: F::EntityFilter,
|
||||
position: usize,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl<Q: WorldQuery, F: QueryFilter> ChunkIter<Q, F> {
|
||||
unsafe fn next<'a>(&mut self) -> Option<<Q::Fetch as Fetch<'a>>::Item> {
|
||||
loop {
|
||||
if self.position == self.len {
|
||||
return None;
|
||||
}
|
||||
|
||||
if !self.filter.matches_entity(self.position as usize) {
|
||||
self.position += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
let item = Some(self.fetch.fetch(self.position as usize));
|
||||
self.position += 1;
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Batched version of `QueryIter`
|
||||
pub struct BatchedIter<'w, Q: WorldQuery, F: QueryFilter> {
|
||||
archetypes: &'w [Archetype],
|
||||
archetype_index: usize,
|
||||
batch_size: usize,
|
||||
batch: usize,
|
||||
_marker: PhantomData<(Q, F)>,
|
||||
}
|
||||
|
||||
impl<'w, Q: WorldQuery, F: QueryFilter> BatchedIter<'w, Q, F> {
|
||||
pub(crate) fn new(archetypes: &'w [Archetype], batch_size: usize) -> Self {
|
||||
Self {
|
||||
archetypes,
|
||||
archetype_index: 0,
|
||||
batch_size,
|
||||
batch: 0,
|
||||
_marker: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'w, Q: WorldQuery, F: QueryFilter> Send for BatchedIter<'w, Q, F> {}
|
||||
unsafe impl<'w, Q: WorldQuery, F: QueryFilter> Sync for BatchedIter<'w, Q, F> {}
|
||||
|
||||
impl<'w, Q: WorldQuery, F: QueryFilter> Iterator for BatchedIter<'w, Q, F> {
|
||||
type Item = Batch<'w, Q, F>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
let archetype = self.archetypes.get(self.archetype_index)?;
|
||||
let offset = self.batch_size * self.batch;
|
||||
if offset >= archetype.len() {
|
||||
self.archetype_index += 1;
|
||||
self.batch = 0;
|
||||
continue;
|
||||
}
|
||||
if let (Some(fetch), Some(filter)) = (
|
||||
unsafe { Q::Fetch::get(archetype, offset) },
|
||||
F::get_entity_filter(archetype),
|
||||
) {
|
||||
self.batch += 1;
|
||||
return Some(Batch {
|
||||
_marker: PhantomData,
|
||||
state: ChunkIter {
|
||||
fetch,
|
||||
position: 0,
|
||||
len: self.batch_size.min(archetype.len() - offset),
|
||||
filter,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
self.archetype_index += 1;
|
||||
debug_assert_eq!(
|
||||
self.batch, 0,
|
||||
"query fetch should always reject at the first batch or not at all"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A sequence of entities yielded by `BatchedIter`
|
||||
pub struct Batch<'q, Q: WorldQuery, F: QueryFilter> {
|
||||
_marker: PhantomData<&'q ()>,
|
||||
state: ChunkIter<Q, F>,
|
||||
}
|
||||
|
||||
impl<'q, 'w, Q: WorldQuery, F: QueryFilter> Iterator for Batch<'q, Q, F> {
|
||||
type Item = <Q::Fetch as Fetch<'q>>::Item;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let components = unsafe { self.state.next()? };
|
||||
Some(components)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'q, Q: WorldQuery, F: QueryFilter> Send for Batch<'q, Q, F> {}
|
||||
unsafe impl<'q, Q: WorldQuery, F: QueryFilter> Sync for Batch<'q, Q, F> {}
|
||||
|
||||
macro_rules! tuple_impl {
|
||||
($($name: ident),*) => {
|
||||
impl<'a, $($name: Fetch<'a>),*> Fetch<'a> for ($($name,)*) {
|
||||
type Item = ($($name::Item,)*);
|
||||
const DANGLING: Self = ($($name::DANGLING,)*);
|
||||
|
||||
#[allow(unused_variables, unused_mut)]
|
||||
fn access() -> QueryAccess {
|
||||
QueryAccess::union(vec![
|
||||
$($name::access(),)*
|
||||
])
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
unsafe fn get(archetype: &'a Archetype, offset: usize) -> Option<Self> {
|
||||
Some(($($name::get(archetype, offset)?,)*))
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
unsafe fn fetch(&self, n: usize) -> Self::Item {
|
||||
#[allow(non_snake_case)]
|
||||
let ($($name,)*) = self;
|
||||
($($name.fetch(n),)*)
|
||||
}
|
||||
}
|
||||
|
||||
impl<$($name: WorldQuery),*> WorldQuery for ($($name,)*) {
|
||||
type Fetch = ($($name::Fetch,)*);
|
||||
}
|
||||
|
||||
unsafe impl<$($name: ReadOnlyFetch),*> ReadOnlyFetch for ($($name,)*) {}
|
||||
};
|
||||
}
|
||||
|
||||
smaller_tuples_too!(tuple_impl, O, N, M, L, K, J, I, H, G, F, E, D, C, B, A);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::core::{Added, Changed, Component, Entity, Flags, Mutated, Or, QueryFilter, World};
|
||||
use std::{vec, vec::Vec};
|
||||
|
||||
use super::Mut;
|
||||
|
||||
struct A(usize);
|
||||
struct B(usize);
|
||||
struct C;
|
||||
|
||||
#[test]
|
||||
fn added_queries() {
|
||||
let mut world = World::default();
|
||||
let e1 = world.spawn((A(0),));
|
||||
|
||||
fn get_added<Com: Component>(world: &World) -> Vec<Entity> {
|
||||
world
|
||||
.query_filtered::<Entity, Added<Com>>()
|
||||
.collect::<Vec<Entity>>()
|
||||
}
|
||||
|
||||
assert_eq!(get_added::<A>(&world), vec![e1]);
|
||||
world.insert(e1, (B(0),)).unwrap();
|
||||
assert_eq!(get_added::<A>(&world), vec![e1]);
|
||||
assert_eq!(get_added::<B>(&world), vec![e1]);
|
||||
|
||||
world.clear_trackers();
|
||||
assert!(get_added::<A>(&world).is_empty());
|
||||
let e2 = world.spawn((A(1), B(1)));
|
||||
assert_eq!(get_added::<A>(&world), vec![e2]);
|
||||
assert_eq!(get_added::<B>(&world), vec![e2]);
|
||||
|
||||
let added = world
|
||||
.query_filtered::<Entity, (Added<A>, Added<B>)>()
|
||||
.collect::<Vec<Entity>>();
|
||||
assert_eq!(added, vec![e2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mutated_trackers() {
|
||||
let mut world = World::default();
|
||||
let e1 = world.spawn((A(0), B(0)));
|
||||
let e2 = world.spawn((A(0), B(0)));
|
||||
let e3 = world.spawn((A(0), B(0)));
|
||||
world.spawn((A(0), B));
|
||||
|
||||
for (i, mut a) in world.query_mut::<Mut<A>>().enumerate() {
|
||||
if i % 2 == 0 {
|
||||
a.0 += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn get_filtered<F: QueryFilter>(world: &mut World) -> Vec<Entity> {
|
||||
world.query_filtered::<Entity, F>().collect::<Vec<Entity>>()
|
||||
}
|
||||
|
||||
assert_eq!(get_filtered::<Mutated<A>>(&mut world), vec![e1, e3]);
|
||||
|
||||
// ensure changing an entity's archetypes also moves its mutated state
|
||||
world.insert(e1, (C,)).unwrap();
|
||||
|
||||
assert_eq!(get_filtered::<Mutated<A>>(&mut world), vec![e3, e1], "changed entities list should not change (although the order will due to archetype moves)");
|
||||
|
||||
// spawning a new A entity should not change existing mutated state
|
||||
world.insert(e1, (A(0), B)).unwrap();
|
||||
assert_eq!(
|
||||
get_filtered::<Mutated<A>>(&mut world),
|
||||
vec![e3, e1],
|
||||
"changed entities list should not change"
|
||||
);
|
||||
|
||||
// removing an unchanged entity should not change mutated state
|
||||
world.despawn(e2).unwrap();
|
||||
assert_eq!(
|
||||
get_filtered::<Mutated<A>>(&mut world),
|
||||
vec![e3, e1],
|
||||
"changed entities list should not change"
|
||||
);
|
||||
|
||||
// removing a changed entity should remove it from enumeration
|
||||
world.despawn(e1).unwrap();
|
||||
assert_eq!(
|
||||
get_filtered::<Mutated<A>>(&mut world),
|
||||
vec![e3],
|
||||
"e1 should no longer be returned"
|
||||
);
|
||||
|
||||
world.clear_trackers();
|
||||
|
||||
assert!(get_filtered::<Mutated<A>>(&mut world).is_empty());
|
||||
|
||||
let e4 = world.spawn(());
|
||||
|
||||
world.insert_one(e4, A(0)).unwrap();
|
||||
assert!(get_filtered::<Mutated<A>>(&mut world).is_empty());
|
||||
assert_eq!(get_filtered::<Added<A>>(&mut world), vec![e4]);
|
||||
|
||||
world.insert_one(e4, A(1)).unwrap();
|
||||
assert_eq!(get_filtered::<Mutated<A>>(&mut world), vec![e4]);
|
||||
|
||||
world.clear_trackers();
|
||||
|
||||
// ensure inserting multiple components set mutated state for
|
||||
// already existing components and set added state for
|
||||
// non existing components even when changing archetype.
|
||||
world.insert(e4, (A(0), B(0))).unwrap();
|
||||
|
||||
assert!(get_filtered::<Added<A>>(&mut world).is_empty());
|
||||
assert_eq!(get_filtered::<Mutated<A>>(&mut world), vec![e4]);
|
||||
assert_eq!(get_filtered::<Added<B>>(&mut world), vec![e4]);
|
||||
assert!(get_filtered::<Mutated<B>>(&mut world).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_mutated_query() {
|
||||
let mut world = World::default();
|
||||
world.spawn((A(0), B(0)));
|
||||
let e2 = world.spawn((A(0), B(0)));
|
||||
world.spawn((A(0), B(0)));
|
||||
|
||||
for mut a in world.query_mut::<Mut<A>>() {
|
||||
a.0 += 1;
|
||||
}
|
||||
|
||||
for mut b in world.query_mut::<Mut<B>>().skip(1).take(1) {
|
||||
b.0 += 1;
|
||||
}
|
||||
|
||||
let a_b_mutated = world
|
||||
.query_filtered_mut::<Entity, (Mutated<A>, Mutated<B>)>()
|
||||
.collect::<Vec<Entity>>();
|
||||
assert_eq!(a_b_mutated, vec![e2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn or_mutated_query() {
|
||||
let mut world = World::default();
|
||||
let e1 = world.spawn((A(0), B(0)));
|
||||
let e2 = world.spawn((A(0), B(0)));
|
||||
let e3 = world.spawn((A(0), B(0)));
|
||||
let _e4 = world.spawn((A(0), B(0)));
|
||||
|
||||
// Mutate A in entities e1 and e2
|
||||
for mut a in world.query_mut::<Mut<A>>().take(2) {
|
||||
a.0 += 1;
|
||||
}
|
||||
// Mutate B in entities e2 and e3
|
||||
for mut b in world.query_mut::<Mut<B>>().skip(1).take(2) {
|
||||
b.0 += 1;
|
||||
}
|
||||
|
||||
let a_b_mutated = world
|
||||
.query_filtered_mut::<Entity, Or<(Mutated<A>, Mutated<B>)>>()
|
||||
.collect::<Vec<Entity>>();
|
||||
// e1 has mutated A, e3 has mutated B, e2 has mutated A and B, _e4 has no mutated component
|
||||
assert_eq!(a_b_mutated, vec![e1, e2, e3]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn changed_query() {
|
||||
let mut world = World::default();
|
||||
let e1 = world.spawn((A(0), B(0)));
|
||||
|
||||
fn get_changed(world: &World) -> Vec<Entity> {
|
||||
world
|
||||
.query_filtered::<Entity, Changed<A>>()
|
||||
.collect::<Vec<Entity>>()
|
||||
}
|
||||
assert_eq!(get_changed(&world), vec![e1]);
|
||||
world.clear_trackers();
|
||||
assert_eq!(get_changed(&world), vec![]);
|
||||
*world.get_mut(e1).unwrap() = A(1);
|
||||
assert_eq!(get_changed(&world), vec![e1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flags_query() {
|
||||
let mut world = World::default();
|
||||
let e1 = world.spawn((A(0), B(0)));
|
||||
world.spawn((B(0),));
|
||||
|
||||
fn get_flags(world: &World) -> Vec<Flags<A>> {
|
||||
world.query::<Flags<A>>().collect::<Vec<Flags<A>>>()
|
||||
}
|
||||
let flags = get_flags(&world);
|
||||
assert!(flags[0].with());
|
||||
assert!(flags[0].added());
|
||||
assert!(!flags[0].mutated());
|
||||
assert!(flags[0].changed());
|
||||
assert!(!flags[1].with());
|
||||
assert!(!flags[1].added());
|
||||
assert!(!flags[1].mutated());
|
||||
assert!(!flags[1].changed());
|
||||
world.clear_trackers();
|
||||
let flags = get_flags(&world);
|
||||
assert!(flags[0].with());
|
||||
assert!(!flags[0].added());
|
||||
assert!(!flags[0].mutated());
|
||||
assert!(!flags[0].changed());
|
||||
*world.get_mut(e1).unwrap() = A(1);
|
||||
let flags = get_flags(&world);
|
||||
assert!(flags[0].with());
|
||||
assert!(!flags[0].added());
|
||||
assert!(flags[0].mutated());
|
||||
assert!(flags[0].changed());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exact_size_query() {
|
||||
let mut world = World::default();
|
||||
world.spawn((A(0), B(0)));
|
||||
world.spawn((A(0), B(0)));
|
||||
world.spawn((C,));
|
||||
|
||||
assert_eq!(world.query::<(&A, &B)>().len(), 2);
|
||||
// the following example shouldn't compile because Changed<A> is not an UnfilteredFetch
|
||||
// assert_eq!(world.query::<(Changed<A>, &B)>().len(), 2);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,78 +0,0 @@
|
|||
use crate::{Bundle, Component, DynamicBundle, Entity, World};
|
||||
|
||||
/// Converts a reference to `Self` to a [WorldBuilder]
|
||||
pub trait WorldBuilderSource {
|
||||
fn build(&mut self) -> WorldBuilder;
|
||||
}
|
||||
|
||||
impl WorldBuilderSource for World {
|
||||
fn build(&mut self) -> WorldBuilder {
|
||||
WorldBuilder {
|
||||
world: self,
|
||||
current_entity: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Modify a [World] using the builder pattern
|
||||
#[derive(Debug)]
|
||||
pub struct WorldBuilder<'a> {
|
||||
pub world: &'a mut World,
|
||||
pub current_entity: Option<Entity>,
|
||||
}
|
||||
|
||||
impl<'a> WorldBuilder<'a> {
|
||||
pub fn entity(&mut self) -> &mut Self {
|
||||
self.current_entity = Some(self.world.reserve_entity());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_entity(&mut self, entity: Entity) -> &mut Self {
|
||||
self.current_entity = Some(entity);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with<T>(&mut self, component: T) -> &mut Self
|
||||
where
|
||||
T: Component,
|
||||
{
|
||||
self.world
|
||||
.insert_one(self.current_entity.expect("Cannot add component because the 'current entity' is not set. You should spawn an entity first."), component)
|
||||
.unwrap();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_bundle(&mut self, bundle: impl DynamicBundle) -> &mut Self {
|
||||
self.world
|
||||
.insert(self.current_entity.expect("Cannot add bundle because the 'current entity' is not set. You should spawn an entity first."), bundle)
|
||||
.unwrap();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn spawn_batch<I>(&mut self, bundle_iter: I) -> &mut Self
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: Bundle,
|
||||
{
|
||||
self.world.spawn_batch(bundle_iter);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn spawn(&mut self, bundle: impl DynamicBundle) -> &mut Self {
|
||||
self.current_entity = Some(self.world.spawn(bundle));
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn current_entity(&self) -> Option<Entity> {
|
||||
self.current_entity
|
||||
}
|
||||
|
||||
pub fn for_current_entity(&mut self, f: impl FnOnce(Entity)) -> &mut Self {
|
||||
let current_entity = self
|
||||
.current_entity
|
||||
.expect("The 'current entity' is not set. You should spawn an entity first.");
|
||||
f(current_entity);
|
||||
self
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
use crate::Entity;
|
||||
use crate::entity::Entity;
|
||||
use bevy_utils::HashMap;
|
||||
use std::collections::hash_map::Entry;
|
||||
use thiserror::Error;
|
489
crates/bevy_ecs/src/entity/mod.rs
Normal file
489
crates/bevy_ecs/src/entity/mod.rs
Normal file
|
@ -0,0 +1,489 @@
|
|||
mod map_entities;
|
||||
mod serde;
|
||||
|
||||
pub use self::serde::*;
|
||||
pub use map_entities::*;
|
||||
|
||||
use crate::{archetype::ArchetypeId, storage::SparseSetIndex};
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
fmt, mem,
|
||||
sync::atomic::{AtomicI64, Ordering},
|
||||
};
|
||||
|
||||
/// Lightweight unique ID of an entity
|
||||
///
|
||||
/// Obtained from `World::spawn`. Can be stored to refer to an entity in the future.
|
||||
#[derive(Clone, Copy, Hash, Eq, Ord, PartialEq, PartialOrd)]
|
||||
pub struct Entity {
|
||||
pub(crate) generation: u32,
|
||||
pub(crate) id: u32,
|
||||
}
|
||||
|
||||
impl Entity {
|
||||
/// Creates a new entity reference with a generation of 0
|
||||
pub fn new(id: u32) -> Entity {
|
||||
Entity { id, generation: 0 }
|
||||
}
|
||||
|
||||
/// Convert to a form convenient for passing outside of rust
|
||||
///
|
||||
/// Only useful for identifying entities within the same instance of an application. Do not use
|
||||
/// for serialization between runs.
|
||||
///
|
||||
/// No particular structure is guaranteed for the returned bits.
|
||||
pub fn to_bits(self) -> u64 {
|
||||
u64::from(self.generation) << 32 | u64::from(self.id)
|
||||
}
|
||||
|
||||
/// Reconstruct an `Entity` previously destructured with `to_bits`
|
||||
///
|
||||
/// Only useful when applied to results from `to_bits` in the same instance of an application.
|
||||
pub fn from_bits(bits: u64) -> Self {
|
||||
Self {
|
||||
generation: (bits >> 32) as u32,
|
||||
id: bits as u32,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a transiently unique identifier
|
||||
///
|
||||
/// No two simultaneously-live entities share the same ID, but dead entities' IDs may collide
|
||||
/// with both live and dead entities. Useful for compactly representing entities within a
|
||||
/// specific snapshot of the world, such as when serializing.
|
||||
#[inline]
|
||||
pub fn id(self) -> u32 {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// Returns the generation of this Entity's id. The generation is incremented each time an entity with
|
||||
/// a given id is despawned. This serves as a "count" of the number of times a given id has been reused
|
||||
/// (id, generation) pairs uniquely identify a given Entity.
|
||||
#[inline]
|
||||
pub fn generation(self) -> u32 {
|
||||
self.generation
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Entity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}v{}", self.id, self.generation)
|
||||
}
|
||||
}
|
||||
|
||||
impl SparseSetIndex for Entity {
|
||||
fn sparse_set_index(&self) -> usize {
|
||||
self.id() as usize
|
||||
}
|
||||
|
||||
fn get_sparse_set_index(value: usize) -> Self {
|
||||
Entity::new(value as u32)
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator returning a sequence of Entity values from `Entities::reserve_entities`.
|
||||
pub struct ReserveEntitiesIterator<'a> {
|
||||
// Metas, so we can recover the current generation for anything in the freelist.
|
||||
meta: &'a [EntityMeta],
|
||||
|
||||
// Reserved IDs formerly in the freelist to hand out.
|
||||
id_iter: std::slice::Iter<'a, u32>,
|
||||
|
||||
// New Entity IDs to hand out, outside the range of meta.len().
|
||||
id_range: std::ops::Range<u32>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for ReserveEntitiesIterator<'a> {
|
||||
type Item = Entity;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.id_iter
|
||||
.next()
|
||||
.map(|&id| Entity {
|
||||
generation: self.meta[id as usize].generation,
|
||||
id,
|
||||
})
|
||||
.or_else(|| self.id_range.next().map(|id| Entity { generation: 0, id }))
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let len = self.id_iter.len() + self.id_range.len();
|
||||
(len, Some(len))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> core::iter::ExactSizeIterator for ReserveEntitiesIterator<'a> {}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Entities {
|
||||
pub meta: Vec<EntityMeta>,
|
||||
|
||||
/// The `pending` and `free_cursor` fields describe three sets of Entity IDs
|
||||
/// that have been freed or are in the process of being allocated:
|
||||
///
|
||||
/// - The `freelist` IDs, previously freed by `free()`. These IDs are available to any
|
||||
/// of `alloc()`, `reserve_entity()` or `reserve_entities()`. Allocation will
|
||||
/// always prefer these over brand new IDs.
|
||||
///
|
||||
/// - The `reserved` list of IDs that were once in the freelist, but got
|
||||
/// reserved by `reserve_entities` or `reserve_entity()`. They are now waiting
|
||||
/// for `flush()` to make them fully allocated.
|
||||
///
|
||||
/// - The count of new IDs that do not yet exist in `self.meta()`, but which
|
||||
/// we have handed out and reserved. `flush()` will allocate room for them in `self.meta()`.
|
||||
///
|
||||
/// The contents of `pending` look like this:
|
||||
///
|
||||
/// ```txt
|
||||
/// ----------------------------
|
||||
/// | freelist | reserved |
|
||||
/// ----------------------------
|
||||
/// ^ ^
|
||||
/// free_cursor pending.len()
|
||||
/// ```
|
||||
///
|
||||
/// As IDs are allocated, `free_cursor` is atomically decremented, moving
|
||||
/// items from the freelist into the reserved list by sliding over the boundary.
|
||||
///
|
||||
/// Once the freelist runs out, `free_cursor` starts going negative.
|
||||
/// The more negative it is, the more IDs have been reserved starting exactly at
|
||||
/// the end of `meta.len()`.
|
||||
///
|
||||
/// This formulation allows us to reserve any number of IDs first from the freelist
|
||||
/// and then from the new IDs, using only a single atomic subtract.
|
||||
///
|
||||
/// Once `flush()` is done, `free_cursor` will equal `pending.len()`.
|
||||
pending: Vec<u32>,
|
||||
free_cursor: AtomicI64,
|
||||
len: u32,
|
||||
}
|
||||
|
||||
impl Entities {
|
||||
/// Reserve entity IDs concurrently
|
||||
///
|
||||
/// Storage for entity generation and location is lazily allocated by calling `flush`.
|
||||
pub fn reserve_entities(&self, count: u32) -> ReserveEntitiesIterator {
|
||||
// Use one atomic subtract to grab a range of new IDs. The range might be
|
||||
// entirely nonnegative, meaning all IDs come from the freelist, or entirely
|
||||
// negative, meaning they are all new IDs to allocate, or a mix of both.
|
||||
let range_end = self.free_cursor.fetch_sub(count as i64, Ordering::Relaxed);
|
||||
let range_start = range_end - count as i64;
|
||||
|
||||
let freelist_range = range_start.max(0) as usize..range_end.max(0) as usize;
|
||||
|
||||
let (new_id_start, new_id_end) = if range_start >= 0 {
|
||||
// We satisfied all requests from the freelist.
|
||||
(0, 0)
|
||||
} else {
|
||||
// We need to allocate some new Entity IDs outside of the range of self.meta.
|
||||
//
|
||||
// `range_start` covers some negative territory, e.g. `-3..6`.
|
||||
// Since the nonnegative values `0..6` are handled by the freelist, that
|
||||
// means we need to handle the negative range here.
|
||||
//
|
||||
// In this example, we truncate the end to 0, leaving us with `-3..0`.
|
||||
// Then we negate these values to indicate how far beyond the end of `meta.end()`
|
||||
// to go, yielding `meta.len()+0 .. meta.len()+3`.
|
||||
let base = self.meta.len() as i64;
|
||||
|
||||
let new_id_end = u32::try_from(base - range_start).expect("too many entities");
|
||||
|
||||
// `new_id_end` is in range, so no need to check `start`.
|
||||
let new_id_start = (base - range_end.min(0)) as u32;
|
||||
|
||||
(new_id_start, new_id_end)
|
||||
};
|
||||
|
||||
ReserveEntitiesIterator {
|
||||
meta: &self.meta[..],
|
||||
id_iter: self.pending[freelist_range].iter(),
|
||||
id_range: new_id_start..new_id_end,
|
||||
}
|
||||
}
|
||||
|
||||
/// Reserve one entity ID concurrently
|
||||
///
|
||||
/// Equivalent to `self.reserve_entities(1).next().unwrap()`, but more efficient.
|
||||
pub fn reserve_entity(&self) -> Entity {
|
||||
let n = self.free_cursor.fetch_sub(1, Ordering::Relaxed);
|
||||
if n > 0 {
|
||||
// Allocate from the freelist.
|
||||
let id = self.pending[(n - 1) as usize];
|
||||
Entity {
|
||||
generation: self.meta[id as usize].generation,
|
||||
id,
|
||||
}
|
||||
} else {
|
||||
// Grab a new ID, outside the range of `meta.len()`. `flush()` must
|
||||
// eventually be called to make it valid.
|
||||
//
|
||||
// As `self.free_cursor` goes more and more negative, we return IDs farther
|
||||
// and farther beyond `meta.len()`.
|
||||
Entity {
|
||||
generation: 0,
|
||||
id: u32::try_from(self.meta.len() as i64 - n).expect("too many entities"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check that we do not have pending work requiring `flush()` to be called.
|
||||
fn verify_flushed(&mut self) {
|
||||
debug_assert!(
|
||||
!self.needs_flush(),
|
||||
"flush() needs to be called before this operation is legal"
|
||||
);
|
||||
}
|
||||
|
||||
/// Allocate an entity ID directly
|
||||
///
|
||||
/// Location should be written immediately.
|
||||
pub fn alloc(&mut self) -> Entity {
|
||||
self.verify_flushed();
|
||||
|
||||
self.len += 1;
|
||||
if let Some(id) = self.pending.pop() {
|
||||
let new_free_cursor = self.pending.len() as i64;
|
||||
*self.free_cursor.get_mut() = new_free_cursor;
|
||||
Entity {
|
||||
generation: self.meta[id as usize].generation,
|
||||
id,
|
||||
}
|
||||
} else {
|
||||
let id = u32::try_from(self.meta.len()).expect("too many entities");
|
||||
self.meta.push(EntityMeta::EMPTY);
|
||||
Entity { generation: 0, id }
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocate a specific entity ID, overwriting its generation
|
||||
///
|
||||
/// Returns the location of the entity currently using the given ID, if any. Location should be written immediately.
|
||||
pub fn alloc_at(&mut self, entity: Entity) -> Option<EntityLocation> {
|
||||
self.verify_flushed();
|
||||
|
||||
let loc = if entity.id as usize >= self.meta.len() {
|
||||
self.pending.extend((self.meta.len() as u32)..entity.id);
|
||||
let new_free_cursor = self.pending.len() as i64;
|
||||
*self.free_cursor.get_mut() = new_free_cursor;
|
||||
self.meta.resize(entity.id as usize + 1, EntityMeta::EMPTY);
|
||||
self.len += 1;
|
||||
None
|
||||
} else if let Some(index) = self.pending.iter().position(|item| *item == entity.id) {
|
||||
self.pending.swap_remove(index);
|
||||
let new_free_cursor = self.pending.len() as i64;
|
||||
*self.free_cursor.get_mut() = new_free_cursor;
|
||||
self.len += 1;
|
||||
None
|
||||
} else {
|
||||
Some(mem::replace(
|
||||
&mut self.meta[entity.id as usize].location,
|
||||
EntityMeta::EMPTY.location,
|
||||
))
|
||||
};
|
||||
|
||||
self.meta[entity.id as usize].generation = entity.generation;
|
||||
|
||||
loc
|
||||
}
|
||||
|
||||
/// Destroy an entity, allowing it to be reused
|
||||
///
|
||||
/// Must not be called while reserved entities are awaiting `flush()`.
|
||||
pub fn free(&mut self, entity: Entity) -> Option<EntityLocation> {
|
||||
self.verify_flushed();
|
||||
|
||||
let meta = &mut self.meta[entity.id as usize];
|
||||
if meta.generation != entity.generation {
|
||||
return None;
|
||||
}
|
||||
meta.generation += 1;
|
||||
|
||||
let loc = mem::replace(&mut meta.location, EntityMeta::EMPTY.location);
|
||||
|
||||
self.pending.push(entity.id);
|
||||
|
||||
let new_free_cursor = self.pending.len() as i64;
|
||||
*self.free_cursor.get_mut() = new_free_cursor;
|
||||
self.len -= 1;
|
||||
Some(loc)
|
||||
}
|
||||
|
||||
/// Ensure at least `n` allocations can succeed without reallocating
|
||||
pub fn reserve(&mut self, additional: u32) {
|
||||
self.verify_flushed();
|
||||
|
||||
let freelist_size = *self.free_cursor.get_mut();
|
||||
let shortfall = additional as i64 - freelist_size;
|
||||
if shortfall > 0 {
|
||||
self.meta.reserve(shortfall as usize);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains(&self, entity: Entity) -> bool {
|
||||
// Note that out-of-range IDs are considered to be "contained" because
|
||||
// they must be reserved IDs that we haven't flushed yet.
|
||||
self.meta
|
||||
.get(entity.id as usize)
|
||||
.map_or(true, |meta| meta.generation == entity.generation)
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.meta.clear();
|
||||
self.pending.clear();
|
||||
*self.free_cursor.get_mut() = 0;
|
||||
}
|
||||
|
||||
/// Access the location storage of an entity
|
||||
///
|
||||
/// Must not be called on pending entities.
|
||||
pub fn get_mut(&mut self, entity: Entity) -> Option<&mut EntityLocation> {
|
||||
let meta = &mut self.meta[entity.id as usize];
|
||||
if meta.generation == entity.generation {
|
||||
Some(&mut meta.location)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `Ok(Location { archetype: 0, index: undefined })` for pending entities
|
||||
pub fn get(&self, entity: Entity) -> Option<EntityLocation> {
|
||||
if (entity.id as usize) < self.meta.len() {
|
||||
let meta = &self.meta[entity.id as usize];
|
||||
if meta.generation != entity.generation {
|
||||
return None;
|
||||
}
|
||||
Some(meta.location)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Panics if the given id would represent an index outside of `meta`.
|
||||
///
|
||||
/// # Safety
|
||||
/// Must only be called for currently allocated `id`s.
|
||||
pub unsafe fn resolve_unknown_gen(&self, id: u32) -> Entity {
|
||||
let meta_len = self.meta.len();
|
||||
|
||||
if meta_len > id as usize {
|
||||
let meta = &self.meta[id as usize];
|
||||
Entity {
|
||||
generation: meta.generation,
|
||||
id,
|
||||
}
|
||||
} else {
|
||||
// See if it's pending, but not yet flushed.
|
||||
let free_cursor = self.free_cursor.load(Ordering::Relaxed);
|
||||
let num_pending = std::cmp::max(-free_cursor, 0) as usize;
|
||||
|
||||
if meta_len + num_pending > id as usize {
|
||||
// Pending entities will have generation 0.
|
||||
Entity { generation: 0, id }
|
||||
} else {
|
||||
panic!("entity id is out of range");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn needs_flush(&mut self) -> bool {
|
||||
*self.free_cursor.get_mut() != self.pending.len() as i64
|
||||
}
|
||||
|
||||
/// Allocates space for entities previously reserved with `reserve_entity` or
|
||||
/// `reserve_entities`, then initializes each one using the supplied function.
|
||||
pub fn flush(&mut self, mut init: impl FnMut(Entity, &mut EntityLocation)) {
|
||||
let free_cursor = self.free_cursor.get_mut();
|
||||
let current_free_cursor = *free_cursor;
|
||||
|
||||
let new_free_cursor = if current_free_cursor >= 0 {
|
||||
current_free_cursor as usize
|
||||
} else {
|
||||
let old_meta_len = self.meta.len();
|
||||
let new_meta_len = old_meta_len + -current_free_cursor as usize;
|
||||
self.meta.resize(new_meta_len, EntityMeta::EMPTY);
|
||||
self.len += -current_free_cursor as u32;
|
||||
for (id, meta) in self.meta.iter_mut().enumerate().skip(old_meta_len) {
|
||||
init(
|
||||
Entity {
|
||||
id: id as u32,
|
||||
generation: meta.generation,
|
||||
},
|
||||
&mut meta.location,
|
||||
);
|
||||
}
|
||||
|
||||
*free_cursor = 0;
|
||||
0
|
||||
};
|
||||
|
||||
self.len += (self.pending.len() - new_free_cursor) as u32;
|
||||
for id in self.pending.drain(new_free_cursor..) {
|
||||
let meta = &mut self.meta[id as usize];
|
||||
init(
|
||||
Entity {
|
||||
id,
|
||||
generation: meta.generation,
|
||||
},
|
||||
&mut meta.location,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> u32 {
|
||||
self.len
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len == 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct EntityMeta {
|
||||
pub generation: u32,
|
||||
pub location: EntityLocation,
|
||||
}
|
||||
|
||||
impl EntityMeta {
|
||||
const EMPTY: EntityMeta = EntityMeta {
|
||||
generation: 0,
|
||||
location: EntityLocation {
|
||||
archetype_id: ArchetypeId::empty(),
|
||||
index: usize::max_value(), // dummy value, to be filled in
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/// A location of an entity in an archetype
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct EntityLocation {
|
||||
/// The archetype index
|
||||
pub archetype_id: ArchetypeId,
|
||||
|
||||
/// The index of the entity in the archetype
|
||||
pub index: usize,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn entity_bits_roundtrip() {
|
||||
let e = Entity {
|
||||
generation: 0xDEADBEEF,
|
||||
id: 0xBAADF00D,
|
||||
};
|
||||
assert_eq!(Entity::from_bits(e.to_bits()), e);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reserve_entity_len() {
|
||||
let mut e = Entities::default();
|
||||
e.reserve_entity();
|
||||
e.flush(|_, _| {});
|
||||
assert_eq!(e.len(), 1);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,4 @@
|
|||
// modified by Bevy contributors
|
||||
|
||||
use crate::Entity;
|
||||
use crate::entity::Entity;
|
||||
use serde::{de::Visitor, Deserialize, Serialize, Serializer};
|
||||
|
||||
impl Serialize for Entity {
|
File diff suppressed because it is too large
Load diff
200
crates/bevy_ecs/src/query/access.rs
Normal file
200
crates/bevy_ecs/src/query/access.rs
Normal file
|
@ -0,0 +1,200 @@
|
|||
use crate::storage::SparseSetIndex;
|
||||
use fixedbitset::FixedBitSet;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
pub struct Access<T: SparseSetIndex> {
|
||||
reads_all: bool,
|
||||
/// A combined set of T read and write accesses.
|
||||
reads_and_writes: FixedBitSet,
|
||||
writes: FixedBitSet,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: SparseSetIndex> Default for Access<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
reads_all: false,
|
||||
reads_and_writes: Default::default(),
|
||||
writes: Default::default(),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: SparseSetIndex> Access<T> {
|
||||
pub fn grow(&mut self, bits: usize) {
|
||||
self.reads_and_writes.grow(bits);
|
||||
self.writes.grow(bits);
|
||||
}
|
||||
|
||||
pub fn add_read(&mut self, index: T) {
|
||||
self.reads_and_writes.grow(index.sparse_set_index() + 1);
|
||||
self.reads_and_writes.insert(index.sparse_set_index());
|
||||
}
|
||||
|
||||
pub fn add_write(&mut self, index: T) {
|
||||
self.reads_and_writes.grow(index.sparse_set_index() + 1);
|
||||
self.writes.grow(index.sparse_set_index() + 1);
|
||||
self.reads_and_writes.insert(index.sparse_set_index());
|
||||
self.writes.insert(index.sparse_set_index());
|
||||
}
|
||||
|
||||
pub fn has_read(&self, index: T) -> bool {
|
||||
if self.reads_all {
|
||||
true
|
||||
} else {
|
||||
self.reads_and_writes.contains(index.sparse_set_index())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_write(&self, index: T) -> bool {
|
||||
self.writes.contains(index.sparse_set_index())
|
||||
}
|
||||
|
||||
pub fn read_all(&mut self) {
|
||||
self.reads_all = true;
|
||||
}
|
||||
|
||||
pub fn reads_all(&self) -> bool {
|
||||
self.reads_all
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.reads_all = false;
|
||||
self.reads_and_writes.clear();
|
||||
self.writes.clear();
|
||||
}
|
||||
|
||||
pub fn extend(&mut self, other: &Access<T>) {
|
||||
self.reads_all = self.reads_all || other.reads_all;
|
||||
self.reads_and_writes.union_with(&other.reads_and_writes);
|
||||
self.writes.union_with(&other.writes);
|
||||
}
|
||||
|
||||
pub fn is_compatible(&self, other: &Access<T>) -> bool {
|
||||
if self.reads_all {
|
||||
0 == other.writes.count_ones(..)
|
||||
} else if other.reads_all {
|
||||
0 == self.writes.count_ones(..)
|
||||
} else {
|
||||
self.writes.is_disjoint(&other.reads_and_writes)
|
||||
&& self.reads_and_writes.is_disjoint(&other.writes)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_conflicts(&self, other: &Access<T>) -> Vec<T> {
|
||||
let mut conflicts = FixedBitSet::default();
|
||||
if self.reads_all {
|
||||
conflicts.extend(other.writes.ones());
|
||||
}
|
||||
|
||||
if other.reads_all {
|
||||
conflicts.extend(self.writes.ones());
|
||||
}
|
||||
conflicts.extend(self.writes.intersection(&other.reads_and_writes));
|
||||
conflicts.extend(self.reads_and_writes.intersection(&other.writes));
|
||||
conflicts
|
||||
.ones()
|
||||
.map(SparseSetIndex::get_sparse_set_index)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FilteredAccess<T: SparseSetIndex> {
|
||||
access: Access<T>,
|
||||
with: FixedBitSet,
|
||||
without: FixedBitSet,
|
||||
}
|
||||
|
||||
impl<T: SparseSetIndex> Default for FilteredAccess<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
access: Access::default(),
|
||||
with: Default::default(),
|
||||
without: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: SparseSetIndex> FilteredAccess<T> {
|
||||
#[inline]
|
||||
pub fn access(&self) -> &Access<T> {
|
||||
&self.access
|
||||
}
|
||||
|
||||
pub fn add_read(&mut self, index: T) {
|
||||
self.access.add_read(index.clone());
|
||||
self.add_with(index);
|
||||
}
|
||||
|
||||
pub fn add_write(&mut self, index: T) {
|
||||
self.access.add_write(index.clone());
|
||||
self.add_with(index);
|
||||
}
|
||||
|
||||
pub fn add_with(&mut self, index: T) {
|
||||
self.with.grow(index.sparse_set_index() + 1);
|
||||
self.with.insert(index.sparse_set_index());
|
||||
}
|
||||
|
||||
pub fn add_without(&mut self, index: T) {
|
||||
self.without.grow(index.sparse_set_index() + 1);
|
||||
self.without.insert(index.sparse_set_index());
|
||||
}
|
||||
|
||||
pub fn is_compatible(&self, other: &FilteredAccess<T>) -> bool {
|
||||
if self.access.is_compatible(&other.access) {
|
||||
true
|
||||
} else {
|
||||
self.with.intersection(&other.without).next().is_some()
|
||||
|| self.without.intersection(&other.with).next().is_some()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FilteredAccessSet<T: SparseSetIndex> {
|
||||
combined_access: Access<T>,
|
||||
filtered_accesses: Vec<FilteredAccess<T>>,
|
||||
}
|
||||
|
||||
impl<T: SparseSetIndex> FilteredAccessSet<T> {
|
||||
#[inline]
|
||||
pub fn combined_access(&self) -> &Access<T> {
|
||||
&self.combined_access
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn combined_access_mut(&mut self) -> &mut Access<T> {
|
||||
&mut self.combined_access
|
||||
}
|
||||
|
||||
pub fn get_conflicts(&self, filtered_access: &FilteredAccess<T>) -> Vec<T> {
|
||||
// if combined unfiltered access is incompatible, check each filtered access for compatibility
|
||||
if !filtered_access.access.is_compatible(&self.combined_access) {
|
||||
for current_filtered_access in self.filtered_accesses.iter() {
|
||||
if !current_filtered_access.is_compatible(&filtered_access) {
|
||||
return current_filtered_access
|
||||
.access
|
||||
.get_conflicts(&filtered_access.access);
|
||||
}
|
||||
}
|
||||
}
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
pub fn add(&mut self, filtered_access: FilteredAccess<T>) {
|
||||
self.combined_access.extend(&filtered_access.access);
|
||||
self.filtered_accesses.push(filtered_access);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: SparseSetIndex> Default for FilteredAccessSet<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
combined_access: Default::default(),
|
||||
filtered_accesses: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
813
crates/bevy_ecs/src/query/fetch.rs
Normal file
813
crates/bevy_ecs/src/query/fetch.rs
Normal file
|
@ -0,0 +1,813 @@
|
|||
use crate::{
|
||||
archetype::{Archetype, ArchetypeComponentId},
|
||||
component::{Component, ComponentFlags, ComponentId, StorageType},
|
||||
entity::Entity,
|
||||
query::{Access, FilteredAccess},
|
||||
storage::{ComponentSparseSet, Table, Tables},
|
||||
world::{Mut, World},
|
||||
};
|
||||
use bevy_ecs_macros::all_tuples;
|
||||
use std::{
|
||||
marker::PhantomData,
|
||||
ptr::{self, NonNull},
|
||||
};
|
||||
|
||||
pub trait WorldQuery {
|
||||
type Fetch: for<'a> Fetch<'a, State = Self::State>;
|
||||
type State: FetchState;
|
||||
}
|
||||
|
||||
pub trait Fetch<'w>: Sized {
|
||||
type Item;
|
||||
type State: FetchState;
|
||||
|
||||
/// Creates a new instance of this fetch.
|
||||
/// # Safety
|
||||
/// `state` must have been initialized (via [FetchState::init]) using the same `world` passed in to this function.
|
||||
unsafe fn init(world: &World, state: &Self::State) -> Self;
|
||||
|
||||
/// Returns true if (and only if) every table of every archetype matched by this Fetch contains all of the matched components.
|
||||
/// This is used to select a more efficient "table iterator" for "dense" queries.
|
||||
/// If this returns true, [Fetch::set_table] and [Fetch::table_fetch] will be called for iterators
|
||||
/// If this returns false, [Fetch::set_archetype] and [Fetch::archetype_fetch] will be called for iterators
|
||||
fn is_dense(&self) -> bool;
|
||||
|
||||
/// Adjusts internal state to account for the next [Archetype]. This will always be called on archetypes that match this [Fetch]
|
||||
/// # Safety
|
||||
/// `archetype` and `tables` must be from the [World] [Fetch::init] was called on. `state` must be the [Self::State] this was initialized with.
|
||||
unsafe fn set_archetype(&mut self, state: &Self::State, archetype: &Archetype, tables: &Tables);
|
||||
|
||||
/// Adjusts internal state to account for the next [Table]. This will always be called on tables that match this [Fetch]
|
||||
/// # Safety
|
||||
/// `table` must be from the [World] [Fetch::init] was called on. `state` must be the [Self::State] this was initialized with.
|
||||
unsafe fn set_table(&mut self, state: &Self::State, table: &Table);
|
||||
|
||||
/// Fetch [Self::Item] for the given `archetype_index` in the current [Archetype]. This must always be called after [Fetch::set_archetype] with an `archetype_index`
|
||||
/// in the range of the current [Archetype]
|
||||
/// # Safety
|
||||
/// Must always be called _after_ [Fetch::set_archetype]. `archetype_index` must be in the range of the current archetype
|
||||
unsafe fn archetype_fetch(&mut self, archetype_index: usize) -> Self::Item;
|
||||
|
||||
/// Fetch [Self::Item] for the given `table_row` in the current [Table]. This must always be called after [Fetch::set_table] with a `table_row`
|
||||
/// in the range of the current [Table]
|
||||
/// # Safety
|
||||
/// Must always be called _after_ [Fetch::set_table]. `table_row` must be in the range of the current table
|
||||
unsafe fn table_fetch(&mut self, table_row: usize) -> Self::Item;
|
||||
}
|
||||
|
||||
/// State used to construct a Fetch. This will be cached inside QueryState, so it is best to move as much data /
|
||||
/// computation here as possible to reduce the cost of constructing Fetch.
|
||||
/// SAFETY:
|
||||
/// Implementor must ensure that [FetchState::update_component_access] and [FetchState::update_archetype_component_access] exactly
|
||||
/// reflects the results of [FetchState::matches_archetype], [FetchState::matches_table], [Fetch::archetype_fetch], and [Fetch::table_fetch]
|
||||
pub unsafe trait FetchState: Send + Sync + Sized {
|
||||
fn init(world: &mut World) -> Self;
|
||||
fn update_component_access(&self, access: &mut FilteredAccess<ComponentId>);
|
||||
fn update_archetype_component_access(
|
||||
&self,
|
||||
archetype: &Archetype,
|
||||
access: &mut Access<ArchetypeComponentId>,
|
||||
);
|
||||
fn matches_archetype(&self, archetype: &Archetype) -> bool;
|
||||
fn matches_table(&self, table: &Table) -> bool;
|
||||
}
|
||||
|
||||
/// A fetch that is read only. This must only be implemented for read-only fetches.
|
||||
pub unsafe trait ReadOnlyFetch {}
|
||||
|
||||
impl WorldQuery for Entity {
|
||||
type Fetch = EntityFetch;
|
||||
type State = EntityState;
|
||||
}
|
||||
|
||||
pub struct EntityFetch {
|
||||
entities: *const Entity,
|
||||
}
|
||||
|
||||
/// SAFE: access is read only
|
||||
unsafe impl ReadOnlyFetch for EntityFetch {}
|
||||
|
||||
pub struct EntityState;
|
||||
|
||||
// SAFE: no component or archetype access
|
||||
unsafe impl FetchState for EntityState {
|
||||
fn init(_world: &mut World) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn update_component_access(&self, _access: &mut FilteredAccess<ComponentId>) {}
|
||||
|
||||
fn update_archetype_component_access(
|
||||
&self,
|
||||
_archetype: &Archetype,
|
||||
_access: &mut Access<ArchetypeComponentId>,
|
||||
) {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn matches_archetype(&self, _archetype: &Archetype) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn matches_table(&self, _table: &Table) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w> Fetch<'w> for EntityFetch {
|
||||
type Item = Entity;
|
||||
type State = EntityState;
|
||||
|
||||
#[inline]
|
||||
fn is_dense(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
unsafe fn init(_world: &World, _state: &Self::State) -> Self {
|
||||
Self {
|
||||
entities: std::ptr::null::<Entity>(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_archetype(
|
||||
&mut self,
|
||||
_state: &Self::State,
|
||||
archetype: &Archetype,
|
||||
_tables: &Tables,
|
||||
) {
|
||||
self.entities = archetype.entities().as_ptr();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_table(&mut self, _state: &Self::State, table: &Table) {
|
||||
self.entities = table.entities().as_ptr();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn table_fetch(&mut self, table_row: usize) -> Self::Item {
|
||||
*self.entities.add(table_row)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn archetype_fetch(&mut self, archetype_index: usize) -> Self::Item {
|
||||
*self.entities.add(archetype_index)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> WorldQuery for &T {
|
||||
type Fetch = ReadFetch<T>;
|
||||
type State = ReadState<T>;
|
||||
}
|
||||
|
||||
pub struct ReadState<T> {
|
||||
component_id: ComponentId,
|
||||
storage_type: StorageType,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
// SAFE: component access and archetype component access are properly updated to reflect that T is read
|
||||
unsafe impl<T: Component> FetchState for ReadState<T> {
|
||||
fn init(world: &mut World) -> Self {
|
||||
let component_info = world.components.get_or_insert_info::<T>();
|
||||
ReadState {
|
||||
component_id: component_info.id(),
|
||||
storage_type: component_info.storage_type(),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_component_access(&self, access: &mut FilteredAccess<ComponentId>) {
|
||||
access.add_read(self.component_id)
|
||||
}
|
||||
|
||||
fn update_archetype_component_access(
|
||||
&self,
|
||||
archetype: &Archetype,
|
||||
access: &mut Access<ArchetypeComponentId>,
|
||||
) {
|
||||
if let Some(archetype_component_id) =
|
||||
archetype.get_archetype_component_id(self.component_id)
|
||||
{
|
||||
access.add_read(archetype_component_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn matches_archetype(&self, archetype: &Archetype) -> bool {
|
||||
archetype.contains(self.component_id)
|
||||
}
|
||||
|
||||
fn matches_table(&self, table: &Table) -> bool {
|
||||
table.has_column(self.component_id)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ReadFetch<T> {
|
||||
storage_type: StorageType,
|
||||
table_components: NonNull<T>,
|
||||
entity_table_rows: *const usize,
|
||||
entities: *const Entity,
|
||||
sparse_set: *const ComponentSparseSet,
|
||||
}
|
||||
|
||||
/// SAFE: access is read only
|
||||
unsafe impl<T> ReadOnlyFetch for ReadFetch<T> {}
|
||||
|
||||
impl<'w, T: Component> Fetch<'w> for ReadFetch<T> {
|
||||
type Item = &'w T;
|
||||
type State = ReadState<T>;
|
||||
|
||||
#[inline]
|
||||
fn is_dense(&self) -> bool {
|
||||
match self.storage_type {
|
||||
StorageType::Table => true,
|
||||
StorageType::SparseSet => false,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn init(world: &World, state: &Self::State) -> Self {
|
||||
let mut value = Self {
|
||||
storage_type: state.storage_type,
|
||||
table_components: NonNull::dangling(),
|
||||
entities: ptr::null::<Entity>(),
|
||||
entity_table_rows: ptr::null::<usize>(),
|
||||
sparse_set: ptr::null::<ComponentSparseSet>(),
|
||||
};
|
||||
if state.storage_type == StorageType::SparseSet {
|
||||
value.sparse_set = world
|
||||
.storages()
|
||||
.sparse_sets
|
||||
.get(state.component_id)
|
||||
.unwrap();
|
||||
}
|
||||
value
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_archetype(
|
||||
&mut self,
|
||||
state: &Self::State,
|
||||
archetype: &Archetype,
|
||||
tables: &Tables,
|
||||
) {
|
||||
match state.storage_type {
|
||||
StorageType::Table => {
|
||||
self.entity_table_rows = archetype.entity_table_rows().as_ptr();
|
||||
// SAFE: archetype tables always exist
|
||||
let table = tables.get_unchecked(archetype.table_id());
|
||||
let column = table.get_column(state.component_id).unwrap();
|
||||
self.table_components = column.get_ptr().cast::<T>();
|
||||
}
|
||||
StorageType::SparseSet => self.entities = archetype.entities().as_ptr(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_table(&mut self, state: &Self::State, table: &Table) {
|
||||
self.table_components = table
|
||||
.get_column(state.component_id)
|
||||
.unwrap()
|
||||
.get_ptr()
|
||||
.cast::<T>();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn archetype_fetch(&mut self, archetype_index: usize) -> Self::Item {
|
||||
match self.storage_type {
|
||||
StorageType::Table => {
|
||||
let table_row = *self.entity_table_rows.add(archetype_index);
|
||||
&*self.table_components.as_ptr().add(table_row)
|
||||
}
|
||||
StorageType::SparseSet => {
|
||||
let entity = *self.entities.add(archetype_index);
|
||||
&*(*self.sparse_set).get(entity).unwrap().cast::<T>()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn table_fetch(&mut self, table_row: usize) -> Self::Item {
|
||||
&*self.table_components.as_ptr().add(table_row)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> WorldQuery for &mut T {
|
||||
type Fetch = WriteFetch<T>;
|
||||
type State = WriteState<T>;
|
||||
}
|
||||
|
||||
pub struct WriteFetch<T> {
|
||||
storage_type: StorageType,
|
||||
table_components: NonNull<T>,
|
||||
table_flags: *mut ComponentFlags,
|
||||
entities: *const Entity,
|
||||
entity_table_rows: *const usize,
|
||||
sparse_set: *const ComponentSparseSet,
|
||||
}
|
||||
|
||||
pub struct WriteState<T> {
|
||||
component_id: ComponentId,
|
||||
storage_type: StorageType,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
// SAFE: component access and archetype component access are properly updated to reflect that T is written
|
||||
unsafe impl<T: Component> FetchState for WriteState<T> {
|
||||
fn init(world: &mut World) -> Self {
|
||||
let component_info = world.components.get_or_insert_info::<T>();
|
||||
WriteState {
|
||||
component_id: component_info.id(),
|
||||
storage_type: component_info.storage_type(),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_component_access(&self, access: &mut FilteredAccess<ComponentId>) {
|
||||
if access.access().has_read(self.component_id) {
|
||||
panic!("&mut {} conflicts with a previous access in this query. Mutable component access must be unique.",
|
||||
std::any::type_name::<T>());
|
||||
}
|
||||
access.add_write(self.component_id);
|
||||
}
|
||||
|
||||
fn update_archetype_component_access(
|
||||
&self,
|
||||
archetype: &Archetype,
|
||||
access: &mut Access<ArchetypeComponentId>,
|
||||
) {
|
||||
if let Some(archetype_component_id) =
|
||||
archetype.get_archetype_component_id(self.component_id)
|
||||
{
|
||||
access.add_write(archetype_component_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn matches_archetype(&self, archetype: &Archetype) -> bool {
|
||||
archetype.contains(self.component_id)
|
||||
}
|
||||
|
||||
fn matches_table(&self, table: &Table) -> bool {
|
||||
table.has_column(self.component_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w, T: Component> Fetch<'w> for WriteFetch<T> {
|
||||
type Item = Mut<'w, T>;
|
||||
type State = WriteState<T>;
|
||||
|
||||
#[inline]
|
||||
fn is_dense(&self) -> bool {
|
||||
match self.storage_type {
|
||||
StorageType::Table => true,
|
||||
StorageType::SparseSet => false,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn init(world: &World, state: &Self::State) -> Self {
|
||||
let mut value = Self {
|
||||
storage_type: state.storage_type,
|
||||
table_components: NonNull::dangling(),
|
||||
entities: ptr::null::<Entity>(),
|
||||
entity_table_rows: ptr::null::<usize>(),
|
||||
sparse_set: ptr::null::<ComponentSparseSet>(),
|
||||
table_flags: ptr::null_mut::<ComponentFlags>(),
|
||||
};
|
||||
if state.storage_type == StorageType::SparseSet {
|
||||
value.sparse_set = world
|
||||
.storages()
|
||||
.sparse_sets
|
||||
.get(state.component_id)
|
||||
.unwrap();
|
||||
}
|
||||
value
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_archetype(
|
||||
&mut self,
|
||||
state: &Self::State,
|
||||
archetype: &Archetype,
|
||||
tables: &Tables,
|
||||
) {
|
||||
match state.storage_type {
|
||||
StorageType::Table => {
|
||||
self.entity_table_rows = archetype.entity_table_rows().as_ptr();
|
||||
// SAFE: archetype tables always exist
|
||||
let table = tables.get_unchecked(archetype.table_id());
|
||||
let column = table.get_column(state.component_id).unwrap();
|
||||
self.table_components = column.get_ptr().cast::<T>();
|
||||
self.table_flags = column.get_flags_mut_ptr();
|
||||
}
|
||||
StorageType::SparseSet => self.entities = archetype.entities().as_ptr(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_table(&mut self, state: &Self::State, table: &Table) {
|
||||
let column = table.get_column(state.component_id).unwrap();
|
||||
self.table_components = column.get_ptr().cast::<T>();
|
||||
self.table_flags = column.get_flags_mut_ptr();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn archetype_fetch(&mut self, archetype_index: usize) -> Self::Item {
|
||||
match self.storage_type {
|
||||
StorageType::Table => {
|
||||
let table_row = *self.entity_table_rows.add(archetype_index);
|
||||
Mut {
|
||||
value: &mut *self.table_components.as_ptr().add(table_row),
|
||||
flags: &mut *self.table_flags.add(table_row),
|
||||
}
|
||||
}
|
||||
StorageType::SparseSet => {
|
||||
let entity = *self.entities.add(archetype_index);
|
||||
let (component, flags) = (*self.sparse_set).get_with_flags(entity).unwrap();
|
||||
Mut {
|
||||
value: &mut *component.cast::<T>(),
|
||||
flags: &mut *flags,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn table_fetch(&mut self, table_row: usize) -> Self::Item {
|
||||
Mut {
|
||||
value: &mut *self.table_components.as_ptr().add(table_row),
|
||||
flags: &mut *self.table_flags.add(table_row),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: WorldQuery> WorldQuery for Option<T> {
|
||||
type Fetch = OptionFetch<T::Fetch>;
|
||||
type State = OptionState<T::State>;
|
||||
}
|
||||
|
||||
pub struct OptionFetch<T> {
|
||||
fetch: T,
|
||||
matches: bool,
|
||||
}
|
||||
|
||||
/// SAFE: OptionFetch is read only because T is read only
|
||||
unsafe impl<T: ReadOnlyFetch> ReadOnlyFetch for OptionFetch<T> {}
|
||||
|
||||
pub struct OptionState<T: FetchState> {
|
||||
state: T,
|
||||
}
|
||||
|
||||
// SAFE: component access and archetype component access are properly updated according to the internal Fetch
|
||||
unsafe impl<T: FetchState> FetchState for OptionState<T> {
|
||||
fn init(world: &mut World) -> Self {
|
||||
Self {
|
||||
state: T::init(world),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_component_access(&self, access: &mut FilteredAccess<ComponentId>) {
|
||||
self.state.update_component_access(access);
|
||||
}
|
||||
|
||||
fn update_archetype_component_access(
|
||||
&self,
|
||||
archetype: &Archetype,
|
||||
access: &mut Access<ArchetypeComponentId>,
|
||||
) {
|
||||
if self.state.matches_archetype(archetype) {
|
||||
self.state
|
||||
.update_archetype_component_access(archetype, access)
|
||||
}
|
||||
}
|
||||
|
||||
fn matches_archetype(&self, _archetype: &Archetype) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn matches_table(&self, _table: &Table) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w, T: Fetch<'w>> Fetch<'w> for OptionFetch<T> {
|
||||
type Item = Option<T::Item>;
|
||||
type State = OptionState<T::State>;
|
||||
|
||||
#[inline]
|
||||
fn is_dense(&self) -> bool {
|
||||
self.fetch.is_dense()
|
||||
}
|
||||
|
||||
unsafe fn init(world: &World, state: &Self::State) -> Self {
|
||||
Self {
|
||||
fetch: T::init(world, &state.state),
|
||||
matches: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_archetype(
|
||||
&mut self,
|
||||
state: &Self::State,
|
||||
archetype: &Archetype,
|
||||
tables: &Tables,
|
||||
) {
|
||||
self.matches = state.state.matches_archetype(archetype);
|
||||
if self.matches {
|
||||
self.fetch.set_archetype(&state.state, archetype, tables);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_table(&mut self, state: &Self::State, table: &Table) {
|
||||
self.matches = state.state.matches_table(table);
|
||||
if self.matches {
|
||||
self.fetch.set_table(&state.state, table);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn archetype_fetch(&mut self, archetype_index: usize) -> Self::Item {
|
||||
if self.matches {
|
||||
Some(self.fetch.archetype_fetch(archetype_index))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn table_fetch(&mut self, table_row: usize) -> Self::Item {
|
||||
if self.matches {
|
||||
Some(self.fetch.table_fetch(table_row))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Flags on component `T` that happened since the start of the frame.
|
||||
#[derive(Clone)]
|
||||
pub struct Flags<T: Component> {
|
||||
flags: ComponentFlags,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
impl<T: Component> std::fmt::Debug for Flags<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Flags")
|
||||
.field("added", &self.added())
|
||||
.field("mutated", &self.mutated())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> Flags<T> {
|
||||
/// Has this component been added since the start of the frame.
|
||||
pub fn added(&self) -> bool {
|
||||
self.flags.contains(ComponentFlags::ADDED)
|
||||
}
|
||||
|
||||
/// Has this component been mutated since the start of the frame.
|
||||
pub fn mutated(&self) -> bool {
|
||||
self.flags.contains(ComponentFlags::MUTATED)
|
||||
}
|
||||
|
||||
/// Has this component been either mutated or added since the start of the frame.
|
||||
pub fn changed(&self) -> bool {
|
||||
self.flags
|
||||
.intersects(ComponentFlags::ADDED | ComponentFlags::MUTATED)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> WorldQuery for Flags<T> {
|
||||
type Fetch = FlagsFetch<T>;
|
||||
type State = FlagsState<T>;
|
||||
}
|
||||
|
||||
pub struct FlagsState<T> {
|
||||
component_id: ComponentId,
|
||||
storage_type: StorageType,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
// SAFE: component access and archetype component access are properly updated to reflect that T is read
|
||||
unsafe impl<T: Component> FetchState for FlagsState<T> {
|
||||
fn init(world: &mut World) -> Self {
|
||||
let component_info = world.components.get_or_insert_info::<T>();
|
||||
Self {
|
||||
component_id: component_info.id(),
|
||||
storage_type: component_info.storage_type(),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_component_access(&self, access: &mut FilteredAccess<ComponentId>) {
|
||||
access.add_read(self.component_id)
|
||||
}
|
||||
|
||||
fn update_archetype_component_access(
|
||||
&self,
|
||||
archetype: &Archetype,
|
||||
access: &mut Access<ArchetypeComponentId>,
|
||||
) {
|
||||
if let Some(archetype_component_id) =
|
||||
archetype.get_archetype_component_id(self.component_id)
|
||||
{
|
||||
access.add_read(archetype_component_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn matches_archetype(&self, archetype: &Archetype) -> bool {
|
||||
archetype.contains(self.component_id)
|
||||
}
|
||||
|
||||
fn matches_table(&self, table: &Table) -> bool {
|
||||
table.has_column(self.component_id)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FlagsFetch<T> {
|
||||
storage_type: StorageType,
|
||||
table_flags: *const ComponentFlags,
|
||||
entity_table_rows: *const usize,
|
||||
entities: *const Entity,
|
||||
sparse_set: *const ComponentSparseSet,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
/// SAFE: access is read only
|
||||
unsafe impl<T> ReadOnlyFetch for FlagsFetch<T> {}
|
||||
|
||||
impl<'w, T: Component> Fetch<'w> for FlagsFetch<T> {
|
||||
type Item = Flags<T>;
|
||||
type State = FlagsState<T>;
|
||||
|
||||
#[inline]
|
||||
fn is_dense(&self) -> bool {
|
||||
match self.storage_type {
|
||||
StorageType::Table => true,
|
||||
StorageType::SparseSet => false,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn init(world: &World, state: &Self::State) -> Self {
|
||||
let mut value = Self {
|
||||
storage_type: state.storage_type,
|
||||
table_flags: ptr::null::<ComponentFlags>(),
|
||||
entities: ptr::null::<Entity>(),
|
||||
entity_table_rows: ptr::null::<usize>(),
|
||||
sparse_set: ptr::null::<ComponentSparseSet>(),
|
||||
marker: PhantomData,
|
||||
};
|
||||
if state.storage_type == StorageType::SparseSet {
|
||||
value.sparse_set = world
|
||||
.storages()
|
||||
.sparse_sets
|
||||
.get(state.component_id)
|
||||
.unwrap();
|
||||
}
|
||||
value
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_archetype(
|
||||
&mut self,
|
||||
state: &Self::State,
|
||||
archetype: &Archetype,
|
||||
tables: &Tables,
|
||||
) {
|
||||
match state.storage_type {
|
||||
StorageType::Table => {
|
||||
self.entity_table_rows = archetype.entity_table_rows().as_ptr();
|
||||
// SAFE: archetype tables always exist
|
||||
let table = tables.get_unchecked(archetype.table_id());
|
||||
let column = table.get_column(state.component_id).unwrap();
|
||||
self.table_flags = column.get_flags_mut_ptr().cast::<ComponentFlags>();
|
||||
}
|
||||
StorageType::SparseSet => self.entities = archetype.entities().as_ptr(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_table(&mut self, state: &Self::State, table: &Table) {
|
||||
self.table_flags = table
|
||||
.get_column(state.component_id)
|
||||
.unwrap()
|
||||
.get_flags_mut_ptr()
|
||||
.cast::<ComponentFlags>();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn archetype_fetch(&mut self, archetype_index: usize) -> Self::Item {
|
||||
match self.storage_type {
|
||||
StorageType::Table => {
|
||||
let table_row = *self.entity_table_rows.add(archetype_index);
|
||||
Flags {
|
||||
flags: *self.table_flags.add(table_row),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
StorageType::SparseSet => {
|
||||
let entity = *self.entities.add(archetype_index);
|
||||
Flags {
|
||||
flags: *(*self.sparse_set).get_flags(entity).unwrap(),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn table_fetch(&mut self, table_row: usize) -> Self::Item {
|
||||
Flags {
|
||||
flags: *self.table_flags.add(table_row),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_tuple_fetch {
|
||||
($(($name: ident, $state: ident)),*) => {
|
||||
#[allow(non_snake_case)]
|
||||
impl<'a, $($name: Fetch<'a>),*> Fetch<'a> for ($($name,)*) {
|
||||
type Item = ($($name::Item,)*);
|
||||
type State = ($($name::State,)*);
|
||||
|
||||
unsafe fn init(_world: &World, state: &Self::State) -> Self {
|
||||
let ($($name,)*) = state;
|
||||
($($name::init(_world, $name),)*)
|
||||
}
|
||||
|
||||
|
||||
#[inline]
|
||||
fn is_dense(&self) -> bool {
|
||||
let ($($name,)*) = self;
|
||||
true $(&& $name.is_dense())*
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_archetype(&mut self, _state: &Self::State, _archetype: &Archetype, _tables: &Tables) {
|
||||
let ($($name,)*) = self;
|
||||
let ($($state,)*) = _state;
|
||||
$($name.set_archetype($state, _archetype, _tables);)*
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_table(&mut self, _state: &Self::State, _table: &Table) {
|
||||
let ($($name,)*) = self;
|
||||
let ($($state,)*) = _state;
|
||||
$($name.set_table($state, _table);)*
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn table_fetch(&mut self, _table_row: usize) -> Self::Item {
|
||||
let ($($name,)*) = self;
|
||||
($($name.table_fetch(_table_row),)*)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn archetype_fetch(&mut self, _archetype_index: usize) -> Self::Item {
|
||||
let ($($name,)*) = self;
|
||||
($($name.archetype_fetch(_archetype_index),)*)
|
||||
}
|
||||
}
|
||||
|
||||
// SAFE: update_component_access and update_archetype_component_access are called for each item in the tuple
|
||||
#[allow(non_snake_case)]
|
||||
unsafe impl<$($name: FetchState),*> FetchState for ($($name,)*) {
|
||||
fn init(_world: &mut World) -> Self {
|
||||
($($name::init(_world),)*)
|
||||
}
|
||||
|
||||
fn update_component_access(&self, _access: &mut FilteredAccess<ComponentId>) {
|
||||
let ($($name,)*) = self;
|
||||
$($name.update_component_access(_access);)*
|
||||
}
|
||||
|
||||
fn update_archetype_component_access(&self, _archetype: &Archetype, _access: &mut Access<ArchetypeComponentId>) {
|
||||
let ($($name,)*) = self;
|
||||
$($name.update_archetype_component_access(_archetype, _access);)*
|
||||
}
|
||||
|
||||
fn matches_archetype(&self, _archetype: &Archetype) -> bool {
|
||||
let ($($name,)*) = self;
|
||||
true $(&& $name.matches_archetype(_archetype))*
|
||||
}
|
||||
|
||||
fn matches_table(&self, _table: &Table) -> bool {
|
||||
let ($($name,)*) = self;
|
||||
true $(&& $name.matches_table(_table))*
|
||||
}
|
||||
}
|
||||
|
||||
impl<$($name: WorldQuery),*> WorldQuery for ($($name,)*) {
|
||||
type Fetch = ($($name::Fetch,)*);
|
||||
type State = ($($name::State,)*);
|
||||
}
|
||||
|
||||
/// SAFE: each item in the tuple is read only
|
||||
unsafe impl<$($name: ReadOnlyFetch),*> ReadOnlyFetch for ($($name,)*) {}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
all_tuples!(impl_tuple_fetch, 0, 15, F, S);
|
599
crates/bevy_ecs/src/query/filter.rs
Normal file
599
crates/bevy_ecs/src/query/filter.rs
Normal file
|
@ -0,0 +1,599 @@
|
|||
use crate::{
|
||||
archetype::{Archetype, ArchetypeComponentId},
|
||||
bundle::Bundle,
|
||||
component::{Component, ComponentFlags, ComponentId, StorageType},
|
||||
entity::Entity,
|
||||
query::{Access, Fetch, FetchState, FilteredAccess, WorldQuery},
|
||||
storage::{ComponentSparseSet, Table, Tables},
|
||||
world::World,
|
||||
};
|
||||
use bevy_ecs_macros::all_tuples;
|
||||
use std::{marker::PhantomData, ptr};
|
||||
|
||||
// TODO: uncomment this and use as shorthand (remove where F::Fetch: FilterFetch everywhere) when this bug is fixed in Rust 1.51:
|
||||
// https://github.com/rust-lang/rust/pull/81671
|
||||
// pub trait QueryFilter: WorldQuery
|
||||
// where
|
||||
// Self::Fetch: FilterFetch,
|
||||
// {
|
||||
// }
|
||||
|
||||
// impl<T: WorldQuery> QueryFilter for T where T::Fetch: FilterFetch {
|
||||
// }
|
||||
|
||||
/// Fetch methods used by query filters. This trait exists to allow "short circuit" behaviors for relevant query filter fetches.
|
||||
pub trait FilterFetch: for<'a> Fetch<'a> {
|
||||
/// # Safety
|
||||
/// Must always be called _after_ [Fetch::set_archetype]. `archetype_index` must be in the range of the current archetype
|
||||
unsafe fn archetype_filter_fetch(&mut self, archetype_index: usize) -> bool;
|
||||
|
||||
/// # Safety
|
||||
/// Must always be called _after_ [Fetch::set_table]. `table_row` must be in the range of the current table
|
||||
unsafe fn table_filter_fetch(&mut self, table_row: usize) -> bool;
|
||||
}
|
||||
|
||||
impl<T> FilterFetch for T
|
||||
where
|
||||
T: for<'a> Fetch<'a, Item = bool>,
|
||||
{
|
||||
#[inline]
|
||||
unsafe fn archetype_filter_fetch(&mut self, archetype_index: usize) -> bool {
|
||||
self.archetype_fetch(archetype_index)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn table_filter_fetch(&mut self, table_row: usize) -> bool {
|
||||
self.table_fetch(table_row)
|
||||
}
|
||||
}
|
||||
|
||||
/// Filter that retrieves components of type `T` that have either been mutated or added since the start of the frame.
|
||||
pub struct With<T>(PhantomData<T>);
|
||||
|
||||
impl<T: Component> WorldQuery for With<T> {
|
||||
type Fetch = WithFetch<T>;
|
||||
type State = WithState<T>;
|
||||
}
|
||||
|
||||
pub struct WithFetch<T> {
|
||||
storage_type: StorageType,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
pub struct WithState<T> {
|
||||
component_id: ComponentId,
|
||||
storage_type: StorageType,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
// SAFE: no component access or archetype component access
|
||||
unsafe impl<T: Component> FetchState for WithState<T> {
|
||||
fn init(world: &mut World) -> Self {
|
||||
let component_info = world.components.get_or_insert_info::<T>();
|
||||
Self {
|
||||
component_id: component_info.id(),
|
||||
storage_type: component_info.storage_type(),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn update_component_access(&self, access: &mut FilteredAccess<ComponentId>) {
|
||||
access.add_with(self.component_id);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn update_archetype_component_access(
|
||||
&self,
|
||||
_archetype: &Archetype,
|
||||
_access: &mut Access<ArchetypeComponentId>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn matches_archetype(&self, archetype: &Archetype) -> bool {
|
||||
archetype.contains(self.component_id)
|
||||
}
|
||||
|
||||
fn matches_table(&self, table: &Table) -> bool {
|
||||
table.has_column(self.component_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Component> Fetch<'a> for WithFetch<T> {
|
||||
type Item = bool;
|
||||
type State = WithState<T>;
|
||||
|
||||
unsafe fn init(_world: &World, state: &Self::State) -> Self {
|
||||
Self {
|
||||
storage_type: state.storage_type,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_dense(&self) -> bool {
|
||||
self.storage_type == StorageType::Table
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_table(&mut self, _state: &Self::State, _table: &Table) {}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_archetype(
|
||||
&mut self,
|
||||
_state: &Self::State,
|
||||
_archetype: &Archetype,
|
||||
_tables: &Tables,
|
||||
) {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn archetype_fetch(&mut self, _archetype_index: usize) -> Self::Item {
|
||||
true
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn table_fetch(&mut self, _table_row: usize) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Filter that retrieves components of type `T` that have either been mutated or added since the start of the frame.
|
||||
pub struct Without<T>(PhantomData<T>);
|
||||
|
||||
impl<T: Component> WorldQuery for Without<T> {
|
||||
type Fetch = WithoutFetch<T>;
|
||||
type State = WithoutState<T>;
|
||||
}
|
||||
|
||||
pub struct WithoutFetch<T> {
|
||||
storage_type: StorageType,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
pub struct WithoutState<T> {
|
||||
component_id: ComponentId,
|
||||
storage_type: StorageType,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
// SAFE: no component access or archetype component access
|
||||
unsafe impl<T: Component> FetchState for WithoutState<T> {
|
||||
fn init(world: &mut World) -> Self {
|
||||
let component_info = world.components.get_or_insert_info::<T>();
|
||||
Self {
|
||||
component_id: component_info.id(),
|
||||
storage_type: component_info.storage_type(),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn update_component_access(&self, access: &mut FilteredAccess<ComponentId>) {
|
||||
access.add_without(self.component_id);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn update_archetype_component_access(
|
||||
&self,
|
||||
_archetype: &Archetype,
|
||||
_access: &mut Access<ArchetypeComponentId>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn matches_archetype(&self, archetype: &Archetype) -> bool {
|
||||
!archetype.contains(self.component_id)
|
||||
}
|
||||
|
||||
fn matches_table(&self, table: &Table) -> bool {
|
||||
!table.has_column(self.component_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Component> Fetch<'a> for WithoutFetch<T> {
|
||||
type Item = bool;
|
||||
type State = WithoutState<T>;
|
||||
|
||||
unsafe fn init(_world: &World, state: &Self::State) -> Self {
|
||||
Self {
|
||||
storage_type: state.storage_type,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_dense(&self) -> bool {
|
||||
self.storage_type == StorageType::Table
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_table(&mut self, _state: &Self::State, _table: &Table) {}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_archetype(
|
||||
&mut self,
|
||||
_state: &Self::State,
|
||||
_archetype: &Archetype,
|
||||
_tables: &Tables,
|
||||
) {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn archetype_fetch(&mut self, _archetype_index: usize) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn table_fetch(&mut self, _table_row: usize) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WithBundle<T: Bundle>(PhantomData<T>);
|
||||
|
||||
pub struct WithBundleFetch<T: Bundle> {
|
||||
is_dense: bool,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
pub struct WithBundleState<T: Bundle> {
|
||||
component_ids: Vec<ComponentId>,
|
||||
is_dense: bool,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
// SAFE: no component access or archetype component access
|
||||
unsafe impl<T: Bundle> FetchState for WithBundleState<T> {
|
||||
fn init(world: &mut World) -> Self {
|
||||
let bundle_info = world.bundles.init_info::<T>(&mut world.components);
|
||||
let components = &world.components;
|
||||
Self {
|
||||
component_ids: bundle_info.component_ids.clone(),
|
||||
is_dense: !bundle_info.component_ids.iter().any(|id| unsafe {
|
||||
components.get_info_unchecked(*id).storage_type() != StorageType::Table
|
||||
}),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn update_component_access(&self, access: &mut FilteredAccess<ComponentId>) {
|
||||
for component_id in self.component_ids.iter().cloned() {
|
||||
access.add_with(component_id);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn update_archetype_component_access(
|
||||
&self,
|
||||
_archetype: &Archetype,
|
||||
_access: &mut Access<ArchetypeComponentId>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn matches_archetype(&self, archetype: &Archetype) -> bool {
|
||||
self.component_ids.iter().all(|id| archetype.contains(*id))
|
||||
}
|
||||
|
||||
fn matches_table(&self, table: &Table) -> bool {
|
||||
self.component_ids.iter().all(|id| table.has_column(*id))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Bundle> Fetch<'a> for WithBundleFetch<T> {
|
||||
type Item = bool;
|
||||
type State = WithBundleState<T>;
|
||||
|
||||
unsafe fn init(_world: &World, state: &Self::State) -> Self {
|
||||
Self {
|
||||
is_dense: state.is_dense,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_dense(&self) -> bool {
|
||||
self.is_dense
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_table(&mut self, _state: &Self::State, _table: &Table) {}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_archetype(
|
||||
&mut self,
|
||||
_state: &Self::State,
|
||||
_archetype: &Archetype,
|
||||
_tables: &Tables,
|
||||
) {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn archetype_fetch(&mut self, _archetype_index: usize) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn table_fetch(&mut self, _table_row: usize) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Or<T>(pub T);
|
||||
pub struct OrFetch<T: FilterFetch> {
|
||||
fetch: T,
|
||||
matches: bool,
|
||||
}
|
||||
|
||||
macro_rules! impl_query_filter_tuple {
|
||||
($(($filter: ident, $state: ident)),*) => {
|
||||
#[allow(unused_variables)]
|
||||
#[allow(non_snake_case)]
|
||||
impl<'a, $($filter: FilterFetch),*> FilterFetch for ($($filter,)*) {
|
||||
#[inline]
|
||||
unsafe fn table_filter_fetch(&mut self, table_row: usize) -> bool {
|
||||
let ($($filter,)*) = self;
|
||||
true $(&& $filter.table_filter_fetch(table_row))*
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn archetype_filter_fetch(&mut self, archetype_index: usize) -> bool {
|
||||
let ($($filter,)*) = self;
|
||||
true $(&& $filter.archetype_filter_fetch(archetype_index))*
|
||||
}
|
||||
}
|
||||
|
||||
impl<$($filter: WorldQuery),*> WorldQuery for Or<($($filter,)*)>
|
||||
where $($filter::Fetch: FilterFetch),*
|
||||
{
|
||||
type Fetch = Or<($(OrFetch<$filter::Fetch>,)*)>;
|
||||
type State = Or<($($filter::State,)*)>;
|
||||
}
|
||||
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[allow(non_snake_case)]
|
||||
impl<'a, $($filter: FilterFetch),*> Fetch<'a> for Or<($(OrFetch<$filter>,)*)> {
|
||||
type State = Or<($(<$filter as Fetch<'a>>::State,)*)>;
|
||||
type Item = bool;
|
||||
|
||||
unsafe fn init(world: &World, state: &Self::State) -> Self {
|
||||
let ($($filter,)*) = &state.0;
|
||||
Or(($(OrFetch {
|
||||
fetch: $filter::init(world, $filter),
|
||||
matches: false,
|
||||
},)*))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_dense(&self) -> bool {
|
||||
let ($($filter,)*) = &self.0;
|
||||
true $(&& $filter.fetch.is_dense())*
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_table(&mut self, state: &Self::State, table: &Table) {
|
||||
let ($($filter,)*) = &mut self.0;
|
||||
let ($($state,)*) = &state.0;
|
||||
$(
|
||||
$filter.matches = $state.matches_table(table);
|
||||
if $filter.matches {
|
||||
$filter.fetch.set_table($state, table);
|
||||
}
|
||||
)*
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_archetype(&mut self, state: &Self::State, archetype: &Archetype, tables: &Tables) {
|
||||
let ($($filter,)*) = &mut self.0;
|
||||
let ($($state,)*) = &state.0;
|
||||
$(
|
||||
$filter.matches = $state.matches_archetype(archetype);
|
||||
if $filter.matches {
|
||||
$filter.fetch.set_archetype($state, archetype, tables);
|
||||
}
|
||||
)*
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn table_fetch(&mut self, table_row: usize) -> bool {
|
||||
let ($($filter,)*) = &mut self.0;
|
||||
false $(|| ($filter.matches && $filter.fetch.table_filter_fetch(table_row)))*
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn archetype_fetch(&mut self, archetype_index: usize) -> bool {
|
||||
let ($($filter,)*) = &mut self.0;
|
||||
false $(|| ($filter.matches && $filter.fetch.archetype_filter_fetch(archetype_index)))*
|
||||
}
|
||||
}
|
||||
|
||||
// SAFE: update_component_access and update_archetype_component_access are called for each item in the tuple
|
||||
#[allow(unused_variables)]
|
||||
#[allow(non_snake_case)]
|
||||
unsafe impl<$($filter: FetchState),*> FetchState for Or<($($filter,)*)> {
|
||||
fn init(world: &mut World) -> Self {
|
||||
Or(($($filter::init(world),)*))
|
||||
}
|
||||
|
||||
fn update_component_access(&self, access: &mut FilteredAccess<ComponentId>) {
|
||||
let ($($filter,)*) = &self.0;
|
||||
$($filter.update_component_access(access);)*
|
||||
}
|
||||
|
||||
fn update_archetype_component_access(&self, archetype: &Archetype, access: &mut Access<ArchetypeComponentId>) {
|
||||
let ($($filter,)*) = &self.0;
|
||||
$($filter.update_archetype_component_access(archetype, access);)*
|
||||
}
|
||||
|
||||
fn matches_archetype(&self, archetype: &Archetype) -> bool {
|
||||
let ($($filter,)*) = &self.0;
|
||||
false $(|| $filter.matches_archetype(archetype))*
|
||||
}
|
||||
|
||||
fn matches_table(&self, table: &Table) -> bool {
|
||||
let ($($filter,)*) = &self.0;
|
||||
false $(|| $filter.matches_table(table))*
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
all_tuples!(impl_query_filter_tuple, 0, 15, F, S);
|
||||
|
||||
macro_rules! impl_flag_filter {
|
||||
(
|
||||
$(#[$meta:meta])*
|
||||
$name: ident, $state_name: ident, $fetch_name: ident, $($flags: expr),+) => {
|
||||
$(#[$meta])*
|
||||
pub struct $name<T>(PhantomData<T>);
|
||||
|
||||
pub struct $fetch_name<T> {
|
||||
storage_type: StorageType,
|
||||
table_flags: *mut ComponentFlags,
|
||||
entity_table_rows: *const usize,
|
||||
marker: PhantomData<T>,
|
||||
entities: *const Entity,
|
||||
sparse_set: *const ComponentSparseSet,
|
||||
}
|
||||
|
||||
pub struct $state_name<T> {
|
||||
component_id: ComponentId,
|
||||
storage_type: StorageType,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Component> WorldQuery for $name<T> {
|
||||
type Fetch = $fetch_name<T>;
|
||||
type State = $state_name<T>;
|
||||
}
|
||||
|
||||
|
||||
// SAFE: this reads the T component. archetype component access and component access are updated to reflect that
|
||||
unsafe impl<T: Component> FetchState for $state_name<T> {
|
||||
fn init(world: &mut World) -> Self {
|
||||
let component_info = world.components.get_or_insert_info::<T>();
|
||||
Self {
|
||||
component_id: component_info.id(),
|
||||
storage_type: component_info.storage_type(),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn update_component_access(&self, access: &mut FilteredAccess<ComponentId>) {
|
||||
access.add_read(self.component_id);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn update_archetype_component_access(
|
||||
&self,
|
||||
archetype: &Archetype,
|
||||
access: &mut Access<ArchetypeComponentId>,
|
||||
) {
|
||||
if let Some(archetype_component_id) = archetype.get_archetype_component_id(self.component_id) {
|
||||
access.add_read(archetype_component_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn matches_archetype(&self, archetype: &Archetype) -> bool {
|
||||
archetype.contains(self.component_id)
|
||||
}
|
||||
|
||||
fn matches_table(&self, table: &Table) -> bool {
|
||||
table.has_column(self.component_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Component> Fetch<'a> for $fetch_name<T> {
|
||||
type State = $state_name<T>;
|
||||
type Item = bool;
|
||||
|
||||
unsafe fn init(world: &World, state: &Self::State) -> Self {
|
||||
let mut value = Self {
|
||||
storage_type: state.storage_type,
|
||||
table_flags: ptr::null_mut::<ComponentFlags>(),
|
||||
entities: ptr::null::<Entity>(),
|
||||
entity_table_rows: ptr::null::<usize>(),
|
||||
sparse_set: ptr::null::<ComponentSparseSet>(),
|
||||
marker: PhantomData,
|
||||
};
|
||||
if state.storage_type == StorageType::SparseSet {
|
||||
value.sparse_set = world
|
||||
.storages()
|
||||
.sparse_sets
|
||||
.get(state.component_id).unwrap();
|
||||
}
|
||||
value
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_dense(&self) -> bool {
|
||||
self.storage_type == StorageType::Table
|
||||
}
|
||||
|
||||
unsafe fn set_table(&mut self, state: &Self::State, table: &Table) {
|
||||
self.table_flags = table
|
||||
.get_column(state.component_id).unwrap()
|
||||
.get_flags_mut_ptr();
|
||||
}
|
||||
|
||||
unsafe fn set_archetype(&mut self, state: &Self::State, archetype: &Archetype, tables: &Tables) {
|
||||
match state.storage_type {
|
||||
StorageType::Table => {
|
||||
self.entity_table_rows = archetype.entity_table_rows().as_ptr();
|
||||
// SAFE: archetype tables always exist
|
||||
let table = tables.get_unchecked(archetype.table_id());
|
||||
self.table_flags = table
|
||||
.get_column(state.component_id).unwrap()
|
||||
.get_flags_mut_ptr();
|
||||
}
|
||||
StorageType::SparseSet => self.entities = archetype.entities().as_ptr(),
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn table_fetch(&mut self, table_row: usize) -> bool {
|
||||
false $(|| (*self.table_flags.add(table_row)).contains($flags))+
|
||||
}
|
||||
|
||||
unsafe fn archetype_fetch(&mut self, archetype_index: usize) -> bool {
|
||||
match self.storage_type {
|
||||
StorageType::Table => {
|
||||
let table_row = *self.entity_table_rows.add(archetype_index);
|
||||
false $(|| (*self.table_flags.add(table_row)).contains($flags))+
|
||||
}
|
||||
StorageType::SparseSet => {
|
||||
let entity = *self.entities.add(archetype_index);
|
||||
let flags = (*(*self.sparse_set).get_flags(entity).unwrap());
|
||||
false $(|| flags.contains($flags))+
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_flag_filter!(
|
||||
/// Filter that retrieves components of type `T` that have been added since the start of the frame
|
||||
Added,
|
||||
AddedState,
|
||||
AddedFetch,
|
||||
ComponentFlags::ADDED
|
||||
);
|
||||
|
||||
impl_flag_filter!(
|
||||
/// Filter that retrieves components of type `T` that have been mutated since the start of the frame.
|
||||
/// Added components do not count as mutated.
|
||||
Mutated,
|
||||
MutatedState,
|
||||
MutatedFetch,
|
||||
ComponentFlags::MUTATED
|
||||
);
|
||||
|
||||
impl_flag_filter!(
|
||||
/// Filter that retrieves components of type `T` that have been added or mutated since the start of the frame
|
||||
Changed,
|
||||
ChangedState,
|
||||
ChangedFetch,
|
||||
ComponentFlags::ADDED,
|
||||
ComponentFlags::MUTATED
|
||||
);
|
131
crates/bevy_ecs/src/query/iter.rs
Normal file
131
crates/bevy_ecs/src/query/iter.rs
Normal file
|
@ -0,0 +1,131 @@
|
|||
use crate::{
|
||||
archetype::{ArchetypeId, Archetypes},
|
||||
query::{Fetch, FilterFetch, QueryState, WorldQuery},
|
||||
storage::{TableId, Tables},
|
||||
world::World,
|
||||
};
|
||||
|
||||
pub struct QueryIter<'w, 's, Q: WorldQuery, F: WorldQuery>
|
||||
where
|
||||
F::Fetch: FilterFetch,
|
||||
{
|
||||
tables: &'w Tables,
|
||||
archetypes: &'w Archetypes,
|
||||
query_state: &'s QueryState<Q, F>,
|
||||
world: &'w World,
|
||||
table_id_iter: std::slice::Iter<'s, TableId>,
|
||||
archetype_id_iter: std::slice::Iter<'s, ArchetypeId>,
|
||||
fetch: Q::Fetch,
|
||||
filter: F::Fetch,
|
||||
is_dense: bool,
|
||||
current_len: usize,
|
||||
current_index: usize,
|
||||
}
|
||||
|
||||
impl<'w, 's, Q: WorldQuery, F: WorldQuery> QueryIter<'w, 's, Q, F>
|
||||
where
|
||||
F::Fetch: FilterFetch,
|
||||
{
|
||||
pub(crate) unsafe fn new(world: &'w World, query_state: &'s QueryState<Q, F>) -> Self {
|
||||
let fetch = <Q::Fetch as Fetch>::init(world, &query_state.fetch_state);
|
||||
let filter = <F::Fetch as Fetch>::init(world, &query_state.filter_state);
|
||||
QueryIter {
|
||||
is_dense: fetch.is_dense() && filter.is_dense(),
|
||||
world,
|
||||
query_state,
|
||||
fetch,
|
||||
filter,
|
||||
tables: &world.storages().tables,
|
||||
archetypes: &world.archetypes,
|
||||
table_id_iter: query_state.matched_table_ids.iter(),
|
||||
archetype_id_iter: query_state.matched_archetype_ids.iter(),
|
||||
current_len: 0,
|
||||
current_index: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w, 's, Q: WorldQuery, F: WorldQuery> Iterator for QueryIter<'w, 's, Q, F>
|
||||
where
|
||||
F::Fetch: FilterFetch,
|
||||
{
|
||||
type Item = <Q::Fetch as Fetch<'w>>::Item;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
unsafe {
|
||||
if self.is_dense {
|
||||
loop {
|
||||
if self.current_index == self.current_len {
|
||||
let table_id = self.table_id_iter.next()?;
|
||||
let table = self.tables.get_unchecked(*table_id);
|
||||
self.fetch.set_table(&self.query_state.fetch_state, table);
|
||||
self.filter.set_table(&self.query_state.filter_state, table);
|
||||
self.current_len = table.len();
|
||||
self.current_index = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if !self.filter.table_filter_fetch(self.current_index) {
|
||||
self.current_index += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
let item = self.fetch.table_fetch(self.current_index);
|
||||
|
||||
self.current_index += 1;
|
||||
return Some(item);
|
||||
}
|
||||
} else {
|
||||
loop {
|
||||
if self.current_index == self.current_len {
|
||||
let archetype_id = self.archetype_id_iter.next()?;
|
||||
let archetype = self.archetypes.get_unchecked(*archetype_id);
|
||||
self.fetch.set_archetype(
|
||||
&self.query_state.fetch_state,
|
||||
archetype,
|
||||
self.tables,
|
||||
);
|
||||
self.filter.set_archetype(
|
||||
&self.query_state.filter_state,
|
||||
archetype,
|
||||
self.tables,
|
||||
);
|
||||
self.current_len = archetype.len();
|
||||
self.current_index = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if !self.filter.archetype_filter_fetch(self.current_index) {
|
||||
self.current_index += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
let item = self.fetch.archetype_fetch(self.current_index);
|
||||
self.current_index += 1;
|
||||
return Some(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: We can cheaply implement this for unfiltered Queries because we have:
|
||||
// (1) pre-computed archetype matches
|
||||
// (2) each archetype pre-computes length
|
||||
// (3) there are no per-entity filters
|
||||
// TODO: add an ArchetypeOnlyFilter that enables us to implement this for filters like With<T>
|
||||
impl<'w, 's, Q: WorldQuery> ExactSizeIterator for QueryIter<'w, 's, Q, ()> {
|
||||
fn len(&self) -> usize {
|
||||
self.query_state
|
||||
.matched_archetypes
|
||||
.ones()
|
||||
.map(|index| {
|
||||
// SAFE: matched archetypes always exist
|
||||
let archetype =
|
||||
unsafe { self.world.archetypes.get_unchecked(ArchetypeId::new(index)) };
|
||||
archetype.len()
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
}
|
60
crates/bevy_ecs/src/query/mod.rs
Normal file
60
crates/bevy_ecs/src/query/mod.rs
Normal file
|
@ -0,0 +1,60 @@
|
|||
mod access;
|
||||
mod fetch;
|
||||
mod filter;
|
||||
mod iter;
|
||||
mod state;
|
||||
|
||||
pub use access::*;
|
||||
pub use fetch::*;
|
||||
pub use filter::*;
|
||||
pub use iter::*;
|
||||
pub use state::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
component::{ComponentDescriptor, StorageType},
|
||||
world::World,
|
||||
};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
struct A(usize);
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
struct B(usize);
|
||||
|
||||
#[test]
|
||||
fn query() {
|
||||
let mut world = World::new();
|
||||
world.spawn().insert_bundle((A(1), B(1)));
|
||||
world.spawn().insert_bundle((A(2),));
|
||||
let values = world.query::<&A>().iter(&world).collect::<Vec<&A>>();
|
||||
assert_eq!(values, vec![&A(1), &A(2)]);
|
||||
|
||||
for (_a, mut b) in world.query::<(&A, &mut B)>().iter_mut(&mut world) {
|
||||
b.0 = 3;
|
||||
}
|
||||
let values = world.query::<&B>().iter(&world).collect::<Vec<&B>>();
|
||||
assert_eq!(values, vec![&B(3)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_storage_query() {
|
||||
let mut world = World::new();
|
||||
world
|
||||
.register_component(ComponentDescriptor::new::<A>(StorageType::SparseSet))
|
||||
.unwrap();
|
||||
|
||||
world.spawn().insert_bundle((A(1), B(2)));
|
||||
world.spawn().insert_bundle((A(2),));
|
||||
|
||||
let values = world.query::<&A>().iter(&world).collect::<Vec<&A>>();
|
||||
assert_eq!(values, vec![&A(1), &A(2)]);
|
||||
|
||||
for (_a, mut b) in world.query::<(&A, &mut B)>().iter_mut(&mut world) {
|
||||
b.0 = 3;
|
||||
}
|
||||
|
||||
let values = world.query::<&B>().iter(&world).collect::<Vec<&B>>();
|
||||
assert_eq!(values, vec![&B(3)]);
|
||||
}
|
||||
}
|
420
crates/bevy_ecs/src/query/state.rs
Normal file
420
crates/bevy_ecs/src/query/state.rs
Normal file
|
@ -0,0 +1,420 @@
|
|||
use crate::{
|
||||
archetype::{Archetype, ArchetypeComponentId, ArchetypeGeneration, ArchetypeId},
|
||||
component::ComponentId,
|
||||
entity::Entity,
|
||||
query::{
|
||||
Access, Fetch, FetchState, FilterFetch, FilteredAccess, QueryIter, ReadOnlyFetch,
|
||||
WorldQuery,
|
||||
},
|
||||
storage::TableId,
|
||||
world::{World, WorldId},
|
||||
};
|
||||
use bevy_tasks::TaskPool;
|
||||
use fixedbitset::FixedBitSet;
|
||||
use thiserror::Error;
|
||||
|
||||
pub struct QueryState<Q: WorldQuery, F: WorldQuery = ()>
|
||||
where
|
||||
F::Fetch: FilterFetch,
|
||||
{
|
||||
world_id: WorldId,
|
||||
pub(crate) archetype_generation: ArchetypeGeneration,
|
||||
pub(crate) matched_tables: FixedBitSet,
|
||||
pub(crate) matched_archetypes: FixedBitSet,
|
||||
pub(crate) archetype_component_access: Access<ArchetypeComponentId>,
|
||||
pub(crate) component_access: FilteredAccess<ComponentId>,
|
||||
// NOTE: we maintain both a TableId bitset and a vec because iterating the vec is faster
|
||||
pub(crate) matched_table_ids: Vec<TableId>,
|
||||
// NOTE: we maintain both a ArchetypeId bitset and a vec because iterating the vec is faster
|
||||
pub(crate) matched_archetype_ids: Vec<ArchetypeId>,
|
||||
pub(crate) fetch_state: Q::State,
|
||||
pub(crate) filter_state: F::State,
|
||||
}
|
||||
|
||||
impl<Q: WorldQuery, F: WorldQuery> QueryState<Q, F>
|
||||
where
|
||||
F::Fetch: FilterFetch,
|
||||
{
|
||||
pub fn new(world: &mut World) -> Self {
|
||||
let fetch_state = <Q::State as FetchState>::init(world);
|
||||
let filter_state = <F::State as FetchState>::init(world);
|
||||
let mut component_access = Default::default();
|
||||
fetch_state.update_component_access(&mut component_access);
|
||||
filter_state.update_component_access(&mut component_access);
|
||||
let mut state = Self {
|
||||
world_id: world.id(),
|
||||
archetype_generation: ArchetypeGeneration::new(usize::MAX),
|
||||
matched_table_ids: Vec::new(),
|
||||
matched_archetype_ids: Vec::new(),
|
||||
fetch_state,
|
||||
filter_state,
|
||||
component_access,
|
||||
matched_tables: Default::default(),
|
||||
matched_archetypes: Default::default(),
|
||||
archetype_component_access: Default::default(),
|
||||
};
|
||||
state.validate_world_and_update_archetypes(world);
|
||||
state
|
||||
}
|
||||
|
||||
pub fn validate_world_and_update_archetypes(&mut self, world: &World) {
|
||||
if world.id() != self.world_id {
|
||||
panic!("Attempted to use {} with a mismatched World. QueryStates can only be used with the World they were created from.",
|
||||
std::any::type_name::<Self>());
|
||||
}
|
||||
let archetypes = world.archetypes();
|
||||
let old_generation = self.archetype_generation;
|
||||
let archetype_index_range = if old_generation == archetypes.generation() {
|
||||
0..0
|
||||
} else {
|
||||
self.archetype_generation = archetypes.generation();
|
||||
if old_generation.value() == usize::MAX {
|
||||
0..archetypes.len()
|
||||
} else {
|
||||
old_generation.value()..archetypes.len()
|
||||
}
|
||||
};
|
||||
for archetype_index in archetype_index_range {
|
||||
// SAFE: archetype indices less than the archetype generation are guaranteed to exist
|
||||
let archetype = unsafe { archetypes.get_unchecked(ArchetypeId::new(archetype_index)) };
|
||||
self.new_archetype(archetype);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_archetype(&mut self, archetype: &Archetype) {
|
||||
let table_index = archetype.table_id().index();
|
||||
if self.fetch_state.matches_archetype(archetype)
|
||||
&& self.filter_state.matches_archetype(archetype)
|
||||
{
|
||||
self.fetch_state
|
||||
.update_archetype_component_access(archetype, &mut self.archetype_component_access);
|
||||
self.filter_state
|
||||
.update_archetype_component_access(archetype, &mut self.archetype_component_access);
|
||||
self.matched_archetypes.grow(archetype.id().index() + 1);
|
||||
self.matched_archetypes.set(archetype.id().index(), true);
|
||||
self.matched_archetype_ids.push(archetype.id());
|
||||
if !self.matched_tables.contains(table_index) {
|
||||
self.matched_tables.grow(table_index + 1);
|
||||
self.matched_tables.set(table_index, true);
|
||||
self.matched_table_ids.push(archetype.table_id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get<'w>(
|
||||
&mut self,
|
||||
world: &'w World,
|
||||
entity: Entity,
|
||||
) -> Result<<Q::Fetch as Fetch<'w>>::Item, QueryEntityError>
|
||||
where
|
||||
Q::Fetch: ReadOnlyFetch,
|
||||
{
|
||||
// SAFE: query is read only
|
||||
unsafe { self.get_unchecked(world, entity) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_mut<'w>(
|
||||
&mut self,
|
||||
world: &'w mut World,
|
||||
entity: Entity,
|
||||
) -> Result<<Q::Fetch as Fetch<'w>>::Item, QueryEntityError> {
|
||||
// SAFE: query has unique world access
|
||||
unsafe { self.get_unchecked(world, entity) }
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// This does not check for mutable query correctness. To be safe, make sure mutable queries
|
||||
/// have unique access to the components they query.
|
||||
#[inline]
|
||||
pub unsafe fn get_unchecked<'w>(
|
||||
&mut self,
|
||||
world: &'w World,
|
||||
entity: Entity,
|
||||
) -> Result<<Q::Fetch as Fetch<'w>>::Item, QueryEntityError> {
|
||||
self.validate_world_and_update_archetypes(world);
|
||||
self.get_unchecked_manual(world, entity)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// This does not check for mutable query correctness. To be safe, make sure mutable queries
|
||||
/// have unique access to the components they query.
|
||||
pub unsafe fn get_unchecked_manual<'w>(
|
||||
&self,
|
||||
world: &'w World,
|
||||
entity: Entity,
|
||||
) -> Result<<Q::Fetch as Fetch<'w>>::Item, QueryEntityError> {
|
||||
let location = world
|
||||
.entities
|
||||
.get(entity)
|
||||
.ok_or(QueryEntityError::NoSuchEntity)?;
|
||||
if !self
|
||||
.matched_archetypes
|
||||
.contains(location.archetype_id.index())
|
||||
{
|
||||
return Err(QueryEntityError::QueryDoesNotMatch);
|
||||
}
|
||||
// SAFE: live entities always exist in an archetype
|
||||
let archetype = world.archetypes.get_unchecked(location.archetype_id);
|
||||
let mut fetch = <Q::Fetch as Fetch>::init(world, &self.fetch_state);
|
||||
let mut filter = <F::Fetch as Fetch>::init(world, &self.filter_state);
|
||||
|
||||
fetch.set_archetype(&self.fetch_state, archetype, &world.storages().tables);
|
||||
filter.set_archetype(&self.filter_state, archetype, &world.storages().tables);
|
||||
if filter.archetype_filter_fetch(location.index) {
|
||||
Ok(fetch.archetype_fetch(location.index))
|
||||
} else {
|
||||
Err(QueryEntityError::QueryDoesNotMatch)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn iter<'w, 's>(&'s mut self, world: &'w World) -> QueryIter<'w, 's, Q, F>
|
||||
where
|
||||
Q::Fetch: ReadOnlyFetch,
|
||||
{
|
||||
// SAFE: query is read only
|
||||
unsafe { self.iter_unchecked(world) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn iter_mut<'w, 's>(&'s mut self, world: &'w mut World) -> QueryIter<'w, 's, Q, F> {
|
||||
// SAFE: query has unique world access
|
||||
unsafe { self.iter_unchecked(world) }
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// This does not check for mutable query correctness. To be safe, make sure mutable queries
|
||||
/// have unique access to the components they query.
|
||||
#[inline]
|
||||
pub unsafe fn iter_unchecked<'w, 's>(
|
||||
&'s mut self,
|
||||
world: &'w World,
|
||||
) -> QueryIter<'w, 's, Q, F> {
|
||||
self.validate_world_and_update_archetypes(world);
|
||||
self.iter_unchecked_manual(world)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// This does not check for mutable query correctness. To be safe, make sure mutable queries
|
||||
/// have unique access to the components they query.
|
||||
/// This does not validate that `world.id()` matches `self.world_id`. Calling this on a `world` with
|
||||
/// a mismatched WorldId is unsafe.
|
||||
#[inline]
|
||||
pub(crate) unsafe fn iter_unchecked_manual<'w, 's>(
|
||||
&'s self,
|
||||
world: &'w World,
|
||||
) -> QueryIter<'w, 's, Q, F> {
|
||||
QueryIter::new(world, self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn for_each<'w>(
|
||||
&mut self,
|
||||
world: &'w World,
|
||||
func: impl FnMut(<Q::Fetch as Fetch<'w>>::Item),
|
||||
) where
|
||||
Q::Fetch: ReadOnlyFetch,
|
||||
{
|
||||
// SAFE: query is read only
|
||||
unsafe {
|
||||
self.for_each_unchecked(world, func);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn for_each_mut<'w>(
|
||||
&mut self,
|
||||
world: &'w mut World,
|
||||
func: impl FnMut(<Q::Fetch as Fetch<'w>>::Item),
|
||||
) {
|
||||
// SAFE: query has unique world access
|
||||
unsafe {
|
||||
self.for_each_unchecked(world, func);
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// This does not check for mutable query correctness. To be safe, make sure mutable queries
|
||||
/// have unique access to the components they query.
|
||||
#[inline]
|
||||
pub unsafe fn for_each_unchecked<'w>(
|
||||
&mut self,
|
||||
world: &'w World,
|
||||
func: impl FnMut(<Q::Fetch as Fetch<'w>>::Item),
|
||||
) {
|
||||
self.validate_world_and_update_archetypes(world);
|
||||
self.for_each_unchecked_manual(world, func);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn par_for_each<'w>(
|
||||
&mut self,
|
||||
world: &'w World,
|
||||
task_pool: &TaskPool,
|
||||
batch_size: usize,
|
||||
func: impl Fn(<Q::Fetch as Fetch<'w>>::Item) + Send + Sync + Clone,
|
||||
) where
|
||||
Q::Fetch: ReadOnlyFetch,
|
||||
{
|
||||
// SAFE: query is read only
|
||||
unsafe {
|
||||
self.par_for_each_unchecked(world, task_pool, batch_size, func);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn par_for_each_mut<'w>(
|
||||
&mut self,
|
||||
world: &'w mut World,
|
||||
task_pool: &TaskPool,
|
||||
batch_size: usize,
|
||||
func: impl Fn(<Q::Fetch as Fetch<'w>>::Item) + Send + Sync + Clone,
|
||||
) {
|
||||
// SAFE: query has unique world access
|
||||
unsafe {
|
||||
self.par_for_each_unchecked(world, task_pool, batch_size, func);
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// This does not check for mutable query correctness. To be safe, make sure mutable queries
|
||||
/// have unique access to the components they query.
|
||||
#[inline]
|
||||
pub unsafe fn par_for_each_unchecked<'w>(
|
||||
&mut self,
|
||||
world: &'w World,
|
||||
task_pool: &TaskPool,
|
||||
batch_size: usize,
|
||||
func: impl Fn(<Q::Fetch as Fetch<'w>>::Item) + Send + Sync + Clone,
|
||||
) {
|
||||
self.validate_world_and_update_archetypes(world);
|
||||
self.par_for_each_unchecked_manual(world, task_pool, batch_size, func);
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// This does not check for mutable query correctness. To be safe, make sure mutable queries
|
||||
/// have unique access to the components they query.
|
||||
/// This does not validate that `world.id()` matches `self.world_id`. Calling this on a `world` with
|
||||
/// a mismatched WorldId is unsafe.
|
||||
pub(crate) unsafe fn for_each_unchecked_manual<'w, 's>(
|
||||
&'s self,
|
||||
world: &'w World,
|
||||
mut func: impl FnMut(<Q::Fetch as Fetch<'w>>::Item),
|
||||
) {
|
||||
let mut fetch = <Q::Fetch as Fetch>::init(world, &self.fetch_state);
|
||||
let mut filter = <F::Fetch as Fetch>::init(world, &self.filter_state);
|
||||
if fetch.is_dense() && filter.is_dense() {
|
||||
let tables = &world.storages().tables;
|
||||
for table_id in self.matched_table_ids.iter() {
|
||||
let table = tables.get_unchecked(*table_id);
|
||||
fetch.set_table(&self.fetch_state, table);
|
||||
filter.set_table(&self.filter_state, table);
|
||||
|
||||
for table_index in 0..table.len() {
|
||||
if !filter.table_filter_fetch(table_index) {
|
||||
continue;
|
||||
}
|
||||
let item = fetch.table_fetch(table_index);
|
||||
func(item);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let archetypes = &world.archetypes;
|
||||
let tables = &world.storages().tables;
|
||||
for archetype_id in self.matched_archetype_ids.iter() {
|
||||
let archetype = archetypes.get_unchecked(*archetype_id);
|
||||
fetch.set_archetype(&self.fetch_state, archetype, tables);
|
||||
filter.set_archetype(&self.filter_state, archetype, tables);
|
||||
|
||||
for archetype_index in 0..archetype.len() {
|
||||
if !filter.archetype_filter_fetch(archetype_index) {
|
||||
continue;
|
||||
}
|
||||
func(fetch.archetype_fetch(archetype_index));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// This does not check for mutable query correctness. To be safe, make sure mutable queries
|
||||
/// have unique access to the components they query.
|
||||
/// This does not validate that `world.id()` matches `self.world_id`. Calling this on a `world` with
|
||||
/// a mismatched WorldId is unsafe.
|
||||
pub unsafe fn par_for_each_unchecked_manual<'w, 's>(
|
||||
&'s self,
|
||||
world: &'w World,
|
||||
task_pool: &TaskPool,
|
||||
batch_size: usize,
|
||||
func: impl Fn(<Q::Fetch as Fetch<'w>>::Item) + Send + Sync + Clone,
|
||||
) {
|
||||
task_pool.scope(|scope| {
|
||||
let fetch = <Q::Fetch as Fetch>::init(world, &self.fetch_state);
|
||||
let filter = <F::Fetch as Fetch>::init(world, &self.filter_state);
|
||||
|
||||
if fetch.is_dense() && filter.is_dense() {
|
||||
let tables = &world.storages().tables;
|
||||
for table_id in self.matched_table_ids.iter() {
|
||||
let table = tables.get_unchecked(*table_id);
|
||||
let mut offset = 0;
|
||||
while offset < table.len() {
|
||||
let func = func.clone();
|
||||
scope.spawn(async move {
|
||||
let mut fetch = <Q::Fetch as Fetch>::init(world, &self.fetch_state);
|
||||
let mut filter = <F::Fetch as Fetch>::init(world, &self.filter_state);
|
||||
let tables = &world.storages().tables;
|
||||
let table = tables.get_unchecked(*table_id);
|
||||
fetch.set_table(&self.fetch_state, table);
|
||||
filter.set_table(&self.filter_state, table);
|
||||
let len = batch_size.min(table.len() - offset);
|
||||
for table_index in offset..offset + len {
|
||||
if !filter.table_filter_fetch(table_index) {
|
||||
continue;
|
||||
}
|
||||
let item = fetch.table_fetch(table_index);
|
||||
func(item);
|
||||
}
|
||||
});
|
||||
offset += batch_size;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let archetypes = &world.archetypes;
|
||||
for archetype_id in self.matched_archetype_ids.iter() {
|
||||
let mut offset = 0;
|
||||
let archetype = archetypes.get_unchecked(*archetype_id);
|
||||
while offset < archetype.len() {
|
||||
let func = func.clone();
|
||||
scope.spawn(async move {
|
||||
let mut fetch = <Q::Fetch as Fetch>::init(world, &self.fetch_state);
|
||||
let mut filter = <F::Fetch as Fetch>::init(world, &self.filter_state);
|
||||
let tables = &world.storages().tables;
|
||||
let archetype = world.archetypes.get_unchecked(*archetype_id);
|
||||
fetch.set_archetype(&self.fetch_state, archetype, tables);
|
||||
filter.set_archetype(&self.filter_state, archetype, tables);
|
||||
|
||||
for archetype_index in 0..archetype.len() {
|
||||
if !filter.archetype_filter_fetch(archetype_index) {
|
||||
continue;
|
||||
}
|
||||
func(fetch.archetype_fetch(archetype_index));
|
||||
}
|
||||
});
|
||||
offset += batch_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// An error that occurs when retrieving a specific [Entity]'s query result.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum QueryEntityError {
|
||||
#[error("The given entity does not have the requested component.")]
|
||||
QueryDoesNotMatch,
|
||||
#[error("The requested entity does not exist.")]
|
||||
NoSuchEntity,
|
||||
}
|
165
crates/bevy_ecs/src/reflect.rs
Normal file
165
crates/bevy_ecs/src/reflect.rs
Normal file
|
@ -0,0 +1,165 @@
|
|||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use crate::{
|
||||
component::{Component, ComponentFlags},
|
||||
entity::{Entity, EntityMap, MapEntities, MapEntitiesError},
|
||||
world::{FromWorld, World},
|
||||
};
|
||||
use bevy_reflect::{impl_reflect_value, FromType, Reflect, ReflectDeserialize};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ReflectComponent {
|
||||
add_component: fn(&mut World, Entity, &dyn Reflect),
|
||||
apply_component: fn(&mut World, Entity, &dyn Reflect),
|
||||
reflect_component: fn(&World, Entity) -> Option<&dyn Reflect>,
|
||||
reflect_component_mut: unsafe fn(&World, Entity) -> Option<ReflectMut>,
|
||||
copy_component: fn(&World, &mut World, Entity, Entity),
|
||||
}
|
||||
|
||||
impl ReflectComponent {
|
||||
pub fn add_component(&self, world: &mut World, entity: Entity, component: &dyn Reflect) {
|
||||
(self.add_component)(world, entity, component);
|
||||
}
|
||||
|
||||
pub fn apply_component(&self, world: &mut World, entity: Entity, component: &dyn Reflect) {
|
||||
(self.apply_component)(world, entity, component);
|
||||
}
|
||||
|
||||
pub fn reflect_component<'a>(
|
||||
&self,
|
||||
world: &'a World,
|
||||
entity: Entity,
|
||||
) -> Option<&'a dyn Reflect> {
|
||||
(self.reflect_component)(world, entity)
|
||||
}
|
||||
|
||||
pub fn reflect_component_mut<'a>(
|
||||
&self,
|
||||
world: &'a mut World,
|
||||
entity: Entity,
|
||||
) -> Option<ReflectMut<'a>> {
|
||||
// SAFE: unique world access
|
||||
unsafe { (self.reflect_component_mut)(world, entity) }
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// This method does not prevent you from having two mutable pointers to the same data, violating Rust's aliasing rules. To avoid this:
|
||||
/// * Only call this method in an exclusive system to avoid sharing across threads (or use a scheduler that enforces safe memory access).
|
||||
/// * Don't call this method more than once in the same scope for a given component.
|
||||
pub unsafe fn reflect_component_unchecked_mut<'a>(
|
||||
&self,
|
||||
world: &'a World,
|
||||
entity: Entity,
|
||||
) -> Option<ReflectMut<'a>> {
|
||||
(self.reflect_component_mut)(world, entity)
|
||||
}
|
||||
|
||||
pub fn copy_component(
|
||||
&self,
|
||||
source_world: &World,
|
||||
destination_world: &mut World,
|
||||
source_entity: Entity,
|
||||
destination_entity: Entity,
|
||||
) {
|
||||
(self.copy_component)(
|
||||
source_world,
|
||||
destination_world,
|
||||
source_entity,
|
||||
destination_entity,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Component + Reflect + FromWorld> FromType<C> for ReflectComponent {
|
||||
fn from_type() -> Self {
|
||||
ReflectComponent {
|
||||
add_component: |world, entity, reflected_component| {
|
||||
let mut component = C::from_world(world);
|
||||
component.apply(reflected_component);
|
||||
world.entity_mut(entity).insert(component);
|
||||
},
|
||||
apply_component: |world, entity, reflected_component| {
|
||||
let mut component = world.get_mut::<C>(entity).unwrap();
|
||||
component.apply(reflected_component);
|
||||
},
|
||||
copy_component: |source_world, destination_world, source_entity, destination_entity| {
|
||||
let source_component = source_world.get::<C>(source_entity).unwrap();
|
||||
let mut destination_component = C::from_world(destination_world);
|
||||
destination_component.apply(source_component);
|
||||
destination_world
|
||||
.entity_mut(destination_entity)
|
||||
.insert(destination_component);
|
||||
},
|
||||
reflect_component: |world, entity| {
|
||||
world
|
||||
.get_entity(entity)?
|
||||
.get::<C>()
|
||||
.map(|c| c as &dyn Reflect)
|
||||
},
|
||||
reflect_component_mut: |world, entity| unsafe {
|
||||
world
|
||||
.get_entity(entity)?
|
||||
.get_unchecked_mut::<C>()
|
||||
.map(|c| ReflectMut {
|
||||
value: c.value as &mut dyn Reflect,
|
||||
flags: c.flags,
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Unique borrow of a Reflected component
|
||||
pub struct ReflectMut<'a> {
|
||||
pub(crate) value: &'a mut dyn Reflect,
|
||||
pub(crate) flags: &'a mut ComponentFlags,
|
||||
}
|
||||
|
||||
impl<'a> Deref for ReflectMut<'a> {
|
||||
type Target = dyn Reflect;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &dyn Reflect {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DerefMut for ReflectMut<'a> {
|
||||
#[inline]
|
||||
fn deref_mut(&mut self) -> &mut dyn Reflect {
|
||||
self.flags.insert(ComponentFlags::MUTATED);
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl_reflect_value!(Entity(Hash, PartialEq, Serialize, Deserialize));
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ReflectMapEntities {
|
||||
map_entities: fn(&mut World, &EntityMap) -> Result<(), MapEntitiesError>,
|
||||
}
|
||||
|
||||
impl ReflectMapEntities {
|
||||
pub fn map_entities(
|
||||
&self,
|
||||
world: &mut World,
|
||||
entity_map: &EntityMap,
|
||||
) -> Result<(), MapEntitiesError> {
|
||||
(self.map_entities)(world, entity_map)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Component + MapEntities> FromType<C> for ReflectMapEntities {
|
||||
fn from_type() -> Self {
|
||||
ReflectMapEntities {
|
||||
map_entities: |world, entity_map| {
|
||||
for entity in entity_map.values() {
|
||||
if let Some(mut component) = world.get_mut::<C>(entity) {
|
||||
component.map_entities(entity_map)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
mod resource_query;
|
||||
mod resources;
|
||||
|
||||
pub use resource_query::*;
|
||||
pub use resources::*;
|
|
@ -1,221 +0,0 @@
|
|||
use super::FromResources;
|
||||
use crate::{Resource, ResourceIndex, Resources, SystemId};
|
||||
use std::{
|
||||
marker::PhantomData,
|
||||
ops::{Deref, DerefMut},
|
||||
ptr::NonNull,
|
||||
};
|
||||
|
||||
// TODO: align TypeAccess api with Query::Fetch
|
||||
|
||||
/// Shared borrow of a Resource
|
||||
#[derive(Debug)]
|
||||
pub struct Res<'a, T: Resource> {
|
||||
value: &'a T,
|
||||
added: bool,
|
||||
mutated: bool,
|
||||
}
|
||||
|
||||
impl<'a, T: Resource> Res<'a, T> {
|
||||
/// Creates a reference cell to a Resource from a pointer
|
||||
///
|
||||
/// # Safety
|
||||
/// The pointer must have correct lifetime / storage
|
||||
pub unsafe fn new(value: NonNull<T>, added: bool, changed: bool) -> Self {
|
||||
Self {
|
||||
value: &*value.as_ptr(),
|
||||
added,
|
||||
mutated: changed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Resource> Deref for Res<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Resource> Res<'a, T> {
|
||||
#[inline(always)]
|
||||
pub fn added(this: &Self) -> bool {
|
||||
this.added
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn mutated(this: &Self) -> bool {
|
||||
this.mutated
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn changed(this: &Self) -> bool {
|
||||
this.added || this.mutated
|
||||
}
|
||||
}
|
||||
|
||||
/// Unique borrow of a Resource
|
||||
#[derive(Debug)]
|
||||
pub struct ResMut<'a, T: Resource> {
|
||||
_marker: PhantomData<&'a T>,
|
||||
value: *mut T,
|
||||
added: bool,
|
||||
mutated: *mut bool,
|
||||
}
|
||||
|
||||
impl<'a, T: Resource> ResMut<'a, T> {
|
||||
/// Creates a mutable reference cell to a Resource from a pointer
|
||||
///
|
||||
/// # Safety
|
||||
/// The pointer must have correct lifetime / storage / ownership
|
||||
pub unsafe fn new(value: NonNull<T>, added: bool, mutated: NonNull<bool>) -> Self {
|
||||
Self {
|
||||
value: value.as_ptr(),
|
||||
mutated: mutated.as_ptr(),
|
||||
added,
|
||||
_marker: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Resource> Deref for ResMut<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
unsafe { &*self.value }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Resource> DerefMut for ResMut<'a, T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
unsafe {
|
||||
*self.mutated = true;
|
||||
&mut *self.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Resource> ResMut<'a, T> {
|
||||
#[inline(always)]
|
||||
pub fn added(this: Self) -> bool {
|
||||
this.added
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn mutated(this: Self) -> bool {
|
||||
unsafe { *this.mutated }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn changed(this: Self) -> bool {
|
||||
this.added || Self::mutated(this)
|
||||
}
|
||||
}
|
||||
|
||||
/// Local<T> resources are unique per-system. Two instances of the same system will each have their own resource.
|
||||
/// Local resources are automatically initialized using the FromResources trait.
|
||||
#[derive(Debug)]
|
||||
pub struct Local<'a, T: Resource + FromResources> {
|
||||
value: *mut T,
|
||||
_marker: PhantomData<&'a T>,
|
||||
}
|
||||
|
||||
impl<'a, T: Resource + FromResources> Local<'a, T> {
|
||||
pub(crate) unsafe fn new(resources: &Resources, id: SystemId) -> Self {
|
||||
Local {
|
||||
value: resources
|
||||
.get_unsafe_ref::<T>(ResourceIndex::System(id))
|
||||
.as_ptr(),
|
||||
_marker: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Resource + FromResources> Deref for Local<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
unsafe { &*self.value }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Resource + FromResources> DerefMut for Local<'a, T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
unsafe { &mut *self.value }
|
||||
}
|
||||
}
|
||||
|
||||
/// `NonSend<T>` resources cannot leave the main thread, so any system that wants access to
|
||||
/// a non-send resource will run on the main thread. See `Resources::insert_non_send()` and friends.
|
||||
#[derive(Debug)]
|
||||
pub struct NonSend<'a, T: 'static> {
|
||||
value: *mut T,
|
||||
_marker: PhantomData<&'a T>,
|
||||
}
|
||||
|
||||
impl<'a, T: 'static> NonSend<'a, T> {
|
||||
pub(crate) unsafe fn new(resources: &Resources) -> Self {
|
||||
NonSend {
|
||||
value: resources.get_unsafe_non_send_ref::<T>().as_ptr(),
|
||||
_marker: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: 'static> Deref for NonSend<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
unsafe { &*self.value }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: 'static> DerefMut for NonSend<'a, T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
unsafe { &mut *self.value }
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
|
||||
// #[test]
|
||||
// fn changed_resource() {
|
||||
// let mut resources = Resources::default();
|
||||
// resources.insert(123);
|
||||
// assert_eq!(
|
||||
// resources.query::<ChangedRes<i32>>().as_deref(),
|
||||
// Some(&(123 as i32))
|
||||
// );
|
||||
// resources.clear_trackers();
|
||||
// assert_eq!(resources.query::<ChangedRes<i32>>().as_deref(), None);
|
||||
// *resources.query::<ResMut<i32>>().unwrap() += 1;
|
||||
// assert_eq!(
|
||||
// resources.query::<ChangedRes<i32>>().as_deref(),
|
||||
// Some(&(124 as i32))
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn or_changed_resource() {
|
||||
// let mut resources = Resources::default();
|
||||
// resources.insert(123);
|
||||
// resources.insert(0.2);
|
||||
// assert!(resources
|
||||
// .query::<OrRes<(ChangedRes<i32>, ChangedRes<f64>)>>()
|
||||
// .is_some(),);
|
||||
// resources.clear_trackers();
|
||||
// assert!(resources
|
||||
// .query::<OrRes<(ChangedRes<i32>, ChangedRes<f64>)>>()
|
||||
// .is_none(),);
|
||||
// *resources.query::<ResMut<i32>>().unwrap() += 1;
|
||||
// assert!(resources
|
||||
// .query::<OrRes<(ChangedRes<i32>, ChangedRes<f64>)>>()
|
||||
// .is_some(),);
|
||||
// assert!(resources
|
||||
// .query::<(ChangedRes<i32>, ChangedRes<f64>)>()
|
||||
// .is_none(),);
|
||||
// }
|
||||
// }
|
|
@ -1,593 +0,0 @@
|
|||
use crate::{system::SystemId, AtomicBorrow};
|
||||
use bevy_utils::HashMap;
|
||||
use core::any::TypeId;
|
||||
use downcast_rs::{impl_downcast, Downcast};
|
||||
use std::{
|
||||
cell::UnsafeCell,
|
||||
fmt::Debug,
|
||||
ops::{Deref, DerefMut},
|
||||
ptr::NonNull,
|
||||
thread::ThreadId,
|
||||
};
|
||||
|
||||
/// A Resource type
|
||||
pub trait Resource: Send + Sync + 'static {}
|
||||
impl<T: Send + Sync + 'static> Resource for T {}
|
||||
|
||||
pub(crate) struct ResourceData {
|
||||
storage: Box<dyn ResourceStorage>,
|
||||
default_index: Option<usize>,
|
||||
system_id_to_archetype_index: HashMap<usize, usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ResourceIndex {
|
||||
Global,
|
||||
System(SystemId),
|
||||
}
|
||||
|
||||
// TODO: consider using this for normal resources (would require change tracking)
|
||||
trait ResourceStorage: Downcast {
|
||||
fn clear_trackers(&mut self);
|
||||
}
|
||||
impl_downcast!(ResourceStorage);
|
||||
|
||||
struct StoredResource<T: 'static> {
|
||||
value: UnsafeCell<T>,
|
||||
added: UnsafeCell<bool>,
|
||||
mutated: UnsafeCell<bool>,
|
||||
atomic_borrow: AtomicBorrow,
|
||||
}
|
||||
|
||||
pub struct VecResourceStorage<T: 'static> {
|
||||
stored: Vec<StoredResource<T>>,
|
||||
}
|
||||
|
||||
impl<T: 'static> VecResourceStorage<T> {
|
||||
fn get(&self, index: usize) -> Option<ResourceRef<'_, T>> {
|
||||
self.stored
|
||||
.get(index)
|
||||
.map(|stored| ResourceRef::new(stored))
|
||||
}
|
||||
|
||||
fn get_mut(&self, index: usize) -> Option<ResourceRefMut<'_, T>> {
|
||||
self.stored
|
||||
.get(index)
|
||||
.map(|stored| ResourceRefMut::new(stored))
|
||||
}
|
||||
|
||||
unsafe fn get_unsafe_ref(&self, index: usize) -> NonNull<T> {
|
||||
NonNull::new_unchecked(self.stored.get_unchecked(index).value.get())
|
||||
}
|
||||
|
||||
fn push(&mut self, resource: T) {
|
||||
self.stored.push(StoredResource {
|
||||
atomic_borrow: AtomicBorrow::new(),
|
||||
value: UnsafeCell::new(resource),
|
||||
added: UnsafeCell::new(true),
|
||||
mutated: UnsafeCell::new(true),
|
||||
});
|
||||
}
|
||||
|
||||
fn set(&mut self, index: usize, resource: T) {
|
||||
self.stored[index].value = UnsafeCell::new(resource);
|
||||
self.stored[index].mutated = UnsafeCell::new(true);
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.stored.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> Default for VecResourceStorage<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
stored: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> ResourceStorage for VecResourceStorage<T> {
|
||||
fn clear_trackers(&mut self) {
|
||||
for stored in &mut self.stored {
|
||||
stored.added = UnsafeCell::new(false);
|
||||
stored.mutated = UnsafeCell::new(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of resource instances identified by their type.
|
||||
pub struct Resources {
|
||||
pub(crate) resource_data: HashMap<TypeId, ResourceData>,
|
||||
non_send_data: HashMap<TypeId, Box<dyn ResourceStorage>>,
|
||||
main_thread_id: ThreadId,
|
||||
}
|
||||
|
||||
impl Default for Resources {
|
||||
fn default() -> Self {
|
||||
Resources {
|
||||
resource_data: Default::default(),
|
||||
non_send_data: Default::default(),
|
||||
main_thread_id: std::thread::current().id(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resources {
|
||||
pub fn insert<T: Resource>(&mut self, resource: T) {
|
||||
self.insert_resource(resource, ResourceIndex::Global);
|
||||
}
|
||||
|
||||
pub fn insert_non_send<T: 'static>(&mut self, resource: T) {
|
||||
self.check_if_main_thread();
|
||||
let entry = self
|
||||
.non_send_data
|
||||
.entry(TypeId::of::<T>())
|
||||
.or_insert_with(|| Box::new(VecResourceStorage::<T>::default()));
|
||||
let resources = entry.downcast_mut::<VecResourceStorage<T>>().unwrap();
|
||||
if resources.is_empty() {
|
||||
resources.push(resource);
|
||||
} else {
|
||||
resources.set(0, resource);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_if_main_thread(&self) {
|
||||
if std::thread::current().id() != self.main_thread_id {
|
||||
panic!("Attempted to access a non-send resource off of the main thread.")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains<T: Resource>(&self) -> bool {
|
||||
self.get_resource::<T>(ResourceIndex::Global).is_some()
|
||||
}
|
||||
|
||||
pub fn get<T: Resource>(&self) -> Option<ResourceRef<'_, T>> {
|
||||
self.get_resource(ResourceIndex::Global)
|
||||
}
|
||||
|
||||
pub fn get_mut<T: Resource>(&self) -> Option<ResourceRefMut<'_, T>> {
|
||||
self.get_resource_mut(ResourceIndex::Global)
|
||||
}
|
||||
|
||||
pub fn get_non_send<T: 'static>(&self) -> Option<ResourceRef<'_, T>> {
|
||||
self.check_if_main_thread();
|
||||
self.non_send_data
|
||||
.get(&TypeId::of::<T>())
|
||||
.and_then(|storage| {
|
||||
let resources = storage.downcast_ref::<VecResourceStorage<T>>().unwrap();
|
||||
resources.get(0)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_non_send_mut<T: 'static>(&self) -> Option<ResourceRefMut<'_, T>> {
|
||||
self.check_if_main_thread();
|
||||
self.non_send_data
|
||||
.get(&TypeId::of::<T>())
|
||||
.and_then(|storage| {
|
||||
let resources = storage.downcast_ref::<VecResourceStorage<T>>().unwrap();
|
||||
resources.get_mut(0)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_or_insert_with<T: Resource>(
|
||||
&mut self,
|
||||
get_resource: impl FnOnce() -> T,
|
||||
) -> ResourceRefMut<'_, T> {
|
||||
// NOTE: this double-get is really weird. why cant we use an if-let here?
|
||||
if self.get::<T>().is_some() {
|
||||
return self.get_mut::<T>().unwrap();
|
||||
}
|
||||
self.insert(get_resource());
|
||||
self.get_mut().unwrap()
|
||||
}
|
||||
|
||||
/// Returns a clone of the underlying resource, this is helpful when borrowing something
|
||||
/// cloneable (like a task pool) without taking a borrow on the resource map
|
||||
pub fn get_cloned<T: Resource + Clone>(&self) -> Option<T> {
|
||||
self.get::<T>().map(|r| (*r).clone())
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_lifetimes)]
|
||||
pub fn get_local<'a, T: Resource>(&'a self, id: SystemId) -> Option<ResourceRef<'a, T>> {
|
||||
self.get_resource(ResourceIndex::System(id))
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_lifetimes)]
|
||||
pub fn get_local_mut<'a, T: Resource>(&'a self, id: SystemId) -> Option<ResourceRefMut<'a, T>> {
|
||||
self.get_resource_mut(ResourceIndex::System(id))
|
||||
}
|
||||
|
||||
pub fn insert_local<T: Resource>(&mut self, id: SystemId, resource: T) {
|
||||
self.insert_resource(resource, ResourceIndex::System(id))
|
||||
}
|
||||
|
||||
fn insert_resource<T: Resource>(&mut self, resource: T, resource_index: ResourceIndex) {
|
||||
let type_id = TypeId::of::<T>();
|
||||
let data = self
|
||||
.resource_data
|
||||
.entry(type_id)
|
||||
.or_insert_with(|| ResourceData {
|
||||
storage: Box::new(VecResourceStorage::<T>::default()),
|
||||
default_index: None,
|
||||
system_id_to_archetype_index: HashMap::default(),
|
||||
});
|
||||
|
||||
let storage = data
|
||||
.storage
|
||||
.downcast_mut::<VecResourceStorage<T>>()
|
||||
.unwrap();
|
||||
let index = match resource_index {
|
||||
ResourceIndex::Global => *data
|
||||
.default_index
|
||||
.get_or_insert_with(|| storage.stored.len()),
|
||||
ResourceIndex::System(id) => *data
|
||||
.system_id_to_archetype_index
|
||||
.entry(id.0)
|
||||
.or_insert_with(|| storage.stored.len()),
|
||||
};
|
||||
|
||||
use std::cmp::Ordering;
|
||||
match index.cmp(&storage.stored.len()) {
|
||||
Ordering::Equal => {
|
||||
storage.push(resource);
|
||||
}
|
||||
Ordering::Greater => panic!("Attempted to access index beyond 'current_capacity + 1'."),
|
||||
Ordering::Less => {
|
||||
*storage.get_mut(index).unwrap() = resource;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_resource<T: Resource>(
|
||||
&self,
|
||||
resource_index: ResourceIndex,
|
||||
) -> Option<ResourceRef<'_, T>> {
|
||||
self.get_resource_data_index::<T>(resource_index)
|
||||
.and_then(|(data, index)| {
|
||||
let resources = data
|
||||
.storage
|
||||
.downcast_ref::<VecResourceStorage<T>>()
|
||||
.unwrap();
|
||||
resources.get(index)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_resource_mut<T: Resource>(
|
||||
&self,
|
||||
resource_index: ResourceIndex,
|
||||
) -> Option<ResourceRefMut<'_, T>> {
|
||||
self.get_resource_data_index::<T>(resource_index)
|
||||
.and_then(|(data, index)| {
|
||||
let resources = data
|
||||
.storage
|
||||
.downcast_ref::<VecResourceStorage<T>>()
|
||||
.unwrap();
|
||||
resources.get_mut(index)
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(clippy::missing_safety_doc)]
|
||||
pub unsafe fn get_unsafe_ref<T: Resource>(&self, resource_index: ResourceIndex) -> NonNull<T> {
|
||||
self.get_resource_data_index::<T>(resource_index)
|
||||
.map(|(data, index)| {
|
||||
let resources = data
|
||||
.storage
|
||||
.downcast_ref::<VecResourceStorage<T>>()
|
||||
.unwrap();
|
||||
resources.get_unsafe_ref(index)
|
||||
})
|
||||
.unwrap_or_else(|| panic!("Resource does not exist {}.", std::any::type_name::<T>()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(clippy::missing_safety_doc)]
|
||||
pub unsafe fn get_unsafe_non_send_ref<T: 'static>(&self) -> NonNull<T> {
|
||||
self.check_if_main_thread();
|
||||
self.non_send_data
|
||||
.get(&TypeId::of::<T>())
|
||||
.map(|storage| {
|
||||
let resources = storage.downcast_ref::<VecResourceStorage<T>>().unwrap();
|
||||
resources.get_unsafe_ref(0)
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Non-send resource does not exist {}.",
|
||||
std::any::type_name::<T>()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(clippy::missing_safety_doc)]
|
||||
pub unsafe fn get_unsafe_ref_with_added_and_mutated<T: Resource>(
|
||||
&self,
|
||||
resource_index: ResourceIndex,
|
||||
) -> (NonNull<T>, NonNull<bool>, NonNull<bool>) {
|
||||
self.get_resource_data_index::<T>(resource_index)
|
||||
.map(|(data, index)| {
|
||||
let resources = data
|
||||
.storage
|
||||
.downcast_ref::<VecResourceStorage<T>>()
|
||||
.unwrap();
|
||||
|
||||
(
|
||||
resources.get_unsafe_ref(index),
|
||||
NonNull::new_unchecked(resources.stored[index].added.get()),
|
||||
NonNull::new_unchecked(resources.stored[index].mutated.get()),
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|| panic!("Resource does not exist {}.", std::any::type_name::<T>()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_resource_data_index<T: Resource>(
|
||||
&self,
|
||||
resource_index: ResourceIndex,
|
||||
) -> Option<(&ResourceData, usize)> {
|
||||
self.resource_data.get(&TypeId::of::<T>()).and_then(|data| {
|
||||
let index = match resource_index {
|
||||
ResourceIndex::Global => data.default_index?,
|
||||
ResourceIndex::System(id) => {
|
||||
data.system_id_to_archetype_index.get(&id.0).cloned()?
|
||||
}
|
||||
};
|
||||
Some((data, index as usize))
|
||||
})
|
||||
}
|
||||
|
||||
/// Clears each resource's tracker state.
|
||||
/// For example, each resource's component "mutated" state will be reset to `false`.
|
||||
pub fn clear_trackers(&mut self) {
|
||||
for (_, resource_data) in self.resource_data.iter_mut() {
|
||||
resource_data.storage.clear_trackers();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove<T: Resource>(&mut self) {
|
||||
self.resource_data.remove(&TypeId::of::<T>());
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for Resources {}
|
||||
unsafe impl Sync for Resources {}
|
||||
|
||||
/// Creates `Self` using data from the `Resources` collection
|
||||
pub trait FromResources {
|
||||
/// Creates `Self` using data from the `Resources` collection
|
||||
fn from_resources(resources: &Resources) -> Self;
|
||||
}
|
||||
|
||||
impl<T> FromResources for T
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
fn from_resources(_resources: &Resources) -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared borrow of an entity's component
|
||||
#[derive(Clone)]
|
||||
pub struct ResourceRef<'a, T: 'static> {
|
||||
borrow: &'a AtomicBorrow,
|
||||
resource: &'a T,
|
||||
}
|
||||
|
||||
impl<'a, T: 'static> ResourceRef<'a, T> {
|
||||
/// Creates a new resource borrow
|
||||
fn new(
|
||||
StoredResource {
|
||||
value,
|
||||
added: _,
|
||||
mutated: _,
|
||||
atomic_borrow,
|
||||
}: &'a StoredResource<T>,
|
||||
) -> Self {
|
||||
if atomic_borrow.borrow() {
|
||||
Self {
|
||||
// Safe because we acquired the lock
|
||||
resource: unsafe { &*value.get() },
|
||||
borrow: atomic_borrow,
|
||||
}
|
||||
} else {
|
||||
panic!(
|
||||
"Failed to acquire shared lock on resource: {}.",
|
||||
std::any::type_name::<T>()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: 'static> Send for ResourceRef<'_, T> {}
|
||||
unsafe impl<T: 'static> Sync for ResourceRef<'_, T> {}
|
||||
|
||||
impl<'a, T: 'static> Drop for ResourceRef<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
self.borrow.release()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: 'static> Deref for ResourceRef<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
self.resource
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: 'static> Debug for ResourceRef<'a, T>
|
||||
where
|
||||
T: Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
self.deref().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Unique borrow of a resource
|
||||
pub struct ResourceRefMut<'a, T: 'static> {
|
||||
borrow: &'a AtomicBorrow,
|
||||
resource: &'a mut T,
|
||||
mutated: &'a mut bool,
|
||||
}
|
||||
|
||||
impl<'a, T: 'static> ResourceRefMut<'a, T> {
|
||||
/// Creates a new entity component mutable borrow
|
||||
fn new(
|
||||
StoredResource {
|
||||
value,
|
||||
added: _,
|
||||
mutated,
|
||||
atomic_borrow,
|
||||
}: &'a StoredResource<T>,
|
||||
) -> Self {
|
||||
if atomic_borrow.borrow_mut() {
|
||||
Self {
|
||||
// Safe because we acquired the lock
|
||||
resource: unsafe { &mut *value.get() },
|
||||
// same
|
||||
mutated: unsafe { &mut *mutated.get() },
|
||||
borrow: atomic_borrow,
|
||||
}
|
||||
} else {
|
||||
panic!(
|
||||
"Failed to acquire exclusive lock on resource: {}.",
|
||||
std::any::type_name::<T>()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: 'static> Send for ResourceRefMut<'_, T> {}
|
||||
unsafe impl<T: 'static> Sync for ResourceRefMut<'_, T> {}
|
||||
|
||||
impl<'a, T: 'static> Drop for ResourceRefMut<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
self.borrow.release_mut();
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: 'static> Deref for ResourceRefMut<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
self.resource
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: 'static> DerefMut for ResourceRefMut<'a, T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
*self.mutated = true;
|
||||
self.resource
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: 'static> Debug for ResourceRefMut<'a, T>
|
||||
where
|
||||
T: Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
self.deref().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Resources;
|
||||
use crate::system::SystemId;
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::float_cmp)]
|
||||
fn resource() {
|
||||
let mut resources = Resources::default();
|
||||
assert!(resources.get::<i32>().is_none());
|
||||
|
||||
resources.insert(123);
|
||||
assert_eq!(*resources.get::<i32>().expect("resource exists"), 123);
|
||||
|
||||
resources.insert(456.0);
|
||||
assert_eq!(*resources.get::<f64>().expect("resource exists"), 456.0);
|
||||
|
||||
resources.insert(789.0);
|
||||
assert_eq!(*resources.get::<f64>().expect("resource exists"), 789.0);
|
||||
|
||||
{
|
||||
let mut value = resources.get_mut::<f64>().expect("resource exists");
|
||||
assert_eq!(*value, 789.0);
|
||||
*value = -1.0;
|
||||
}
|
||||
|
||||
assert_eq!(*resources.get::<f64>().expect("resource exists"), -1.0);
|
||||
|
||||
assert!(resources.get_local::<i32>(SystemId(0)).is_none());
|
||||
resources.insert_local(SystemId(0), 111);
|
||||
assert_eq!(
|
||||
*resources
|
||||
.get_local::<i32>(SystemId(0))
|
||||
.expect("resource exists"),
|
||||
111
|
||||
);
|
||||
assert_eq!(*resources.get::<i32>().expect("resource exists"), 123);
|
||||
resources.insert_local(SystemId(0), 222);
|
||||
assert_eq!(
|
||||
*resources
|
||||
.get_local::<i32>(SystemId(0))
|
||||
.expect("resource exists"),
|
||||
222
|
||||
);
|
||||
assert_eq!(*resources.get::<i32>().expect("resource exists"), 123);
|
||||
resources.remove::<i32>();
|
||||
assert!(resources.get::<i32>().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Failed to acquire exclusive lock on resource: i32")]
|
||||
fn resource_double_mut_panic() {
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(123);
|
||||
let _x = resources.get_mut::<i32>();
|
||||
let _y = resources.get_mut::<i32>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_send_resource() {
|
||||
let mut resources = Resources::default();
|
||||
resources.insert_non_send(123i32);
|
||||
resources.insert_non_send(456i64);
|
||||
assert_eq!(*resources.get_non_send::<i32>().unwrap(), 123);
|
||||
assert_eq!(*resources.get_non_send_mut::<i64>().unwrap(), 456);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_send_resource_ref_aliasing() {
|
||||
let mut resources = Resources::default();
|
||||
resources.insert_non_send(123i32);
|
||||
let a = resources.get_non_send::<i32>().unwrap();
|
||||
let b = resources.get_non_send::<i32>().unwrap();
|
||||
assert_eq!(*a, 123);
|
||||
assert_eq!(*b, 123);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn non_send_resource_mut_ref_aliasing() {
|
||||
let mut resources = Resources::default();
|
||||
resources.insert_non_send(123i32);
|
||||
let _a = resources.get_non_send::<i32>().unwrap();
|
||||
let _b = resources.get_non_send_mut::<i32>().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn non_send_resource_panic() {
|
||||
let mut resources = Resources::default();
|
||||
resources.insert_non_send(0i32);
|
||||
std::thread::spawn(move || {
|
||||
let _ = resources.get_non_send_mut::<i32>();
|
||||
})
|
||||
.join()
|
||||
.unwrap();
|
||||
}
|
||||
}
|
|
@ -1,17 +1,11 @@
|
|||
use crate::{schedule::ParallelSystemContainer, world::World};
|
||||
use downcast_rs::{impl_downcast, Downcast};
|
||||
|
||||
use crate::{ParallelSystemContainer, Resources, World};
|
||||
|
||||
pub trait ParallelSystemExecutor: Downcast + Send + Sync {
|
||||
/// Called by `SystemStage` whenever `systems` have been changed.
|
||||
fn rebuild_cached_data(&mut self, systems: &mut [ParallelSystemContainer], world: &World);
|
||||
fn rebuild_cached_data(&mut self, systems: &[ParallelSystemContainer]);
|
||||
|
||||
fn run_systems(
|
||||
&mut self,
|
||||
systems: &mut [ParallelSystemContainer],
|
||||
world: &mut World,
|
||||
resources: &mut Resources,
|
||||
);
|
||||
fn run_systems(&mut self, systems: &mut [ParallelSystemContainer], world: &mut World);
|
||||
}
|
||||
|
||||
impl_downcast!(ParallelSystemExecutor);
|
||||
|
@ -20,17 +14,12 @@ impl_downcast!(ParallelSystemExecutor);
|
|||
pub struct SingleThreadedExecutor;
|
||||
|
||||
impl ParallelSystemExecutor for SingleThreadedExecutor {
|
||||
fn rebuild_cached_data(&mut self, _: &mut [ParallelSystemContainer], _: &World) {}
|
||||
fn rebuild_cached_data(&mut self, _: &[ParallelSystemContainer]) {}
|
||||
|
||||
fn run_systems(
|
||||
&mut self,
|
||||
systems: &mut [ParallelSystemContainer],
|
||||
world: &mut World,
|
||||
resources: &mut Resources,
|
||||
) {
|
||||
fn run_systems(&mut self, systems: &mut [ParallelSystemContainer], world: &mut World) {
|
||||
for system in systems {
|
||||
if system.should_run() {
|
||||
system.system_mut().run((), world, resources);
|
||||
system.system_mut().run((), world);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
use crate::{
|
||||
archetype::{ArchetypeComponentId, ArchetypeGeneration},
|
||||
query::Access,
|
||||
schedule::{ParallelSystemContainer, ParallelSystemExecutor},
|
||||
world::World,
|
||||
};
|
||||
use async_channel::{Receiver, Sender};
|
||||
use bevy_tasks::{ComputeTaskPool, Scope, TaskPool};
|
||||
use bevy_utils::HashSet;
|
||||
use fixedbitset::FixedBitSet;
|
||||
|
||||
use crate::{
|
||||
ArchetypesGeneration, CondensedTypeAccess, ParallelSystemContainer, ParallelSystemExecutor,
|
||||
Resources, System, World,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
use SchedulingEvent::*;
|
||||
|
||||
|
@ -23,23 +23,21 @@ struct SystemSchedulingMetadata {
|
|||
dependencies_total: usize,
|
||||
/// Amount of unsatisfied dependencies, when it reaches 0 the system is queued to be started.
|
||||
dependencies_now: usize,
|
||||
/// Archetype-component access information condensed into executor-specific bitsets.
|
||||
archetype_component_access: CondensedTypeAccess,
|
||||
/// Resource access information condensed into executor-specific bitsets.
|
||||
resource_access: CondensedTypeAccess,
|
||||
/// Archetype-component access information.
|
||||
archetype_component_access: Access<ArchetypeComponentId>,
|
||||
/// Whether or not this system is send-able
|
||||
is_send: bool,
|
||||
}
|
||||
|
||||
pub struct ParallelExecutor {
|
||||
/// Last archetypes generation observed by parallel systems.
|
||||
last_archetypes_generation: ArchetypesGeneration,
|
||||
archetype_generation: ArchetypeGeneration,
|
||||
/// Cached metadata of every system.
|
||||
system_metadata: Vec<SystemSchedulingMetadata>,
|
||||
/// Used by systems to notify the executor that they have finished.
|
||||
finish_sender: Sender<usize>,
|
||||
/// Receives finish events from systems.
|
||||
finish_receiver: Receiver<usize>,
|
||||
/// Systems that must run on the main thread.
|
||||
non_send: FixedBitSet,
|
||||
/// Systems that should be started at next opportunity.
|
||||
queued: FixedBitSet,
|
||||
/// Systems that are currently running.
|
||||
|
@ -49,9 +47,7 @@ pub struct ParallelExecutor {
|
|||
/// Systems that should run this iteration.
|
||||
should_run: FixedBitSet,
|
||||
/// Compound archetype-component access information of currently running systems.
|
||||
active_archetype_component_access: CondensedTypeAccess,
|
||||
/// Compound resource access information of currently running systems.
|
||||
active_resource_access: CondensedTypeAccess,
|
||||
active_archetype_component_access: Access<ArchetypeComponentId>,
|
||||
/// Scratch space to avoid reallocating a vector when updating dependency counters.
|
||||
dependants_scratch: Vec<usize>,
|
||||
#[cfg(test)]
|
||||
|
@ -63,17 +59,15 @@ impl Default for ParallelExecutor {
|
|||
let (finish_sender, finish_receiver) = async_channel::unbounded();
|
||||
Self {
|
||||
// MAX ensures access information will be initialized on first run.
|
||||
last_archetypes_generation: ArchetypesGeneration(u64::MAX),
|
||||
archetype_generation: ArchetypeGeneration::new(usize::MAX),
|
||||
system_metadata: Default::default(),
|
||||
finish_sender,
|
||||
finish_receiver,
|
||||
non_send: Default::default(),
|
||||
queued: Default::default(),
|
||||
running: Default::default(),
|
||||
non_send_running: false,
|
||||
should_run: Default::default(),
|
||||
active_archetype_component_access: Default::default(),
|
||||
active_resource_access: Default::default(),
|
||||
dependants_scratch: Default::default(),
|
||||
#[cfg(test)]
|
||||
events_sender: None,
|
||||
|
@ -82,50 +76,16 @@ impl Default for ParallelExecutor {
|
|||
}
|
||||
|
||||
impl ParallelSystemExecutor for ParallelExecutor {
|
||||
fn rebuild_cached_data(&mut self, systems: &mut [ParallelSystemContainer], world: &World) {
|
||||
fn rebuild_cached_data(&mut self, systems: &[ParallelSystemContainer]) {
|
||||
self.system_metadata.clear();
|
||||
self.non_send.clear();
|
||||
self.non_send.grow(systems.len());
|
||||
self.queued.grow(systems.len());
|
||||
self.running.grow(systems.len());
|
||||
self.should_run.grow(systems.len());
|
||||
// Collect all distinct types accessed by systems in order to condense their
|
||||
// access sets into bitsets.
|
||||
let mut all_archetype_components = HashSet::default();
|
||||
let mut all_resource_types = HashSet::default();
|
||||
let mut gather_distinct_access_types = |system: &dyn System<In = (), Out = ()>| {
|
||||
if let Some(archetype_components) =
|
||||
system.archetype_component_access().all_distinct_types()
|
||||
{
|
||||
all_archetype_components.extend(archetype_components);
|
||||
}
|
||||
if let Some(resources) = system.resource_access().all_distinct_types() {
|
||||
all_resource_types.extend(resources);
|
||||
}
|
||||
};
|
||||
// If the archetypes were changed too, system access should be updated
|
||||
// before gathering the types.
|
||||
if self.last_archetypes_generation != world.archetypes_generation() {
|
||||
for container in systems.iter_mut() {
|
||||
let system = container.system_mut();
|
||||
system.update_access(world);
|
||||
gather_distinct_access_types(system);
|
||||
}
|
||||
self.last_archetypes_generation = world.archetypes_generation();
|
||||
} else {
|
||||
for container in systems.iter() {
|
||||
gather_distinct_access_types(container.system());
|
||||
}
|
||||
}
|
||||
let all_archetype_components = all_archetype_components.drain().collect::<Vec<_>>();
|
||||
let all_resource_types = all_resource_types.drain().collect::<Vec<_>>();
|
||||
|
||||
// Construct scheduling data for systems.
|
||||
for container in systems.iter() {
|
||||
let dependencies_total = container.dependencies().len();
|
||||
let system = container.system();
|
||||
if system.is_non_send() {
|
||||
self.non_send.insert(self.system_metadata.len());
|
||||
}
|
||||
let (start_sender, start_receiver) = async_channel::bounded(1);
|
||||
self.system_metadata.push(SystemSchedulingMetadata {
|
||||
start_sender,
|
||||
|
@ -133,10 +93,8 @@ impl ParallelSystemExecutor for ParallelExecutor {
|
|||
dependants: vec![],
|
||||
dependencies_total,
|
||||
dependencies_now: 0,
|
||||
archetype_component_access: system
|
||||
.archetype_component_access()
|
||||
.condense(&all_archetype_components),
|
||||
resource_access: system.resource_access().condense(&all_resource_types),
|
||||
is_send: system.is_send(),
|
||||
archetype_component_access: Default::default(),
|
||||
});
|
||||
}
|
||||
// Populate the dependants lists in the scheduling metadata.
|
||||
|
@ -147,27 +105,21 @@ impl ParallelSystemExecutor for ParallelExecutor {
|
|||
}
|
||||
}
|
||||
|
||||
fn run_systems(
|
||||
&mut self,
|
||||
systems: &mut [ParallelSystemContainer],
|
||||
world: &mut World,
|
||||
resources: &mut Resources,
|
||||
) {
|
||||
fn run_systems(&mut self, systems: &mut [ParallelSystemContainer], world: &mut World) {
|
||||
#[cfg(test)]
|
||||
if self.events_sender.is_none() {
|
||||
let (sender, receiver) = async_channel::unbounded::<SchedulingEvent>();
|
||||
resources.insert(receiver);
|
||||
world.insert_resource(receiver);
|
||||
self.events_sender = Some(sender);
|
||||
}
|
||||
if self.last_archetypes_generation != world.archetypes_generation() {
|
||||
self.update_access(systems, world);
|
||||
self.last_archetypes_generation = world.archetypes_generation();
|
||||
}
|
||||
let compute_pool = resources
|
||||
.get_or_insert_with(|| ComputeTaskPool(TaskPool::default()))
|
||||
|
||||
self.update_archetypes(systems, world);
|
||||
|
||||
let compute_pool = world
|
||||
.get_resource_or_insert_with(|| ComputeTaskPool(TaskPool::default()))
|
||||
.clone();
|
||||
compute_pool.scope(|scope| {
|
||||
self.prepare_systems(scope, systems, world, resources);
|
||||
self.prepare_systems(scope, systems, world);
|
||||
scope.spawn(async {
|
||||
// All systems have been ran if there are no queued or running systems.
|
||||
while 0 != self.queued.count_ones(..) + self.running.count_ones(..) {
|
||||
|
@ -196,27 +148,32 @@ impl ParallelSystemExecutor for ParallelExecutor {
|
|||
}
|
||||
|
||||
impl ParallelExecutor {
|
||||
/// Updates access and recondenses the archetype component bitsets of systems.
|
||||
fn update_access(&mut self, systems: &mut [ParallelSystemContainer], world: &mut World) {
|
||||
let mut all_archetype_components = HashSet::default();
|
||||
for container in systems.iter_mut() {
|
||||
let system = container.system_mut();
|
||||
system.update_access(world);
|
||||
if let Some(archetype_components) =
|
||||
system.archetype_component_access().all_distinct_types()
|
||||
{
|
||||
all_archetype_components.extend(archetype_components);
|
||||
}
|
||||
}
|
||||
let all_archetype_components = all_archetype_components.drain().collect::<Vec<_>>();
|
||||
for (index, container) in systems.iter().enumerate() {
|
||||
let system = container.system();
|
||||
if !system.archetype_component_access().reads_all() {
|
||||
self.system_metadata[index].archetype_component_access = system
|
||||
.archetype_component_access()
|
||||
.condense(&all_archetype_components);
|
||||
/// Calls system.new_archetype() for each archetype added since the last call to [update_archetypes] and
|
||||
/// updates cached archetype_component_access.
|
||||
fn update_archetypes(&mut self, systems: &mut [ParallelSystemContainer], world: &World) {
|
||||
let archetypes = world.archetypes();
|
||||
let old_generation = self.archetype_generation;
|
||||
let new_generation = archetypes.generation();
|
||||
if old_generation == new_generation {
|
||||
return;
|
||||
}
|
||||
|
||||
let archetype_index_range = if old_generation.value() == usize::MAX {
|
||||
0..archetypes.len()
|
||||
} else {
|
||||
old_generation.value()..archetypes.len()
|
||||
};
|
||||
for archetype in archetypes.archetypes[archetype_index_range].iter() {
|
||||
for (index, container) in systems.iter_mut().enumerate() {
|
||||
let meta = &mut self.system_metadata[index];
|
||||
let system = container.system_mut();
|
||||
system.new_archetype(archetype);
|
||||
meta.archetype_component_access
|
||||
.extend(system.archetype_component_access());
|
||||
}
|
||||
}
|
||||
|
||||
self.archetype_generation = new_generation;
|
||||
}
|
||||
|
||||
/// Populates `should_run` bitset, spawns tasks for systems that should run this iteration,
|
||||
|
@ -226,7 +183,6 @@ impl ParallelExecutor {
|
|||
scope: &mut Scope<'scope, ()>,
|
||||
systems: &'scope [ParallelSystemContainer],
|
||||
world: &'scope World,
|
||||
resources: &'scope Resources,
|
||||
) {
|
||||
self.should_run.clear();
|
||||
for (index, system_data) in self.system_metadata.iter_mut().enumerate() {
|
||||
|
@ -241,16 +197,16 @@ impl ParallelExecutor {
|
|||
.recv()
|
||||
.await
|
||||
.unwrap_or_else(|error| unreachable!(error));
|
||||
unsafe { system.run_unsafe((), world, resources) };
|
||||
unsafe { system.run_unsafe((), world) };
|
||||
finish_sender
|
||||
.send(index)
|
||||
.await
|
||||
.unwrap_or_else(|error| unreachable!(error));
|
||||
};
|
||||
if self.non_send[index] {
|
||||
scope.spawn_local(task);
|
||||
} else {
|
||||
if system_data.is_send {
|
||||
scope.spawn(task);
|
||||
} else {
|
||||
scope.spawn_local(task);
|
||||
}
|
||||
}
|
||||
// Queue the system if it has no dependencies, otherwise reset its dependency counter.
|
||||
|
@ -266,10 +222,7 @@ impl ParallelExecutor {
|
|||
fn can_start_now(&self, index: usize) -> bool {
|
||||
let system_data = &self.system_metadata[index];
|
||||
// Non-send systems are considered conflicting with each other.
|
||||
!(self.non_send[index] && self.non_send_running)
|
||||
&& system_data
|
||||
.resource_access
|
||||
.is_compatible(&self.active_resource_access)
|
||||
(!self.non_send_running || system_data.is_send)
|
||||
&& system_data
|
||||
.archetype_component_access
|
||||
.is_compatible(&self.active_archetype_component_access)
|
||||
|
@ -284,29 +237,26 @@ impl ParallelExecutor {
|
|||
for index in self.queued.ones() {
|
||||
// If the system shouldn't actually run this iteration, process it as completed
|
||||
// immediately; otherwise, check for conflicts and signal its task to start.
|
||||
let system_metadata = &self.system_metadata[index];
|
||||
if !self.should_run[index] {
|
||||
self.dependants_scratch
|
||||
.extend(&self.system_metadata[index].dependants);
|
||||
self.dependants_scratch.extend(&system_metadata.dependants);
|
||||
} else if self.can_start_now(index) {
|
||||
#[cfg(test)]
|
||||
{
|
||||
started_systems += 1;
|
||||
}
|
||||
let system_data = &self.system_metadata[index];
|
||||
system_data
|
||||
system_metadata
|
||||
.start_sender
|
||||
.send(())
|
||||
.await
|
||||
.unwrap_or_else(|error| unreachable!(error));
|
||||
self.running.set(index, true);
|
||||
if self.non_send[index] {
|
||||
if !system_metadata.is_send {
|
||||
self.non_send_running = true;
|
||||
}
|
||||
// Add this system's access information to the active access information.
|
||||
self.active_archetype_component_access
|
||||
.extend(&system_data.archetype_component_access);
|
||||
self.active_resource_access
|
||||
.extend(&system_data.resource_access);
|
||||
.extend(&system_metadata.archetype_component_access);
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
|
@ -322,24 +272,21 @@ impl ParallelExecutor {
|
|||
/// Unmarks the system give index as running, caches indices of its dependants
|
||||
/// in the `dependants_scratch`.
|
||||
fn process_finished_system(&mut self, index: usize) {
|
||||
if self.non_send[index] {
|
||||
let system_data = &self.system_metadata[index];
|
||||
if !system_data.is_send {
|
||||
self.non_send_running = false;
|
||||
}
|
||||
self.running.set(index, false);
|
||||
self.dependants_scratch
|
||||
.extend(&self.system_metadata[index].dependants);
|
||||
self.dependants_scratch.extend(&system_data.dependants);
|
||||
}
|
||||
|
||||
/// Discards active access information and builds it again using currently
|
||||
/// running systems' access information.
|
||||
fn rebuild_active_access(&mut self) {
|
||||
self.active_archetype_component_access.clear();
|
||||
self.active_resource_access.clear();
|
||||
for index in self.running.ones() {
|
||||
self.active_archetype_component_access
|
||||
.extend(&self.system_metadata[index].archetype_component_access);
|
||||
self.active_resource_access
|
||||
.extend(&self.system_metadata[index].resource_access);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -374,14 +321,17 @@ enum SchedulingEvent {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SchedulingEvent::{self, *};
|
||||
use crate::{prelude::*, SingleThreadedExecutor};
|
||||
use crate::{
|
||||
schedule::{SingleThreadedExecutor, Stage, SystemStage},
|
||||
system::{IntoSystem, NonSend, Query, Res, ResMut},
|
||||
world::World,
|
||||
};
|
||||
use async_channel::Receiver;
|
||||
use std::thread::{self, ThreadId};
|
||||
|
||||
fn receive_events(resources: &Resources) -> Vec<SchedulingEvent> {
|
||||
fn receive_events(world: &World) -> Vec<SchedulingEvent> {
|
||||
let mut events = Vec::new();
|
||||
while let Ok(event) = resources
|
||||
.get::<Receiver<SchedulingEvent>>()
|
||||
while let Ok(event) = world
|
||||
.get_resource::<Receiver<SchedulingEvent>>()
|
||||
.unwrap()
|
||||
.try_recv()
|
||||
{
|
||||
|
@ -393,16 +343,15 @@ mod tests {
|
|||
#[test]
|
||||
fn trivial() {
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
fn wants_for_nothing() {}
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(wants_for_nothing.system())
|
||||
.with_system(wants_for_nothing.system())
|
||||
.with_system(wants_for_nothing.system());
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
stage.run(&mut world);
|
||||
assert_eq!(
|
||||
receive_events(&resources),
|
||||
receive_events(&world),
|
||||
vec![StartedSystems(3), StartedSystems(3),]
|
||||
)
|
||||
}
|
||||
|
@ -410,78 +359,76 @@ mod tests {
|
|||
#[test]
|
||||
fn resources() {
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(0usize);
|
||||
world.insert_resource(0usize);
|
||||
fn wants_mut(_: ResMut<usize>) {}
|
||||
fn wants_ref(_: Res<usize>) {}
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(wants_mut.system())
|
||||
.with_system(wants_mut.system());
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
assert_eq!(
|
||||
receive_events(&resources),
|
||||
receive_events(&world),
|
||||
vec![StartedSystems(1), StartedSystems(1),]
|
||||
);
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(wants_mut.system())
|
||||
.with_system(wants_ref.system());
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
assert_eq!(
|
||||
receive_events(&resources),
|
||||
receive_events(&world),
|
||||
vec![StartedSystems(1), StartedSystems(1),]
|
||||
);
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(wants_ref.system())
|
||||
.with_system(wants_ref.system());
|
||||
stage.run(&mut world, &mut resources);
|
||||
assert_eq!(receive_events(&resources), vec![StartedSystems(2),]);
|
||||
stage.run(&mut world);
|
||||
assert_eq!(receive_events(&world), vec![StartedSystems(2),]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn queries() {
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
world.spawn((0usize,));
|
||||
world.spawn().insert(0usize);
|
||||
fn wants_mut(_: Query<&mut usize>) {}
|
||||
fn wants_ref(_: Query<&usize>) {}
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(wants_mut.system())
|
||||
.with_system(wants_mut.system());
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
assert_eq!(
|
||||
receive_events(&resources),
|
||||
receive_events(&world),
|
||||
vec![StartedSystems(1), StartedSystems(1),]
|
||||
);
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(wants_mut.system())
|
||||
.with_system(wants_ref.system());
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
assert_eq!(
|
||||
receive_events(&resources),
|
||||
receive_events(&world),
|
||||
vec![StartedSystems(1), StartedSystems(1),]
|
||||
);
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(wants_ref.system())
|
||||
.with_system(wants_ref.system());
|
||||
stage.run(&mut world, &mut resources);
|
||||
assert_eq!(receive_events(&resources), vec![StartedSystems(2),]);
|
||||
stage.run(&mut world);
|
||||
assert_eq!(receive_events(&world), vec![StartedSystems(2),]);
|
||||
let mut world = World::new();
|
||||
world.spawn((0usize, 0u32, 0f32));
|
||||
world.spawn().insert_bundle((0usize, 0u32, 0f32));
|
||||
fn wants_mut_usize(_: Query<(&mut usize, &f32)>) {}
|
||||
fn wants_mut_u32(_: Query<(&mut u32, &f32)>) {}
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(wants_mut_usize.system())
|
||||
.with_system(wants_mut_u32.system());
|
||||
stage.run(&mut world, &mut resources);
|
||||
assert_eq!(receive_events(&resources), vec![StartedSystems(2),]);
|
||||
stage.run(&mut world);
|
||||
assert_eq!(receive_events(&world), vec![StartedSystems(2),]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_send_resource() {
|
||||
use std::thread;
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert_non_send(thread::current().id());
|
||||
fn non_send(thread_id: NonSend<ThreadId>) {
|
||||
world.insert_non_send(thread::current().id());
|
||||
fn non_send(thread_id: NonSend<thread::ThreadId>) {
|
||||
assert_eq!(thread::current().id(), *thread_id);
|
||||
}
|
||||
fn empty() {}
|
||||
|
@ -492,9 +439,9 @@ mod tests {
|
|||
.with_system(empty.system())
|
||||
.with_system(non_send.system())
|
||||
.with_system(non_send.system());
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
assert_eq!(
|
||||
receive_events(&resources),
|
||||
receive_events(&world),
|
||||
vec![
|
||||
StartedSystems(3),
|
||||
StartedSystems(1),
|
||||
|
@ -503,6 +450,6 @@ mod tests {
|
|||
]
|
||||
);
|
||||
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
pub use bevy_ecs_macros::{AmbiguitySetLabel, StageLabel, SystemLabel};
|
||||
|
||||
use std::{
|
||||
any::Any,
|
||||
borrow::Cow,
|
||||
|
|
|
@ -2,7 +2,6 @@ mod executor;
|
|||
mod executor_parallel;
|
||||
mod label;
|
||||
mod stage;
|
||||
//mod stageless;
|
||||
mod state;
|
||||
mod system_container;
|
||||
mod system_descriptor;
|
||||
|
@ -18,10 +17,14 @@ pub use system_descriptor::*;
|
|||
pub use system_set::*;
|
||||
|
||||
use crate::{
|
||||
ArchetypeComponent, BoxedSystem, IntoSystem, Resources, System, SystemId, TypeAccess, World,
|
||||
archetype::{Archetype, ArchetypeComponentId},
|
||||
component::ComponentId,
|
||||
query::Access,
|
||||
system::{BoxedSystem, IntoSystem, System, SystemId},
|
||||
world::World,
|
||||
};
|
||||
use bevy_utils::HashMap;
|
||||
use std::{any::TypeId, borrow::Cow};
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Schedule {
|
||||
|
@ -187,7 +190,7 @@ impl Schedule {
|
|||
.and_then(|stage| stage.downcast_mut::<T>())
|
||||
}
|
||||
|
||||
pub fn run_once(&mut self, world: &mut World, resources: &mut Resources) {
|
||||
pub fn run_once(&mut self, world: &mut World) {
|
||||
for label in self.stage_order.iter() {
|
||||
#[cfg(feature = "trace")]
|
||||
let stage_span =
|
||||
|
@ -195,22 +198,22 @@ impl Schedule {
|
|||
#[cfg(feature = "trace")]
|
||||
let _stage_guard = stage_span.enter();
|
||||
let stage = self.stages.get_mut(label).unwrap();
|
||||
stage.run(world, resources);
|
||||
stage.run(world);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Stage for Schedule {
|
||||
fn run(&mut self, world: &mut World, resources: &mut Resources) {
|
||||
fn run(&mut self, world: &mut World) {
|
||||
loop {
|
||||
match self.run_criteria.should_run(world, resources) {
|
||||
match self.run_criteria.should_run(world) {
|
||||
ShouldRun::No => return,
|
||||
ShouldRun::Yes => {
|
||||
self.run_once(world, resources);
|
||||
self.run_once(world);
|
||||
return;
|
||||
}
|
||||
ShouldRun::YesAndCheckAgain => {
|
||||
self.run_once(world, resources);
|
||||
self.run_once(world);
|
||||
}
|
||||
ShouldRun::NoAndCheckAgain => {
|
||||
panic!("`NoAndCheckAgain` would loop infinitely in this situation.")
|
||||
|
@ -220,11 +223,6 @@ impl Stage for Schedule {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn clear_trackers_system(world: &mut World, resources: &mut Resources) {
|
||||
world.clear_trackers();
|
||||
resources.clear_trackers();
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ShouldRun {
|
||||
/// Yes, the system should run.
|
||||
|
@ -257,16 +255,15 @@ impl RunCriteria {
|
|||
self.initialized = false;
|
||||
}
|
||||
|
||||
pub fn should_run(&mut self, world: &mut World, resources: &mut Resources) -> ShouldRun {
|
||||
pub fn should_run(&mut self, world: &mut World) -> ShouldRun {
|
||||
if let Some(ref mut run_criteria) = self.criteria_system {
|
||||
if !self.initialized {
|
||||
run_criteria.initialize(world, resources);
|
||||
run_criteria.initialize(world);
|
||||
self.initialized = true;
|
||||
}
|
||||
let should_run = run_criteria.run((), world, resources);
|
||||
run_criteria.apply_buffers(world, resources);
|
||||
// don't run when no result is returned or false is returned
|
||||
should_run.unwrap_or(ShouldRun::No)
|
||||
let should_run = run_criteria.run((), world);
|
||||
run_criteria.apply_buffers(world);
|
||||
should_run
|
||||
} else {
|
||||
ShouldRun::Yes
|
||||
}
|
||||
|
@ -276,9 +273,8 @@ impl RunCriteria {
|
|||
pub struct RunOnce {
|
||||
ran: bool,
|
||||
system_id: SystemId,
|
||||
archetype_component_access: TypeAccess<ArchetypeComponent>,
|
||||
component_access: TypeAccess<TypeId>,
|
||||
resource_access: TypeAccess<TypeId>,
|
||||
archetype_component_access: Access<ArchetypeComponentId>,
|
||||
component_access: Access<ComponentId>,
|
||||
}
|
||||
|
||||
impl Default for RunOnce {
|
||||
|
@ -288,7 +284,6 @@ impl Default for RunOnce {
|
|||
system_id: SystemId::new(),
|
||||
archetype_component_access: Default::default(),
|
||||
component_access: Default::default(),
|
||||
resource_access: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -305,39 +300,30 @@ impl System for RunOnce {
|
|||
self.system_id
|
||||
}
|
||||
|
||||
fn update_access(&mut self, _world: &World) {}
|
||||
fn new_archetype(&mut self, _archetype: &Archetype) {}
|
||||
|
||||
fn archetype_component_access(&self) -> &TypeAccess<ArchetypeComponent> {
|
||||
fn archetype_component_access(&self) -> &Access<ArchetypeComponentId> {
|
||||
&self.archetype_component_access
|
||||
}
|
||||
|
||||
fn component_access(&self) -> &TypeAccess<TypeId> {
|
||||
fn component_access(&self) -> &Access<ComponentId> {
|
||||
&self.component_access
|
||||
}
|
||||
|
||||
fn resource_access(&self) -> &TypeAccess<TypeId> {
|
||||
&self.resource_access
|
||||
fn is_send(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn is_non_send(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
unsafe fn run_unsafe(
|
||||
&mut self,
|
||||
_input: Self::In,
|
||||
_world: &World,
|
||||
_resources: &Resources,
|
||||
) -> Option<Self::Out> {
|
||||
Some(if self.ran {
|
||||
unsafe fn run_unsafe(&mut self, _input: Self::In, _world: &World) -> Self::Out {
|
||||
if self.ran {
|
||||
ShouldRun::No
|
||||
} else {
|
||||
self.ran = true;
|
||||
ShouldRun::Yes
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_buffers(&mut self, _world: &mut World, _resources: &mut Resources) {}
|
||||
fn apply_buffers(&mut self, _world: &mut World) {}
|
||||
|
||||
fn initialize(&mut self, _world: &mut World, _resources: &mut Resources) {}
|
||||
fn initialize(&mut self, _world: &mut World) {}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,21 @@
|
|||
use crate::{
|
||||
schedule::{
|
||||
BoxedSystemLabel, ExclusiveSystemContainer, InsertionPoint, ParallelExecutor,
|
||||
ParallelSystemContainer, ParallelSystemExecutor, RunCriteria, ShouldRun,
|
||||
SingleThreadedExecutor, SystemContainer, SystemDescriptor, SystemLabel, SystemSet,
|
||||
},
|
||||
system::System,
|
||||
world::{World, WorldId},
|
||||
};
|
||||
use bevy_utils::{tracing::info, HashMap, HashSet};
|
||||
use downcast_rs::{impl_downcast, Downcast};
|
||||
use fixedbitset::FixedBitSet;
|
||||
use std::borrow::Cow;
|
||||
|
||||
use super::{
|
||||
ExclusiveSystemContainer, ParallelExecutor, ParallelSystemContainer, ParallelSystemExecutor,
|
||||
SingleThreadedExecutor, SystemContainer,
|
||||
};
|
||||
use crate::{
|
||||
BoxedSystemLabel, InsertionPoint, Resources, RunCriteria,
|
||||
ShouldRun::{self, *},
|
||||
System, SystemDescriptor, SystemLabel, SystemSet, World,
|
||||
};
|
||||
|
||||
pub trait Stage: Downcast + Send + Sync {
|
||||
/// Runs the stage; this happens once per update.
|
||||
/// Implementors must initialize all of their state and systems before running the first time.
|
||||
fn run(&mut self, world: &mut World, resources: &mut Resources);
|
||||
fn run(&mut self, world: &mut World);
|
||||
}
|
||||
|
||||
impl_downcast!(Stage);
|
||||
|
@ -49,6 +48,8 @@ struct VirtualSystemSet {
|
|||
/// Stores and executes systems. Execution order is not defined unless explicitly specified;
|
||||
/// see `SystemDescriptor` documentation.
|
||||
pub struct SystemStage {
|
||||
/// The WorldId this stage was last run on.
|
||||
world_id: Option<WorldId>,
|
||||
/// Instance of a scheduling algorithm for running the systems.
|
||||
executor: Box<dyn ParallelSystemExecutor>,
|
||||
/// Groups of systems; each set has its own run criterion.
|
||||
|
@ -83,6 +84,7 @@ impl SystemStage {
|
|||
should_run: ShouldRun::Yes,
|
||||
};
|
||||
SystemStage {
|
||||
world_id: None,
|
||||
executor,
|
||||
system_sets: vec![set],
|
||||
exclusive_at_start: Default::default(),
|
||||
|
@ -194,26 +196,22 @@ impl SystemStage {
|
|||
self
|
||||
}
|
||||
|
||||
fn initialize_systems(&mut self, world: &mut World, resources: &mut Resources) {
|
||||
fn initialize_systems(&mut self, world: &mut World) {
|
||||
for index in self.uninitialized_at_start.drain(..) {
|
||||
self.exclusive_at_start[index]
|
||||
.system_mut()
|
||||
.initialize(world, resources);
|
||||
.initialize(world);
|
||||
}
|
||||
for index in self.uninitialized_before_commands.drain(..) {
|
||||
self.exclusive_before_commands[index]
|
||||
.system_mut()
|
||||
.initialize(world, resources);
|
||||
.initialize(world);
|
||||
}
|
||||
for index in self.uninitialized_at_end.drain(..) {
|
||||
self.exclusive_at_end[index]
|
||||
.system_mut()
|
||||
.initialize(world, resources);
|
||||
self.exclusive_at_end[index].system_mut().initialize(world);
|
||||
}
|
||||
for index in self.uninitialized_parallel.drain(..) {
|
||||
self.parallel[index]
|
||||
.system_mut()
|
||||
.initialize(world, resources);
|
||||
self.parallel[index].system_mut().initialize(world);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -502,6 +500,7 @@ fn find_ambiguities(systems: &[impl SystemContainer]) -> Vec<(usize, usize)> {
|
|||
dependencies.insert(dependency);
|
||||
all_dependants[dependency].insert(index);
|
||||
}
|
||||
|
||||
all_dependants.push(FixedBitSet::with_capacity(systems.len()));
|
||||
all_dependencies.push(dependencies);
|
||||
}
|
||||
|
@ -546,38 +545,47 @@ fn find_ambiguities(systems: &[impl SystemContainer]) -> Vec<(usize, usize)> {
|
|||
}
|
||||
|
||||
impl Stage for SystemStage {
|
||||
fn run(&mut self, world: &mut World, resources: &mut Resources) {
|
||||
fn run(&mut self, world: &mut World) {
|
||||
if let Some(world_id) = self.world_id {
|
||||
assert!(
|
||||
world.id() == world_id,
|
||||
"Cannot run SystemStage on two different Worlds"
|
||||
);
|
||||
} else {
|
||||
self.world_id = Some(world.id());
|
||||
}
|
||||
// Evaluate sets' run criteria, initialize sets as needed, detect if any sets were changed.
|
||||
let mut has_work = false;
|
||||
for system_set in self.system_sets.iter_mut() {
|
||||
let result = system_set.run_criteria.should_run(world, resources);
|
||||
let result = system_set.run_criteria.should_run(world);
|
||||
match result {
|
||||
Yes | YesAndCheckAgain => has_work = true,
|
||||
No | NoAndCheckAgain => (),
|
||||
ShouldRun::Yes | ShouldRun::YesAndCheckAgain => has_work = true,
|
||||
ShouldRun::No | ShouldRun::NoAndCheckAgain => (),
|
||||
}
|
||||
system_set.should_run = result;
|
||||
}
|
||||
|
||||
if self.systems_modified {
|
||||
self.initialize_systems(world, resources);
|
||||
self.initialize_systems(world);
|
||||
self.rebuild_orders_and_dependencies();
|
||||
self.systems_modified = false;
|
||||
self.executor.rebuild_cached_data(&mut self.parallel, world);
|
||||
self.executor.rebuild_cached_data(&self.parallel);
|
||||
self.executor_modified = false;
|
||||
if resources.contains::<ReportExecutionOrderAmbiguities>() {
|
||||
if world.contains_resource::<ReportExecutionOrderAmbiguities>() {
|
||||
self.report_ambiguities();
|
||||
}
|
||||
} else if self.executor_modified {
|
||||
self.executor.rebuild_cached_data(&mut self.parallel, world);
|
||||
self.executor.rebuild_cached_data(&self.parallel);
|
||||
self.executor_modified = false;
|
||||
}
|
||||
|
||||
while has_work {
|
||||
// Run systems that want to be at the start of stage.
|
||||
for container in &mut self.exclusive_at_start {
|
||||
if let Yes | YesAndCheckAgain = self.system_sets[container.system_set()].should_run
|
||||
if let ShouldRun::Yes | ShouldRun::YesAndCheckAgain =
|
||||
self.system_sets[container.system_set()].should_run
|
||||
{
|
||||
container.system_mut().run(world, resources);
|
||||
container.system_mut().run(world);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -585,33 +593,34 @@ impl Stage for SystemStage {
|
|||
// TODO: hard dependencies, nested sets, whatever... should be evaluated here.
|
||||
for container in &mut self.parallel {
|
||||
match self.system_sets[container.system_set()].should_run {
|
||||
Yes | YesAndCheckAgain => container.should_run = true,
|
||||
No | NoAndCheckAgain => container.should_run = false,
|
||||
ShouldRun::Yes | ShouldRun::YesAndCheckAgain => container.should_run = true,
|
||||
ShouldRun::No | ShouldRun::NoAndCheckAgain => container.should_run = false,
|
||||
}
|
||||
}
|
||||
self.executor
|
||||
.run_systems(&mut self.parallel, world, resources);
|
||||
self.executor.run_systems(&mut self.parallel, world);
|
||||
|
||||
// Run systems that want to be between parallel systems and their command buffers.
|
||||
for container in &mut self.exclusive_before_commands {
|
||||
if let Yes | YesAndCheckAgain = self.system_sets[container.system_set()].should_run
|
||||
if let ShouldRun::Yes | ShouldRun::YesAndCheckAgain =
|
||||
self.system_sets[container.system_set()].should_run
|
||||
{
|
||||
container.system_mut().run(world, resources);
|
||||
container.system_mut().run(world);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply parallel systems' buffers.
|
||||
for container in &mut self.parallel {
|
||||
if container.should_run {
|
||||
container.system_mut().apply_buffers(world, resources);
|
||||
container.system_mut().apply_buffers(world);
|
||||
}
|
||||
}
|
||||
|
||||
// Run systems that want to be at the end of stage.
|
||||
for container in &mut self.exclusive_at_end {
|
||||
if let Yes | YesAndCheckAgain = self.system_sets[container.system_set()].should_run
|
||||
if let ShouldRun::Yes | ShouldRun::YesAndCheckAgain =
|
||||
self.system_sets[container.system_set()].should_run
|
||||
{
|
||||
container.system_mut().run(world, resources);
|
||||
container.system_mut().run(world);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -619,13 +628,13 @@ impl Stage for SystemStage {
|
|||
has_work = false;
|
||||
for system_set in self.system_sets.iter_mut() {
|
||||
match system_set.should_run {
|
||||
No => (),
|
||||
Yes => system_set.should_run = No,
|
||||
YesAndCheckAgain | NoAndCheckAgain => {
|
||||
let new_result = system_set.run_criteria.should_run(world, resources);
|
||||
ShouldRun::No => (),
|
||||
ShouldRun::Yes => system_set.should_run = ShouldRun::No,
|
||||
ShouldRun::YesAndCheckAgain | ShouldRun::NoAndCheckAgain => {
|
||||
let new_result = system_set.run_criteria.should_run(world);
|
||||
match new_result {
|
||||
Yes | YesAndCheckAgain => has_work = true,
|
||||
No | NoAndCheckAgain => (),
|
||||
ShouldRun::Yes | ShouldRun::YesAndCheckAgain => has_work = true,
|
||||
ShouldRun::No | ShouldRun::NoAndCheckAgain => (),
|
||||
}
|
||||
system_set.should_run = new_result;
|
||||
}
|
||||
|
@ -637,10 +646,17 @@ impl Stage for SystemStage {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{prelude::*, BoxedSystemLabel, SingleThreadedExecutor};
|
||||
use crate::{
|
||||
schedule::{
|
||||
BoxedSystemLabel, ExclusiveSystemDescriptorCoercion, ParallelSystemDescriptorCoercion,
|
||||
ShouldRun, SingleThreadedExecutor, Stage, SystemSet, SystemStage,
|
||||
},
|
||||
system::{IntoExclusiveSystem, IntoSystem, Query, ResMut},
|
||||
world::World,
|
||||
};
|
||||
|
||||
fn make_exclusive(tag: usize) -> impl FnMut(&mut Resources) {
|
||||
move |resources| resources.get_mut::<Vec<usize>>().unwrap().push(tag)
|
||||
fn make_exclusive(tag: usize) -> impl FnMut(&mut World) {
|
||||
move |world| world.get_resource_mut::<Vec<usize>>().unwrap().push(tag)
|
||||
}
|
||||
|
||||
// This is silly. https://github.com/bevyengine/bevy/issues/1029
|
||||
|
@ -666,50 +682,57 @@ mod tests {
|
|||
#[test]
|
||||
fn insertion_points() {
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
|
||||
resources.insert(Vec::<usize>::new());
|
||||
world.insert_resource(Vec::<usize>::new());
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(make_exclusive(0).exclusive_system().at_start())
|
||||
.with_system(make_parallel!(1).system())
|
||||
.with_system(make_exclusive(2).exclusive_system().before_commands())
|
||||
.with_system(make_exclusive(3).exclusive_system().at_end());
|
||||
stage.run(&mut world, &mut resources);
|
||||
assert_eq!(*resources.get::<Vec<usize>>().unwrap(), vec![0, 1, 2, 3]);
|
||||
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
assert_eq!(
|
||||
*resources.get::<Vec<usize>>().unwrap(),
|
||||
*world.get_resource_mut::<Vec<usize>>().unwrap(),
|
||||
vec![0, 1, 2, 3]
|
||||
);
|
||||
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
|
||||
stage.run(&mut world);
|
||||
assert_eq!(
|
||||
*world.get_resource::<Vec<usize>>().unwrap(),
|
||||
vec![0, 1, 2, 3, 0, 1, 2, 3]
|
||||
);
|
||||
|
||||
resources.get_mut::<Vec<usize>>().unwrap().clear();
|
||||
world.get_resource_mut::<Vec<usize>>().unwrap().clear();
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(make_exclusive(2).exclusive_system().before_commands())
|
||||
.with_system(make_exclusive(3).exclusive_system().at_end())
|
||||
.with_system(make_parallel!(1).system())
|
||||
.with_system(make_exclusive(0).exclusive_system().at_start());
|
||||
stage.run(&mut world, &mut resources);
|
||||
assert_eq!(*resources.get::<Vec<usize>>().unwrap(), vec![0, 1, 2, 3]);
|
||||
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
assert_eq!(
|
||||
*resources.get::<Vec<usize>>().unwrap(),
|
||||
*world.get_resource::<Vec<usize>>().unwrap(),
|
||||
vec![0, 1, 2, 3]
|
||||
);
|
||||
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
|
||||
stage.run(&mut world);
|
||||
assert_eq!(
|
||||
*world.get_resource::<Vec<usize>>().unwrap(),
|
||||
vec![0, 1, 2, 3, 0, 1, 2, 3]
|
||||
);
|
||||
|
||||
resources.get_mut::<Vec<usize>>().unwrap().clear();
|
||||
world.get_resource_mut::<Vec<usize>>().unwrap().clear();
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(make_parallel!(2).exclusive_system().before_commands())
|
||||
.with_system(make_parallel!(3).exclusive_system().at_end())
|
||||
.with_system(make_parallel!(1).system())
|
||||
.with_system(make_parallel!(0).exclusive_system().at_start());
|
||||
stage.run(&mut world, &mut resources);
|
||||
assert_eq!(*resources.get::<Vec<usize>>().unwrap(), vec![0, 1, 2, 3]);
|
||||
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
assert_eq!(
|
||||
*resources.get::<Vec<usize>>().unwrap(),
|
||||
*world.get_resource::<Vec<usize>>().unwrap(),
|
||||
vec![0, 1, 2, 3]
|
||||
);
|
||||
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
|
||||
stage.run(&mut world);
|
||||
assert_eq!(
|
||||
*world.get_resource::<Vec<usize>>().unwrap(),
|
||||
vec![0, 1, 2, 3, 0, 1, 2, 3]
|
||||
);
|
||||
}
|
||||
|
@ -718,12 +741,11 @@ mod tests {
|
|||
#[should_panic(expected = "No exclusive system with label \"empty\" at start of stage.")]
|
||||
fn exclusive_unknown_label() {
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(Vec::<usize>::new());
|
||||
world.insert_resource(Vec::<usize>::new());
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(empty.exclusive_system().at_end().label("empty"))
|
||||
.with_system(empty.exclusive_system().after("empty"));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -732,32 +754,30 @@ mod tests {
|
|||
)]
|
||||
fn exclusive_duplicate_label() {
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(Vec::<usize>::new());
|
||||
world.insert_resource(Vec::<usize>::new());
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(empty.exclusive_system().at_end().label("empty"))
|
||||
.with_system(empty.exclusive_system().before_commands().label("empty"));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(empty.exclusive_system().label("empty"))
|
||||
.with_system(empty.exclusive_system().label("empty"));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exclusive_after() {
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(Vec::<usize>::new());
|
||||
world.insert_resource(Vec::<usize>::new());
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(make_exclusive(1).exclusive_system().label("1").after("0"))
|
||||
.with_system(make_exclusive(2).exclusive_system().after("1"))
|
||||
.with_system(make_exclusive(0).exclusive_system().label("0"));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
assert_eq!(
|
||||
*resources.get::<Vec<usize>>().unwrap(),
|
||||
*world.get_resource::<Vec<usize>>().unwrap(),
|
||||
vec![0, 1, 2, 0, 1, 2]
|
||||
);
|
||||
}
|
||||
|
@ -765,17 +785,16 @@ mod tests {
|
|||
#[test]
|
||||
fn exclusive_before() {
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(Vec::<usize>::new());
|
||||
world.insert_resource(Vec::<usize>::new());
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(make_exclusive(1).exclusive_system().label("1").before("2"))
|
||||
.with_system(make_exclusive(2).exclusive_system().label("2"))
|
||||
.with_system(make_exclusive(0).exclusive_system().before("1"));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
assert_eq!(
|
||||
*resources.get::<Vec<usize>>().unwrap(),
|
||||
*world.get_resource::<Vec<usize>>().unwrap(),
|
||||
vec![0, 1, 2, 0, 1, 2]
|
||||
);
|
||||
}
|
||||
|
@ -783,19 +802,18 @@ mod tests {
|
|||
#[test]
|
||||
fn exclusive_mixed() {
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(Vec::<usize>::new());
|
||||
world.insert_resource(Vec::<usize>::new());
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(make_exclusive(2).exclusive_system().label("2"))
|
||||
.with_system(make_exclusive(1).exclusive_system().after("0").before("2"))
|
||||
.with_system(make_exclusive(0).exclusive_system().label("0"))
|
||||
.with_system(make_exclusive(4).exclusive_system().label("4"))
|
||||
.with_system(make_exclusive(3).exclusive_system().after("2").before("4"));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
assert_eq!(
|
||||
*resources.get::<Vec<usize>>().unwrap(),
|
||||
*world.get_resource::<Vec<usize>>().unwrap(),
|
||||
vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
|
||||
);
|
||||
}
|
||||
|
@ -803,8 +821,7 @@ mod tests {
|
|||
#[test]
|
||||
fn exclusive_redundant_constraints() {
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(Vec::<usize>::new());
|
||||
world.insert_resource(Vec::<usize>::new());
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(
|
||||
make_exclusive(2)
|
||||
|
@ -831,11 +848,11 @@ mod tests {
|
|||
.after("2")
|
||||
.before("4"),
|
||||
);
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
assert_eq!(
|
||||
*resources.get::<Vec<usize>>().unwrap(),
|
||||
*world.get_resource::<Vec<usize>>().unwrap(),
|
||||
vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
|
||||
);
|
||||
}
|
||||
|
@ -843,8 +860,7 @@ mod tests {
|
|||
#[test]
|
||||
fn exclusive_mixed_across_sets() {
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(Vec::<usize>::new());
|
||||
world.insert_resource(Vec::<usize>::new());
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(make_exclusive(2).exclusive_system().label("2"))
|
||||
.with_system_set(
|
||||
|
@ -854,11 +870,11 @@ mod tests {
|
|||
.with_system(make_exclusive(3).exclusive_system().after("2").before("4")),
|
||||
)
|
||||
.with_system(make_exclusive(1).exclusive_system().after("0").before("2"));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
assert_eq!(
|
||||
*resources.get::<Vec<usize>>().unwrap(),
|
||||
*world.get_resource::<Vec<usize>>().unwrap(),
|
||||
vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
|
||||
);
|
||||
}
|
||||
|
@ -866,9 +882,8 @@ mod tests {
|
|||
#[test]
|
||||
fn exclusive_run_criteria() {
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(Vec::<usize>::new());
|
||||
resources.insert(false);
|
||||
world.insert_resource(Vec::<usize>::new());
|
||||
world.insert_resource(false);
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(make_exclusive(0).exclusive_system().before("1"))
|
||||
.with_system_set(
|
||||
|
@ -877,14 +892,14 @@ mod tests {
|
|||
.with_system(make_exclusive(1).exclusive_system().label("1")),
|
||||
)
|
||||
.with_system(make_exclusive(2).exclusive_system().after("1"));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world, &mut resources);
|
||||
*resources.get_mut::<bool>().unwrap() = false;
|
||||
stage.run(&mut world);
|
||||
stage.run(&mut world);
|
||||
*world.get_resource_mut::<bool>().unwrap() = false;
|
||||
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
stage.run(&mut world);
|
||||
assert_eq!(
|
||||
*resources.get::<Vec<usize>>().unwrap(),
|
||||
*world.get_resource::<Vec<usize>>().unwrap(),
|
||||
vec![0, 1, 2, 0, 2, 0, 1, 2, 0, 2]
|
||||
);
|
||||
}
|
||||
|
@ -893,76 +908,70 @@ mod tests {
|
|||
#[should_panic]
|
||||
fn exclusive_cycle_1() {
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(Vec::<usize>::new());
|
||||
world.insert_resource(Vec::<usize>::new());
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(make_exclusive(0).exclusive_system().label("0").after("0"));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn exclusive_cycle_2() {
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(Vec::<usize>::new());
|
||||
world.insert_resource(Vec::<usize>::new());
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(make_exclusive(0).exclusive_system().label("0").after("1"))
|
||||
.with_system(make_exclusive(1).exclusive_system().label("1").after("0"));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn exclusive_cycle_3() {
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(Vec::<usize>::new());
|
||||
world.insert_resource(Vec::<usize>::new());
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(make_exclusive(0).exclusive_system().label("0"))
|
||||
.with_system(make_exclusive(1).exclusive_system().after("0").before("2"))
|
||||
.with_system(make_exclusive(2).exclusive_system().label("2").before("0"));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "No parallel system with label \"empty\" in stage.")]
|
||||
fn parallel_unknown_label() {
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(Vec::<usize>::new());
|
||||
world.insert_resource(Vec::<usize>::new());
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(empty.system())
|
||||
.with_system(empty.system().after("empty"));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Label \"empty\" already used by a parallel system.")]
|
||||
fn parallel_duplicate_label() {
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(Vec::<usize>::new());
|
||||
world.insert_resource(Vec::<usize>::new());
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(empty.system().label("empty"))
|
||||
.with_system(empty.system().label("empty"));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parallel_after() {
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(Vec::<usize>::new());
|
||||
world.insert_resource(Vec::<usize>::new());
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(make_parallel!(1).system().after("0").label("1"))
|
||||
.with_system(make_parallel!(2).system().after("1"))
|
||||
.with_system(make_parallel!(0).system().label("0"));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
assert_eq!(
|
||||
*resources.get::<Vec<usize>>().unwrap(),
|
||||
*world.get_resource::<Vec<usize>>().unwrap(),
|
||||
vec![0, 1, 2, 0, 1, 2]
|
||||
);
|
||||
}
|
||||
|
@ -970,17 +979,16 @@ mod tests {
|
|||
#[test]
|
||||
fn parallel_before() {
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(Vec::<usize>::new());
|
||||
world.insert_resource(Vec::<usize>::new());
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(make_parallel!(1).system().label("1").before("2"))
|
||||
.with_system(make_parallel!(2).system().label("2"))
|
||||
.with_system(make_parallel!(0).system().before("1"));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
assert_eq!(
|
||||
*resources.get::<Vec<usize>>().unwrap(),
|
||||
*world.get_resource::<Vec<usize>>().unwrap(),
|
||||
vec![0, 1, 2, 0, 1, 2]
|
||||
);
|
||||
}
|
||||
|
@ -988,19 +996,18 @@ mod tests {
|
|||
#[test]
|
||||
fn parallel_mixed() {
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(Vec::<usize>::new());
|
||||
world.insert_resource(Vec::<usize>::new());
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(make_parallel!(2).system().label("2"))
|
||||
.with_system(make_parallel!(1).system().after("0").before("2"))
|
||||
.with_system(make_parallel!(0).system().label("0"))
|
||||
.with_system(make_parallel!(4).system().label("4"))
|
||||
.with_system(make_parallel!(3).system().after("2").before("4"));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
assert_eq!(
|
||||
*resources.get::<Vec<usize>>().unwrap(),
|
||||
*world.get_resource::<Vec<usize>>().unwrap(),
|
||||
vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
|
||||
);
|
||||
}
|
||||
|
@ -1008,8 +1015,7 @@ mod tests {
|
|||
#[test]
|
||||
fn parallel_redundant_constraints() {
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(Vec::<usize>::new());
|
||||
world.insert_resource(Vec::<usize>::new());
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(
|
||||
make_parallel!(2)
|
||||
|
@ -1030,14 +1036,14 @@ mod tests {
|
|||
.with_system(make_parallel!(0).system().label("0").before("1"))
|
||||
.with_system(make_parallel!(4).system().label("4").after("3"))
|
||||
.with_system(make_parallel!(3).system().label("3").after("2").before("4"));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
for container in stage.parallel.iter() {
|
||||
assert!(container.dependencies().len() <= 1);
|
||||
}
|
||||
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
assert_eq!(
|
||||
*resources.get::<Vec<usize>>().unwrap(),
|
||||
*world.get_resource::<Vec<usize>>().unwrap(),
|
||||
vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
|
||||
);
|
||||
}
|
||||
|
@ -1045,8 +1051,7 @@ mod tests {
|
|||
#[test]
|
||||
fn parallel_mixed_across_sets() {
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(Vec::<usize>::new());
|
||||
world.insert_resource(Vec::<usize>::new());
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(make_parallel!(2).system().label("2"))
|
||||
.with_system_set(
|
||||
|
@ -1056,11 +1061,11 @@ mod tests {
|
|||
.with_system(make_parallel!(3).system().after("2").before("4")),
|
||||
)
|
||||
.with_system(make_parallel!(1).system().after("0").before("2"));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
assert_eq!(
|
||||
*resources.get::<Vec<usize>>().unwrap(),
|
||||
*world.get_resource::<Vec<usize>>().unwrap(),
|
||||
vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
|
||||
);
|
||||
}
|
||||
|
@ -1068,9 +1073,8 @@ mod tests {
|
|||
#[test]
|
||||
fn parallel_run_criteria() {
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(Vec::<usize>::new());
|
||||
resources.insert(false);
|
||||
world.insert_resource(Vec::<usize>::new());
|
||||
world.insert_resource(false);
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(make_parallel!(0).system().before("1"))
|
||||
.with_system_set(
|
||||
|
@ -1079,14 +1083,14 @@ mod tests {
|
|||
.with_system(make_parallel!(1).system().label("1")),
|
||||
)
|
||||
.with_system(make_parallel!(2).system().after("1"));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world, &mut resources);
|
||||
*resources.get_mut::<bool>().unwrap() = false;
|
||||
stage.run(&mut world);
|
||||
stage.run(&mut world);
|
||||
*world.get_resource_mut::<bool>().unwrap() = false;
|
||||
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
stage.run(&mut world);
|
||||
assert_eq!(
|
||||
*resources.get::<Vec<usize>>().unwrap(),
|
||||
*world.get_resource::<Vec<usize>>().unwrap(),
|
||||
vec![0, 1, 2, 0, 2, 0, 1, 2, 0, 2]
|
||||
);
|
||||
}
|
||||
|
@ -1095,36 +1099,34 @@ mod tests {
|
|||
#[should_panic]
|
||||
fn parallel_cycle_1() {
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(Vec::<usize>::new());
|
||||
world.insert_resource(Vec::<usize>::new());
|
||||
let mut stage =
|
||||
SystemStage::parallel().with_system(make_parallel!(0).system().label("0").after("0"));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn parallel_cycle_2() {
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(Vec::<usize>::new());
|
||||
world.insert_resource(Vec::<usize>::new());
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(make_parallel!(0).system().label("0").after("1"))
|
||||
.with_system(make_parallel!(1).system().label("1").after("0"));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn parallel_cycle_3() {
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(Vec::<usize>::new());
|
||||
|
||||
world.insert_resource(Vec::<usize>::new());
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(make_parallel!(0).system().label("0"))
|
||||
.with_system(make_parallel!(1).system().after("0").before("2"))
|
||||
.with_system(make_parallel!(2).system().label("2").before("0"));
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1150,7 +1152,6 @@ mod tests {
|
|||
fn component(_: Query<&mut f32>) {}
|
||||
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
|
||||
let mut stage = SystemStage::parallel()
|
||||
.with_system(empty.system().label("0"))
|
||||
|
@ -1158,7 +1159,7 @@ mod tests {
|
|||
.with_system(empty.system().label("2"))
|
||||
.with_system(empty.system().label("3").after("2").before("4"))
|
||||
.with_system(empty.system().label("4"));
|
||||
stage.initialize_systems(&mut world, &mut resources);
|
||||
stage.initialize_systems(&mut world);
|
||||
stage.rebuild_orders_and_dependencies();
|
||||
assert_eq!(find_ambiguities(&stage.parallel).len(), 0);
|
||||
|
||||
|
@ -1168,7 +1169,7 @@ mod tests {
|
|||
.with_system(empty.system().label("2"))
|
||||
.with_system(empty.system().label("3").after("2").before("4"))
|
||||
.with_system(component.system().label("4"));
|
||||
stage.initialize_systems(&mut world, &mut resources);
|
||||
stage.initialize_systems(&mut world);
|
||||
stage.rebuild_orders_and_dependencies();
|
||||
let ambiguities = find_ambiguities_labels(&stage.parallel);
|
||||
assert!(
|
||||
|
@ -1183,7 +1184,7 @@ mod tests {
|
|||
.with_system(empty.system().label("2"))
|
||||
.with_system(empty.system().label("3").after("2").before("4"))
|
||||
.with_system(resource.system().label("4"));
|
||||
stage.initialize_systems(&mut world, &mut resources);
|
||||
stage.initialize_systems(&mut world);
|
||||
stage.rebuild_orders_and_dependencies();
|
||||
let ambiguities = find_ambiguities_labels(&stage.parallel);
|
||||
assert!(
|
||||
|
@ -1198,7 +1199,7 @@ mod tests {
|
|||
.with_system(empty.system().label("2"))
|
||||
.with_system(empty.system().label("3").after("2").before("4"))
|
||||
.with_system(component.system().label("4"));
|
||||
stage.initialize_systems(&mut world, &mut resources);
|
||||
stage.initialize_systems(&mut world);
|
||||
stage.rebuild_orders_and_dependencies();
|
||||
assert_eq!(find_ambiguities(&stage.parallel).len(), 0);
|
||||
|
||||
|
@ -1208,7 +1209,7 @@ mod tests {
|
|||
.with_system(empty.system().label("2"))
|
||||
.with_system(component.system().label("3").after("2").before("4"))
|
||||
.with_system(resource.system().label("4"));
|
||||
stage.initialize_systems(&mut world, &mut resources);
|
||||
stage.initialize_systems(&mut world);
|
||||
stage.rebuild_orders_and_dependencies();
|
||||
let ambiguities = find_ambiguities_labels(&stage.parallel);
|
||||
assert!(
|
||||
|
@ -1233,7 +1234,7 @@ mod tests {
|
|||
.with_system(empty.system().label("2"))
|
||||
.with_system(component.system().label("3").after("2").before("4"))
|
||||
.with_system(resource.system().label("4").in_ambiguity_set("a"));
|
||||
stage.initialize_systems(&mut world, &mut resources);
|
||||
stage.initialize_systems(&mut world);
|
||||
stage.rebuild_orders_and_dependencies();
|
||||
let ambiguities = find_ambiguities_labels(&stage.parallel);
|
||||
assert!(
|
||||
|
@ -1246,7 +1247,7 @@ mod tests {
|
|||
.with_system(component.system().label("0").before("2"))
|
||||
.with_system(component.system().label("1").before("2"))
|
||||
.with_system(component.system().label("2"));
|
||||
stage.initialize_systems(&mut world, &mut resources);
|
||||
stage.initialize_systems(&mut world);
|
||||
stage.rebuild_orders_and_dependencies();
|
||||
let ambiguities = find_ambiguities_labels(&stage.parallel);
|
||||
assert!(
|
||||
|
@ -1259,7 +1260,7 @@ mod tests {
|
|||
.with_system(component.system().label("0"))
|
||||
.with_system(component.system().label("1").after("0"))
|
||||
.with_system(component.system().label("2").after("0"));
|
||||
stage.initialize_systems(&mut world, &mut resources);
|
||||
stage.initialize_systems(&mut world);
|
||||
stage.rebuild_orders_and_dependencies();
|
||||
let ambiguities = find_ambiguities_labels(&stage.parallel);
|
||||
assert!(
|
||||
|
@ -1273,7 +1274,7 @@ mod tests {
|
|||
.with_system(component.system().label("1"))
|
||||
.with_system(component.system().label("2"))
|
||||
.with_system(component.system().label("3").after("1").after("2"));
|
||||
stage.initialize_systems(&mut world, &mut resources);
|
||||
stage.initialize_systems(&mut world);
|
||||
stage.rebuild_orders_and_dependencies();
|
||||
let ambiguities = find_ambiguities_labels(&stage.parallel);
|
||||
assert!(
|
||||
|
@ -1287,7 +1288,7 @@ mod tests {
|
|||
.with_system(component.system().label("1").in_ambiguity_set("a"))
|
||||
.with_system(component.system().label("2").in_ambiguity_set("a"))
|
||||
.with_system(component.system().label("3").after("1").after("2"));
|
||||
stage.initialize_systems(&mut world, &mut resources);
|
||||
stage.initialize_systems(&mut world);
|
||||
stage.rebuild_orders_and_dependencies();
|
||||
let ambiguities = find_ambiguities_labels(&stage.parallel);
|
||||
assert_eq!(ambiguities.len(), 0);
|
||||
|
@ -1297,7 +1298,7 @@ mod tests {
|
|||
.with_system(component.system().label("1").in_ambiguity_set("a"))
|
||||
.with_system(component.system().label("2").in_ambiguity_set("b"))
|
||||
.with_system(component.system().label("3").after("1").after("2"));
|
||||
stage.initialize_systems(&mut world, &mut resources);
|
||||
stage.initialize_systems(&mut world);
|
||||
stage.rebuild_orders_and_dependencies();
|
||||
let ambiguities = find_ambiguities_labels(&stage.parallel);
|
||||
assert!(
|
||||
|
@ -1329,7 +1330,7 @@ mod tests {
|
|||
.after("3")
|
||||
.after("4"),
|
||||
);
|
||||
stage.initialize_systems(&mut world, &mut resources);
|
||||
stage.initialize_systems(&mut world);
|
||||
stage.rebuild_orders_and_dependencies();
|
||||
let ambiguities = find_ambiguities_labels(&stage.parallel);
|
||||
assert!(
|
||||
|
@ -1381,7 +1382,7 @@ mod tests {
|
|||
.after("3")
|
||||
.after("4"),
|
||||
);
|
||||
stage.initialize_systems(&mut world, &mut resources);
|
||||
stage.initialize_systems(&mut world);
|
||||
stage.rebuild_orders_and_dependencies();
|
||||
let ambiguities = find_ambiguities_labels(&stage.parallel);
|
||||
assert_eq!(ambiguities.len(), 0);
|
||||
|
@ -1415,7 +1416,7 @@ mod tests {
|
|||
.after("3")
|
||||
.after("4"),
|
||||
);
|
||||
stage.initialize_systems(&mut world, &mut resources);
|
||||
stage.initialize_systems(&mut world);
|
||||
stage.rebuild_orders_and_dependencies();
|
||||
let ambiguities = find_ambiguities_labels(&stage.parallel);
|
||||
assert!(
|
||||
|
@ -1437,7 +1438,7 @@ mod tests {
|
|||
.with_system(empty.exclusive_system().label("5").after("4"))
|
||||
.with_system(empty.exclusive_system().label("6").after("5"))
|
||||
.with_system(empty.exclusive_system().label("7").after("6"));
|
||||
stage.initialize_systems(&mut world, &mut resources);
|
||||
stage.initialize_systems(&mut world);
|
||||
stage.rebuild_orders_and_dependencies();
|
||||
assert_eq!(find_ambiguities(&stage.exclusive_at_start).len(), 0);
|
||||
|
||||
|
@ -1449,7 +1450,7 @@ mod tests {
|
|||
.with_system(empty.exclusive_system().label("4").after("3").before("5"))
|
||||
.with_system(empty.exclusive_system().label("5"))
|
||||
.with_system(empty.exclusive_system().label("6").after("2").after("5"));
|
||||
stage.initialize_systems(&mut world, &mut resources);
|
||||
stage.initialize_systems(&mut world);
|
||||
stage.rebuild_orders_and_dependencies();
|
||||
let ambiguities = find_ambiguities_labels(&stage.exclusive_at_start);
|
||||
assert!(
|
||||
|
@ -1486,7 +1487,7 @@ mod tests {
|
|||
.with_system(empty.exclusive_system().label("4").after("3").before("5"))
|
||||
.with_system(empty.exclusive_system().label("5").in_ambiguity_set("a"))
|
||||
.with_system(empty.exclusive_system().label("6").after("2").after("5"));
|
||||
stage.initialize_systems(&mut world, &mut resources);
|
||||
stage.initialize_systems(&mut world);
|
||||
stage.rebuild_orders_and_dependencies();
|
||||
let ambiguities = find_ambiguities_labels(&stage.exclusive_at_start);
|
||||
assert!(
|
||||
|
@ -1512,9 +1513,19 @@ mod tests {
|
|||
.with_system(empty.exclusive_system().label("1").in_ambiguity_set("a"))
|
||||
.with_system(empty.exclusive_system().label("2").in_ambiguity_set("a"))
|
||||
.with_system(empty.exclusive_system().label("3").in_ambiguity_set("a"));
|
||||
stage.initialize_systems(&mut world, &mut resources);
|
||||
stage.initialize_systems(&mut world);
|
||||
stage.rebuild_orders_and_dependencies();
|
||||
let ambiguities = find_ambiguities_labels(&stage.exclusive_at_start);
|
||||
assert_eq!(ambiguities.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn multiple_worlds_same_stage() {
|
||||
let mut world_a = World::default();
|
||||
let mut world_b = World::default();
|
||||
let mut stage = SystemStage::parallel();
|
||||
stage.run(&mut world_a);
|
||||
stage.run(&mut world_b);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
use crate::{Resource, Resources, Stage, SystemDescriptor, SystemStage, World};
|
||||
use crate::{
|
||||
component::Component,
|
||||
schedule::{Stage, SystemDescriptor, SystemStage},
|
||||
world::World,
|
||||
};
|
||||
use bevy_utils::HashMap;
|
||||
use std::{mem::Discriminant, ops::Deref};
|
||||
use thiserror::Error;
|
||||
|
@ -137,12 +141,12 @@ impl<T> StateStage<T> {
|
|||
}
|
||||
|
||||
#[allow(clippy::mem_discriminant_non_enum)]
|
||||
impl<T: Resource + Clone> Stage for StateStage<T> {
|
||||
fn run(&mut self, world: &mut World, resources: &mut Resources) {
|
||||
impl<T: Component + Clone> Stage for StateStage<T> {
|
||||
fn run(&mut self, world: &mut World) {
|
||||
let current_stage = loop {
|
||||
let (next_stage, current_stage) = {
|
||||
let mut state = resources
|
||||
.get_mut::<State<T>>()
|
||||
let mut state = world
|
||||
.get_resource_mut::<State<T>>()
|
||||
.expect("Missing state resource");
|
||||
let result = (
|
||||
state.next.as_ref().map(|next| std::mem::discriminant(next)),
|
||||
|
@ -158,12 +162,12 @@ impl<T: Resource + Clone> Stage for StateStage<T> {
|
|||
if let Some(next_stage) = next_stage {
|
||||
if next_stage != current_stage {
|
||||
if let Some(current_state_stages) = self.stages.get_mut(¤t_stage) {
|
||||
current_state_stages.exit.run(world, resources);
|
||||
current_state_stages.exit.run(world);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(next_state_stages) = self.stages.get_mut(&next_stage) {
|
||||
next_state_stages.enter.run(world, resources);
|
||||
next_state_stages.enter.run(world);
|
||||
}
|
||||
} else {
|
||||
break current_stage;
|
||||
|
@ -171,7 +175,7 @@ impl<T: Resource + Clone> Stage for StateStage<T> {
|
|||
};
|
||||
|
||||
if let Some(current_state_stages) = self.stages.get_mut(¤t_stage) {
|
||||
current_state_stages.update.run(world, resources);
|
||||
current_state_stages.update.run(world);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
use std::{borrow::Cow, ptr::NonNull};
|
||||
|
||||
use crate::{
|
||||
BoxedAmbiguitySetLabel, BoxedSystemLabel, ExclusiveSystem, ExclusiveSystemDescriptor,
|
||||
ParallelSystemDescriptor, System,
|
||||
schedule::{
|
||||
BoxedAmbiguitySetLabel, BoxedSystemLabel, ExclusiveSystemDescriptor,
|
||||
ParallelSystemDescriptor,
|
||||
},
|
||||
system::{ExclusiveSystem, System},
|
||||
};
|
||||
use std::{borrow::Cow, ptr::NonNull};
|
||||
|
||||
pub(super) trait SystemContainer {
|
||||
fn display_name(&self) -> Cow<'static, str>;
|
||||
|
@ -139,10 +141,6 @@ impl SystemContainer for ParallelSystemContainer {
|
|||
self.system()
|
||||
.component_access()
|
||||
.is_compatible(other.system().component_access())
|
||||
&& self
|
||||
.system()
|
||||
.resource_access()
|
||||
.is_compatible(other.system().resource_access())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
AmbiguitySetLabel, BoxedAmbiguitySetLabel, BoxedSystem, BoxedSystemLabel, ExclusiveSystem,
|
||||
ExclusiveSystemCoerced, ExclusiveSystemFn, System, SystemLabel,
|
||||
schedule::{AmbiguitySetLabel, BoxedAmbiguitySetLabel, BoxedSystemLabel, SystemLabel},
|
||||
system::{BoxedSystem, ExclusiveSystem, ExclusiveSystemCoerced, ExclusiveSystemFn, System},
|
||||
};
|
||||
|
||||
/// Encapsulates a system and information on when it run in a `SystemStage`.
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use crate::{RunCriteria, ShouldRun, System, SystemDescriptor};
|
||||
use crate::{
|
||||
schedule::{RunCriteria, ShouldRun, SystemDescriptor},
|
||||
system::System,
|
||||
};
|
||||
|
||||
/// Describes a group of systems sharing one run criterion.
|
||||
pub struct SystemSet {
|
||||
|
|
376
crates/bevy_ecs/src/storage/blob_vec.rs
Normal file
376
crates/bevy_ecs/src/storage/blob_vec.rs
Normal file
|
@ -0,0 +1,376 @@
|
|||
use std::{
|
||||
alloc::{handle_alloc_error, Layout},
|
||||
cell::UnsafeCell,
|
||||
ptr::NonNull,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BlobVec {
|
||||
item_layout: Layout,
|
||||
capacity: usize,
|
||||
len: usize,
|
||||
data: UnsafeCell<NonNull<u8>>,
|
||||
swap_scratch: UnsafeCell<NonNull<u8>>,
|
||||
drop: unsafe fn(*mut u8),
|
||||
}
|
||||
|
||||
impl BlobVec {
|
||||
pub fn new(item_layout: Layout, drop: unsafe fn(*mut u8), capacity: usize) -> BlobVec {
|
||||
if item_layout.size() == 0 {
|
||||
BlobVec {
|
||||
swap_scratch: UnsafeCell::new(NonNull::dangling()),
|
||||
data: UnsafeCell::new(NonNull::dangling()),
|
||||
capacity: usize::MAX,
|
||||
len: 0,
|
||||
item_layout,
|
||||
drop,
|
||||
}
|
||||
} else {
|
||||
let swap_scratch = NonNull::new(unsafe { std::alloc::alloc(item_layout) })
|
||||
.unwrap_or_else(|| std::alloc::handle_alloc_error(item_layout));
|
||||
let mut blob_vec = BlobVec {
|
||||
swap_scratch: UnsafeCell::new(swap_scratch),
|
||||
data: UnsafeCell::new(NonNull::dangling()),
|
||||
capacity: 0,
|
||||
len: 0,
|
||||
item_layout,
|
||||
drop,
|
||||
};
|
||||
blob_vec.reserve(capacity);
|
||||
blob_vec
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len == 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn capacity(&self) -> usize {
|
||||
self.capacity
|
||||
}
|
||||
|
||||
pub fn reserve(&mut self, amount: usize) {
|
||||
let available_space = self.capacity - self.len;
|
||||
if available_space < amount {
|
||||
self.grow(amount - available_space);
|
||||
}
|
||||
}
|
||||
|
||||
fn grow(&mut self, increment: usize) {
|
||||
debug_assert!(self.item_layout.size() != 0);
|
||||
|
||||
let new_capacity = self.capacity + increment;
|
||||
let new_layout =
|
||||
array_layout(&self.item_layout, new_capacity).expect("array layout should be valid");
|
||||
unsafe {
|
||||
let new_data = if self.capacity == 0 {
|
||||
std::alloc::alloc(new_layout)
|
||||
} else {
|
||||
std::alloc::realloc(
|
||||
self.get_ptr().as_ptr(),
|
||||
array_layout(&self.item_layout, self.capacity)
|
||||
.expect("array layout should be valid"),
|
||||
new_layout.size(),
|
||||
)
|
||||
};
|
||||
|
||||
self.data = UnsafeCell::new(
|
||||
NonNull::new(new_data).unwrap_or_else(|| handle_alloc_error(new_layout)),
|
||||
);
|
||||
}
|
||||
self.capacity = new_capacity;
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `index` must be in bounds
|
||||
/// Allows aliased mutable access to `index`'s data. Caller must ensure this does not happen
|
||||
#[inline]
|
||||
pub unsafe fn set_unchecked(&self, index: usize, value: *mut u8) {
|
||||
debug_assert!(index < self.len());
|
||||
let ptr = self.get_unchecked(index);
|
||||
std::ptr::copy_nonoverlapping(value, ptr, self.item_layout.size());
|
||||
}
|
||||
|
||||
/// increases the length by one (and grows the vec if needed) with uninitialized memory and returns the index
|
||||
/// # Safety
|
||||
/// the newly allocated space must be immediately populated with a valid value
|
||||
#[inline]
|
||||
pub unsafe fn push_uninit(&mut self) -> usize {
|
||||
self.reserve(1);
|
||||
let index = self.len;
|
||||
self.len += 1;
|
||||
index
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// len must be <= capacity. if length is decreased, "out of bounds" items must be dropped. Newly added items must be
|
||||
/// immediately populated with valid values and length must be increased. For better unwind safety, call [BlobVec::set_len]
|
||||
/// _after_ populating a new value.
|
||||
pub unsafe fn set_len(&mut self, len: usize) {
|
||||
debug_assert!(len <= self.capacity());
|
||||
self.len = len;
|
||||
}
|
||||
|
||||
/// Performs a "swap remove" at the given `index`, which removes the item at `index` and moves the last item
|
||||
/// in the [BlobVec] to `index` (if `index` is not the last item). It is the caller's responsibility to
|
||||
/// drop the returned pointer, if that is desirable.
|
||||
/// # Safety
|
||||
/// It is the caller's responsibility to ensure that `index` is < self.len()
|
||||
/// Callers should _only_ access the returned pointer immediately after calling this function.
|
||||
#[inline]
|
||||
pub unsafe fn swap_remove_and_forget_unchecked(&mut self, index: usize) -> *mut u8 {
|
||||
debug_assert!(index < self.len());
|
||||
let last = self.len - 1;
|
||||
let swap_scratch = (*self.swap_scratch.get()).as_ptr();
|
||||
std::ptr::copy_nonoverlapping(
|
||||
self.get_unchecked(index),
|
||||
swap_scratch,
|
||||
self.item_layout.size(),
|
||||
);
|
||||
std::ptr::copy(
|
||||
self.get_unchecked(last),
|
||||
self.get_unchecked(index),
|
||||
self.item_layout.size(),
|
||||
);
|
||||
self.len -= 1;
|
||||
swap_scratch
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// index must be in-bounds
|
||||
#[inline]
|
||||
pub unsafe fn swap_remove_and_drop_unchecked(&mut self, index: usize) {
|
||||
debug_assert!(index < self.len());
|
||||
let value = self.swap_remove_and_forget_unchecked(index);
|
||||
(self.drop)(value)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// It is the caller's responsibility to ensure that `index` is < self.len()
|
||||
#[inline]
|
||||
pub unsafe fn get_unchecked(&self, index: usize) -> *mut u8 {
|
||||
debug_assert!(index < self.len());
|
||||
self.get_ptr().as_ptr().add(index * self.item_layout.size())
|
||||
}
|
||||
|
||||
/// Gets a pointer to the start of the vec
|
||||
/// # Safety
|
||||
/// must ensure rust mutability rules are not violated
|
||||
#[inline]
|
||||
pub unsafe fn get_ptr(&self) -> NonNull<u8> {
|
||||
*self.data.get()
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
let len = self.len;
|
||||
// We set len to 0 _before_ dropping elements for unwind safety. This ensures we don't accidentally
|
||||
// drop elements twice in the event of a drop impl panicking.
|
||||
self.len = 0;
|
||||
for i in 0..len {
|
||||
unsafe {
|
||||
// NOTE: this doesn't use self.get_unchecked(i) because the debug_assert on index will
|
||||
// panic here due to self.len being set to 0
|
||||
let ptr = self.get_ptr().as_ptr().add(i * self.item_layout.size());
|
||||
(self.drop)(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for BlobVec {
|
||||
fn drop(&mut self) {
|
||||
self.clear();
|
||||
if self.item_layout.size() > 0 {
|
||||
unsafe {
|
||||
std::alloc::dealloc(
|
||||
self.get_ptr().as_ptr(),
|
||||
array_layout(&self.item_layout, self.capacity)
|
||||
.expect("array layout should be valid"),
|
||||
);
|
||||
std::alloc::dealloc((*self.swap_scratch.get()).as_ptr(), self.item_layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// From https://doc.rust-lang.org/beta/src/core/alloc/layout.rs.html
|
||||
fn array_layout(layout: &Layout, n: usize) -> Option<Layout> {
|
||||
let (array_layout, offset) = repeat_layout(layout, n)?;
|
||||
debug_assert_eq!(layout.size(), offset);
|
||||
Some(array_layout)
|
||||
}
|
||||
|
||||
// TODO: replace with Layout::repeat if/when it stabilizes
|
||||
/// From https://doc.rust-lang.org/beta/src/core/alloc/layout.rs.html
|
||||
fn repeat_layout(layout: &Layout, n: usize) -> Option<(Layout, usize)> {
|
||||
// This cannot overflow. Quoting from the invariant of Layout:
|
||||
// > `size`, when rounded up to the nearest multiple of `align`,
|
||||
// > must not overflow (i.e., the rounded value must be less than
|
||||
// > `usize::MAX`)
|
||||
let padded_size = layout.size() + padding_needed_for(layout, layout.align());
|
||||
let alloc_size = padded_size.checked_mul(n)?;
|
||||
|
||||
// SAFETY: self.align is already known to be valid and alloc_size has been
|
||||
// padded already.
|
||||
unsafe {
|
||||
Some((
|
||||
Layout::from_size_align_unchecked(alloc_size, layout.align()),
|
||||
padded_size,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// From https://doc.rust-lang.org/beta/src/core/alloc/layout.rs.html
|
||||
const fn padding_needed_for(layout: &Layout, align: usize) -> usize {
|
||||
let len = layout.size();
|
||||
|
||||
// Rounded up value is:
|
||||
// len_rounded_up = (len + align - 1) & !(align - 1);
|
||||
// and then we return the padding difference: `len_rounded_up - len`.
|
||||
//
|
||||
// We use modular arithmetic throughout:
|
||||
//
|
||||
// 1. align is guaranteed to be > 0, so align - 1 is always
|
||||
// valid.
|
||||
//
|
||||
// 2. `len + align - 1` can overflow by at most `align - 1`,
|
||||
// so the &-mask with `!(align - 1)` will ensure that in the
|
||||
// case of overflow, `len_rounded_up` will itself be 0.
|
||||
// Thus the returned padding, when added to `len`, yields 0,
|
||||
// which trivially satisfies the alignment `align`.
|
||||
//
|
||||
// (Of course, attempts to allocate blocks of memory whose
|
||||
// size and padding overflow in the above manner should cause
|
||||
// the allocator to yield an error anyway.)
|
||||
|
||||
let len_rounded_up = len.wrapping_add(align).wrapping_sub(1) & !align.wrapping_sub(1);
|
||||
len_rounded_up.wrapping_sub(len)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::BlobVec;
|
||||
use crate::component::TypeInfo;
|
||||
use std::{alloc::Layout, cell::RefCell, rc::Rc};
|
||||
|
||||
/// # Safety:
|
||||
/// `blob_vec` must have a layout that matches Layout::new::<T>()
|
||||
unsafe fn push<T>(blob_vec: &mut BlobVec, mut value: T) {
|
||||
let index = blob_vec.push_uninit();
|
||||
blob_vec.set_unchecked(index, (&mut value as *mut T).cast::<u8>());
|
||||
std::mem::forget(value);
|
||||
}
|
||||
|
||||
/// # Safety:
|
||||
/// `blob_vec` must have a layout that matches Layout::new::<T>()
|
||||
unsafe fn swap_remove<T>(blob_vec: &mut BlobVec, index: usize) -> T {
|
||||
assert!(index < blob_vec.len());
|
||||
let value = blob_vec.swap_remove_and_forget_unchecked(index);
|
||||
value.cast::<T>().read()
|
||||
}
|
||||
|
||||
/// # Safety:
|
||||
/// `blob_vec` must have a layout that matches Layout::new::<T>(), it most store a valid T value at
|
||||
/// the given `index`
|
||||
unsafe fn get_mut<T>(blob_vec: &mut BlobVec, index: usize) -> &mut T {
|
||||
assert!(index < blob_vec.len());
|
||||
&mut *blob_vec.get_unchecked(index).cast::<T>()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resize_test() {
|
||||
let item_layout = Layout::new::<usize>();
|
||||
let drop = TypeInfo::drop_ptr::<usize>;
|
||||
let mut blob_vec = BlobVec::new(item_layout, drop, 64);
|
||||
unsafe {
|
||||
for i in 0..1_000 {
|
||||
push(&mut blob_vec, i as usize);
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(blob_vec.len(), 1_000);
|
||||
assert_eq!(blob_vec.capacity(), 1_000);
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
struct Foo {
|
||||
a: u8,
|
||||
b: String,
|
||||
drop_counter: Rc<RefCell<usize>>,
|
||||
}
|
||||
|
||||
impl Drop for Foo {
|
||||
fn drop(&mut self) {
|
||||
*self.drop_counter.borrow_mut() += 1;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blob_vec() {
|
||||
let drop_counter = Rc::new(RefCell::new(0));
|
||||
{
|
||||
let item_layout = Layout::new::<Foo>();
|
||||
let drop = TypeInfo::drop_ptr::<Foo>;
|
||||
let mut blob_vec = BlobVec::new(item_layout, drop, 2);
|
||||
assert_eq!(blob_vec.capacity(), 2);
|
||||
unsafe {
|
||||
let foo1 = Foo {
|
||||
a: 42,
|
||||
b: "abc".to_string(),
|
||||
drop_counter: drop_counter.clone(),
|
||||
};
|
||||
push(&mut blob_vec, foo1.clone());
|
||||
assert_eq!(blob_vec.len(), 1);
|
||||
assert_eq!(get_mut::<Foo>(&mut blob_vec, 0), &foo1);
|
||||
|
||||
let mut foo2 = Foo {
|
||||
a: 7,
|
||||
b: "xyz".to_string(),
|
||||
drop_counter: drop_counter.clone(),
|
||||
};
|
||||
push::<Foo>(&mut blob_vec, foo2.clone());
|
||||
assert_eq!(blob_vec.len(), 2);
|
||||
assert_eq!(blob_vec.capacity(), 2);
|
||||
assert_eq!(get_mut::<Foo>(&mut blob_vec, 0), &foo1);
|
||||
assert_eq!(get_mut::<Foo>(&mut blob_vec, 1), &foo2);
|
||||
|
||||
get_mut::<Foo>(&mut blob_vec, 1).a += 1;
|
||||
assert_eq!(get_mut::<Foo>(&mut blob_vec, 1).a, 8);
|
||||
|
||||
let foo3 = Foo {
|
||||
a: 16,
|
||||
b: "123".to_string(),
|
||||
drop_counter: drop_counter.clone(),
|
||||
};
|
||||
|
||||
push(&mut blob_vec, foo3.clone());
|
||||
assert_eq!(blob_vec.len(), 3);
|
||||
assert_eq!(blob_vec.capacity(), 3);
|
||||
|
||||
let last_index = blob_vec.len() - 1;
|
||||
let value = swap_remove::<Foo>(&mut blob_vec, last_index);
|
||||
assert_eq!(foo3, value);
|
||||
|
||||
assert_eq!(blob_vec.len(), 2);
|
||||
assert_eq!(blob_vec.capacity(), 3);
|
||||
|
||||
let value = swap_remove::<Foo>(&mut blob_vec, 0);
|
||||
assert_eq!(foo1, value);
|
||||
assert_eq!(blob_vec.len(), 1);
|
||||
assert_eq!(blob_vec.capacity(), 3);
|
||||
|
||||
foo2.a = 8;
|
||||
assert_eq!(get_mut::<Foo>(&mut blob_vec, 0), &foo2);
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(*drop_counter.borrow(), 6);
|
||||
}
|
||||
}
|
13
crates/bevy_ecs/src/storage/mod.rs
Normal file
13
crates/bevy_ecs/src/storage/mod.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
mod blob_vec;
|
||||
mod sparse_set;
|
||||
mod table;
|
||||
|
||||
pub use blob_vec::*;
|
||||
pub use sparse_set::*;
|
||||
pub use table::*;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Storages {
|
||||
pub sparse_sets: SparseSets,
|
||||
pub tables: Tables,
|
||||
}
|
507
crates/bevy_ecs/src/storage/sparse_set.rs
Normal file
507
crates/bevy_ecs/src/storage/sparse_set.rs
Normal file
|
@ -0,0 +1,507 @@
|
|||
use crate::{
|
||||
component::{ComponentFlags, ComponentId, ComponentInfo},
|
||||
entity::Entity,
|
||||
storage::BlobVec,
|
||||
};
|
||||
use std::{cell::UnsafeCell, marker::PhantomData};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SparseArray<I, V = I> {
|
||||
values: Vec<Option<V>>,
|
||||
marker: PhantomData<I>,
|
||||
}
|
||||
|
||||
impl<I: SparseSetIndex, V> Default for SparseArray<I, V> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, V> SparseArray<I, V> {
|
||||
#[inline]
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
values: Vec::new(),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: SparseSetIndex, V> SparseArray<I, V> {
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
values: Vec::with_capacity(capacity),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn insert(&mut self, index: I, value: V) {
|
||||
let index = index.sparse_set_index();
|
||||
if index >= self.values.len() {
|
||||
self.values.resize_with(index + 1, || None);
|
||||
}
|
||||
self.values[index] = Some(value);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn contains(&self, index: I) -> bool {
|
||||
let index = index.sparse_set_index();
|
||||
self.values.get(index).map(|v| v.is_some()).unwrap_or(false)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get(&self, index: I) -> Option<&V> {
|
||||
let index = index.sparse_set_index();
|
||||
self.values.get(index).map(|v| v.as_ref()).unwrap_or(None)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_mut(&mut self, index: I) -> Option<&mut V> {
|
||||
let index = index.sparse_set_index();
|
||||
self.values
|
||||
.get_mut(index)
|
||||
.map(|v| v.as_mut())
|
||||
.unwrap_or(None)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn remove(&mut self, index: I) -> Option<V> {
|
||||
let index = index.sparse_set_index();
|
||||
self.values.get_mut(index).and_then(|value| value.take())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_or_insert_with(&mut self, index: I, func: impl FnOnce() -> V) -> &mut V {
|
||||
let index = index.sparse_set_index();
|
||||
if index < self.values.len() {
|
||||
return self.values[index].get_or_insert_with(func);
|
||||
}
|
||||
self.values.resize_with(index + 1, || None);
|
||||
let value = &mut self.values[index];
|
||||
*value = Some(func());
|
||||
value.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ComponentSparseSet {
|
||||
dense: BlobVec,
|
||||
flags: UnsafeCell<Vec<ComponentFlags>>,
|
||||
entities: Vec<Entity>,
|
||||
sparse: SparseArray<Entity, usize>,
|
||||
}
|
||||
|
||||
impl ComponentSparseSet {
|
||||
pub fn new(component_info: &ComponentInfo, capacity: usize) -> Self {
|
||||
Self {
|
||||
dense: BlobVec::new(component_info.layout(), component_info.drop(), capacity),
|
||||
flags: UnsafeCell::new(Vec::with_capacity(capacity)),
|
||||
entities: Vec::with_capacity(capacity),
|
||||
sparse: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.dense.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.dense.len() == 0
|
||||
}
|
||||
|
||||
/// Inserts the `entity` key and component `value` pair into this sparse set.
|
||||
/// The caller is responsible for ensuring the value is not dropped. This collection will drop the value when needed.
|
||||
/// # Safety
|
||||
/// The `value` pointer must point to a valid address that matches the `Layout` inside the `ComponentInfo` given
|
||||
/// when constructing this sparse set.
|
||||
pub unsafe fn insert(&mut self, entity: Entity, value: *mut u8, flags: ComponentFlags) {
|
||||
let dense = &mut self.dense;
|
||||
let entities = &mut self.entities;
|
||||
let flag_list = self.flags.get_mut();
|
||||
let dense_index = *self.sparse.get_or_insert_with(entity, move || {
|
||||
flag_list.push(ComponentFlags::empty());
|
||||
entities.push(entity);
|
||||
dense.push_uninit()
|
||||
});
|
||||
// SAFE: dense_index exists thanks to the call above
|
||||
self.dense.set_unchecked(dense_index, value);
|
||||
(*self.flags.get())
|
||||
.get_unchecked_mut(dense_index)
|
||||
.insert(flags);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn contains(&self, entity: Entity) -> bool {
|
||||
self.sparse.contains(entity)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// ensure the same entity is not accessed twice at the same time
|
||||
#[inline]
|
||||
pub fn get(&self, entity: Entity) -> Option<*mut u8> {
|
||||
self.sparse.get(entity).map(|dense_index| {
|
||||
// SAFE: if the sparse index points to something in the dense vec, it exists
|
||||
unsafe { self.dense.get_unchecked(*dense_index) }
|
||||
})
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// ensure the same entity is not accessed twice at the same time
|
||||
#[inline]
|
||||
pub unsafe fn get_with_flags(&self, entity: Entity) -> Option<(*mut u8, *mut ComponentFlags)> {
|
||||
let flags = &mut *self.flags.get();
|
||||
self.sparse.get(entity).map(move |dense_index| {
|
||||
let dense_index = *dense_index;
|
||||
// SAFE: if the sparse index points to something in the dense vec, it exists
|
||||
(
|
||||
self.dense.get_unchecked(dense_index),
|
||||
flags.get_unchecked_mut(dense_index) as *mut ComponentFlags,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// ensure the same entity is not accessed twice at the same time
|
||||
#[inline]
|
||||
pub unsafe fn get_flags(&self, entity: Entity) -> Option<&mut ComponentFlags> {
|
||||
let flags = &mut *self.flags.get();
|
||||
self.sparse.get(entity).map(move |dense_index| {
|
||||
let dense_index = *dense_index;
|
||||
// SAFE: if the sparse index points to something in the dense vec, it exists
|
||||
flags.get_unchecked_mut(dense_index)
|
||||
})
|
||||
}
|
||||
|
||||
/// Removes the `entity` from this sparse set and returns a pointer to the associated value (if it exists).
|
||||
/// It is the caller's responsibility to drop the returned ptr (if Some is returned).
|
||||
pub fn remove_and_forget(&mut self, entity: Entity) -> Option<*mut u8> {
|
||||
self.sparse.remove(entity).map(|dense_index| {
|
||||
// SAFE: unique access to flags
|
||||
unsafe {
|
||||
(*self.flags.get()).swap_remove(dense_index);
|
||||
}
|
||||
self.entities.swap_remove(dense_index);
|
||||
let is_last = dense_index == self.dense.len() - 1;
|
||||
// SAFE: dense_index was just removed from `sparse`, which ensures that it is valid
|
||||
let value = unsafe { self.dense.swap_remove_and_forget_unchecked(dense_index) };
|
||||
if !is_last {
|
||||
let swapped_entity = self.entities[dense_index];
|
||||
*self.sparse.get_mut(swapped_entity).unwrap() = dense_index;
|
||||
}
|
||||
value
|
||||
})
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, entity: Entity) -> bool {
|
||||
if let Some(dense_index) = self.sparse.remove(entity) {
|
||||
self.flags.get_mut().swap_remove(dense_index);
|
||||
self.entities.swap_remove(dense_index);
|
||||
let is_last = dense_index == self.dense.len() - 1;
|
||||
// SAFE: if the sparse index points to something in the dense vec, it exists
|
||||
unsafe { self.dense.swap_remove_and_drop_unchecked(dense_index) }
|
||||
if !is_last {
|
||||
let swapped_entity = self.entities[dense_index];
|
||||
*self.sparse.get_mut(swapped_entity).unwrap() = dense_index;
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn clear_flags(&mut self) {
|
||||
let flags = self.flags.get_mut().iter_mut();
|
||||
for component_flags in flags {
|
||||
*component_flags = ComponentFlags::empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SparseSet<I, V: 'static> {
|
||||
dense: Vec<V>,
|
||||
indices: Vec<I>,
|
||||
sparse: SparseArray<I, usize>,
|
||||
}
|
||||
|
||||
impl<I: SparseSetIndex, V> Default for SparseSet<I, V> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
impl<I, V> SparseSet<I, V> {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
dense: Vec::new(),
|
||||
indices: Vec::new(),
|
||||
sparse: SparseArray::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: SparseSetIndex, V> SparseSet<I, V> {
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
dense: Vec::with_capacity(capacity),
|
||||
indices: Vec::with_capacity(capacity),
|
||||
sparse: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn capacity(&self) -> usize {
|
||||
self.dense.capacity()
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, index: I, value: V) {
|
||||
if let Some(dense_index) = self.sparse.get(index.clone()).cloned() {
|
||||
// SAFE: dense indices stored in self.sparse always exist
|
||||
unsafe {
|
||||
*self.dense.get_unchecked_mut(dense_index) = value;
|
||||
}
|
||||
} else {
|
||||
self.sparse.insert(index.clone(), self.dense.len());
|
||||
self.indices.push(index);
|
||||
self.dense.push(value);
|
||||
}
|
||||
|
||||
// PERF: switch to this. it's faster but it has an invalid memory access on table_add_remove_many
|
||||
// let dense = &mut self.dense;
|
||||
// let indices = &mut self.indices;
|
||||
// let dense_index = *self.sparse.get_or_insert_with(index.clone(), move || {
|
||||
// if dense.len() == dense.capacity() {
|
||||
// dense.reserve(64);
|
||||
// indices.reserve(64);
|
||||
// }
|
||||
// let len = dense.len();
|
||||
// // SAFE: we set the index immediately
|
||||
// unsafe {
|
||||
// dense.set_len(len + 1);
|
||||
// indices.set_len(len + 1);
|
||||
// }
|
||||
// len
|
||||
// });
|
||||
// // SAFE: index either already existed or was just allocated
|
||||
// unsafe {
|
||||
// *self.dense.get_unchecked_mut(dense_index) = value;
|
||||
// *self.indices.get_unchecked_mut(dense_index) = index;
|
||||
// }
|
||||
}
|
||||
|
||||
pub fn get_or_insert_with(&mut self, index: I, func: impl FnOnce() -> V) -> &mut V {
|
||||
if let Some(dense_index) = self.sparse.get(index.clone()).cloned() {
|
||||
// SAFE: dense indices stored in self.sparse always exist
|
||||
unsafe { self.dense.get_unchecked_mut(dense_index) }
|
||||
} else {
|
||||
let value = func();
|
||||
let dense_index = self.dense.len();
|
||||
self.sparse.insert(index.clone(), dense_index);
|
||||
self.indices.push(index);
|
||||
self.dense.push(value);
|
||||
// SAFE: dense index was just populated above
|
||||
unsafe { self.dense.get_unchecked_mut(dense_index) }
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.dense.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.dense.len() == 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn contains(&self, index: I) -> bool {
|
||||
self.sparse.contains(index)
|
||||
}
|
||||
|
||||
pub fn get(&self, index: I) -> Option<&V> {
|
||||
self.sparse.get(index).map(|dense_index| {
|
||||
// SAFE: if the sparse index points to something in the dense vec, it exists
|
||||
unsafe { self.dense.get_unchecked(*dense_index) }
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, index: I) -> Option<&mut V> {
|
||||
let dense = &mut self.dense;
|
||||
self.sparse.get(index).map(move |dense_index| {
|
||||
// SAFE: if the sparse index points to something in the dense vec, it exists
|
||||
unsafe { dense.get_unchecked_mut(*dense_index) }
|
||||
})
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, index: I) -> Option<V> {
|
||||
self.sparse.remove(index).map(|dense_index| {
|
||||
let is_last = dense_index == self.dense.len() - 1;
|
||||
let value = self.dense.swap_remove(dense_index);
|
||||
self.indices.swap_remove(dense_index);
|
||||
if !is_last {
|
||||
let swapped_index = self.indices[dense_index].clone();
|
||||
*self.sparse.get_mut(swapped_index).unwrap() = dense_index;
|
||||
}
|
||||
value
|
||||
})
|
||||
}
|
||||
|
||||
pub fn indices(&self) -> impl Iterator<Item = I> + '_ {
|
||||
self.indices.iter().cloned()
|
||||
}
|
||||
|
||||
pub fn values(&self) -> impl Iterator<Item = &V> {
|
||||
self.dense.iter()
|
||||
}
|
||||
|
||||
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut V> {
|
||||
self.dense.iter_mut()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SparseSetIndex: Clone {
|
||||
fn sparse_set_index(&self) -> usize;
|
||||
fn get_sparse_set_index(value: usize) -> Self;
|
||||
}
|
||||
|
||||
impl SparseSetIndex for u8 {
|
||||
fn sparse_set_index(&self) -> usize {
|
||||
*self as usize
|
||||
}
|
||||
|
||||
fn get_sparse_set_index(value: usize) -> Self {
|
||||
value as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl SparseSetIndex for u16 {
|
||||
fn sparse_set_index(&self) -> usize {
|
||||
*self as usize
|
||||
}
|
||||
|
||||
fn get_sparse_set_index(value: usize) -> Self {
|
||||
value as u16
|
||||
}
|
||||
}
|
||||
|
||||
impl SparseSetIndex for u32 {
|
||||
fn sparse_set_index(&self) -> usize {
|
||||
*self as usize
|
||||
}
|
||||
|
||||
fn get_sparse_set_index(value: usize) -> Self {
|
||||
value as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl SparseSetIndex for u64 {
|
||||
fn sparse_set_index(&self) -> usize {
|
||||
*self as usize
|
||||
}
|
||||
|
||||
fn get_sparse_set_index(value: usize) -> Self {
|
||||
value as u64
|
||||
}
|
||||
}
|
||||
|
||||
impl SparseSetIndex for usize {
|
||||
fn sparse_set_index(&self) -> usize {
|
||||
*self
|
||||
}
|
||||
|
||||
fn get_sparse_set_index(value: usize) -> Self {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SparseSets {
|
||||
sets: SparseSet<ComponentId, ComponentSparseSet>,
|
||||
}
|
||||
|
||||
impl SparseSets {
|
||||
pub fn get_or_insert(&mut self, component_info: &ComponentInfo) -> &mut ComponentSparseSet {
|
||||
if !self.sets.contains(component_info.id()) {
|
||||
self.sets.insert(
|
||||
component_info.id(),
|
||||
ComponentSparseSet::new(component_info, 64),
|
||||
);
|
||||
}
|
||||
|
||||
self.sets.get_mut(component_info.id()).unwrap()
|
||||
}
|
||||
|
||||
pub fn get(&self, component_id: ComponentId) -> Option<&ComponentSparseSet> {
|
||||
self.sets.get(component_id)
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, component_id: ComponentId) -> Option<&mut ComponentSparseSet> {
|
||||
self.sets.get_mut(component_id)
|
||||
}
|
||||
|
||||
pub(crate) fn clear_flags(&mut self) {
|
||||
for set in self.sets.values_mut() {
|
||||
set.clear_flags();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{entity::Entity, storage::SparseSet};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
struct Foo(usize);
|
||||
|
||||
#[test]
|
||||
fn sparse_set() {
|
||||
let mut set = SparseSet::<Entity, Foo>::default();
|
||||
let e0 = Entity::new(0);
|
||||
let e1 = Entity::new(1);
|
||||
let e2 = Entity::new(2);
|
||||
let e3 = Entity::new(3);
|
||||
let e4 = Entity::new(4);
|
||||
|
||||
set.insert(e1, Foo(1));
|
||||
set.insert(e2, Foo(2));
|
||||
set.insert(e3, Foo(3));
|
||||
|
||||
assert_eq!(set.get(e0), None);
|
||||
assert_eq!(set.get(e1), Some(&Foo(1)));
|
||||
assert_eq!(set.get(e2), Some(&Foo(2)));
|
||||
assert_eq!(set.get(e3), Some(&Foo(3)));
|
||||
assert_eq!(set.get(e4), None);
|
||||
|
||||
{
|
||||
let iter_results = set.values().collect::<Vec<_>>();
|
||||
assert_eq!(iter_results, vec![&Foo(1), &Foo(2), &Foo(3)])
|
||||
}
|
||||
|
||||
assert_eq!(set.remove(e2), Some(Foo(2)));
|
||||
assert_eq!(set.remove(e2), None);
|
||||
|
||||
assert_eq!(set.get(e0), None);
|
||||
assert_eq!(set.get(e1), Some(&Foo(1)));
|
||||
assert_eq!(set.get(e2), None);
|
||||
assert_eq!(set.get(e3), Some(&Foo(3)));
|
||||
assert_eq!(set.get(e4), None);
|
||||
|
||||
assert_eq!(set.remove(e1), Some(Foo(1)));
|
||||
|
||||
assert_eq!(set.get(e0), None);
|
||||
assert_eq!(set.get(e1), None);
|
||||
assert_eq!(set.get(e2), None);
|
||||
assert_eq!(set.get(e3), Some(&Foo(3)));
|
||||
assert_eq!(set.get(e4), None);
|
||||
|
||||
set.insert(e1, Foo(10));
|
||||
|
||||
assert_eq!(set.get(e1), Some(&Foo(10)));
|
||||
|
||||
*set.get_mut(e1).unwrap() = Foo(11);
|
||||
assert_eq!(set.get(e1), Some(&Foo(11)));
|
||||
}
|
||||
}
|
500
crates/bevy_ecs/src/storage/table.rs
Normal file
500
crates/bevy_ecs/src/storage/table.rs
Normal file
|
@ -0,0 +1,500 @@
|
|||
use crate::{
|
||||
archetype::ArchetypeId,
|
||||
component::{ComponentFlags, ComponentId, ComponentInfo, Components},
|
||||
entity::Entity,
|
||||
storage::{BlobVec, SparseSet},
|
||||
};
|
||||
use bevy_utils::{AHasher, HashMap};
|
||||
use std::{
|
||||
cell::UnsafeCell,
|
||||
hash::{Hash, Hasher},
|
||||
ptr::NonNull,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct TableId(usize);
|
||||
|
||||
impl TableId {
|
||||
#[inline]
|
||||
pub fn new(index: usize) -> Self {
|
||||
TableId(index)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn index(self) -> usize {
|
||||
self.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn empty() -> TableId {
|
||||
TableId(0)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Column {
|
||||
pub(crate) component_id: ComponentId,
|
||||
pub(crate) data: BlobVec,
|
||||
pub(crate) flags: UnsafeCell<Vec<ComponentFlags>>,
|
||||
}
|
||||
|
||||
impl Column {
|
||||
#[inline]
|
||||
pub fn with_capacity(component_info: &ComponentInfo, capacity: usize) -> Self {
|
||||
Column {
|
||||
component_id: component_info.id(),
|
||||
data: BlobVec::new(component_info.layout(), component_info.drop(), capacity),
|
||||
flags: UnsafeCell::new(Vec::with_capacity(capacity)),
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// Assumes data has already been allocated for the given row/column.
|
||||
/// Allows aliased mutable accesses to the data at the given `row`. Caller must ensure that this does not happen.
|
||||
#[inline]
|
||||
pub unsafe fn set_unchecked(&self, row: usize, data: *mut u8) {
|
||||
self.data.set_unchecked(row, data);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.data.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.data.is_empty()
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// Assumes data has already been allocated for the given row/column.
|
||||
/// Allows aliased mutable accesses to the row's ComponentFlags. Caller must ensure that this does not happen.
|
||||
#[inline]
|
||||
#[allow(clippy::mut_from_ref)]
|
||||
pub unsafe fn get_flags_unchecked_mut(&self, row: usize) -> &mut ComponentFlags {
|
||||
debug_assert!(row < self.len());
|
||||
(*self.flags.get()).get_unchecked_mut(row)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) unsafe fn swap_remove_unchecked(&mut self, row: usize) {
|
||||
self.data.swap_remove_and_drop_unchecked(row);
|
||||
(*self.flags.get()).swap_remove(row);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) unsafe fn swap_remove_and_forget_unchecked(
|
||||
&mut self,
|
||||
row: usize,
|
||||
) -> (*mut u8, ComponentFlags) {
|
||||
let data = self.data.swap_remove_and_forget_unchecked(row);
|
||||
let flags = (*self.flags.get()).swap_remove(row);
|
||||
(data, flags)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// allocated value must be immediately set at the returned row
|
||||
pub(crate) unsafe fn push_uninit(&mut self) -> usize {
|
||||
let row = self.data.push_uninit();
|
||||
(*self.flags.get()).push(ComponentFlags::empty());
|
||||
row
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn reserve(&mut self, additional: usize) {
|
||||
self.data.reserve(additional);
|
||||
// SAFE: unique access to self
|
||||
unsafe {
|
||||
let flags = &mut (*self.flags.get());
|
||||
flags.reserve(additional);
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// must ensure rust mutability rules are not violated
|
||||
#[inline]
|
||||
pub unsafe fn get_ptr(&self) -> NonNull<u8> {
|
||||
self.data.get_ptr()
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// must ensure rust mutability rules are not violated
|
||||
#[inline]
|
||||
pub unsafe fn get_flags_mut_ptr(&self) -> *mut ComponentFlags {
|
||||
(*self.flags.get()).as_mut_ptr()
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// must ensure rust mutability rules are not violated
|
||||
#[inline]
|
||||
pub unsafe fn get_unchecked(&self, row: usize) -> *mut u8 {
|
||||
debug_assert!(row < self.data.len());
|
||||
self.data.get_unchecked(row)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// must ensure rust mutability rules are not violated
|
||||
#[inline]
|
||||
pub unsafe fn get_flags_unchecked(&self, row: usize) -> *mut ComponentFlags {
|
||||
debug_assert!(row < (*self.flags.get()).len());
|
||||
self.get_flags_mut_ptr().add(row)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn clear_flags(&mut self) {
|
||||
let flags = unsafe { (*self.flags.get()).iter_mut() };
|
||||
for component_flags in flags {
|
||||
*component_flags = ComponentFlags::empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Table {
|
||||
columns: SparseSet<ComponentId, Column>,
|
||||
entities: Vec<Entity>,
|
||||
archetypes: Vec<ArchetypeId>,
|
||||
grow_amount: usize,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
impl Table {
|
||||
pub const fn new(grow_amount: usize) -> Table {
|
||||
Self {
|
||||
columns: SparseSet::new(),
|
||||
entities: Vec::new(),
|
||||
archetypes: Vec::new(),
|
||||
grow_amount,
|
||||
capacity: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_capacity(capacity: usize, column_capacity: usize, grow_amount: usize) -> Table {
|
||||
Self {
|
||||
columns: SparseSet::with_capacity(column_capacity),
|
||||
entities: Vec::with_capacity(capacity),
|
||||
archetypes: Vec::new(),
|
||||
grow_amount,
|
||||
capacity,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn entities(&self) -> &[Entity] {
|
||||
&self.entities
|
||||
}
|
||||
|
||||
pub fn add_archetype(&mut self, archetype_id: ArchetypeId) {
|
||||
self.archetypes.push(archetype_id);
|
||||
}
|
||||
|
||||
pub fn add_column(&mut self, component_info: &ComponentInfo) {
|
||||
self.columns.insert(
|
||||
component_info.id(),
|
||||
Column::with_capacity(component_info, self.capacity()),
|
||||
)
|
||||
}
|
||||
|
||||
/// Removes the entity at the given row and returns the entity swapped in to replace it (if an entity was swapped in)
|
||||
/// # Safety
|
||||
/// `row` must be in-bounds
|
||||
pub unsafe fn swap_remove_unchecked(&mut self, row: usize) -> Option<Entity> {
|
||||
for column in self.columns.values_mut() {
|
||||
column.swap_remove_unchecked(row);
|
||||
}
|
||||
let is_last = row == self.entities.len() - 1;
|
||||
self.entities.swap_remove(row);
|
||||
if is_last {
|
||||
None
|
||||
} else {
|
||||
Some(self.entities[row])
|
||||
}
|
||||
}
|
||||
|
||||
/// Moves the `row` column values to `new_table`, for the columns shared between both tables. Returns the index of the
|
||||
/// new row in `new_table` and the entity in this table swapped in to replace it (if an entity was swapped in).
|
||||
/// missing columns will be "forgotten". It is the caller's responsibility to drop them
|
||||
/// # Safety
|
||||
/// Row must be in-bounds
|
||||
pub unsafe fn move_to_and_forget_missing_unchecked(
|
||||
&mut self,
|
||||
row: usize,
|
||||
new_table: &mut Table,
|
||||
) -> TableMoveResult {
|
||||
debug_assert!(row < self.len());
|
||||
let is_last = row == self.entities.len() - 1;
|
||||
let new_row = new_table.allocate(self.entities.swap_remove(row));
|
||||
for column in self.columns.values_mut() {
|
||||
let (data, flags) = column.swap_remove_and_forget_unchecked(row);
|
||||
if let Some(new_column) = new_table.get_column_mut(column.component_id) {
|
||||
new_column.set_unchecked(new_row, data);
|
||||
*new_column.get_flags_unchecked_mut(new_row) = flags;
|
||||
}
|
||||
}
|
||||
TableMoveResult {
|
||||
new_row,
|
||||
swapped_entity: if is_last {
|
||||
None
|
||||
} else {
|
||||
Some(self.entities[row])
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Moves the `row` column values to `new_table`, for the columns shared between both tables. Returns the index of the
|
||||
/// new row in `new_table` and the entity in this table swapped in to replace it (if an entity was swapped in).
|
||||
/// # Safety
|
||||
/// row must be in-bounds
|
||||
pub unsafe fn move_to_and_drop_missing_unchecked(
|
||||
&mut self,
|
||||
row: usize,
|
||||
new_table: &mut Table,
|
||||
) -> TableMoveResult {
|
||||
debug_assert!(row < self.len());
|
||||
let is_last = row == self.entities.len() - 1;
|
||||
let new_row = new_table.allocate(self.entities.swap_remove(row));
|
||||
for column in self.columns.values_mut() {
|
||||
if let Some(new_column) = new_table.get_column_mut(column.component_id) {
|
||||
let (data, flags) = column.swap_remove_and_forget_unchecked(row);
|
||||
new_column.set_unchecked(new_row, data);
|
||||
*new_column.get_flags_unchecked_mut(new_row) = flags;
|
||||
} else {
|
||||
column.swap_remove_unchecked(row);
|
||||
}
|
||||
}
|
||||
TableMoveResult {
|
||||
new_row,
|
||||
swapped_entity: if is_last {
|
||||
None
|
||||
} else {
|
||||
Some(self.entities[row])
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Moves the `row` column values to `new_table`, for the columns shared between both tables. Returns the index of the
|
||||
/// new row in `new_table` and the entity in this table swapped in to replace it (if an entity was swapped in).
|
||||
/// # Safety
|
||||
/// `row` must be in-bounds. `new_table` must contain every component this table has
|
||||
pub unsafe fn move_to_superset_unchecked(
|
||||
&mut self,
|
||||
row: usize,
|
||||
new_table: &mut Table,
|
||||
) -> TableMoveResult {
|
||||
debug_assert!(row < self.len());
|
||||
let is_last = row == self.entities.len() - 1;
|
||||
let new_row = new_table.allocate(self.entities.swap_remove(row));
|
||||
for column in self.columns.values_mut() {
|
||||
let new_column = new_table.get_column_mut(column.component_id).unwrap();
|
||||
let (data, flags) = column.swap_remove_and_forget_unchecked(row);
|
||||
new_column.set_unchecked(new_row, data);
|
||||
*new_column.get_flags_unchecked_mut(new_row) = flags;
|
||||
}
|
||||
TableMoveResult {
|
||||
new_row,
|
||||
swapped_entity: if is_last {
|
||||
None
|
||||
} else {
|
||||
Some(self.entities[row])
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_column(&self, component_id: ComponentId) -> Option<&Column> {
|
||||
self.columns.get(component_id)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_column_mut(&mut self, component_id: ComponentId) -> Option<&mut Column> {
|
||||
self.columns.get_mut(component_id)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn has_column(&self, component_id: ComponentId) -> bool {
|
||||
self.columns.contains(component_id)
|
||||
}
|
||||
|
||||
pub fn reserve(&mut self, amount: usize) {
|
||||
let available_space = self.capacity - self.len();
|
||||
if available_space < amount {
|
||||
let reserve_amount = (amount - available_space).max(self.grow_amount);
|
||||
for column in self.columns.values_mut() {
|
||||
column.reserve(reserve_amount);
|
||||
}
|
||||
self.entities.reserve(reserve_amount);
|
||||
self.capacity += reserve_amount;
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocates space for a new entity
|
||||
/// # Safety
|
||||
/// the allocated row must be written to immediately with valid values in each column
|
||||
pub unsafe fn allocate(&mut self, entity: Entity) -> usize {
|
||||
self.reserve(1);
|
||||
let index = self.entities.len();
|
||||
self.entities.push(entity);
|
||||
for column in self.columns.values_mut() {
|
||||
column.data.set_len(self.entities.len());
|
||||
(*column.flags.get()).push(ComponentFlags::empty());
|
||||
}
|
||||
index
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn capacity(&self) -> usize {
|
||||
self.capacity
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.entities.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.entities.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn clear_flags(&mut self) {
|
||||
for column in self.columns.values_mut() {
|
||||
column.clear_flags();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &Column> {
|
||||
self.columns.values()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Tables {
|
||||
tables: Vec<Table>,
|
||||
table_ids: HashMap<u64, TableId>,
|
||||
}
|
||||
|
||||
impl Default for Tables {
|
||||
fn default() -> Self {
|
||||
let empty_table = Table::with_capacity(0, 0, 64);
|
||||
Tables {
|
||||
tables: vec![empty_table],
|
||||
table_ids: HashMap::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TableMoveResult {
|
||||
pub swapped_entity: Option<Entity>,
|
||||
pub new_row: usize,
|
||||
}
|
||||
|
||||
impl Tables {
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.tables.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.tables.is_empty()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_mut(&mut self, id: TableId) -> Option<&mut Table> {
|
||||
self.tables.get_mut(id.index())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get(&self, id: TableId) -> Option<&Table> {
|
||||
self.tables.get(id.index())
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `id` must be a valid table
|
||||
#[inline]
|
||||
pub unsafe fn get_unchecked_mut(&mut self, id: TableId) -> &mut Table {
|
||||
debug_assert!(id.index() < self.tables.len());
|
||||
self.tables.get_unchecked_mut(id.index())
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `id` must be a valid table
|
||||
#[inline]
|
||||
pub unsafe fn get_unchecked(&self, id: TableId) -> &Table {
|
||||
debug_assert!(id.index() < self.tables.len());
|
||||
self.tables.get_unchecked(id.index())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn get_2_mut(&mut self, a: TableId, b: TableId) -> (&mut Table, &mut Table) {
|
||||
if a.index() > b.index() {
|
||||
let (b_slice, a_slice) = self.tables.split_at_mut(a.index());
|
||||
(&mut a_slice[0], &mut b_slice[b.index()])
|
||||
} else {
|
||||
let (a_slice, b_slice) = self.tables.split_at_mut(b.index());
|
||||
(&mut a_slice[a.index()], &mut b_slice[0])
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `component_ids` must contain components that exist in `components`
|
||||
pub unsafe fn get_id_or_insert(
|
||||
&mut self,
|
||||
component_ids: &[ComponentId],
|
||||
components: &Components,
|
||||
) -> TableId {
|
||||
let mut hasher = AHasher::default();
|
||||
component_ids.hash(&mut hasher);
|
||||
let hash = hasher.finish();
|
||||
let tables = &mut self.tables;
|
||||
*self.table_ids.entry(hash).or_insert_with(move || {
|
||||
let mut table = Table::with_capacity(0, component_ids.len(), 64);
|
||||
for component_id in component_ids.iter() {
|
||||
table.add_column(components.get_info_unchecked(*component_id));
|
||||
}
|
||||
tables.push(table);
|
||||
TableId(tables.len() - 1)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> std::slice::Iter<'_, Table> {
|
||||
self.tables.iter()
|
||||
}
|
||||
|
||||
pub(crate) fn clear_flags(&mut self) {
|
||||
for table in self.tables.iter_mut() {
|
||||
table.clear_flags();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
component::{Components, TypeInfo},
|
||||
entity::Entity,
|
||||
storage::Table,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn table() {
|
||||
let mut components = Components::default();
|
||||
let type_info = TypeInfo::of::<usize>();
|
||||
let component_id = components.get_or_insert_with(type_info.type_id(), || type_info);
|
||||
let columns = &[component_id];
|
||||
let mut table = Table::with_capacity(0, columns.len(), 64);
|
||||
table.add_column(components.get_info(component_id).unwrap());
|
||||
let entities = (0..200).map(Entity::new).collect::<Vec<_>>();
|
||||
for (row, entity) in entities.iter().cloned().enumerate() {
|
||||
unsafe {
|
||||
table.allocate(entity);
|
||||
let mut value = row;
|
||||
let value_ptr = ((&mut value) as *mut usize).cast::<u8>();
|
||||
table
|
||||
.get_column(component_id)
|
||||
.unwrap()
|
||||
.set_unchecked(row, value_ptr);
|
||||
};
|
||||
}
|
||||
|
||||
assert_eq!(table.capacity(), 256);
|
||||
assert_eq!(table.len(), 200);
|
||||
}
|
||||
}
|
|
@ -1,211 +1,62 @@
|
|||
use super::SystemId;
|
||||
use crate::{
|
||||
resource::{Resource, Resources},
|
||||
Bundle, Component, ComponentError, DynamicBundle, Entity, EntityReserver, World,
|
||||
bundle::Bundle,
|
||||
component::Component,
|
||||
entity::{Entities, Entity},
|
||||
world::World,
|
||||
};
|
||||
use bevy_utils::tracing::{debug, warn};
|
||||
use bevy_utils::tracing::debug;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// A [World] mutation
|
||||
pub trait Command: Send + Sync {
|
||||
fn write(self: Box<Self>, world: &mut World, resources: &mut Resources);
|
||||
pub trait Command: Send + Sync + 'static {
|
||||
fn write(self: Box<Self>, world: &mut World);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Spawn<T>
|
||||
where
|
||||
T: DynamicBundle + Send + Sync + 'static,
|
||||
{
|
||||
bundle: T,
|
||||
}
|
||||
|
||||
impl<T> Command for Spawn<T>
|
||||
where
|
||||
T: DynamicBundle + Send + Sync + 'static,
|
||||
{
|
||||
fn write(self: Box<Self>, world: &mut World, _resources: &mut Resources) {
|
||||
world.spawn(self.bundle);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct SpawnBatch<I>
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: Bundle,
|
||||
{
|
||||
bundles_iter: I,
|
||||
}
|
||||
|
||||
impl<I> Command for SpawnBatch<I>
|
||||
where
|
||||
I: IntoIterator + Send + Sync,
|
||||
I::Item: Bundle,
|
||||
{
|
||||
fn write(self: Box<Self>, world: &mut World, _resources: &mut Resources) {
|
||||
world.spawn_batch(self.bundles_iter);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Despawn {
|
||||
entity: Entity,
|
||||
}
|
||||
|
||||
impl Command for Despawn {
|
||||
fn write(self: Box<Self>, world: &mut World, _resources: &mut Resources) {
|
||||
if let Err(e) = world.despawn(self.entity) {
|
||||
debug!("Failed to despawn entity {:?}: {}", self.entity, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Insert<T>
|
||||
where
|
||||
T: DynamicBundle + Send + Sync + 'static,
|
||||
{
|
||||
entity: Entity,
|
||||
bundle: T,
|
||||
}
|
||||
|
||||
impl<T> Command for Insert<T>
|
||||
where
|
||||
T: DynamicBundle + Send + Sync + 'static,
|
||||
{
|
||||
fn write(self: Box<Self>, world: &mut World, _resources: &mut Resources) {
|
||||
world.insert(self.entity, self.bundle).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct InsertOne<T>
|
||||
where
|
||||
T: Component,
|
||||
{
|
||||
entity: Entity,
|
||||
component: T,
|
||||
}
|
||||
|
||||
impl<T> Command for InsertOne<T>
|
||||
where
|
||||
T: Component,
|
||||
{
|
||||
fn write(self: Box<Self>, world: &mut World, _resources: &mut Resources) {
|
||||
world.insert(self.entity, (self.component,)).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct RemoveOne<T>
|
||||
where
|
||||
T: Component,
|
||||
{
|
||||
entity: Entity,
|
||||
phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> Command for RemoveOne<T>
|
||||
where
|
||||
T: Component,
|
||||
{
|
||||
fn write(self: Box<Self>, world: &mut World, _resources: &mut Resources) {
|
||||
if world.get::<T>(self.entity).is_ok() {
|
||||
world.remove_one::<T>(self.entity).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Remove<T>
|
||||
where
|
||||
T: Bundle + Send + Sync + 'static,
|
||||
{
|
||||
entity: Entity,
|
||||
phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> Command for Remove<T>
|
||||
where
|
||||
T: Bundle + Send + Sync + 'static,
|
||||
{
|
||||
fn write(self: Box<Self>, world: &mut World, _resources: &mut Resources) {
|
||||
match world.remove::<T>(self.entity) {
|
||||
Ok(_) => (),
|
||||
Err(ComponentError::MissingComponent(e)) => {
|
||||
warn!(
|
||||
"Failed to remove components {:?} with error: {}. Falling back to inefficient one-by-one component removing.",
|
||||
std::any::type_name::<T>(),
|
||||
e
|
||||
);
|
||||
if let Err(e) = world.remove_one_by_one::<T>(self.entity) {
|
||||
debug!(
|
||||
"Failed to remove components {:?} with error: {}",
|
||||
std::any::type_name::<T>(),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
debug!(
|
||||
"Failed to remove components {:?} with error: {}",
|
||||
std::any::type_name::<T>(),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InsertResource<T: Resource> {
|
||||
resource: T,
|
||||
}
|
||||
|
||||
impl<T: Resource> Command for InsertResource<T> {
|
||||
fn write(self: Box<Self>, _world: &mut World, resources: &mut Resources) {
|
||||
resources.insert(self.resource);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RemoveResource<T: Resource> {
|
||||
phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Resource> Command for RemoveResource<T> {
|
||||
fn write(self: Box<Self>, _world: &mut World, resources: &mut Resources) {
|
||||
resources.remove::<T>();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct InsertLocalResource<T: Resource> {
|
||||
resource: T,
|
||||
system_id: SystemId,
|
||||
}
|
||||
|
||||
impl<T: Resource> Command for InsertLocalResource<T> {
|
||||
fn write(self: Box<Self>, _world: &mut World, resources: &mut Resources) {
|
||||
resources.insert_local(self.system_id, self.resource);
|
||||
}
|
||||
}
|
||||
|
||||
/// A list of commands that will be run to populate a `World` and `Resources`.
|
||||
#[derive(Default)]
|
||||
pub struct Commands {
|
||||
pub struct CommandQueue {
|
||||
commands: Vec<Box<dyn Command>>,
|
||||
current_entity: Option<Entity>,
|
||||
entity_reserver: Option<EntityReserver>,
|
||||
}
|
||||
|
||||
impl Commands {
|
||||
impl CommandQueue {
|
||||
pub fn apply(&mut self, world: &mut World) {
|
||||
world.flush();
|
||||
for command in self.commands.drain(..) {
|
||||
command.write(world);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn push(&mut self, command: Box<dyn Command>) {
|
||||
self.commands.push(command);
|
||||
}
|
||||
}
|
||||
|
||||
/// A list of commands that will be run to modify a `World`
|
||||
pub struct Commands<'a> {
|
||||
queue: &'a mut CommandQueue,
|
||||
entities: &'a Entities,
|
||||
current_entity: Option<Entity>,
|
||||
}
|
||||
|
||||
impl<'a> Commands<'a> {
|
||||
pub fn new(queue: &'a mut CommandQueue, world: &'a World) -> Self {
|
||||
Self {
|
||||
queue,
|
||||
entities: world.entities(),
|
||||
current_entity: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new entity with the components contained in `bundle`.
|
||||
///
|
||||
/// Note that `bundle` is a [DynamicBundle], which is a collection of components. [DynamicBundle] is automatically implemented for tuples of components. You can also create your own bundle types by deriving [`derive@Bundle`]. If you would like to spawn an entity with a single component, consider wrapping the component in a tuple (which [DynamicBundle] is implemented for).
|
||||
/// Note that `bundle` is a [Bundle], which is a collection of components. [Bundle] is automatically implemented for tuples of components. You can also create your own bundle types by deriving [`derive@Bundle`]. If you would like to spawn an entity with a single component, consider wrapping the component in a tuple (which [Bundle] is implemented for).
|
||||
///
|
||||
/// See [`Self::set_current_entity`], [`Self::insert`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// use bevy_ecs::prelude::*;
|
||||
///
|
||||
/// struct Component1;
|
||||
/// struct Component2;
|
||||
|
@ -216,7 +67,7 @@ impl Commands {
|
|||
/// b: Component2,
|
||||
/// }
|
||||
///
|
||||
/// fn example_system(commands: &mut Commands) {
|
||||
/// fn example_system(mut commands: Commands) {
|
||||
/// // Create a new entity with a component bundle.
|
||||
/// commands.spawn(ExampleBundle {
|
||||
/// a: Component1,
|
||||
|
@ -228,17 +79,12 @@ impl Commands {
|
|||
/// // Create a new entity with two components.
|
||||
/// commands.spawn((Component1, Component2));
|
||||
/// }
|
||||
///
|
||||
/// # example_system.system();
|
||||
/// ```
|
||||
pub fn spawn(&mut self, bundle: impl DynamicBundle + Send + Sync + 'static) -> &mut Self {
|
||||
let entity = self
|
||||
.entity_reserver
|
||||
.as_ref()
|
||||
.expect("Entity reserver has not been set.")
|
||||
.reserve_entity();
|
||||
pub fn spawn(&mut self, bundle: impl Bundle) -> &mut Self {
|
||||
let entity = self.entities.reserve_entity();
|
||||
self.set_current_entity(entity);
|
||||
self.insert(entity, bundle);
|
||||
self.insert_bundle(entity, bundle);
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -258,55 +104,22 @@ impl Commands {
|
|||
|
||||
/// Inserts a bundle of components into `entity`.
|
||||
///
|
||||
/// See [`World::insert`].
|
||||
pub fn insert(
|
||||
&mut self,
|
||||
entity: Entity,
|
||||
bundle: impl DynamicBundle + Send + Sync + 'static,
|
||||
) -> &mut Self {
|
||||
self.add_command(Insert { entity, bundle })
|
||||
/// See [crate::world::EntityMut::insert_bundle].
|
||||
pub fn insert_bundle(&mut self, entity: Entity, bundle: impl Bundle) -> &mut Self {
|
||||
self.add_command(InsertBundle { entity, bundle })
|
||||
}
|
||||
|
||||
/// Inserts a single component into `entity`.
|
||||
///
|
||||
/// See [`World::insert_one`].
|
||||
pub fn insert_one(&mut self, entity: Entity, component: impl Component) -> &mut Self {
|
||||
self.add_command(InsertOne { entity, component })
|
||||
/// See [crate::world::EntityMut::insert].
|
||||
pub fn insert(&mut self, entity: Entity, component: impl Component) -> &mut Self {
|
||||
self.add_command(Insert { entity, component })
|
||||
}
|
||||
|
||||
pub fn insert_resource<T: Resource>(&mut self, resource: T) -> &mut Self {
|
||||
self.add_command(InsertResource { resource })
|
||||
}
|
||||
|
||||
/// Insert a resource that is local to a specific system.
|
||||
///
|
||||
/// See [`crate::System::id`].
|
||||
pub fn insert_local_resource<T: Resource>(
|
||||
&mut self,
|
||||
system_id: SystemId,
|
||||
resource: T,
|
||||
) -> &mut Self {
|
||||
self.add_command(InsertLocalResource {
|
||||
system_id,
|
||||
resource,
|
||||
})
|
||||
}
|
||||
|
||||
/// See [`World::remove_one`].
|
||||
pub fn remove_one<T>(&mut self, entity: Entity) -> &mut Self
|
||||
where
|
||||
T: Component,
|
||||
{
|
||||
self.add_command(RemoveOne::<T> {
|
||||
entity,
|
||||
phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// See [`World::remove`].
|
||||
/// See [crate::world::EntityMut::remove].
|
||||
pub fn remove<T>(&mut self, entity: Entity) -> &mut Self
|
||||
where
|
||||
T: Bundle + Send + Sync + 'static,
|
||||
T: Component,
|
||||
{
|
||||
self.add_command(Remove::<T> {
|
||||
entity,
|
||||
|
@ -314,7 +127,23 @@ impl Commands {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn remove_resource<T: Resource>(&mut self) -> &mut Self {
|
||||
/// See [World::insert_resource].
|
||||
pub fn insert_resource<T: Component>(&mut self, resource: T) -> &mut Self {
|
||||
self.add_command(InsertResource { resource })
|
||||
}
|
||||
|
||||
/// See [crate::world::EntityMut::remove_bundle].
|
||||
pub fn remove_bundle<T>(&mut self, entity: Entity) -> &mut Self
|
||||
where
|
||||
T: Bundle,
|
||||
{
|
||||
self.add_command(RemoveBundle::<T> {
|
||||
entity,
|
||||
phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn remove_resource<T: Component>(&mut self) -> &mut Self {
|
||||
self.add_command(RemoveResource::<T> {
|
||||
phantom: PhantomData,
|
||||
})
|
||||
|
@ -323,9 +152,9 @@ impl Commands {
|
|||
/// Adds a bundle of components to the current entity.
|
||||
///
|
||||
/// See [`Self::with`], [`Self::current_entity`].
|
||||
pub fn with_bundle(&mut self, bundle: impl DynamicBundle + Send + Sync + 'static) -> &mut Self {
|
||||
pub fn with_bundle(&mut self, bundle: impl Bundle) -> &mut Self {
|
||||
let current_entity = self.current_entity.expect("Cannot add bundle because the 'current entity' is not set. You should spawn an entity first.");
|
||||
self.commands.push(Box::new(Insert {
|
||||
self.queue.push(Box::new(InsertBundle {
|
||||
entity: current_entity,
|
||||
bundle,
|
||||
}));
|
||||
|
@ -345,12 +174,12 @@ impl Commands {
|
|||
/// `with` can be chained with [`Self::spawn`].
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// use bevy_ecs::prelude::*;
|
||||
///
|
||||
/// struct Component1;
|
||||
/// struct Component2;
|
||||
///
|
||||
/// fn example_system(commands: &mut Commands) {
|
||||
/// fn example_system(mut commands: Commands) {
|
||||
/// // Create a new entity with a `Component1` and `Component2`.
|
||||
/// commands.spawn((Component1,)).with(Component2);
|
||||
///
|
||||
|
@ -367,12 +196,11 @@ impl Commands {
|
|||
/// b: Component2,
|
||||
/// });
|
||||
/// }
|
||||
///
|
||||
/// # example_system.system();
|
||||
/// ```
|
||||
pub fn with(&mut self, component: impl Component) -> &mut Self {
|
||||
let current_entity = self.current_entity.expect("Cannot add component because the 'current entity' is not set. You should spawn an entity first.");
|
||||
self.commands.push(Box::new(InsertOne {
|
||||
self.queue.push(Box::new(Insert {
|
||||
entity: current_entity,
|
||||
component,
|
||||
}));
|
||||
|
@ -380,24 +208,17 @@ impl Commands {
|
|||
}
|
||||
|
||||
/// Adds a command directly to the command list. Prefer this to [`Self::add_command_boxed`] if the type of `command` is statically known.
|
||||
pub fn add_command<C: Command + 'static>(&mut self, command: C) -> &mut Self {
|
||||
self.commands.push(Box::new(command));
|
||||
pub fn add_command<C: Command>(&mut self, command: C) -> &mut Self {
|
||||
self.queue.push(Box::new(command));
|
||||
self
|
||||
}
|
||||
|
||||
/// See [`Self::add_command`].
|
||||
pub fn add_command_boxed(&mut self, command: Box<dyn Command>) -> &mut Self {
|
||||
self.commands.push(command);
|
||||
self.queue.push(command);
|
||||
self
|
||||
}
|
||||
|
||||
/// Runs all the stored commands on `world` and `resources`. The command buffer is emptied as a part of this call.
|
||||
pub fn apply(&mut self, world: &mut World, resources: &mut Resources) {
|
||||
for command in self.commands.drain(..) {
|
||||
command.write(world, resources);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the current entity, set by [`Self::spawn`] or with [`Self::set_current_entity`].
|
||||
pub fn current_entity(&self) -> Option<Entity> {
|
||||
self.current_entity
|
||||
|
@ -418,40 +239,169 @@ impl Commands {
|
|||
f(current_entity);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_entity_reserver(&mut self, entity_reserver: EntityReserver) {
|
||||
self.entity_reserver = Some(entity_reserver);
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Spawn<T> {
|
||||
bundle: T,
|
||||
}
|
||||
|
||||
impl<T> Command for Spawn<T>
|
||||
where
|
||||
T: Bundle,
|
||||
{
|
||||
fn write(self: Box<Self>, world: &mut World) {
|
||||
world.spawn().insert_bundle(self.bundle);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct SpawnBatch<I>
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: Bundle,
|
||||
{
|
||||
bundles_iter: I,
|
||||
}
|
||||
|
||||
impl<I> Command for SpawnBatch<I>
|
||||
where
|
||||
I: IntoIterator + Send + Sync + 'static,
|
||||
I::Item: Bundle,
|
||||
{
|
||||
fn write(self: Box<Self>, world: &mut World) {
|
||||
world.spawn_batch(self.bundles_iter);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Despawn {
|
||||
entity: Entity,
|
||||
}
|
||||
|
||||
impl Command for Despawn {
|
||||
fn write(self: Box<Self>, world: &mut World) {
|
||||
if !world.despawn(self.entity) {
|
||||
debug!("Failed to despawn non-existent entity {:?}", self.entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InsertBundle<T> {
|
||||
entity: Entity,
|
||||
bundle: T,
|
||||
}
|
||||
|
||||
impl<T> Command for InsertBundle<T>
|
||||
where
|
||||
T: Bundle + 'static,
|
||||
{
|
||||
fn write(self: Box<Self>, world: &mut World) {
|
||||
world.entity_mut(self.entity).insert_bundle(self.bundle);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Insert<T> {
|
||||
entity: Entity,
|
||||
component: T,
|
||||
}
|
||||
|
||||
impl<T> Command for Insert<T>
|
||||
where
|
||||
T: Component,
|
||||
{
|
||||
fn write(self: Box<Self>, world: &mut World) {
|
||||
world.entity_mut(self.entity).insert(self.component);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Remove<T> {
|
||||
entity: Entity,
|
||||
phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> Command for Remove<T>
|
||||
where
|
||||
T: Component,
|
||||
{
|
||||
fn write(self: Box<Self>, world: &mut World) {
|
||||
if let Some(mut entity_mut) = world.get_entity_mut(self.entity) {
|
||||
entity_mut.remove::<T>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct RemoveBundle<T> {
|
||||
entity: Entity,
|
||||
phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> Command for RemoveBundle<T>
|
||||
where
|
||||
T: Bundle,
|
||||
{
|
||||
fn write(self: Box<Self>, world: &mut World) {
|
||||
if let Some(mut entity_mut) = world.get_entity_mut(self.entity) {
|
||||
// remove intersection to gracefully handle components that were removed before running this command
|
||||
entity_mut.remove_bundle_intersection::<T>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InsertResource<T: Component> {
|
||||
resource: T,
|
||||
}
|
||||
|
||||
impl<T: Component> Command for InsertResource<T> {
|
||||
fn write(self: Box<Self>, world: &mut World) {
|
||||
world.insert_resource(self.resource);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RemoveResource<T: Component> {
|
||||
phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Component> Command for RemoveResource<T> {
|
||||
fn write(self: Box<Self>, world: &mut World) {
|
||||
world.remove_resource::<T>();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::float_cmp, clippy::approx_constant)]
|
||||
mod tests {
|
||||
use crate::{resource::Resources, Commands, World};
|
||||
use core::any::TypeId;
|
||||
use crate::{
|
||||
system::{CommandQueue, Commands},
|
||||
world::World,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn command_buffer() {
|
||||
fn commands() {
|
||||
let mut world = World::default();
|
||||
let mut resources = Resources::default();
|
||||
let mut command_buffer = Commands::default();
|
||||
command_buffer.set_entity_reserver(world.get_entity_reserver());
|
||||
command_buffer.spawn((1u32, 2u64));
|
||||
let entity = command_buffer.current_entity().unwrap();
|
||||
command_buffer.insert_resource(3.14f32);
|
||||
command_buffer.apply(&mut world, &mut resources);
|
||||
let mut command_queue = CommandQueue::default();
|
||||
let entity = Commands::new(&mut command_queue, &world)
|
||||
.spawn((1u32, 2u64))
|
||||
.current_entity()
|
||||
.unwrap();
|
||||
command_queue.apply(&mut world);
|
||||
assert!(world.entities().len() == 1);
|
||||
let results = world
|
||||
.query::<(&u32, &u64)>()
|
||||
.iter(&world)
|
||||
.map(|(a, b)| (*a, *b))
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(results, vec![(1u32, 2u64)]);
|
||||
assert_eq!(*resources.get::<f32>().unwrap(), 3.14f32);
|
||||
// test entity despawn
|
||||
command_buffer.despawn(entity);
|
||||
command_buffer.despawn(entity); // double despawn shouldn't panic
|
||||
command_buffer.apply(&mut world, &mut resources);
|
||||
Commands::new(&mut command_queue, &world)
|
||||
.despawn(entity)
|
||||
.despawn(entity); // double despawn shouldn't panic
|
||||
command_queue.apply(&mut world);
|
||||
let results2 = world
|
||||
.query::<(&u32, &u64)>()
|
||||
.iter(&world)
|
||||
.map(|(a, b)| (*a, *b))
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(results2, vec![]);
|
||||
|
@ -460,58 +410,59 @@ mod tests {
|
|||
#[test]
|
||||
fn remove_components() {
|
||||
let mut world = World::default();
|
||||
let mut resources = Resources::default();
|
||||
let mut command_buffer = Commands::default();
|
||||
command_buffer.set_entity_reserver(world.get_entity_reserver());
|
||||
command_buffer.spawn((1u32, 2u64));
|
||||
let entity = command_buffer.current_entity().unwrap();
|
||||
command_buffer.apply(&mut world, &mut resources);
|
||||
let mut command_queue = CommandQueue::default();
|
||||
let entity = Commands::new(&mut command_queue, &world)
|
||||
.spawn((1u32, 2u64))
|
||||
.current_entity()
|
||||
.unwrap();
|
||||
command_queue.apply(&mut world);
|
||||
let results_before = world
|
||||
.query::<(&u32, &u64)>()
|
||||
.iter(&world)
|
||||
.map(|(a, b)| (*a, *b))
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(results_before, vec![(1u32, 2u64)]);
|
||||
|
||||
// test component removal
|
||||
command_buffer.remove_one::<u32>(entity);
|
||||
command_buffer.remove::<(u32, u64)>(entity);
|
||||
command_buffer.apply(&mut world, &mut resources);
|
||||
Commands::new(&mut command_queue, &world)
|
||||
.remove::<u32>(entity)
|
||||
.remove_bundle::<(u32, u64)>(entity);
|
||||
command_queue.apply(&mut world);
|
||||
let results_after = world
|
||||
.query::<(&u32, &u64)>()
|
||||
.iter(&world)
|
||||
.map(|(a, b)| (*a, *b))
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(results_after, vec![]);
|
||||
let results_after_u64 = world.query::<&u64>().copied().collect::<Vec<_>>();
|
||||
let results_after_u64 = world
|
||||
.query::<&u64>()
|
||||
.iter(&world)
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(results_after_u64, vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_resources() {
|
||||
let mut world = World::default();
|
||||
let mut resources = Resources::default();
|
||||
let mut command_buffer = Commands::default();
|
||||
command_buffer.insert_resource(123);
|
||||
command_buffer.insert_resource(456.0);
|
||||
command_buffer.apply(&mut world, &mut resources);
|
||||
assert_eq!(
|
||||
resources.resource_data.contains_key(&TypeId::of::<i32>()),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
resources.resource_data.contains_key(&TypeId::of::<f64>()),
|
||||
true
|
||||
);
|
||||
let mut queue = CommandQueue::default();
|
||||
{
|
||||
let mut commands = Commands::new(&mut queue, &world);
|
||||
commands.insert_resource(123);
|
||||
commands.insert_resource(456.0);
|
||||
}
|
||||
|
||||
// test resource removal
|
||||
command_buffer.remove_resource::<i32>();
|
||||
command_buffer.apply(&mut world, &mut resources);
|
||||
assert_eq!(
|
||||
resources.resource_data.contains_key(&TypeId::of::<i32>()),
|
||||
false
|
||||
);
|
||||
assert_eq!(
|
||||
resources.resource_data.contains_key(&TypeId::of::<f64>()),
|
||||
true
|
||||
);
|
||||
queue.apply(&mut world);
|
||||
assert!(world.contains_resource::<i32>());
|
||||
assert!(world.contains_resource::<f64>());
|
||||
|
||||
{
|
||||
let mut commands = Commands::new(&mut queue, &world);
|
||||
// test resource removal
|
||||
commands.remove_resource::<i32>();
|
||||
}
|
||||
queue.apply(&mut world);
|
||||
assert!(!world.contains_resource::<i32>());
|
||||
assert!(world.contains_resource::<f64>());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
pub use super::Query;
|
||||
use crate::{resource::Resources, system::SystemId, BoxedSystem, IntoSystem, System, World};
|
||||
use crate::{
|
||||
system::{BoxedSystem, IntoSystem, System, SystemId},
|
||||
world::World,
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub trait ExclusiveSystem: Send + Sync + 'static {
|
||||
|
@ -7,13 +9,13 @@ pub trait ExclusiveSystem: Send + Sync + 'static {
|
|||
|
||||
fn id(&self) -> SystemId;
|
||||
|
||||
fn run(&mut self, world: &mut World, resources: &mut Resources);
|
||||
fn run(&mut self, world: &mut World);
|
||||
|
||||
fn initialize(&mut self, world: &mut World, resources: &mut Resources);
|
||||
fn initialize(&mut self, world: &mut World);
|
||||
}
|
||||
|
||||
pub struct ExclusiveSystemFn {
|
||||
func: Box<dyn FnMut(&mut World, &mut Resources) + Send + Sync + 'static>,
|
||||
func: Box<dyn FnMut(&mut World) + Send + Sync + 'static>,
|
||||
name: Cow<'static, str>,
|
||||
id: SystemId,
|
||||
}
|
||||
|
@ -27,63 +29,24 @@ impl ExclusiveSystem for ExclusiveSystemFn {
|
|||
self.id
|
||||
}
|
||||
|
||||
fn run(&mut self, world: &mut World, resources: &mut Resources) {
|
||||
(self.func)(world, resources);
|
||||
fn run(&mut self, world: &mut World) {
|
||||
(self.func)(world);
|
||||
}
|
||||
|
||||
fn initialize(&mut self, _: &mut World, _: &mut Resources) {}
|
||||
fn initialize(&mut self, _: &mut World) {}
|
||||
}
|
||||
|
||||
pub trait IntoExclusiveSystem<Params, SystemType> {
|
||||
fn exclusive_system(self) -> SystemType;
|
||||
}
|
||||
|
||||
impl<F> IntoExclusiveSystem<(&mut World, &mut Resources), ExclusiveSystemFn> for F
|
||||
where
|
||||
F: FnMut(&mut World, &mut Resources) + Send + Sync + 'static,
|
||||
{
|
||||
fn exclusive_system(self) -> ExclusiveSystemFn {
|
||||
ExclusiveSystemFn {
|
||||
func: Box::new(self),
|
||||
name: core::any::type_name::<F>().into(),
|
||||
id: SystemId::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> IntoExclusiveSystem<(&mut Resources, &mut World), ExclusiveSystemFn> for F
|
||||
where
|
||||
F: FnMut(&mut Resources, &mut World) + Send + Sync + 'static,
|
||||
{
|
||||
fn exclusive_system(mut self) -> ExclusiveSystemFn {
|
||||
ExclusiveSystemFn {
|
||||
func: Box::new(move |world, resources| self(resources, world)),
|
||||
name: core::any::type_name::<F>().into(),
|
||||
id: SystemId::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> IntoExclusiveSystem<&mut World, ExclusiveSystemFn> for F
|
||||
where
|
||||
F: FnMut(&mut World) + Send + Sync + 'static,
|
||||
{
|
||||
fn exclusive_system(mut self) -> ExclusiveSystemFn {
|
||||
fn exclusive_system(self) -> ExclusiveSystemFn {
|
||||
ExclusiveSystemFn {
|
||||
func: Box::new(move |world, _| self(world)),
|
||||
name: core::any::type_name::<F>().into(),
|
||||
id: SystemId::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> IntoExclusiveSystem<&mut Resources, ExclusiveSystemFn> for F
|
||||
where
|
||||
F: FnMut(&mut Resources) + Send + Sync + 'static,
|
||||
{
|
||||
fn exclusive_system(mut self) -> ExclusiveSystemFn {
|
||||
ExclusiveSystemFn {
|
||||
func: Box::new(move |_, resources| self(resources)),
|
||||
func: Box::new(self),
|
||||
name: core::any::type_name::<F>().into(),
|
||||
id: SystemId::new(),
|
||||
}
|
||||
|
@ -103,13 +66,13 @@ impl ExclusiveSystem for ExclusiveSystemCoerced {
|
|||
self.system.id()
|
||||
}
|
||||
|
||||
fn run(&mut self, world: &mut World, resources: &mut Resources) {
|
||||
self.system.run((), world, resources);
|
||||
self.system.apply_buffers(world, resources);
|
||||
fn run(&mut self, world: &mut World) {
|
||||
self.system.run((), world);
|
||||
self.system.apply_buffers(world);
|
||||
}
|
||||
|
||||
fn initialize(&mut self, world: &mut World, resources: &mut Resources) {
|
||||
self.system.initialize(world, resources);
|
||||
fn initialize(&mut self, world: &mut World) {
|
||||
self.system.initialize(world);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,37 +88,42 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parallel_with_commands_as_exclusive() {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
Commands, Entity, IntoExclusiveSystem, IntoSystem, ResMut, Resources, Stage, SystemStage,
|
||||
With, World,
|
||||
entity::Entity,
|
||||
query::With,
|
||||
schedule::{Stage, SystemStage},
|
||||
system::{Commands, IntoExclusiveSystem, IntoSystem, Query, ResMut},
|
||||
world::World,
|
||||
};
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
#[test]
|
||||
fn parallel_with_commands_as_exclusive() {
|
||||
let mut world = World::new();
|
||||
|
||||
fn removal(
|
||||
commands: &mut Commands,
|
||||
query: Query<Entity, With<f32>>,
|
||||
mut counter: ResMut<usize>,
|
||||
) {
|
||||
for entity in query.iter() {
|
||||
*counter += 1;
|
||||
commands.remove_one::<f32>(entity);
|
||||
fn removal(
|
||||
mut commands: Commands,
|
||||
query: Query<Entity, With<f32>>,
|
||||
mut counter: ResMut<usize>,
|
||||
) {
|
||||
for entity in query.iter() {
|
||||
*counter += 1;
|
||||
commands.remove::<f32>(entity);
|
||||
}
|
||||
}
|
||||
|
||||
let mut stage = SystemStage::parallel().with_system(removal.system());
|
||||
world.spawn().insert(0.0f32);
|
||||
world.insert_resource(0usize);
|
||||
stage.run(&mut world);
|
||||
stage.run(&mut world);
|
||||
assert_eq!(*world.get_resource::<usize>().unwrap(), 1);
|
||||
|
||||
let mut stage = SystemStage::parallel().with_system(removal.exclusive_system());
|
||||
world.spawn().insert(0.0f32);
|
||||
world.insert_resource(0usize);
|
||||
stage.run(&mut world);
|
||||
stage.run(&mut world);
|
||||
assert_eq!(*world.get_resource::<usize>().unwrap(), 1);
|
||||
}
|
||||
|
||||
let mut stage = SystemStage::parallel().with_system(removal.system());
|
||||
world.spawn((0.0f32,));
|
||||
resources.insert(0usize);
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world, &mut resources);
|
||||
assert_eq!(*resources.get::<usize>().unwrap(), 1);
|
||||
|
||||
let mut stage = SystemStage::parallel().with_system(removal.exclusive_system());
|
||||
world.spawn((0.0f32,));
|
||||
resources.insert(0usize);
|
||||
stage.run(&mut world, &mut resources);
|
||||
stage.run(&mut world, &mut resources);
|
||||
assert_eq!(*resources.get::<usize>().unwrap(), 1);
|
||||
}
|
||||
|
|
|
@ -1,216 +1,45 @@
|
|||
use super::system_param::FetchSystemParam;
|
||||
use crate::{
|
||||
ArchetypeComponent, Commands, QueryAccess, Resources, System, SystemId, SystemParam,
|
||||
TypeAccess, World,
|
||||
archetype::{Archetype, ArchetypeComponentId},
|
||||
component::ComponentId,
|
||||
query::{Access, FilteredAccessSet},
|
||||
system::{System, SystemId, SystemParam, SystemParamFetch, SystemParamState},
|
||||
world::World,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use std::{any::TypeId, borrow::Cow, cell::UnsafeCell, sync::Arc};
|
||||
use bevy_ecs_macros::all_tuples;
|
||||
use std::{borrow::Cow, marker::PhantomData};
|
||||
|
||||
pub struct SystemState {
|
||||
pub(crate) id: SystemId,
|
||||
pub(crate) name: Cow<'static, str>,
|
||||
pub(crate) archetype_component_access: TypeAccess<ArchetypeComponent>,
|
||||
pub(crate) component_access: TypeAccess<TypeId>,
|
||||
pub(crate) resource_access: TypeAccess<TypeId>,
|
||||
pub(crate) is_non_send: bool,
|
||||
pub(crate) local_resource_access: TypeAccess<TypeId>,
|
||||
pub(crate) query_archetype_component_accesses: Vec<TypeAccess<ArchetypeComponent>>,
|
||||
pub(crate) query_accesses: Vec<Vec<QueryAccess>>,
|
||||
pub(crate) query_type_names: Vec<&'static str>,
|
||||
pub(crate) commands: UnsafeCell<Commands>,
|
||||
pub(crate) arc_commands: Option<Arc<Mutex<Commands>>>,
|
||||
pub(crate) current_query_index: UnsafeCell<usize>,
|
||||
pub(crate) component_access_set: FilteredAccessSet<ComponentId>,
|
||||
pub(crate) archetype_component_access: Access<ArchetypeComponentId>,
|
||||
// NOTE: this must be kept private. making a SystemState non-send is irreversible to prevent SystemParams from overriding each other
|
||||
is_send: bool,
|
||||
}
|
||||
|
||||
// SAFE: UnsafeCell<Commands> and UnsafeCell<usize> only accessed from the thread they are scheduled on
|
||||
unsafe impl Sync for SystemState {}
|
||||
|
||||
impl SystemState {
|
||||
pub fn reset_indices(&mut self) {
|
||||
// SAFE: done with unique mutable access to Self
|
||||
unsafe {
|
||||
*self.current_query_index.get() = 0;
|
||||
fn new<T>() -> Self {
|
||||
Self {
|
||||
name: std::any::type_name::<T>().into(),
|
||||
archetype_component_access: Access::default(),
|
||||
component_access_set: FilteredAccessSet::default(),
|
||||
is_send: true,
|
||||
id: SystemId::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, world: &World) {
|
||||
self.archetype_component_access.clear();
|
||||
let mut conflict_index = None;
|
||||
let mut conflict_name = None;
|
||||
for (i, (query_accesses, component_access)) in self
|
||||
.query_accesses
|
||||
.iter()
|
||||
.zip(self.query_archetype_component_accesses.iter_mut())
|
||||
.enumerate()
|
||||
{
|
||||
component_access.clear();
|
||||
for query_access in query_accesses.iter() {
|
||||
query_access.get_world_archetype_access(world, Some(component_access));
|
||||
}
|
||||
if !component_access.is_compatible(&self.archetype_component_access) {
|
||||
conflict_index = Some(i);
|
||||
conflict_name = component_access
|
||||
.get_conflict(&self.archetype_component_access)
|
||||
.and_then(|archetype_component| {
|
||||
query_accesses
|
||||
.iter()
|
||||
.filter_map(|query_access| {
|
||||
query_access.get_type_name(archetype_component.component)
|
||||
})
|
||||
.next()
|
||||
});
|
||||
break;
|
||||
}
|
||||
self.archetype_component_access.extend(component_access);
|
||||
}
|
||||
if let Some(conflict_index) = conflict_index {
|
||||
let mut conflicts_with_index = None;
|
||||
for prior_index in 0..conflict_index {
|
||||
if !self.query_archetype_component_accesses[conflict_index]
|
||||
.is_compatible(&self.query_archetype_component_accesses[prior_index])
|
||||
{
|
||||
conflicts_with_index = Some(prior_index);
|
||||
}
|
||||
}
|
||||
panic!("System {} has conflicting queries. {} conflicts with the component access [{}] in this prior query: {}.",
|
||||
self.name,
|
||||
self.query_type_names[conflict_index],
|
||||
conflict_name.unwrap_or("Unknown"),
|
||||
conflicts_with_index.map(|index| self.query_type_names[index]).unwrap_or("Unknown"));
|
||||
}
|
||||
#[inline]
|
||||
pub fn is_send(&self) -> bool {
|
||||
self.is_send
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_non_send(&mut self) {
|
||||
self.is_send = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FuncSystem<Out> {
|
||||
func:
|
||||
Box<dyn FnMut(&mut SystemState, &World, &Resources) -> Option<Out> + Send + Sync + 'static>,
|
||||
init_func: Box<dyn FnMut(&mut SystemState, &World, &mut Resources) + Send + Sync + 'static>,
|
||||
state: SystemState,
|
||||
}
|
||||
|
||||
impl<Out: 'static> System for FuncSystem<Out> {
|
||||
type In = ();
|
||||
type Out = Out;
|
||||
|
||||
fn name(&self) -> std::borrow::Cow<'static, str> {
|
||||
self.state.name.clone()
|
||||
}
|
||||
|
||||
fn id(&self) -> SystemId {
|
||||
self.state.id
|
||||
}
|
||||
|
||||
fn update_access(&mut self, world: &World) {
|
||||
self.state.update(world);
|
||||
}
|
||||
|
||||
fn archetype_component_access(&self) -> &TypeAccess<ArchetypeComponent> {
|
||||
&self.state.archetype_component_access
|
||||
}
|
||||
|
||||
fn component_access(&self) -> &TypeAccess<TypeId> {
|
||||
&self.state.component_access
|
||||
}
|
||||
|
||||
fn resource_access(&self) -> &TypeAccess<std::any::TypeId> {
|
||||
&self.state.resource_access
|
||||
}
|
||||
|
||||
fn is_non_send(&self) -> bool {
|
||||
self.state.is_non_send
|
||||
}
|
||||
|
||||
unsafe fn run_unsafe(
|
||||
&mut self,
|
||||
_input: Self::In,
|
||||
world: &World,
|
||||
resources: &Resources,
|
||||
) -> Option<Out> {
|
||||
(self.func)(&mut self.state, world, resources)
|
||||
}
|
||||
|
||||
fn apply_buffers(&mut self, world: &mut World, resources: &mut Resources) {
|
||||
// SAFE: this is called with unique access to SystemState
|
||||
unsafe {
|
||||
(&mut *self.state.commands.get()).apply(world, resources);
|
||||
}
|
||||
if let Some(ref commands) = self.state.arc_commands {
|
||||
let mut commands = commands.lock();
|
||||
commands.apply(world, resources);
|
||||
}
|
||||
}
|
||||
|
||||
fn initialize(&mut self, world: &mut World, resources: &mut Resources) {
|
||||
(self.init_func)(&mut self.state, world, resources);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InputFuncSystem<In, Out> {
|
||||
func: Box<
|
||||
dyn FnMut(In, &mut SystemState, &World, &Resources) -> Option<Out> + Send + Sync + 'static,
|
||||
>,
|
||||
init_func: Box<dyn FnMut(&mut SystemState, &World, &mut Resources) + Send + Sync + 'static>,
|
||||
state: SystemState,
|
||||
}
|
||||
|
||||
impl<In: 'static, Out: 'static> System for InputFuncSystem<In, Out> {
|
||||
type In = In;
|
||||
type Out = Out;
|
||||
|
||||
fn name(&self) -> std::borrow::Cow<'static, str> {
|
||||
self.state.name.clone()
|
||||
}
|
||||
|
||||
fn id(&self) -> SystemId {
|
||||
self.state.id
|
||||
}
|
||||
|
||||
fn update_access(&mut self, world: &World) {
|
||||
self.state.update(world);
|
||||
}
|
||||
|
||||
fn archetype_component_access(&self) -> &TypeAccess<ArchetypeComponent> {
|
||||
&self.state.archetype_component_access
|
||||
}
|
||||
|
||||
fn component_access(&self) -> &TypeAccess<TypeId> {
|
||||
&self.state.component_access
|
||||
}
|
||||
|
||||
fn resource_access(&self) -> &TypeAccess<std::any::TypeId> {
|
||||
&self.state.resource_access
|
||||
}
|
||||
|
||||
fn is_non_send(&self) -> bool {
|
||||
self.state.is_non_send
|
||||
}
|
||||
|
||||
unsafe fn run_unsafe(
|
||||
&mut self,
|
||||
input: In,
|
||||
world: &World,
|
||||
resources: &Resources,
|
||||
) -> Option<Out> {
|
||||
(self.func)(input, &mut self.state, world, resources)
|
||||
}
|
||||
|
||||
fn apply_buffers(&mut self, world: &mut World, resources: &mut Resources) {
|
||||
// SAFE: this is called with unique access to SystemState
|
||||
unsafe {
|
||||
(&mut *self.state.commands.get()).apply(world, resources);
|
||||
}
|
||||
if let Some(ref commands) = self.state.arc_commands {
|
||||
let mut commands = commands.lock();
|
||||
commands.apply(world, resources);
|
||||
}
|
||||
}
|
||||
|
||||
fn initialize(&mut self, world: &mut World, resources: &mut Resources) {
|
||||
(self.init_func)(&mut self.state, world, resources);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoSystem<Params, SystemType> {
|
||||
pub trait IntoSystem<Params, SystemType: System> {
|
||||
fn system(self) -> SystemType;
|
||||
}
|
||||
|
||||
|
@ -220,380 +49,163 @@ impl<Sys: System> IntoSystem<(), Sys> for Sys {
|
|||
self
|
||||
}
|
||||
}
|
||||
pub struct In<In>(pub In);
|
||||
|
||||
macro_rules! impl_into_system {
|
||||
pub struct In<In>(pub In);
|
||||
pub struct InputMarker;
|
||||
|
||||
pub struct FunctionSystem<In, Out, Param, Marker, F>
|
||||
where
|
||||
Param: SystemParam,
|
||||
{
|
||||
func: F,
|
||||
param_state: Option<Param::Fetch>,
|
||||
system_state: SystemState,
|
||||
config: Option<<Param::Fetch as SystemParamState>::Config>,
|
||||
// NOTE: PhantomData<fn()-> T> gives this safe Send/Sync impls
|
||||
marker: PhantomData<fn() -> (In, Out, Marker)>,
|
||||
}
|
||||
|
||||
impl<In, Out, Param: SystemParam, Marker, F> FunctionSystem<In, Out, Param, Marker, F> {
|
||||
pub fn config(
|
||||
mut self,
|
||||
f: impl FnOnce(&mut <Param::Fetch as SystemParamState>::Config),
|
||||
) -> Self {
|
||||
f(self.config.as_mut().unwrap());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<In, Out, Param, Marker, F> IntoSystem<Param, FunctionSystem<In, Out, Param, Marker, F>> for F
|
||||
where
|
||||
In: 'static,
|
||||
Out: 'static,
|
||||
Param: SystemParam + 'static,
|
||||
Marker: 'static,
|
||||
F: SystemParamFunction<In, Out, Param, Marker> + Send + Sync + 'static,
|
||||
{
|
||||
fn system(self) -> FunctionSystem<In, Out, Param, Marker, F> {
|
||||
FunctionSystem {
|
||||
func: self,
|
||||
param_state: None,
|
||||
config: Some(Default::default()),
|
||||
system_state: SystemState::new::<F>(),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<In, Out, Param, Marker, F> System for FunctionSystem<In, Out, Param, Marker, F>
|
||||
where
|
||||
In: 'static,
|
||||
Out: 'static,
|
||||
Param: SystemParam + 'static,
|
||||
Marker: 'static,
|
||||
F: SystemParamFunction<In, Out, Param, Marker> + Send + Sync + 'static,
|
||||
{
|
||||
type In = In;
|
||||
type Out = Out;
|
||||
|
||||
#[inline]
|
||||
fn name(&self) -> Cow<'static, str> {
|
||||
self.system_state.name.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn id(&self) -> SystemId {
|
||||
self.system_state.id
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn new_archetype(&mut self, archetype: &Archetype) {
|
||||
let param_state = self.param_state.as_mut().unwrap();
|
||||
param_state.new_archetype(archetype, &mut self.system_state);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn component_access(&self) -> &Access<ComponentId> {
|
||||
&self.system_state.component_access_set.combined_access()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn archetype_component_access(&self) -> &Access<ArchetypeComponentId> {
|
||||
&self.system_state.archetype_component_access
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_send(&self) -> bool {
|
||||
self.system_state.is_send
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn run_unsafe(&mut self, input: Self::In, world: &World) -> Self::Out {
|
||||
self.func.run(
|
||||
input,
|
||||
self.param_state.as_mut().unwrap(),
|
||||
&self.system_state,
|
||||
world,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn apply_buffers(&mut self, world: &mut World) {
|
||||
let param_state = self.param_state.as_mut().unwrap();
|
||||
param_state.apply(world);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn initialize(&mut self, world: &mut World) {
|
||||
self.param_state = Some(<Param::Fetch as SystemParamState>::init(
|
||||
world,
|
||||
&mut self.system_state,
|
||||
self.config.take().unwrap(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SystemParamFunction<In, Out, Param: SystemParam, Marker>: Send + Sync + 'static {
|
||||
fn run(
|
||||
&mut self,
|
||||
input: In,
|
||||
state: &mut Param::Fetch,
|
||||
system_state: &SystemState,
|
||||
world: &World,
|
||||
) -> Out;
|
||||
}
|
||||
|
||||
macro_rules! impl_system_function {
|
||||
($($param: ident),*) => {
|
||||
impl<Func, Out, $($param: SystemParam),*> IntoSystem<($($param,)*), FuncSystem<Out>> for Func
|
||||
#[allow(non_snake_case)]
|
||||
impl<Out, Func, $($param: SystemParam),*> SystemParamFunction<(), Out, ($($param,)*), ()> for Func
|
||||
where
|
||||
Func:
|
||||
FnMut($($param),*) -> Out +
|
||||
FnMut($(<<$param as SystemParam>::Fetch as FetchSystemParam>::Item),*) -> Out +
|
||||
Send + Sync + 'static, Out: 'static
|
||||
FnMut($(<<$param as SystemParam>::Fetch as SystemParamFetch>::Item),*) -> Out + Send + Sync + 'static, Out: 'static
|
||||
{
|
||||
#[allow(unused_variables)]
|
||||
#[allow(unused_unsafe)]
|
||||
#[allow(non_snake_case)]
|
||||
fn system(mut self) -> FuncSystem<Out> {
|
||||
FuncSystem {
|
||||
state: SystemState {
|
||||
name: std::any::type_name::<Self>().into(),
|
||||
archetype_component_access: TypeAccess::default(),
|
||||
component_access: TypeAccess::default(),
|
||||
resource_access: TypeAccess::default(),
|
||||
is_non_send: false,
|
||||
local_resource_access: TypeAccess::default(),
|
||||
id: SystemId::new(),
|
||||
commands: Default::default(),
|
||||
arc_commands: Default::default(),
|
||||
current_query_index: Default::default(),
|
||||
query_archetype_component_accesses: Vec::new(),
|
||||
query_accesses: Vec::new(),
|
||||
query_type_names: Vec::new(),
|
||||
},
|
||||
func: Box::new(move |state, world, resources| {
|
||||
state.reset_indices();
|
||||
// let mut input = Some(input);
|
||||
unsafe {
|
||||
if let Some(($($param,)*)) = <<($($param,)*) as SystemParam>::Fetch as FetchSystemParam>::get_param(state, world, resources) {
|
||||
Some(self($($param),*))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}),
|
||||
init_func: Box::new(|state, world, resources| {
|
||||
<<($($param,)*) as SystemParam>::Fetch as FetchSystemParam>::init(state, world, resources)
|
||||
}),
|
||||
#[inline]
|
||||
fn run(&mut self, _input: (), state: &mut <($($param,)*) as SystemParam>::Fetch, system_state: &SystemState, world: &World) -> Out {
|
||||
unsafe {
|
||||
let ($($param,)*) = <<($($param,)*) as SystemParam>::Fetch as SystemParamFetch>::get_param(state, system_state, world);
|
||||
self($($param),*)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<Func, Input, Out, $($param: SystemParam),*> IntoSystem<(Input, $($param,)*), InputFuncSystem<Input, Out>> for Func
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
impl<Input, Out, Func, $($param: SystemParam),*> SystemParamFunction<Input, Out, ($($param,)*), InputMarker> for Func
|
||||
where
|
||||
Func:
|
||||
FnMut(In<Input>, $($param),*) -> Out +
|
||||
FnMut(In<Input>, $(<<$param as SystemParam>::Fetch as FetchSystemParam>::Item),*) -> Out +
|
||||
Send + Sync + 'static, Input: 'static, Out: 'static
|
||||
FnMut(In<Input>, $(<<$param as SystemParam>::Fetch as SystemParamFetch>::Item),*) -> Out + Send + Sync + 'static, Out: 'static
|
||||
{
|
||||
#[allow(unused_variables)]
|
||||
#[allow(unused_unsafe)]
|
||||
#[allow(non_snake_case)]
|
||||
fn system(mut self) -> InputFuncSystem<Input, Out> {
|
||||
InputFuncSystem {
|
||||
state: SystemState {
|
||||
name: std::any::type_name::<Self>().into(),
|
||||
archetype_component_access: TypeAccess::default(),
|
||||
component_access: TypeAccess::default(),
|
||||
resource_access: TypeAccess::default(),
|
||||
is_non_send: false,
|
||||
local_resource_access: TypeAccess::default(),
|
||||
id: SystemId::new(),
|
||||
commands: Default::default(),
|
||||
arc_commands: Default::default(),
|
||||
current_query_index: Default::default(),
|
||||
query_archetype_component_accesses: Vec::new(),
|
||||
query_accesses: Vec::new(),
|
||||
query_type_names: Vec::new(),
|
||||
},
|
||||
func: Box::new(move |input, state, world, resources| {
|
||||
state.reset_indices();
|
||||
// let mut input = Some(input);
|
||||
unsafe {
|
||||
if let Some(($($param,)*)) = <<($($param,)*) as SystemParam>::Fetch as FetchSystemParam>::get_param(state, world, resources) {
|
||||
Some(self(In(input), $($param),*))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}),
|
||||
init_func: Box::new(|state, world, resources| {
|
||||
<<($($param,)*) as SystemParam>::Fetch as FetchSystemParam>::init(state, world, resources)
|
||||
}),
|
||||
#[inline]
|
||||
fn run(&mut self, input: Input, state: &mut <($($param,)*) as SystemParam>::Fetch, system_state: &SystemState, world: &World) -> Out {
|
||||
unsafe {
|
||||
let ($($param,)*) = <<($($param,)*) as SystemParam>::Fetch as SystemParamFetch>::get_param(state, system_state, world);
|
||||
self(In(input), $($param),*)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_into_system!();
|
||||
impl_into_system!(A);
|
||||
impl_into_system!(A, B);
|
||||
impl_into_system!(A, B, C);
|
||||
impl_into_system!(A, B, C, D);
|
||||
impl_into_system!(A, B, C, D, E);
|
||||
impl_into_system!(A, B, C, D, E, F);
|
||||
impl_into_system!(A, B, C, D, E, F, G);
|
||||
impl_into_system!(A, B, C, D, E, F, G, H);
|
||||
impl_into_system!(A, B, C, D, E, F, G, H, I);
|
||||
impl_into_system!(A, B, C, D, E, F, G, H, I, J);
|
||||
impl_into_system!(A, B, C, D, E, F, G, H, I, J, K);
|
||||
impl_into_system!(A, B, C, D, E, F, G, H, I, J, K, L);
|
||||
impl_into_system!(A, B, C, D, E, F, G, H, I, J, K, L, M);
|
||||
impl_into_system!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
|
||||
impl_into_system!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
|
||||
impl_into_system!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::IntoSystem;
|
||||
use crate::{
|
||||
clear_trackers_system,
|
||||
resource::{Res, ResMut, Resources},
|
||||
schedule::Schedule,
|
||||
Entity, IntoExclusiveSystem, Local, Query, QuerySet, Stage, System, SystemStage, With,
|
||||
World,
|
||||
};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Default)]
|
||||
struct A;
|
||||
struct B;
|
||||
struct C;
|
||||
struct D;
|
||||
|
||||
#[test]
|
||||
fn query_system_gets() {
|
||||
fn query_system(
|
||||
mut ran: ResMut<bool>,
|
||||
entity_query: Query<Entity, With<A>>,
|
||||
b_query: Query<&B>,
|
||||
a_c_query: Query<(&A, &C)>,
|
||||
d_query: Query<&D>,
|
||||
) {
|
||||
let entities = entity_query.iter().collect::<Vec<Entity>>();
|
||||
assert!(
|
||||
b_query.get_component::<B>(entities[0]).is_err(),
|
||||
"entity 0 should not have B"
|
||||
);
|
||||
assert!(
|
||||
b_query.get_component::<B>(entities[1]).is_ok(),
|
||||
"entity 1 should have B"
|
||||
);
|
||||
assert!(
|
||||
b_query.get_component::<A>(entities[1]).is_err(),
|
||||
"entity 1 should have A, but b_query shouldn't have access to it"
|
||||
);
|
||||
assert!(
|
||||
b_query.get_component::<D>(entities[3]).is_err(),
|
||||
"entity 3 should have D, but it shouldn't be accessible from b_query"
|
||||
);
|
||||
assert!(
|
||||
b_query.get_component::<C>(entities[2]).is_err(),
|
||||
"entity 2 has C, but it shouldn't be accessible from b_query"
|
||||
);
|
||||
assert!(
|
||||
a_c_query.get_component::<C>(entities[2]).is_ok(),
|
||||
"entity 2 has C, and it should be accessible from a_c_query"
|
||||
);
|
||||
assert!(
|
||||
a_c_query.get_component::<D>(entities[3]).is_err(),
|
||||
"entity 3 should have D, but it shouldn't be accessible from b_query"
|
||||
);
|
||||
assert!(
|
||||
d_query.get_component::<D>(entities[3]).is_ok(),
|
||||
"entity 3 should have D"
|
||||
);
|
||||
|
||||
*ran = true;
|
||||
}
|
||||
|
||||
let mut world = World::default();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(false);
|
||||
world.spawn((A,));
|
||||
world.spawn((A, B));
|
||||
world.spawn((A, C));
|
||||
world.spawn((A, D));
|
||||
|
||||
run_system(&mut world, &mut resources, query_system.system());
|
||||
|
||||
assert!(*resources.get::<bool>().unwrap(), "system ran");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn or_query_set_system() {
|
||||
// Regression test for issue #762
|
||||
use crate::{Added, Changed, Mutated, Or};
|
||||
fn query_system(
|
||||
mut ran: ResMut<bool>,
|
||||
set: QuerySet<(
|
||||
Query<(), Or<(Changed<A>, Changed<B>)>>,
|
||||
Query<(), Or<(Added<A>, Added<B>)>>,
|
||||
Query<(), Or<(Mutated<A>, Mutated<B>)>>,
|
||||
)>,
|
||||
) {
|
||||
let changed = set.q0().iter().count();
|
||||
let added = set.q1().iter().count();
|
||||
let mutated = set.q2().iter().count();
|
||||
|
||||
assert_eq!(changed, 1);
|
||||
assert_eq!(added, 1);
|
||||
assert_eq!(mutated, 0);
|
||||
|
||||
*ran = true;
|
||||
}
|
||||
|
||||
let mut world = World::default();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(false);
|
||||
world.spawn((A, B));
|
||||
|
||||
run_system(&mut world, &mut resources, query_system.system());
|
||||
|
||||
assert!(*resources.get::<bool>().unwrap(), "system ran");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn changed_resource_system() {
|
||||
fn incr_e_on_flip(run_on_flip: Res<bool>, mut query: Query<&mut i32>) {
|
||||
if !Res::changed(&run_on_flip) {
|
||||
return;
|
||||
}
|
||||
|
||||
for mut i in query.iter_mut() {
|
||||
*i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let mut world = World::default();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(false);
|
||||
let ent = world.spawn((0,));
|
||||
|
||||
let mut schedule = Schedule::default();
|
||||
let mut update = SystemStage::parallel();
|
||||
update.add_system(incr_e_on_flip.system());
|
||||
schedule.add_stage("update", update);
|
||||
schedule.add_stage(
|
||||
"clear_trackers",
|
||||
SystemStage::single(clear_trackers_system.exclusive_system()),
|
||||
);
|
||||
|
||||
schedule.run(&mut world, &mut resources);
|
||||
assert_eq!(*(world.get::<i32>(ent).unwrap()), 1);
|
||||
|
||||
schedule.run(&mut world, &mut resources);
|
||||
assert_eq!(*(world.get::<i32>(ent).unwrap()), 1);
|
||||
|
||||
*resources.get_mut::<bool>().unwrap() = true;
|
||||
schedule.run(&mut world, &mut resources);
|
||||
assert_eq!(*(world.get::<i32>(ent).unwrap()), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn conflicting_query_mut_system() {
|
||||
fn sys(_q1: Query<&mut A>, _q2: Query<&mut A>) {}
|
||||
|
||||
let mut world = World::default();
|
||||
let mut resources = Resources::default();
|
||||
world.spawn((A,));
|
||||
|
||||
run_system(&mut world, &mut resources, sys.system());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn conflicting_query_immut_system() {
|
||||
fn sys(_q1: Query<&A>, _q2: Query<&mut A>) {}
|
||||
|
||||
let mut world = World::default();
|
||||
let mut resources = Resources::default();
|
||||
world.spawn((A,));
|
||||
|
||||
run_system(&mut world, &mut resources, sys.system());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn query_set_system() {
|
||||
fn sys(_set: QuerySet<(Query<&mut A>, Query<&B>)>) {}
|
||||
|
||||
let mut world = World::default();
|
||||
let mut resources = Resources::default();
|
||||
world.spawn((A,));
|
||||
|
||||
run_system(&mut world, &mut resources, sys.system());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn conflicting_query_with_query_set_system() {
|
||||
fn sys(_query: Query<&mut A>, _set: QuerySet<(Query<&mut A>, Query<&B>)>) {}
|
||||
|
||||
let mut world = World::default();
|
||||
let mut resources = Resources::default();
|
||||
world.spawn((A,));
|
||||
|
||||
run_system(&mut world, &mut resources, sys.system());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn conflicting_query_sets_system() {
|
||||
fn sys(_set_1: QuerySet<(Query<&mut A>,)>, _set_2: QuerySet<(Query<&mut A>, Query<&B>)>) {}
|
||||
|
||||
let mut world = World::default();
|
||||
let mut resources = Resources::default();
|
||||
world.spawn((A,));
|
||||
run_system(&mut world, &mut resources, sys.system());
|
||||
}
|
||||
|
||||
fn run_system<S: System<In = (), Out = ()>>(
|
||||
world: &mut World,
|
||||
resources: &mut Resources,
|
||||
system: S,
|
||||
) {
|
||||
let mut schedule = Schedule::default();
|
||||
let mut update = SystemStage::parallel();
|
||||
update.add_system(system);
|
||||
schedule.add_stage("update", update);
|
||||
schedule.run(world, resources);
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct BufferRes {
|
||||
_buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
fn test_for_conflicting_resources<S: System<In = (), Out = ()>>(sys: S) {
|
||||
let mut world = World::default();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(BufferRes::default());
|
||||
resources.insert(A);
|
||||
resources.insert(B);
|
||||
run_system(&mut world, &mut resources, sys);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn conflicting_system_resources() {
|
||||
fn sys(_: ResMut<BufferRes>, _: Res<BufferRes>) {}
|
||||
test_for_conflicting_resources(sys.system())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn conflicting_system_resources_reverse_order() {
|
||||
fn sys(_: Res<BufferRes>, _: ResMut<BufferRes>) {}
|
||||
test_for_conflicting_resources(sys.system())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn conflicting_system_resources_multiple_mutable() {
|
||||
fn sys(_: ResMut<BufferRes>, _: ResMut<BufferRes>) {}
|
||||
test_for_conflicting_resources(sys.system())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn conflicting_system_local_resources() {
|
||||
fn sys(_: Local<BufferRes>, _: Local<BufferRes>) {}
|
||||
test_for_conflicting_resources(sys.system())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nonconflicting_system_resources() {
|
||||
fn sys(_: Local<BufferRes>, _: ResMut<BufferRes>, _: Local<A>, _: ResMut<A>) {}
|
||||
test_for_conflicting_resources(sys.system())
|
||||
}
|
||||
}
|
||||
all_tuples!(impl_system_function, 0, 12, F);
|
||||
|
|
|
@ -14,3 +14,405 @@ pub use query::*;
|
|||
pub use system::*;
|
||||
pub use system_chaining::*;
|
||||
pub use system_param::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
archetype::Archetypes,
|
||||
bundle::Bundles,
|
||||
component::Components,
|
||||
entity::{Entities, Entity},
|
||||
query::{Added, Changed, Mutated, Or, With, Without},
|
||||
schedule::{Schedule, Stage, SystemStage},
|
||||
system::{
|
||||
IntoExclusiveSystem, IntoSystem, Local, Query, QuerySet, RemovedComponents, Res,
|
||||
ResMut, System,
|
||||
},
|
||||
world::{FromWorld, World},
|
||||
};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Default)]
|
||||
struct A;
|
||||
struct B;
|
||||
struct C;
|
||||
struct D;
|
||||
|
||||
#[test]
|
||||
fn simple_system() {
|
||||
fn sys(query: Query<&A>) {
|
||||
for a in query.iter() {
|
||||
println!("{:?}", a);
|
||||
}
|
||||
}
|
||||
|
||||
let mut system = sys.system();
|
||||
let mut world = World::new();
|
||||
world.spawn().insert(A);
|
||||
|
||||
system.initialize(&mut world);
|
||||
for archetype in world.archetypes.iter() {
|
||||
system.new_archetype(archetype);
|
||||
}
|
||||
system.run((), &mut world);
|
||||
}
|
||||
|
||||
fn run_system<S: System<In = (), Out = ()>>(world: &mut World, system: S) {
|
||||
let mut schedule = Schedule::default();
|
||||
let mut update = SystemStage::parallel();
|
||||
update.add_system(system);
|
||||
schedule.add_stage("update", update);
|
||||
schedule.run(world);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn query_system_gets() {
|
||||
fn query_system(
|
||||
mut ran: ResMut<bool>,
|
||||
entity_query: Query<Entity, With<A>>,
|
||||
b_query: Query<&B>,
|
||||
a_c_query: Query<(&A, &C)>,
|
||||
d_query: Query<&D>,
|
||||
) {
|
||||
let entities = entity_query.iter().collect::<Vec<Entity>>();
|
||||
assert!(
|
||||
b_query.get_component::<B>(entities[0]).is_err(),
|
||||
"entity 0 should not have B"
|
||||
);
|
||||
assert!(
|
||||
b_query.get_component::<B>(entities[1]).is_ok(),
|
||||
"entity 1 should have B"
|
||||
);
|
||||
assert!(
|
||||
b_query.get_component::<A>(entities[1]).is_err(),
|
||||
"entity 1 should have A, but b_query shouldn't have access to it"
|
||||
);
|
||||
assert!(
|
||||
b_query.get_component::<D>(entities[3]).is_err(),
|
||||
"entity 3 should have D, but it shouldn't be accessible from b_query"
|
||||
);
|
||||
assert!(
|
||||
b_query.get_component::<C>(entities[2]).is_err(),
|
||||
"entity 2 has C, but it shouldn't be accessible from b_query"
|
||||
);
|
||||
assert!(
|
||||
a_c_query.get_component::<C>(entities[2]).is_ok(),
|
||||
"entity 2 has C, and it should be accessible from a_c_query"
|
||||
);
|
||||
assert!(
|
||||
a_c_query.get_component::<D>(entities[3]).is_err(),
|
||||
"entity 3 should have D, but it shouldn't be accessible from b_query"
|
||||
);
|
||||
assert!(
|
||||
d_query.get_component::<D>(entities[3]).is_ok(),
|
||||
"entity 3 should have D"
|
||||
);
|
||||
|
||||
*ran = true;
|
||||
}
|
||||
|
||||
let mut world = World::default();
|
||||
world.insert_resource(false);
|
||||
world.spawn().insert_bundle((A,));
|
||||
world.spawn().insert_bundle((A, B));
|
||||
world.spawn().insert_bundle((A, C));
|
||||
world.spawn().insert_bundle((A, D));
|
||||
|
||||
run_system(&mut world, query_system.system());
|
||||
|
||||
assert!(*world.get_resource::<bool>().unwrap(), "system ran");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn or_query_set_system() {
|
||||
// Regression test for issue #762
|
||||
fn query_system(
|
||||
mut ran: ResMut<bool>,
|
||||
set: QuerySet<(
|
||||
Query<(), Or<(Changed<A>, Changed<B>)>>,
|
||||
Query<(), Or<(Added<A>, Added<B>)>>,
|
||||
Query<(), Or<(Mutated<A>, Mutated<B>)>>,
|
||||
)>,
|
||||
) {
|
||||
let changed = set.q0().iter().count();
|
||||
let added = set.q1().iter().count();
|
||||
let mutated = set.q2().iter().count();
|
||||
|
||||
assert_eq!(changed, 1);
|
||||
assert_eq!(added, 1);
|
||||
assert_eq!(mutated, 0);
|
||||
|
||||
*ran = true;
|
||||
}
|
||||
|
||||
let mut world = World::default();
|
||||
world.insert_resource(false);
|
||||
world.spawn().insert_bundle((A, B));
|
||||
|
||||
run_system(&mut world, query_system.system());
|
||||
|
||||
assert!(*world.get_resource::<bool>().unwrap(), "system ran");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn changed_resource_system() {
|
||||
struct Added(usize);
|
||||
struct Changed(usize);
|
||||
fn incr_e_on_flip(
|
||||
value: Res<bool>,
|
||||
mut changed: ResMut<Changed>,
|
||||
mut added: ResMut<Added>,
|
||||
) {
|
||||
if value.added() {
|
||||
added.0 += 1;
|
||||
}
|
||||
|
||||
if value.changed() {
|
||||
changed.0 += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let mut world = World::default();
|
||||
world.insert_resource(false);
|
||||
world.insert_resource(Added(0));
|
||||
world.insert_resource(Changed(0));
|
||||
|
||||
let mut schedule = Schedule::default();
|
||||
let mut update = SystemStage::parallel();
|
||||
update.add_system(incr_e_on_flip.system());
|
||||
schedule.add_stage("update", update);
|
||||
schedule.add_stage(
|
||||
"clear_trackers",
|
||||
SystemStage::single(World::clear_trackers.exclusive_system()),
|
||||
);
|
||||
|
||||
schedule.run(&mut world);
|
||||
assert_eq!(world.get_resource::<Added>().unwrap().0, 1);
|
||||
assert_eq!(world.get_resource::<Changed>().unwrap().0, 1);
|
||||
|
||||
schedule.run(&mut world);
|
||||
assert_eq!(world.get_resource::<Added>().unwrap().0, 1);
|
||||
assert_eq!(world.get_resource::<Changed>().unwrap().0, 1);
|
||||
|
||||
*world.get_resource_mut::<bool>().unwrap() = true;
|
||||
schedule.run(&mut world);
|
||||
assert_eq!(world.get_resource::<Added>().unwrap().0, 1);
|
||||
assert_eq!(world.get_resource::<Changed>().unwrap().0, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn conflicting_query_mut_system() {
|
||||
fn sys(_q1: Query<&mut A>, _q2: Query<&mut A>) {}
|
||||
|
||||
let mut world = World::default();
|
||||
run_system(&mut world, sys.system());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disjoint_query_mut_system() {
|
||||
fn sys(_q1: Query<&mut A, With<B>>, _q2: Query<&mut A, Without<B>>) {}
|
||||
|
||||
let mut world = World::default();
|
||||
run_system(&mut world, sys.system());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disjoint_query_mut_read_component_system() {
|
||||
fn sys(_q1: Query<(&mut A, &B)>, _q2: Query<&mut A, Without<B>>) {}
|
||||
|
||||
let mut world = World::default();
|
||||
run_system(&mut world, sys.system());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn conflicting_query_immut_system() {
|
||||
fn sys(_q1: Query<&A>, _q2: Query<&mut A>) {}
|
||||
|
||||
let mut world = World::default();
|
||||
run_system(&mut world, sys.system());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn query_set_system() {
|
||||
fn sys(mut _set: QuerySet<(Query<&mut A>, Query<&A>)>) {}
|
||||
let mut world = World::default();
|
||||
run_system(&mut world, sys.system());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn conflicting_query_with_query_set_system() {
|
||||
fn sys(_query: Query<&mut A>, _set: QuerySet<(Query<&mut A>, Query<&B>)>) {}
|
||||
|
||||
let mut world = World::default();
|
||||
run_system(&mut world, sys.system());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn conflicting_query_sets_system() {
|
||||
fn sys(_set_1: QuerySet<(Query<&mut A>,)>, _set_2: QuerySet<(Query<&mut A>, Query<&B>)>) {}
|
||||
|
||||
let mut world = World::default();
|
||||
run_system(&mut world, sys.system());
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct BufferRes {
|
||||
_buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
fn test_for_conflicting_resources<S: System<In = (), Out = ()>>(sys: S) {
|
||||
let mut world = World::default();
|
||||
world.insert_resource(BufferRes::default());
|
||||
world.insert_resource(A);
|
||||
world.insert_resource(B);
|
||||
run_system(&mut world, sys.system());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn conflicting_system_resources() {
|
||||
fn sys(_: ResMut<BufferRes>, _: Res<BufferRes>) {}
|
||||
test_for_conflicting_resources(sys.system())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn conflicting_system_resources_reverse_order() {
|
||||
fn sys(_: Res<BufferRes>, _: ResMut<BufferRes>) {}
|
||||
test_for_conflicting_resources(sys.system())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn conflicting_system_resources_multiple_mutable() {
|
||||
fn sys(_: ResMut<BufferRes>, _: ResMut<BufferRes>) {}
|
||||
test_for_conflicting_resources(sys.system())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nonconflicting_system_resources() {
|
||||
fn sys(_: Local<BufferRes>, _: ResMut<BufferRes>, _: Local<A>, _: ResMut<A>) {}
|
||||
test_for_conflicting_resources(sys.system())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_system() {
|
||||
let mut world = World::default();
|
||||
world.insert_resource(1u32);
|
||||
world.insert_resource(false);
|
||||
struct Foo {
|
||||
value: u32,
|
||||
}
|
||||
|
||||
impl FromWorld for Foo {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
Foo {
|
||||
value: *world.get_resource::<u32>().unwrap() + 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sys(local: Local<Foo>, mut modified: ResMut<bool>) {
|
||||
assert_eq!(local.value, 2);
|
||||
*modified = true;
|
||||
}
|
||||
|
||||
run_system(&mut world, sys.system());
|
||||
|
||||
// ensure the system actually ran
|
||||
assert_eq!(*world.get_resource::<bool>().unwrap(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_tracking() {
|
||||
let mut world = World::new();
|
||||
struct Despawned(Entity);
|
||||
let a = world.spawn().insert_bundle(("abc", 123)).id();
|
||||
world.spawn().insert_bundle(("abc", 123));
|
||||
world.insert_resource(false);
|
||||
world.insert_resource(Despawned(a));
|
||||
|
||||
world.entity_mut(a).despawn();
|
||||
|
||||
fn validate_removed(
|
||||
removed_i32: RemovedComponents<i32>,
|
||||
despawned: Res<Despawned>,
|
||||
mut ran: ResMut<bool>,
|
||||
) {
|
||||
assert_eq!(
|
||||
removed_i32.iter().collect::<Vec<_>>(),
|
||||
&[despawned.0],
|
||||
"despawning results in 'removed component' state"
|
||||
);
|
||||
|
||||
*ran = true;
|
||||
}
|
||||
|
||||
run_system(&mut world, validate_removed.system());
|
||||
assert_eq!(*world.get_resource::<bool>().unwrap(), true, "system ran");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn configure_system_local() {
|
||||
let mut world = World::default();
|
||||
world.insert_resource(false);
|
||||
fn sys(local: Local<usize>, mut modified: ResMut<bool>) {
|
||||
assert_eq!(*local, 42);
|
||||
*modified = true;
|
||||
}
|
||||
|
||||
run_system(
|
||||
&mut world,
|
||||
sys.system().config(|config| config.0 = Some(42)),
|
||||
);
|
||||
|
||||
// ensure the system actually ran
|
||||
assert_eq!(*world.get_resource::<bool>().unwrap(), true);
|
||||
}
|
||||
#[test]
|
||||
fn world_collections_system() {
|
||||
let mut world = World::default();
|
||||
world.insert_resource(false);
|
||||
world.spawn().insert_bundle((42, true));
|
||||
fn sys(
|
||||
archetypes: &Archetypes,
|
||||
components: &Components,
|
||||
entities: &Entities,
|
||||
bundles: &Bundles,
|
||||
query: Query<Entity, With<i32>>,
|
||||
mut modified: ResMut<bool>,
|
||||
) {
|
||||
assert_eq!(query.iter().count(), 1, "entity exists");
|
||||
for entity in query.iter() {
|
||||
let location = entities.get(entity).unwrap();
|
||||
let archetype = archetypes.get(location.archetype_id).unwrap();
|
||||
let archetype_components = archetype.components().collect::<Vec<_>>();
|
||||
let bundle_id = bundles
|
||||
.get_id(std::any::TypeId::of::<(i32, bool)>())
|
||||
.expect("Bundle used to spawn entity should exist");
|
||||
let bundle_info = bundles.get(bundle_id).unwrap();
|
||||
let mut bundle_components = bundle_info.components().to_vec();
|
||||
bundle_components.sort();
|
||||
for component_id in bundle_components.iter() {
|
||||
assert!(
|
||||
components.get_info(*component_id).is_some(),
|
||||
"every bundle component exists in Components"
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
bundle_components, archetype_components,
|
||||
"entity's bundle components exactly match entity's archetype components"
|
||||
);
|
||||
}
|
||||
*modified = true;
|
||||
}
|
||||
|
||||
run_system(&mut world, sys.system());
|
||||
|
||||
// ensure the system actually ran
|
||||
assert_eq!(*world.get_resource::<bool>().unwrap(), true);
|
||||
}
|
||||
}
|
||||
|
|
228
crates/bevy_ecs/src/system/query.rs
Normal file
228
crates/bevy_ecs/src/system/query.rs
Normal file
|
@ -0,0 +1,228 @@
|
|||
use crate::{
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
query::{
|
||||
Fetch, FilterFetch, QueryEntityError, QueryIter, QueryState, ReadOnlyFetch, WorldQuery,
|
||||
},
|
||||
world::{Mut, World},
|
||||
};
|
||||
use bevy_tasks::TaskPool;
|
||||
use std::any::TypeId;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Provides scoped access to a World according to a given [WorldQuery] and query filter
|
||||
pub struct Query<'w, Q: WorldQuery, F: WorldQuery = ()>
|
||||
where
|
||||
F::Fetch: FilterFetch,
|
||||
{
|
||||
pub(crate) world: &'w World,
|
||||
pub(crate) state: &'w QueryState<Q, F>,
|
||||
}
|
||||
|
||||
impl<'w, Q: WorldQuery, F: WorldQuery> Query<'w, Q, F>
|
||||
where
|
||||
F::Fetch: FilterFetch,
|
||||
{
|
||||
/// # Safety
|
||||
/// This will create a Query that could violate memory safety rules. Make sure that this is only called in
|
||||
/// ways that ensure the Queries have unique mutable access.
|
||||
#[inline]
|
||||
pub(crate) unsafe fn new(world: &'w World, state: &'w QueryState<Q, F>) -> Self {
|
||||
Self { world, state }
|
||||
}
|
||||
|
||||
/// Iterates over the query results. This can only be called for read-only queries
|
||||
#[inline]
|
||||
pub fn iter(&self) -> QueryIter<'_, '_, Q, F>
|
||||
where
|
||||
Q::Fetch: ReadOnlyFetch,
|
||||
{
|
||||
// SAFE: system runs without conflicts with other systems. same-system queries have runtime borrow checks when they conflict
|
||||
unsafe { self.state.iter_unchecked_manual(self.world) }
|
||||
}
|
||||
|
||||
/// Iterates over the query results
|
||||
#[inline]
|
||||
pub fn iter_mut(&mut self) -> QueryIter<'_, '_, Q, F> {
|
||||
// SAFE: system runs without conflicts with other systems. same-system queries have runtime borrow checks when they conflict
|
||||
unsafe { self.state.iter_unchecked_manual(self.world) }
|
||||
}
|
||||
|
||||
/// Iterates over the query results
|
||||
/// # Safety
|
||||
/// This allows aliased mutability. You must make sure this call does not result in multiple mutable references to the same component
|
||||
#[inline]
|
||||
pub unsafe fn iter_unsafe(&self) -> QueryIter<'_, '_, Q, F> {
|
||||
// SEMI-SAFE: system runs without conflicts with other systems. same-system queries have runtime borrow checks when they conflict
|
||||
self.state.iter_unchecked_manual(self.world)
|
||||
}
|
||||
|
||||
/// Runs `f` on each query result. This is faster than the equivalent iter() method, but cannot be chained like a normal iterator.
|
||||
/// This can only be called for read-only queries
|
||||
#[inline]
|
||||
pub fn for_each(&self, f: impl FnMut(<Q::Fetch as Fetch<'w>>::Item))
|
||||
where
|
||||
Q::Fetch: ReadOnlyFetch,
|
||||
{
|
||||
// SAFE: system runs without conflicts with other systems. same-system queries have runtime borrow checks when they conflict
|
||||
unsafe { self.state.for_each_unchecked_manual(self.world, f) };
|
||||
}
|
||||
|
||||
/// Runs `f` on each query result. This is faster than the equivalent iter() method, but cannot be chained like a normal iterator.
|
||||
#[inline]
|
||||
pub fn for_each_mut(&self, f: impl FnMut(<Q::Fetch as Fetch<'w>>::Item)) {
|
||||
// SAFE: system runs without conflicts with other systems. same-system queries have runtime borrow checks when they conflict
|
||||
unsafe { self.state.for_each_unchecked_manual(self.world, f) };
|
||||
}
|
||||
|
||||
/// Runs `f` on each query result in parallel using the given task pool.
|
||||
#[inline]
|
||||
pub fn par_for_each(
|
||||
&self,
|
||||
task_pool: &TaskPool,
|
||||
batch_size: usize,
|
||||
f: impl Fn(<Q::Fetch as Fetch<'w>>::Item) + Send + Sync + Clone,
|
||||
) where
|
||||
Q::Fetch: ReadOnlyFetch,
|
||||
{
|
||||
// SAFE: system runs without conflicts with other systems. same-system queries have runtime borrow checks when they conflict
|
||||
unsafe {
|
||||
self.state
|
||||
.par_for_each_unchecked_manual(self.world, task_pool, batch_size, f)
|
||||
};
|
||||
}
|
||||
|
||||
/// Runs `f` on each query result in parallel using the given task pool.
|
||||
#[inline]
|
||||
pub fn par_for_each_mut(
|
||||
&mut self,
|
||||
task_pool: &TaskPool,
|
||||
batch_size: usize,
|
||||
f: impl Fn(<Q::Fetch as Fetch<'w>>::Item) + Send + Sync + Clone,
|
||||
) {
|
||||
// SAFE: system runs without conflicts with other systems. same-system queries have runtime borrow checks when they conflict
|
||||
unsafe {
|
||||
self.state
|
||||
.par_for_each_unchecked_manual(self.world, task_pool, batch_size, f)
|
||||
};
|
||||
}
|
||||
|
||||
/// Gets the query result for the given `entity`
|
||||
#[inline]
|
||||
pub fn get(&self, entity: Entity) -> Result<<Q::Fetch as Fetch>::Item, QueryEntityError>
|
||||
where
|
||||
Q::Fetch: ReadOnlyFetch,
|
||||
{
|
||||
// SAFE: system runs without conflicts with other systems. same-system queries have runtime borrow checks when they conflict
|
||||
unsafe { self.state.get_unchecked_manual(self.world, entity) }
|
||||
}
|
||||
|
||||
/// Gets the query result for the given `entity`
|
||||
#[inline]
|
||||
pub fn get_mut(
|
||||
&mut self,
|
||||
entity: Entity,
|
||||
) -> Result<<Q::Fetch as Fetch>::Item, QueryEntityError> {
|
||||
// // SAFE: system runs without conflicts with other systems. same-system queries have runtime borrow checks when they conflict
|
||||
unsafe { self.state.get_unchecked_manual(self.world, entity) }
|
||||
}
|
||||
|
||||
/// Gets the query result for the given `entity`
|
||||
/// # Safety
|
||||
/// This allows aliased mutability. You must make sure this call does not result in multiple mutable references to the same component
|
||||
#[inline]
|
||||
pub unsafe fn get_unchecked(
|
||||
&self,
|
||||
entity: Entity,
|
||||
) -> Result<<Q::Fetch as Fetch>::Item, QueryEntityError> {
|
||||
// SEMI-SAFE: system runs without conflicts with other systems. same-system queries have runtime borrow checks when they conflict
|
||||
self.state.get_unchecked_manual(self.world, entity)
|
||||
}
|
||||
|
||||
/// Gets a reference to the entity's component of the given type. This will fail if the entity does not have
|
||||
/// the given component type or if the given component type does not match this query.
|
||||
#[inline]
|
||||
pub fn get_component<T: Component>(&self, entity: Entity) -> Result<&T, QueryComponentError> {
|
||||
let world = self.world;
|
||||
let entity_ref = world
|
||||
.get_entity(entity)
|
||||
.ok_or(QueryComponentError::NoSuchEntity)?;
|
||||
let component_id = world
|
||||
.components()
|
||||
.get_id(TypeId::of::<T>())
|
||||
.ok_or(QueryComponentError::MissingComponent)?;
|
||||
let archetype_component = entity_ref
|
||||
.archetype()
|
||||
.get_archetype_component_id(component_id)
|
||||
.ok_or(QueryComponentError::MissingComponent)?;
|
||||
if self
|
||||
.state
|
||||
.archetype_component_access
|
||||
.has_read(archetype_component)
|
||||
{
|
||||
entity_ref
|
||||
.get::<T>()
|
||||
.ok_or(QueryComponentError::MissingComponent)
|
||||
} else {
|
||||
Err(QueryComponentError::MissingReadAccess)
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the entity's component of the given type. This will fail if the entity does not have
|
||||
/// the given component type or if the given component type does not match this query.
|
||||
#[inline]
|
||||
pub fn get_component_mut<T: Component>(
|
||||
&mut self,
|
||||
entity: Entity,
|
||||
) -> Result<Mut<'_, T>, QueryComponentError> {
|
||||
// SAFE: unique access to query (preventing aliased access)
|
||||
unsafe { self.get_component_unchecked_mut(entity) }
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the entity's component of the given type. This will fail if the entity does not have
|
||||
/// the given component type or the component does not match the query.
|
||||
/// # Safety
|
||||
/// This allows aliased mutability. You must make sure this call does not result in multiple mutable references to the same component
|
||||
#[inline]
|
||||
pub unsafe fn get_component_unchecked_mut<T: Component>(
|
||||
&self,
|
||||
entity: Entity,
|
||||
) -> Result<Mut<'_, T>, QueryComponentError> {
|
||||
let world = self.world;
|
||||
let entity_ref = world
|
||||
.get_entity(entity)
|
||||
.ok_or(QueryComponentError::NoSuchEntity)?;
|
||||
let component_id = world
|
||||
.components()
|
||||
.get_id(TypeId::of::<T>())
|
||||
.ok_or(QueryComponentError::MissingComponent)?;
|
||||
let archetype_component = entity_ref
|
||||
.archetype()
|
||||
.get_archetype_component_id(component_id)
|
||||
.ok_or(QueryComponentError::MissingComponent)?;
|
||||
if self
|
||||
.state
|
||||
.archetype_component_access
|
||||
.has_write(archetype_component)
|
||||
{
|
||||
entity_ref
|
||||
.get_unchecked_mut::<T>()
|
||||
.ok_or(QueryComponentError::MissingComponent)
|
||||
} else {
|
||||
Err(QueryComponentError::MissingWriteAccess)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An error that occurs when retrieving a specific [Entity]'s component from a [Query]
|
||||
#[derive(Error, Debug)]
|
||||
pub enum QueryComponentError {
|
||||
#[error("This query does not have read access to the requested component.")]
|
||||
MissingReadAccess,
|
||||
#[error("This query does not have read access to the requested component.")]
|
||||
MissingWriteAccess,
|
||||
#[error("The given entity does not have the requested component.")]
|
||||
MissingComponent,
|
||||
#[error("The requested entity does not exist.")]
|
||||
NoSuchEntity,
|
||||
}
|
|
@ -1,232 +0,0 @@
|
|||
mod query_set;
|
||||
pub use query_set::*;
|
||||
|
||||
use crate::{
|
||||
ArchetypeComponent, Batch, BatchedIter, Component, ComponentError, Entity, Fetch, Mut,
|
||||
QueryFilter, QueryIter, ReadOnlyFetch, TypeAccess, World, WorldQuery,
|
||||
};
|
||||
use bevy_tasks::ParallelIterator;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// Provides scoped access to a World according to a given [WorldQuery]
|
||||
#[derive(Debug)]
|
||||
pub struct Query<'a, Q: WorldQuery, F: QueryFilter = ()> {
|
||||
pub(crate) world: &'a World,
|
||||
pub(crate) component_access: &'a TypeAccess<ArchetypeComponent>,
|
||||
_marker: PhantomData<(Q, F)>,
|
||||
}
|
||||
|
||||
/// An error that occurs when using a [Query]
|
||||
#[derive(Debug)]
|
||||
pub enum QueryError {
|
||||
CannotReadArchetype,
|
||||
CannotWriteArchetype,
|
||||
ComponentError(ComponentError),
|
||||
NoSuchEntity,
|
||||
}
|
||||
|
||||
impl<'a, Q: WorldQuery, F: QueryFilter> Query<'a, Q, F> {
|
||||
/// # Safety
|
||||
/// This will create a Query that could violate memory safety rules. Make sure that this is only called in
|
||||
/// ways that ensure the Queries have unique mutable access.
|
||||
#[inline]
|
||||
pub(crate) unsafe fn new(
|
||||
world: &'a World,
|
||||
component_access: &'a TypeAccess<ArchetypeComponent>,
|
||||
) -> Self {
|
||||
Self {
|
||||
world,
|
||||
component_access,
|
||||
_marker: PhantomData::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterates over the query results. This can only be called for read-only queries
|
||||
#[inline]
|
||||
pub fn iter(&self) -> QueryIter<'_, Q, F>
|
||||
where
|
||||
Q::Fetch: ReadOnlyFetch,
|
||||
{
|
||||
// SAFE: system runs without conflicts with other systems. same-system queries have runtime borrow checks when they conflict
|
||||
unsafe { self.world.query_unchecked() }
|
||||
}
|
||||
|
||||
/// Iterates over the query results
|
||||
#[inline]
|
||||
pub fn iter_mut(&mut self) -> QueryIter<'_, Q, F> {
|
||||
// SAFE: system runs without conflicts with other systems. same-system queries have runtime borrow checks when they conflict
|
||||
unsafe { self.world.query_unchecked() }
|
||||
}
|
||||
|
||||
/// Iterates over the query results
|
||||
/// # Safety
|
||||
/// This allows aliased mutability. You must make sure this call does not result in multiple mutable references to the same component
|
||||
#[inline]
|
||||
pub unsafe fn iter_unsafe(&self) -> QueryIter<'_, Q, F> {
|
||||
// SAFE: system runs without conflicts with other systems. same-system queries have runtime borrow checks when they conflict
|
||||
self.world.query_unchecked()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn par_iter(&self, batch_size: usize) -> ParIter<'_, Q, F>
|
||||
where
|
||||
Q::Fetch: ReadOnlyFetch,
|
||||
{
|
||||
// SAFE: system runs without conflicts with other systems. same-system queries have runtime borrow checks when they conflict
|
||||
unsafe { ParIter::new(self.world.query_batched_unchecked(batch_size)) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn par_iter_mut(&mut self, batch_size: usize) -> ParIter<'_, Q, F> {
|
||||
// SAFE: system runs without conflicts with other systems. same-system queries have runtime borrow checks when they conflict
|
||||
unsafe { ParIter::new(self.world.query_batched_unchecked(batch_size)) }
|
||||
}
|
||||
|
||||
/// Gets the query result for the given `entity`
|
||||
#[inline]
|
||||
pub fn get(&self, entity: Entity) -> Result<<Q::Fetch as Fetch>::Item, QueryError>
|
||||
where
|
||||
Q::Fetch: ReadOnlyFetch,
|
||||
{
|
||||
// SAFE: system runs without conflicts with other systems. same-system queries have runtime borrow checks when they conflict
|
||||
unsafe {
|
||||
self.world
|
||||
.query_one_unchecked::<Q, F>(entity)
|
||||
.map_err(|_err| QueryError::NoSuchEntity)
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the query result for the given `entity`
|
||||
#[inline]
|
||||
pub fn get_mut(&mut self, entity: Entity) -> Result<<Q::Fetch as Fetch>::Item, QueryError> {
|
||||
// SAFE: system runs without conflicts with other systems. same-system queries have runtime borrow checks when they conflict
|
||||
unsafe {
|
||||
self.world
|
||||
.query_one_unchecked::<Q, F>(entity)
|
||||
.map_err(|_err| QueryError::NoSuchEntity)
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the query result for the given `entity`
|
||||
/// # Safety
|
||||
/// This allows aliased mutability. You must make sure this call does not result in multiple mutable references to the same component
|
||||
#[inline]
|
||||
pub unsafe fn get_unsafe(
|
||||
&self,
|
||||
entity: Entity,
|
||||
) -> Result<<Q::Fetch as Fetch>::Item, QueryError> {
|
||||
self.world
|
||||
.query_one_unchecked::<Q, F>(entity)
|
||||
.map_err(|_err| QueryError::NoSuchEntity)
|
||||
}
|
||||
|
||||
/// Gets a reference to the entity's component of the given type. This will fail if the entity does not have
|
||||
/// the given component type or if the given component type does not match this query.
|
||||
pub fn get_component<T: Component>(&self, entity: Entity) -> Result<&T, QueryError> {
|
||||
if let Some(location) = self.world.get_entity_location(entity) {
|
||||
if self
|
||||
.component_access
|
||||
.is_read_or_write(&ArchetypeComponent::new::<T>(location.archetype))
|
||||
{
|
||||
// SAFE: we have already checked that the entity/component matches our archetype access. and systems are scheduled to run with safe archetype access
|
||||
unsafe {
|
||||
self.world
|
||||
.get_at_location_unchecked(location)
|
||||
.map_err(QueryError::ComponentError)
|
||||
}
|
||||
} else {
|
||||
Err(QueryError::CannotReadArchetype)
|
||||
}
|
||||
} else {
|
||||
Err(QueryError::ComponentError(ComponentError::NoSuchEntity))
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the entity's component of the given type. This will fail if the entity does not have
|
||||
/// the given component type or if the given component type does not match this query.
|
||||
pub fn get_component_mut<T: Component>(
|
||||
&mut self,
|
||||
entity: Entity,
|
||||
) -> Result<Mut<'_, T>, QueryError> {
|
||||
let location = match self.world.get_entity_location(entity) {
|
||||
None => return Err(QueryError::ComponentError(ComponentError::NoSuchEntity)),
|
||||
Some(location) => location,
|
||||
};
|
||||
|
||||
if self
|
||||
.component_access
|
||||
.is_write(&ArchetypeComponent::new::<T>(location.archetype))
|
||||
{
|
||||
// SAFE: RefMut does exclusivity checks and we have already validated the entity
|
||||
unsafe {
|
||||
self.world
|
||||
.get_mut_at_location_unchecked(location)
|
||||
.map_err(QueryError::ComponentError)
|
||||
}
|
||||
} else {
|
||||
Err(QueryError::CannotWriteArchetype)
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the entity's component of the given type. This will fail if the entity does not have
|
||||
/// the given component type
|
||||
/// # Safety
|
||||
/// This allows aliased mutability. You must make sure this call does not result in multiple mutable references to the same component
|
||||
pub unsafe fn get_component_unsafe<T: Component>(
|
||||
&self,
|
||||
entity: Entity,
|
||||
) -> Result<Mut<'_, T>, QueryError> {
|
||||
self.world
|
||||
.get_mut_unchecked(entity)
|
||||
.map_err(QueryError::ComponentError)
|
||||
}
|
||||
|
||||
/// Returns an array containing the `Entity`s in this `Query` that had the given `Component`
|
||||
/// removed in this update.
|
||||
///
|
||||
/// `removed::<C>()` only returns entities whose components were removed before the
|
||||
/// current system started.
|
||||
///
|
||||
/// Regular systems do not apply `Commands` until the end of their stage. This means component
|
||||
/// removals in a regular system won't be accessible through `removed::<C>()` in the same
|
||||
/// stage, because the removal hasn't actually occurred yet. This can be solved by executing
|
||||
/// `removed::<C>()` in a later stage. `AppBuilder::add_system_to_stage()` can be used to
|
||||
/// control at what stage a system runs.
|
||||
///
|
||||
/// Thread local systems manipulate the world directly, so removes are applied immediately. This
|
||||
/// means any system that runs after a thread local system in the same update will pick up
|
||||
/// removals that happened in the thread local system, regardless of stages.
|
||||
pub fn removed<C: Component>(&self) -> &[Entity] {
|
||||
self.world.removed::<C>()
|
||||
}
|
||||
|
||||
/// Sets the entity's component to the given value. This will fail if the entity does not already have
|
||||
/// the given component type or if the given component type does not match this query.
|
||||
pub fn set<T: Component>(&mut self, entity: Entity, component: T) -> Result<(), QueryError> {
|
||||
let mut current = self.get_component_mut::<T>(entity)?;
|
||||
*current = component;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Parallel version of QueryIter
|
||||
pub struct ParIter<'w, Q: WorldQuery, F: QueryFilter> {
|
||||
batched_iter: BatchedIter<'w, Q, F>,
|
||||
}
|
||||
|
||||
impl<'w, Q: WorldQuery, F: QueryFilter> ParIter<'w, Q, F> {
|
||||
pub fn new(batched_iter: BatchedIter<'w, Q, F>) -> Self {
|
||||
Self { batched_iter }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'w, Q: WorldQuery, F: QueryFilter> Send for ParIter<'w, Q, F> {}
|
||||
|
||||
impl<'w, Q: WorldQuery, F: QueryFilter> ParallelIterator<Batch<'w, Q, F>> for ParIter<'w, Q, F> {
|
||||
type Item = <Q::Fetch as Fetch<'w>>::Item;
|
||||
|
||||
#[inline]
|
||||
fn next_batch(&mut self) -> Option<Batch<'w, Q, F>> {
|
||||
self.batched_iter.next()
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
use crate::{
|
||||
impl_query_set, ArchetypeComponent, Fetch, Query, QueryAccess, QueryFilter, TypeAccess, World,
|
||||
WorldQuery,
|
||||
};
|
||||
|
||||
pub struct QuerySet<T: QueryTuple> {
|
||||
value: T,
|
||||
}
|
||||
|
||||
impl_query_set!();
|
||||
|
||||
pub trait QueryTuple {
|
||||
/// # Safety
|
||||
/// this might cast world and component access to the relevant Self lifetimes. verify that this is safe in each impl
|
||||
unsafe fn new(world: &World, component_access: &TypeAccess<ArchetypeComponent>) -> Self;
|
||||
fn get_accesses() -> Vec<QueryAccess>;
|
||||
}
|
||||
|
||||
impl<T: QueryTuple> QuerySet<T> {
|
||||
/// # Safety
|
||||
/// This will create a set of Query types that could violate memory safety rules. Make sure that this is only called in
|
||||
/// ways that ensure the Queries have unique mutable access.
|
||||
pub(crate) unsafe fn new(
|
||||
world: &World,
|
||||
component_access: &TypeAccess<ArchetypeComponent>,
|
||||
) -> Self {
|
||||
QuerySet {
|
||||
value: T::new(world, component_access),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,10 @@
|
|||
use crate::{ArchetypeComponent, Resources, TypeAccess, World};
|
||||
use std::{any::TypeId, borrow::Cow};
|
||||
use crate::{
|
||||
archetype::{Archetype, ArchetypeComponentId},
|
||||
component::ComponentId,
|
||||
query::Access,
|
||||
world::World,
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct SystemId(pub usize);
|
||||
|
@ -11,38 +16,27 @@ impl SystemId {
|
|||
}
|
||||
}
|
||||
|
||||
/// An ECS system that can be added to a [Schedule](crate::Schedule)
|
||||
/// An ECS system that can be added to a [Schedule](crate::schedule::Schedule)
|
||||
pub trait System: Send + Sync + 'static {
|
||||
type In;
|
||||
type Out;
|
||||
fn name(&self) -> Cow<'static, str>;
|
||||
fn id(&self) -> SystemId;
|
||||
fn update_access(&mut self, world: &World);
|
||||
fn archetype_component_access(&self) -> &TypeAccess<ArchetypeComponent>;
|
||||
fn component_access(&self) -> &TypeAccess<TypeId>;
|
||||
fn resource_access(&self) -> &TypeAccess<TypeId>;
|
||||
fn is_non_send(&self) -> bool;
|
||||
fn new_archetype(&mut self, archetype: &Archetype);
|
||||
fn component_access(&self) -> &Access<ComponentId>;
|
||||
fn archetype_component_access(&self) -> &Access<ArchetypeComponentId>;
|
||||
fn is_send(&self) -> bool;
|
||||
/// # Safety
|
||||
/// This might access World and Resources in an unsafe manner. This should only be called in one of the following contexts:
|
||||
/// 1. This system is the only system running on the given World and Resources across all threads
|
||||
/// 2. This system only runs in parallel with other systems that do not conflict with the `archetype_component_access()` or `resource_access()`
|
||||
unsafe fn run_unsafe(
|
||||
&mut self,
|
||||
input: Self::In,
|
||||
world: &World,
|
||||
resources: &Resources,
|
||||
) -> Option<Self::Out>;
|
||||
fn run(
|
||||
&mut self,
|
||||
input: Self::In,
|
||||
world: &mut World,
|
||||
resources: &mut Resources,
|
||||
) -> Option<Self::Out> {
|
||||
/// 1. This system is the only system running on the given World across all threads
|
||||
/// 2. This system only runs in parallel with other systems that do not conflict with the `archetype_component_access()`
|
||||
unsafe fn run_unsafe(&mut self, input: Self::In, world: &World) -> Self::Out;
|
||||
fn run(&mut self, input: Self::In, world: &mut World) -> Self::Out {
|
||||
// SAFE: world and resources are exclusively borrowed
|
||||
unsafe { self.run_unsafe(input, world, resources) }
|
||||
unsafe { self.run_unsafe(input, world) }
|
||||
}
|
||||
fn apply_buffers(&mut self, world: &mut World, resources: &mut Resources);
|
||||
fn initialize(&mut self, world: &mut World, resources: &mut Resources);
|
||||
fn apply_buffers(&mut self, world: &mut World);
|
||||
fn initialize(&mut self, _world: &mut World);
|
||||
}
|
||||
|
||||
pub type BoxedSystem<In = (), Out = ()> = Box<dyn System<In = In, Out = Out>>;
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
use crate::{ArchetypeComponent, Resources, System, SystemId, TypeAccess, World};
|
||||
use std::{any::TypeId, borrow::Cow};
|
||||
use crate::{
|
||||
archetype::{Archetype, ArchetypeComponentId},
|
||||
component::ComponentId,
|
||||
query::Access,
|
||||
system::{System, SystemId},
|
||||
world::World,
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub struct ChainSystem<SystemA, SystemB> {
|
||||
system_a: SystemA,
|
||||
system_b: SystemB,
|
||||
name: Cow<'static, str>,
|
||||
id: SystemId,
|
||||
archetype_component_access: TypeAccess<ArchetypeComponent>,
|
||||
component_access: TypeAccess<TypeId>,
|
||||
resource_access: TypeAccess<TypeId>,
|
||||
component_access: Access<ComponentId>,
|
||||
archetype_component_access: Access<ArchetypeComponentId>,
|
||||
}
|
||||
|
||||
impl<SystemA: System, SystemB: System<In = SystemA::Out>> System for ChainSystem<SystemA, SystemB> {
|
||||
|
@ -23,59 +28,45 @@ impl<SystemA: System, SystemB: System<In = SystemA::Out>> System for ChainSystem
|
|||
self.id
|
||||
}
|
||||
|
||||
fn update_access(&mut self, world: &World) {
|
||||
self.archetype_component_access.clear();
|
||||
self.component_access.clear();
|
||||
self.resource_access.clear();
|
||||
self.system_a.update_access(world);
|
||||
self.system_b.update_access(world);
|
||||
fn new_archetype(&mut self, archetype: &Archetype) {
|
||||
self.system_a.new_archetype(archetype);
|
||||
self.system_b.new_archetype(archetype);
|
||||
|
||||
self.archetype_component_access
|
||||
.extend(self.system_a.archetype_component_access());
|
||||
self.archetype_component_access
|
||||
.extend(self.system_b.archetype_component_access());
|
||||
}
|
||||
|
||||
fn archetype_component_access(&self) -> &Access<ArchetypeComponentId> {
|
||||
&self.archetype_component_access
|
||||
}
|
||||
|
||||
fn component_access(&self) -> &Access<ComponentId> {
|
||||
&self.component_access
|
||||
}
|
||||
|
||||
fn is_send(&self) -> bool {
|
||||
self.system_a.is_send() && self.system_b.is_send()
|
||||
}
|
||||
|
||||
unsafe fn run_unsafe(&mut self, input: Self::In, world: &World) -> Self::Out {
|
||||
let out = self.system_a.run_unsafe(input, world);
|
||||
self.system_b.run_unsafe(out, world)
|
||||
}
|
||||
|
||||
fn apply_buffers(&mut self, world: &mut World) {
|
||||
self.system_a.apply_buffers(world);
|
||||
self.system_b.apply_buffers(world);
|
||||
}
|
||||
|
||||
fn initialize(&mut self, world: &mut World) {
|
||||
self.system_a.initialize(world);
|
||||
self.system_b.initialize(world);
|
||||
self.component_access
|
||||
.extend(self.system_a.component_access());
|
||||
self.component_access
|
||||
.extend(self.system_b.component_access());
|
||||
self.resource_access.extend(self.system_a.resource_access());
|
||||
self.resource_access.extend(self.system_b.resource_access());
|
||||
}
|
||||
|
||||
fn archetype_component_access(&self) -> &TypeAccess<ArchetypeComponent> {
|
||||
&self.archetype_component_access
|
||||
}
|
||||
|
||||
fn component_access(&self) -> &TypeAccess<TypeId> {
|
||||
&self.component_access
|
||||
}
|
||||
|
||||
fn resource_access(&self) -> &TypeAccess<TypeId> {
|
||||
&self.resource_access
|
||||
}
|
||||
|
||||
fn is_non_send(&self) -> bool {
|
||||
self.system_a.is_non_send() || self.system_b.is_non_send()
|
||||
}
|
||||
|
||||
unsafe fn run_unsafe(
|
||||
&mut self,
|
||||
input: Self::In,
|
||||
world: &World,
|
||||
resources: &Resources,
|
||||
) -> Option<Self::Out> {
|
||||
let out = self.system_a.run_unsafe(input, world, resources).unwrap();
|
||||
self.system_b.run_unsafe(out, world, resources)
|
||||
}
|
||||
|
||||
fn apply_buffers(&mut self, world: &mut World, resources: &mut Resources) {
|
||||
self.system_a.apply_buffers(world, resources);
|
||||
self.system_b.apply_buffers(world, resources);
|
||||
}
|
||||
|
||||
fn initialize(&mut self, world: &mut World, resources: &mut Resources) {
|
||||
self.system_a.initialize(world, resources);
|
||||
self.system_b.initialize(world, resources);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,7 +89,6 @@ where
|
|||
system_b: system,
|
||||
archetype_component_access: Default::default(),
|
||||
component_access: Default::default(),
|
||||
resource_access: Default::default(),
|
||||
id: SystemId::new(),
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,391 +0,0 @@
|
|||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// modified by Bevy contributors
|
||||
|
||||
use bevy_ecs::*;
|
||||
|
||||
#[test]
|
||||
fn random_access() {
|
||||
let mut world = World::new();
|
||||
let e = world.spawn(("abc", 123));
|
||||
let f = world.spawn(("def", 456, true));
|
||||
assert_eq!(*world.get::<&str>(e).unwrap(), "abc");
|
||||
assert_eq!(*world.get::<i32>(e).unwrap(), 123);
|
||||
assert_eq!(*world.get::<&str>(f).unwrap(), "def");
|
||||
assert_eq!(*world.get::<i32>(f).unwrap(), 456);
|
||||
*world.get_mut::<i32>(f).unwrap() = 42;
|
||||
assert_eq!(*world.get::<i32>(f).unwrap(), 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn despawn() {
|
||||
let mut world = World::new();
|
||||
let e = world.spawn(("abc", 123));
|
||||
let f = world.spawn(("def", 456));
|
||||
assert_eq!(world.query::<()>().count(), 2);
|
||||
world.despawn(e).unwrap();
|
||||
assert_eq!(world.query::<()>().count(), 1);
|
||||
assert!(world.get::<&str>(e).is_err());
|
||||
assert!(world.get::<i32>(e).is_err());
|
||||
assert_eq!(*world.get::<&str>(f).unwrap(), "def");
|
||||
assert_eq!(*world.get::<i32>(f).unwrap(), 456);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn query_all() {
|
||||
let mut world = World::new();
|
||||
let e = world.spawn(("abc", 123));
|
||||
let f = world.spawn(("def", 456));
|
||||
|
||||
let ents = world
|
||||
.query::<(Entity, &i32, &&str)>()
|
||||
.map(|(e, &i, &s)| (e, i, s))
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(ents.len(), 2);
|
||||
assert!(ents.contains(&(e, 123, "abc")));
|
||||
assert!(ents.contains(&(f, 456, "def")));
|
||||
|
||||
let ents = world.query::<Entity>().collect::<Vec<_>>();
|
||||
assert_eq!(ents.len(), 2);
|
||||
assert!(ents.contains(&e));
|
||||
assert!(ents.contains(&f));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn query_single_component() {
|
||||
let mut world = World::new();
|
||||
let e = world.spawn(("abc", 123));
|
||||
let f = world.spawn(("def", 456, true));
|
||||
let ents = world
|
||||
.query::<(Entity, &i32)>()
|
||||
.map(|(e, &i)| (e, i))
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(ents.len(), 2);
|
||||
assert!(ents.contains(&(e, 123)));
|
||||
assert!(ents.contains(&(f, 456)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn query_missing_component() {
|
||||
let mut world = World::new();
|
||||
world.spawn(("abc", 123));
|
||||
world.spawn(("def", 456));
|
||||
assert!(world.query::<(&bool, &i32)>().next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn query_sparse_component() {
|
||||
let mut world = World::new();
|
||||
world.spawn(("abc", 123));
|
||||
let f = world.spawn(("def", 456, true));
|
||||
let ents = world
|
||||
.query::<(Entity, &bool)>()
|
||||
.map(|(e, &b)| (e, b))
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(ents, &[(f, true)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn query_optional_component() {
|
||||
let mut world = World::new();
|
||||
let e = world.spawn(("abc", 123));
|
||||
let f = world.spawn(("def", 456, true));
|
||||
let ents = world
|
||||
.query::<(Entity, Option<&bool>, &i32)>()
|
||||
.map(|(e, b, &i)| (e, b.copied(), i))
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(ents.len(), 2);
|
||||
assert!(ents.contains(&(e, None, 123)));
|
||||
assert!(ents.contains(&(f, Some(true), 456)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_entity() {
|
||||
let mut world = World::new();
|
||||
let mut entity = EntityBuilder::new();
|
||||
entity.add("abc");
|
||||
entity.add(123);
|
||||
let e = world.spawn(entity.build());
|
||||
entity.add("def");
|
||||
entity.add([0u8; 1024]);
|
||||
entity.add(456);
|
||||
let f = world.spawn(entity.build());
|
||||
assert_eq!(*world.get::<&str>(e).unwrap(), "abc");
|
||||
assert_eq!(*world.get::<i32>(e).unwrap(), 123);
|
||||
assert_eq!(*world.get::<&str>(f).unwrap(), "def");
|
||||
assert_eq!(*world.get::<i32>(f).unwrap(), 456);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dynamic_components() {
|
||||
let mut world = World::new();
|
||||
let e = world.spawn((42,));
|
||||
world.insert(e, (true, "abc")).unwrap();
|
||||
assert_eq!(
|
||||
world
|
||||
.query::<(Entity, &i32, &bool)>()
|
||||
.map(|(e, &i, &b)| (e, i, b))
|
||||
.collect::<Vec<_>>(),
|
||||
&[(e, 42, true)]
|
||||
);
|
||||
assert_eq!(world.remove_one::<i32>(e), Ok(42));
|
||||
assert_eq!(
|
||||
world
|
||||
.query::<(Entity, &i32, &bool)>()
|
||||
.map(|(e, &i, &b)| (e, i, b))
|
||||
.collect::<Vec<_>>(),
|
||||
&[]
|
||||
);
|
||||
assert_eq!(
|
||||
world
|
||||
.query::<(Entity, &bool, &&str)>()
|
||||
.map(|(e, &b, &s)| (e, b, s))
|
||||
.collect::<Vec<_>>(),
|
||||
&[(e, true, "abc")]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shared_borrow() {
|
||||
let mut world = World::new();
|
||||
world.spawn(("abc", 123));
|
||||
world.spawn(("def", 456));
|
||||
|
||||
world.query::<(&i32, &i32)>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "macros")]
|
||||
fn derived_bundle() {
|
||||
#[derive(Bundle)]
|
||||
struct Foo {
|
||||
x: i32,
|
||||
y: f64,
|
||||
}
|
||||
|
||||
let mut world = World::new();
|
||||
let e = world.spawn(Foo { x: 42, y: 1.0 });
|
||||
assert_eq!(*world.get::<i32>(e).unwrap(), 42);
|
||||
assert_eq!(*world.get::<f64>(e).unwrap(), 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "macros")]
|
||||
#[cfg_attr(
|
||||
debug_assertions,
|
||||
should_panic(
|
||||
expected = "attempted to allocate entity with duplicate i32 components; each type must occur at most once!"
|
||||
)
|
||||
)]
|
||||
#[cfg_attr(
|
||||
not(debug_assertions),
|
||||
should_panic(
|
||||
expected = "attempted to allocate entity with duplicate components; each type must occur at most once!"
|
||||
)
|
||||
)]
|
||||
fn bad_bundle_derive() {
|
||||
#[derive(Bundle)]
|
||||
struct Foo {
|
||||
x: i32,
|
||||
y: i32,
|
||||
}
|
||||
|
||||
let mut world = World::new();
|
||||
world.spawn(Foo { x: 42, y: 42 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn spawn_many() {
|
||||
let mut world = World::new();
|
||||
const N: usize = 100_000;
|
||||
for _ in 0..N {
|
||||
world.spawn((42u128,));
|
||||
}
|
||||
assert_eq!(world.iter().count(), N);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear() {
|
||||
let mut world = World::new();
|
||||
world.spawn(("abc", 123));
|
||||
world.spawn(("def", 456, true));
|
||||
world.clear();
|
||||
assert_eq!(world.iter().count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_missing() {
|
||||
let mut world = World::new();
|
||||
let e = world.spawn(("abc", 123));
|
||||
assert!(world.remove_one::<bool>(e).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn query_batched() {
|
||||
let mut world = World::new();
|
||||
let a = world.spawn(());
|
||||
let b = world.spawn(());
|
||||
let c = world.spawn((42,));
|
||||
assert_eq!(world.query_batched::<()>(1).count(), 3);
|
||||
assert_eq!(world.query_batched::<()>(2).count(), 2);
|
||||
assert_eq!(world.query_batched::<()>(2).flat_map(|x| x).count(), 3);
|
||||
// different archetypes are always in different batches
|
||||
assert_eq!(world.query_batched::<()>(3).count(), 2);
|
||||
assert_eq!(world.query_batched::<()>(3).flat_map(|x| x).count(), 3);
|
||||
assert_eq!(world.query_batched::<()>(4).count(), 2);
|
||||
let entities = world
|
||||
.query_batched::<Entity>(1)
|
||||
.flat_map(|x| x)
|
||||
.map(|e| e)
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(entities.len(), 3);
|
||||
assert!(entities.contains(&a));
|
||||
assert!(entities.contains(&b));
|
||||
assert!(entities.contains(&c));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_batch() {
|
||||
let mut world = World::new();
|
||||
world.spawn_batch((0..100).map(|x| (x, "abc")));
|
||||
let entities = world.query::<&i32>().map(|&x| x).collect::<Vec<_>>();
|
||||
assert_eq!(entities.len(), 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn query_one() {
|
||||
let mut world = World::new();
|
||||
let a = world.spawn(("abc", 123));
|
||||
let b = world.spawn(("def", 456));
|
||||
let c = world.spawn(("ghi", 789, true));
|
||||
assert_eq!(world.query_one::<&i32>(a), Ok(&123));
|
||||
assert_eq!(world.query_one::<&i32>(b), Ok(&456));
|
||||
assert!(world.query_one::<(&i32, &bool)>(a).is_err());
|
||||
assert_eq!(world.query_one::<(&i32, &bool)>(c), Ok((&789, &true)));
|
||||
world.despawn(a).unwrap();
|
||||
assert!(world.query_one::<&i32>(a).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_tracking() {
|
||||
let mut world = World::new();
|
||||
let a = world.spawn(("abc", 123));
|
||||
let b = world.spawn(("abc", 123));
|
||||
|
||||
world.despawn(a).unwrap();
|
||||
assert_eq!(
|
||||
world.removed::<i32>(),
|
||||
&[a],
|
||||
"despawning results in 'removed component' state"
|
||||
);
|
||||
assert_eq!(
|
||||
world.removed::<&'static str>(),
|
||||
&[a],
|
||||
"despawning results in 'removed component' state"
|
||||
);
|
||||
|
||||
world.insert_one(b, 10.0).unwrap();
|
||||
assert_eq!(
|
||||
world.removed::<i32>(),
|
||||
&[a],
|
||||
"archetype moves does not result in 'removed component' state"
|
||||
);
|
||||
|
||||
world.remove_one::<i32>(b).unwrap();
|
||||
assert_eq!(
|
||||
world.removed::<i32>(),
|
||||
&[a, b],
|
||||
"removing a component results in a 'removed component' state"
|
||||
);
|
||||
|
||||
world.clear_trackers();
|
||||
assert_eq!(
|
||||
world.removed::<i32>(),
|
||||
&[],
|
||||
"clearning trackers clears removals"
|
||||
);
|
||||
assert_eq!(
|
||||
world.removed::<&'static str>(),
|
||||
&[],
|
||||
"clearning trackers clears removals"
|
||||
);
|
||||
assert_eq!(
|
||||
world.removed::<f64>(),
|
||||
&[],
|
||||
"clearning trackers clears removals"
|
||||
);
|
||||
|
||||
let c = world.spawn(("abc", 123));
|
||||
let d = world.spawn(("abc", 123));
|
||||
world.clear();
|
||||
assert_eq!(
|
||||
world.removed::<i32>(),
|
||||
&[c, d],
|
||||
"world clears result in 'removed component' states"
|
||||
);
|
||||
assert_eq!(
|
||||
world.removed::<&'static str>(),
|
||||
&[c, d, b],
|
||||
"world clears result in 'removed component' states"
|
||||
);
|
||||
assert_eq!(
|
||||
world.removed::<f64>(),
|
||||
&[b],
|
||||
"world clears result in 'removed component' states"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn added_tracking() {
|
||||
let mut world = World::new();
|
||||
let a = world.spawn((123,));
|
||||
|
||||
assert_eq!(world.query::<&i32>().count(), 1);
|
||||
assert_eq!(world.query_filtered::<(), Added<i32>>().count(), 1);
|
||||
assert_eq!(world.query_mut::<&i32>().count(), 1);
|
||||
assert_eq!(world.query_filtered_mut::<(), Added<i32>>().count(), 1);
|
||||
assert!(world.query_one::<&i32>(a).is_ok());
|
||||
assert!(world.query_one_filtered::<(), Added<i32>>(a).is_ok());
|
||||
assert!(world.query_one_mut::<&i32>(a).is_ok());
|
||||
assert!(world.query_one_filtered_mut::<(), Added<i32>>(a).is_ok());
|
||||
|
||||
world.clear_trackers();
|
||||
|
||||
assert_eq!(world.query::<&i32>().count(), 1);
|
||||
assert_eq!(world.query_filtered::<(), Added<i32>>().count(), 0);
|
||||
assert_eq!(world.query_mut::<&i32>().count(), 1);
|
||||
assert_eq!(world.query_filtered_mut::<(), Added<i32>>().count(), 0);
|
||||
assert!(world.query_one_mut::<&i32>(a).is_ok());
|
||||
assert!(world.query_one_filtered_mut::<(), Added<i32>>(a).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(
|
||||
debug_assertions,
|
||||
should_panic(
|
||||
expected = "attempted to allocate entity with duplicate f32 components; each type must occur at most once!"
|
||||
)
|
||||
)]
|
||||
#[cfg_attr(
|
||||
not(debug_assertions),
|
||||
should_panic(
|
||||
expected = "attempted to allocate entity with duplicate components; each type must occur at most once!"
|
||||
)
|
||||
)]
|
||||
fn duplicate_components_panic() {
|
||||
let mut world = World::new();
|
||||
world.reserve::<(f32, i64, f32)>(1);
|
||||
}
|
900
crates/bevy_ecs/src/world/entity_ref.rs
Normal file
900
crates/bevy_ecs/src/world/entity_ref.rs
Normal file
|
@ -0,0 +1,900 @@
|
|||
use crate::{
|
||||
archetype::{Archetype, ArchetypeId, Archetypes},
|
||||
bundle::{Bundle, BundleInfo},
|
||||
component::{Component, ComponentFlags, ComponentId, Components, StorageType},
|
||||
entity::{Entity, EntityLocation},
|
||||
storage::{SparseSet, Storages},
|
||||
world::{Mut, World},
|
||||
};
|
||||
use std::any::TypeId;
|
||||
|
||||
pub struct EntityRef<'w> {
|
||||
world: &'w World,
|
||||
entity: Entity,
|
||||
location: EntityLocation,
|
||||
}
|
||||
|
||||
impl<'w> EntityRef<'w> {
|
||||
#[inline]
|
||||
pub(crate) fn new(world: &'w World, entity: Entity, location: EntityLocation) -> Self {
|
||||
Self {
|
||||
world,
|
||||
entity,
|
||||
location,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> Entity {
|
||||
self.entity
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn location(&self) -> EntityLocation {
|
||||
self.location
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn archetype(&self) -> &Archetype {
|
||||
// SAFE: EntityRefs always point to valid entities. Valid entities always have valid archetypes
|
||||
unsafe {
|
||||
self.world
|
||||
.archetypes
|
||||
.get_unchecked(self.location.archetype_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn world(&mut self) -> &World {
|
||||
self.world
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn contains<T: Component>(&self) -> bool {
|
||||
self.contains_type_id(TypeId::of::<T>())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn contains_id(&self, component_id: ComponentId) -> bool {
|
||||
// SAFE: entity location is valid
|
||||
unsafe { contains_component_with_id(self.world, component_id, self.location) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn contains_type_id(&self, type_id: TypeId) -> bool {
|
||||
// SAFE: entity location is valid
|
||||
unsafe { contains_component_with_type(self.world, type_id, self.location) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get<T: Component>(&self) -> Option<&'w T> {
|
||||
// SAFE: entity location is valid and returned component is of type T
|
||||
unsafe {
|
||||
get_component_with_type(self.world, TypeId::of::<T>(), self.entity, self.location)
|
||||
.map(|value| &*value.cast::<T>())
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// This allows aliased mutability. You must make sure this call does not result in multiple mutable references to the same component
|
||||
#[inline]
|
||||
pub unsafe fn get_unchecked_mut<T: Component>(&self) -> Option<Mut<'w, T>> {
|
||||
get_component_and_flags_with_type(self.world, TypeId::of::<T>(), self.entity, self.location)
|
||||
.map(|(value, flags)| Mut {
|
||||
value: &mut *value.cast::<T>(),
|
||||
flags: &mut *flags,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EntityMut<'w> {
|
||||
world: &'w mut World,
|
||||
entity: Entity,
|
||||
location: EntityLocation,
|
||||
}
|
||||
|
||||
impl<'w> EntityMut<'w> {
|
||||
/// # Safety
|
||||
/// entity and location _must_ be valid
|
||||
#[inline]
|
||||
pub(crate) unsafe fn new(
|
||||
world: &'w mut World,
|
||||
entity: Entity,
|
||||
location: EntityLocation,
|
||||
) -> Self {
|
||||
EntityMut {
|
||||
world,
|
||||
entity,
|
||||
location,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> Entity {
|
||||
self.entity
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn location(&self) -> EntityLocation {
|
||||
self.location
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn archetype(&self) -> &Archetype {
|
||||
// SAFE: EntityRefs always point to valid entities. Valid entities always have valid archetypes
|
||||
unsafe {
|
||||
self.world
|
||||
.archetypes
|
||||
.get_unchecked(self.location.archetype_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn contains<T: Component>(&self) -> bool {
|
||||
self.contains_type_id(TypeId::of::<T>())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn contains_id(&self, component_id: ComponentId) -> bool {
|
||||
// SAFE: entity location is valid
|
||||
unsafe { contains_component_with_id(self.world, component_id, self.location) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn contains_type_id(&self, type_id: TypeId) -> bool {
|
||||
// SAFE: entity location is valid
|
||||
unsafe { contains_component_with_type(self.world, type_id, self.location) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get<T: Component>(&self) -> Option<&'w T> {
|
||||
// SAFE: entity location is valid and returned component is of type T
|
||||
unsafe {
|
||||
get_component_with_type(self.world, TypeId::of::<T>(), self.entity, self.location)
|
||||
.map(|value| &*value.cast::<T>())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_mut<T: Component>(&mut self) -> Option<Mut<'w, T>> {
|
||||
// SAFE: world access is unique, entity location is valid, and returned component is of type T
|
||||
unsafe {
|
||||
get_component_and_flags_with_type(
|
||||
self.world,
|
||||
TypeId::of::<T>(),
|
||||
self.entity,
|
||||
self.location,
|
||||
)
|
||||
.map(|(value, flags)| Mut {
|
||||
value: &mut *value.cast::<T>(),
|
||||
flags: &mut *flags,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// This allows aliased mutability. You must make sure this call does not result in multiple mutable references to the same component
|
||||
#[inline]
|
||||
pub unsafe fn get_unchecked_mut<T: Component>(&self) -> Option<Mut<'w, T>> {
|
||||
get_component_and_flags_with_type(self.world, TypeId::of::<T>(), self.entity, self.location)
|
||||
.map(|(value, flags)| Mut {
|
||||
value: &mut *value.cast::<T>(),
|
||||
flags: &mut *flags,
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: factor out non-generic part to cut down on monomorphization (just check perf)
|
||||
// TODO: move relevant methods to World (add/remove bundle)
|
||||
pub fn insert_bundle<T: Bundle>(&mut self, bundle: T) -> &mut Self {
|
||||
let entity = self.entity;
|
||||
let entities = &mut self.world.entities;
|
||||
let archetypes = &mut self.world.archetypes;
|
||||
let components = &mut self.world.components;
|
||||
let storages = &mut self.world.storages;
|
||||
|
||||
let bundle_info = self.world.bundles.init_info::<T>(components);
|
||||
let current_location = self.location;
|
||||
|
||||
let new_location = unsafe {
|
||||
// SAFE: component ids in `bundle_info` and self.location are valid
|
||||
let new_archetype_id = add_bundle_to_archetype(
|
||||
archetypes,
|
||||
storages,
|
||||
components,
|
||||
self.location.archetype_id,
|
||||
bundle_info,
|
||||
);
|
||||
if new_archetype_id == current_location.archetype_id {
|
||||
current_location
|
||||
} else {
|
||||
let old_table_row;
|
||||
let old_table_id;
|
||||
{
|
||||
let old_archetype = archetypes.get_unchecked_mut(current_location.archetype_id);
|
||||
let result = old_archetype.swap_remove(current_location.index);
|
||||
if let Some(swapped_entity) = result.swapped_entity {
|
||||
// SAFE: entity is live and is contained in an archetype that exists
|
||||
entities.meta[swapped_entity.id as usize].location = current_location;
|
||||
}
|
||||
old_table_row = result.table_row;
|
||||
old_table_id = old_archetype.table_id()
|
||||
}
|
||||
let new_archetype = archetypes.get_unchecked_mut(new_archetype_id);
|
||||
|
||||
if old_table_id == new_archetype.table_id() {
|
||||
new_archetype.allocate(entity, old_table_row)
|
||||
} else {
|
||||
let (old_table, new_table) = storages
|
||||
.tables
|
||||
.get_2_mut(old_table_id, new_archetype.table_id());
|
||||
// PERF: store "non bundle" components in edge, then just move those to avoid redundant copies
|
||||
let move_result =
|
||||
old_table.move_to_superset_unchecked(old_table_row, new_table);
|
||||
|
||||
let new_location = new_archetype.allocate(entity, move_result.new_row);
|
||||
// if an entity was moved into this entity's table spot, update its table row
|
||||
if let Some(swapped_entity) = move_result.swapped_entity {
|
||||
let swapped_location = entities.get(swapped_entity).unwrap();
|
||||
// SAFE: entity is live and is therefore contained in an archetype that exists
|
||||
archetypes
|
||||
.get_unchecked_mut(swapped_location.archetype_id)
|
||||
.set_entity_table_row(swapped_location.index, old_table_row);
|
||||
}
|
||||
new_location
|
||||
}
|
||||
|
||||
// Sparse set components are intentionally ignored here. They don't need to move
|
||||
}
|
||||
};
|
||||
self.location = new_location;
|
||||
entities.meta[self.entity.id as usize].location = new_location;
|
||||
|
||||
// SAFE: archetype was created if it didn't already exist
|
||||
let archetype = unsafe { archetypes.get_unchecked_mut(new_location.archetype_id) };
|
||||
// SAFE: archetype tables always exists
|
||||
let table = unsafe { storages.tables.get_unchecked_mut(archetype.table_id()) };
|
||||
let table_row = archetype.entity_table_row(new_location.index);
|
||||
let from_bundle = archetype.edges().get_from_bundle(bundle_info.id).unwrap();
|
||||
// SAFE: table row is valid
|
||||
unsafe {
|
||||
bundle_info.write_components(
|
||||
&mut storages.sparse_sets,
|
||||
entity,
|
||||
table,
|
||||
table_row,
|
||||
&from_bundle.bundle_flags,
|
||||
bundle,
|
||||
)
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
pub fn remove_bundle<T: Bundle>(&mut self) -> Option<T> {
|
||||
let archetypes = &mut self.world.archetypes;
|
||||
let storages = &mut self.world.storages;
|
||||
let components = &mut self.world.components;
|
||||
let entities = &mut self.world.entities;
|
||||
let removed_components = &mut self.world.removed_components;
|
||||
|
||||
let bundle_info = self.world.bundles.init_info::<T>(components);
|
||||
let old_location = self.location;
|
||||
let new_archetype_id = unsafe {
|
||||
remove_bundle_from_archetype(
|
||||
archetypes,
|
||||
storages,
|
||||
components,
|
||||
old_location.archetype_id,
|
||||
bundle_info,
|
||||
false,
|
||||
)?
|
||||
};
|
||||
|
||||
if new_archetype_id == old_location.archetype_id {
|
||||
return None;
|
||||
}
|
||||
|
||||
// SAFE: current entity archetype is valid
|
||||
let old_archetype = unsafe { archetypes.get_unchecked_mut(old_location.archetype_id) };
|
||||
let mut bundle_components = bundle_info.component_ids.iter().cloned();
|
||||
let entity = self.entity;
|
||||
// SAFE: bundle components are iterated in order, which guarantees that the component type matches
|
||||
let result = unsafe {
|
||||
T::from_components(|| {
|
||||
let component_id = bundle_components.next().unwrap();
|
||||
// SAFE: entity location is valid and table row is removed below
|
||||
remove_component(
|
||||
components,
|
||||
storages,
|
||||
old_archetype,
|
||||
removed_components,
|
||||
component_id,
|
||||
entity,
|
||||
old_location,
|
||||
)
|
||||
})
|
||||
};
|
||||
|
||||
let remove_result = old_archetype.swap_remove(old_location.index);
|
||||
if let Some(swapped_entity) = remove_result.swapped_entity {
|
||||
entities.meta[swapped_entity.id as usize].location = old_location;
|
||||
}
|
||||
let old_table_row = remove_result.table_row;
|
||||
let old_table_id = old_archetype.table_id();
|
||||
// SAFE: new archetype exists thanks to remove_bundle_from_archetype
|
||||
let new_archetype = unsafe { archetypes.get_unchecked_mut(new_archetype_id) };
|
||||
|
||||
let new_location = if old_table_id == new_archetype.table_id() {
|
||||
unsafe { new_archetype.allocate(entity, old_table_row) }
|
||||
} else {
|
||||
let (old_table, new_table) = storages
|
||||
.tables
|
||||
.get_2_mut(old_table_id, new_archetype.table_id());
|
||||
|
||||
// SAFE: table_row exists. All "missing" components have been extracted into the bundle above and the caller takes ownership
|
||||
let move_result =
|
||||
unsafe { old_table.move_to_and_forget_missing_unchecked(old_table_row, new_table) };
|
||||
|
||||
// SAFE: new_table_row is a valid position in new_archetype's table
|
||||
let new_location = unsafe { new_archetype.allocate(entity, move_result.new_row) };
|
||||
|
||||
// if an entity was moved into this entity's table spot, update its table row
|
||||
if let Some(swapped_entity) = move_result.swapped_entity {
|
||||
let swapped_location = entities.get(swapped_entity).unwrap();
|
||||
// SAFE: entity is live and is contained in an archetype that exists
|
||||
let archetype =
|
||||
unsafe { archetypes.get_unchecked_mut(swapped_location.archetype_id) };
|
||||
archetype.set_entity_table_row(swapped_location.index, old_table_row);
|
||||
}
|
||||
|
||||
new_location
|
||||
};
|
||||
|
||||
self.location = new_location;
|
||||
entities.meta[self.entity.id as usize].location = new_location;
|
||||
|
||||
Some(result)
|
||||
}
|
||||
|
||||
/// Remove any components in the bundle that the entity has.
|
||||
pub fn remove_bundle_intersection<T: Bundle>(&mut self) {
|
||||
let archetypes = &mut self.world.archetypes;
|
||||
let storages = &mut self.world.storages;
|
||||
let components = &mut self.world.components;
|
||||
let entities = &mut self.world.entities;
|
||||
let removed_components = &mut self.world.removed_components;
|
||||
|
||||
let bundle_info = self.world.bundles.init_info::<T>(components);
|
||||
let old_location = self.location;
|
||||
let new_archetype_id = unsafe {
|
||||
remove_bundle_from_archetype(
|
||||
archetypes,
|
||||
storages,
|
||||
components,
|
||||
old_location.archetype_id,
|
||||
bundle_info,
|
||||
true,
|
||||
)
|
||||
.expect("intersections should always return a result")
|
||||
};
|
||||
|
||||
if new_archetype_id == old_location.archetype_id {
|
||||
return;
|
||||
}
|
||||
|
||||
// SAFE: current entity archetype is valid
|
||||
let old_archetype = unsafe { archetypes.get_unchecked_mut(old_location.archetype_id) };
|
||||
let entity = self.entity;
|
||||
for component_id in bundle_info.component_ids.iter().cloned() {
|
||||
if old_archetype.contains(component_id) {
|
||||
// SAFE: entity location is valid and table row is removed below
|
||||
unsafe {
|
||||
remove_component(
|
||||
components,
|
||||
storages,
|
||||
old_archetype,
|
||||
removed_components,
|
||||
component_id,
|
||||
entity,
|
||||
old_location,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let remove_result = old_archetype.swap_remove(old_location.index);
|
||||
if let Some(swapped_entity) = remove_result.swapped_entity {
|
||||
entities.meta[swapped_entity.id as usize].location = old_location;
|
||||
}
|
||||
let old_table_row = remove_result.table_row;
|
||||
let old_table_id = old_archetype.table_id();
|
||||
// SAFE: new archetype exists thanks to remove_bundle_from_archetype
|
||||
let new_archetype = unsafe { archetypes.get_unchecked_mut(new_archetype_id) };
|
||||
|
||||
let new_location = if old_table_id == new_archetype.table_id() {
|
||||
unsafe { new_archetype.allocate(entity, old_table_row) }
|
||||
} else {
|
||||
let (old_table, new_table) = storages
|
||||
.tables
|
||||
.get_2_mut(old_table_id, new_archetype.table_id());
|
||||
|
||||
// SAFE: table_row exists
|
||||
let move_result =
|
||||
unsafe { old_table.move_to_and_drop_missing_unchecked(old_table_row, new_table) };
|
||||
|
||||
// SAFE: new_table_row is a valid position in new_archetype's table
|
||||
let new_location = unsafe { new_archetype.allocate(entity, move_result.new_row) };
|
||||
|
||||
// if an entity was moved into this entity's table spot, update its table row
|
||||
if let Some(swapped_entity) = move_result.swapped_entity {
|
||||
let swapped_location = entities.get(swapped_entity).unwrap();
|
||||
// SAFE: entity is live and is contained in an archetype that exists
|
||||
let archetype =
|
||||
unsafe { archetypes.get_unchecked_mut(swapped_location.archetype_id) };
|
||||
archetype.set_entity_table_row(swapped_location.index, old_table_row);
|
||||
}
|
||||
|
||||
new_location
|
||||
};
|
||||
|
||||
self.location = new_location;
|
||||
entities.meta[self.entity.id as usize].location = new_location;
|
||||
}
|
||||
|
||||
pub fn insert<T: Component>(&mut self, value: T) -> &mut Self {
|
||||
self.insert_bundle((value,))
|
||||
}
|
||||
|
||||
pub fn remove<T: Component>(&mut self) -> Option<T> {
|
||||
self.remove_bundle::<(T,)>().map(|v| v.0)
|
||||
}
|
||||
|
||||
pub fn despawn(self) {
|
||||
let world = self.world;
|
||||
world.flush();
|
||||
let location = world
|
||||
.entities
|
||||
.free(self.entity)
|
||||
.expect("entity should exist at this point.");
|
||||
let table_row;
|
||||
let moved_entity;
|
||||
{
|
||||
// SAFE: entity is live and is contained in an archetype that exists
|
||||
let archetype = unsafe { world.archetypes.get_unchecked_mut(location.archetype_id) };
|
||||
for component_id in archetype.components() {
|
||||
let removed_components = world
|
||||
.removed_components
|
||||
.get_or_insert_with(component_id, Vec::new);
|
||||
removed_components.push(self.entity);
|
||||
}
|
||||
let remove_result = archetype.swap_remove(location.index);
|
||||
if let Some(swapped_entity) = remove_result.swapped_entity {
|
||||
world.entities.meta[swapped_entity.id as usize].location = location;
|
||||
}
|
||||
table_row = remove_result.table_row;
|
||||
|
||||
for component_id in archetype.sparse_set_components() {
|
||||
let sparse_set = world.storages.sparse_sets.get_mut(*component_id).unwrap();
|
||||
sparse_set.remove(self.entity);
|
||||
}
|
||||
// SAFE: tables and table rows stored in archetypes always exist
|
||||
moved_entity = unsafe {
|
||||
world
|
||||
.storages
|
||||
.tables
|
||||
.get_unchecked_mut(archetype.table_id())
|
||||
.swap_remove_unchecked(table_row)
|
||||
};
|
||||
};
|
||||
|
||||
if let Some(moved_entity) = moved_entity {
|
||||
let moved_location = world.entities.get(moved_entity).unwrap();
|
||||
// SAFE: entity is live and is contained in an archetype that exists
|
||||
let archetype = unsafe {
|
||||
world
|
||||
.archetypes
|
||||
.get_unchecked_mut(moved_location.archetype_id)
|
||||
};
|
||||
archetype.set_entity_table_row(moved_location.index, table_row);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn world(&mut self) -> &World {
|
||||
self.world
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// Caller must not modify the world in a way that changes the current entity's location
|
||||
/// If the caller _does_ do something that could change the location, self.update_location() must be
|
||||
/// called before using any other methods in EntityMut
|
||||
#[inline]
|
||||
pub unsafe fn world_mut(&mut self) -> &mut World {
|
||||
self.world
|
||||
}
|
||||
|
||||
/// Updates the internal entity location to match the current location in the internal [World].
|
||||
/// This is only needed if the user called [EntityMut::world], which enables the location to change.
|
||||
pub fn update_location(&mut self) {
|
||||
self.location = self.world.entities().get(self.entity).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `entity_location` must be within bounds of the given archetype and `entity` must exist inside the archetype
|
||||
#[inline]
|
||||
unsafe fn get_component(
|
||||
world: &World,
|
||||
component_id: ComponentId,
|
||||
entity: Entity,
|
||||
location: EntityLocation,
|
||||
) -> Option<*mut u8> {
|
||||
let archetype = world.archetypes.get_unchecked(location.archetype_id);
|
||||
// SAFE: component_id exists and is therefore valid
|
||||
let component_info = world.components.get_info_unchecked(component_id);
|
||||
match component_info.storage_type() {
|
||||
StorageType::Table => {
|
||||
// SAFE: tables stored in archetype always exist
|
||||
let table = world.storages.tables.get_unchecked(archetype.table_id());
|
||||
let components = table.get_column(component_id)?;
|
||||
let table_row = archetype.entity_table_row(location.index);
|
||||
// SAFE: archetypes only store valid table_rows and the stored component type is T
|
||||
Some(components.get_unchecked(table_row))
|
||||
}
|
||||
StorageType::SparseSet => world
|
||||
.storages
|
||||
.sparse_sets
|
||||
.get(component_id)
|
||||
.and_then(|sparse_set| sparse_set.get(entity)),
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// Caller must ensure that `component_id` is valid
|
||||
#[inline]
|
||||
unsafe fn get_component_and_flags(
|
||||
world: &World,
|
||||
component_id: ComponentId,
|
||||
entity: Entity,
|
||||
location: EntityLocation,
|
||||
) -> Option<(*mut u8, *mut ComponentFlags)> {
|
||||
let archetype = world.archetypes.get_unchecked(location.archetype_id);
|
||||
let component_info = world.components.get_info_unchecked(component_id);
|
||||
match component_info.storage_type() {
|
||||
StorageType::Table => {
|
||||
// SAFE: tables stored in archetype always exist
|
||||
let table = world.storages.tables.get_unchecked(archetype.table_id());
|
||||
let components = table.get_column(component_id)?;
|
||||
let table_row = archetype.entity_table_row(location.index);
|
||||
// SAFE: archetypes only store valid table_rows and the stored component type is T
|
||||
Some((
|
||||
components.get_unchecked(table_row),
|
||||
components.get_flags_unchecked(table_row),
|
||||
))
|
||||
}
|
||||
StorageType::SparseSet => world
|
||||
.storages
|
||||
.sparse_sets
|
||||
.get(component_id)
|
||||
.and_then(|sparse_set| sparse_set.get_with_flags(entity)),
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
// `entity_location` must be within bounds of the given archetype and `entity` must exist inside the archetype
|
||||
/// The relevant table row must be removed separately
|
||||
/// `component_id` must be valid
|
||||
#[inline]
|
||||
unsafe fn remove_component(
|
||||
components: &Components,
|
||||
storages: &mut Storages,
|
||||
archetype: &Archetype,
|
||||
removed_components: &mut SparseSet<ComponentId, Vec<Entity>>,
|
||||
component_id: ComponentId,
|
||||
entity: Entity,
|
||||
location: EntityLocation,
|
||||
) -> *mut u8 {
|
||||
let component_info = components.get_info_unchecked(component_id);
|
||||
let removed_components = removed_components.get_or_insert_with(component_id, Vec::new);
|
||||
removed_components.push(entity);
|
||||
match component_info.storage_type() {
|
||||
StorageType::Table => {
|
||||
// SAFE: tables stored in archetype always exist
|
||||
let table = storages.tables.get_unchecked(archetype.table_id());
|
||||
// SAFE: archetypes will always point to valid columns
|
||||
let components = table.get_column(component_id).unwrap();
|
||||
let table_row = archetype.entity_table_row(location.index);
|
||||
// SAFE: archetypes only store valid table_rows and the stored component type is T
|
||||
components.get_unchecked(table_row)
|
||||
}
|
||||
StorageType::SparseSet => storages
|
||||
.sparse_sets
|
||||
.get_mut(component_id)
|
||||
.unwrap()
|
||||
.remove_and_forget(entity)
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `entity_location` must be within bounds of an archetype that exists.
|
||||
unsafe fn get_component_with_type(
|
||||
world: &World,
|
||||
type_id: TypeId,
|
||||
entity: Entity,
|
||||
location: EntityLocation,
|
||||
) -> Option<*mut u8> {
|
||||
let component_id = world.components.get_id(type_id)?;
|
||||
get_component(world, component_id, entity, location)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `entity_location` must be within bounds of an archetype that exists.
|
||||
pub(crate) unsafe fn get_component_and_flags_with_type(
|
||||
world: &World,
|
||||
type_id: TypeId,
|
||||
entity: Entity,
|
||||
location: EntityLocation,
|
||||
) -> Option<(*mut u8, *mut ComponentFlags)> {
|
||||
let component_id = world.components.get_id(type_id)?;
|
||||
get_component_and_flags(world, component_id, entity, location)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `entity_location` must be within bounds of an archetype that exists.
|
||||
unsafe fn contains_component_with_type(
|
||||
world: &World,
|
||||
type_id: TypeId,
|
||||
location: EntityLocation,
|
||||
) -> bool {
|
||||
if let Some(component_id) = world.components.get_id(type_id) {
|
||||
contains_component_with_id(world, component_id, location)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `entity_location` must be within bounds of an archetype that exists.
|
||||
unsafe fn contains_component_with_id(
|
||||
world: &World,
|
||||
component_id: ComponentId,
|
||||
location: EntityLocation,
|
||||
) -> bool {
|
||||
world
|
||||
.archetypes
|
||||
.get_unchecked(location.archetype_id)
|
||||
.contains(component_id)
|
||||
}
|
||||
|
||||
/// Adds a bundle to the given archetype and returns the resulting archetype. This could be the same [ArchetypeId],
|
||||
/// in the event that adding the given bundle does not result in an Archetype change. Results are cached in the
|
||||
/// Archetype Graph to avoid redundant work.
|
||||
/// # Safety
|
||||
/// `archetype_id` must exist and components in `bundle_info` must exist
|
||||
pub(crate) unsafe fn add_bundle_to_archetype(
|
||||
archetypes: &mut Archetypes,
|
||||
storages: &mut Storages,
|
||||
components: &mut Components,
|
||||
archetype_id: ArchetypeId,
|
||||
bundle_info: &BundleInfo,
|
||||
) -> ArchetypeId {
|
||||
if let Some(archetype_id) = archetypes
|
||||
.get_unchecked(archetype_id)
|
||||
.edges()
|
||||
.get_add_bundle(bundle_info.id)
|
||||
{
|
||||
return archetype_id;
|
||||
}
|
||||
let mut new_table_components = Vec::new();
|
||||
let mut new_sparse_set_components = Vec::new();
|
||||
let mut tracking_flags = Vec::with_capacity(bundle_info.component_ids.len());
|
||||
|
||||
let current_archetype = archetypes.get_unchecked_mut(archetype_id);
|
||||
for component_id in bundle_info.component_ids.iter().cloned() {
|
||||
if current_archetype.contains(component_id) {
|
||||
tracking_flags.push(ComponentFlags::MUTATED);
|
||||
} else {
|
||||
tracking_flags.push(ComponentFlags::ADDED);
|
||||
let component_info = components.get_info_unchecked(component_id);
|
||||
match component_info.storage_type() {
|
||||
StorageType::Table => new_table_components.push(component_id),
|
||||
StorageType::SparseSet => {
|
||||
storages.sparse_sets.get_or_insert(component_info);
|
||||
new_sparse_set_components.push(component_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if new_table_components.is_empty() && new_sparse_set_components.is_empty() {
|
||||
let edges = current_archetype.edges_mut();
|
||||
// the archetype does not change when we add this bundle
|
||||
edges.set_add_bundle(bundle_info.id, archetype_id);
|
||||
edges.set_from_bundle(bundle_info.id, archetype_id, tracking_flags);
|
||||
archetype_id
|
||||
} else {
|
||||
let table_id;
|
||||
let table_components;
|
||||
let sparse_set_components;
|
||||
// the archetype changes when we add this bundle. prepare the new archetype and storages
|
||||
{
|
||||
let current_archetype = archetypes.get_unchecked_mut(archetype_id);
|
||||
table_components = if new_table_components.is_empty() {
|
||||
// if there are no new table components, we can keep using this table
|
||||
table_id = current_archetype.table_id();
|
||||
current_archetype.table_components().to_vec()
|
||||
} else {
|
||||
new_table_components.extend(current_archetype.table_components());
|
||||
// sort to ignore order while hashing
|
||||
new_table_components.sort();
|
||||
// SAFE: all component ids in `new_table_components` exist
|
||||
table_id = storages
|
||||
.tables
|
||||
.get_id_or_insert(&new_table_components, components);
|
||||
|
||||
new_table_components
|
||||
};
|
||||
|
||||
sparse_set_components = if new_sparse_set_components.is_empty() {
|
||||
current_archetype.sparse_set_components().to_vec()
|
||||
} else {
|
||||
new_sparse_set_components.extend(current_archetype.sparse_set_components());
|
||||
// sort to ignore order while hashing
|
||||
new_sparse_set_components.sort();
|
||||
new_sparse_set_components
|
||||
};
|
||||
};
|
||||
let new_archetype_id =
|
||||
archetypes.get_id_or_insert(table_id, table_components, sparse_set_components);
|
||||
// add an edge from the old archetype to the new archetype
|
||||
archetypes
|
||||
.get_unchecked_mut(archetype_id)
|
||||
.edges_mut()
|
||||
.set_add_bundle(bundle_info.id, new_archetype_id);
|
||||
// add a "from bundle" edge from the new archetype to the old archetype
|
||||
archetypes
|
||||
.get_unchecked_mut(new_archetype_id)
|
||||
.edges_mut()
|
||||
.set_from_bundle(bundle_info.id, new_archetype_id, tracking_flags);
|
||||
new_archetype_id
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes a bundle from the given archetype and returns the resulting archetype (or None if the removal was invalid).
|
||||
/// in the event that adding the given bundle does not result in an Archetype change. Results are cached in the
|
||||
/// Archetype Graph to avoid redundant work.
|
||||
/// if `intersection` is false, attempting to remove a bundle with components _not_ contained in the current archetype will fail,
|
||||
/// returning None. if `intersection` is true, components in the bundle but not in the current archetype will be ignored
|
||||
/// # Safety
|
||||
/// `archetype_id` must exist and components in `bundle_info` must exist
|
||||
unsafe fn remove_bundle_from_archetype(
|
||||
archetypes: &mut Archetypes,
|
||||
storages: &mut Storages,
|
||||
components: &mut Components,
|
||||
archetype_id: ArchetypeId,
|
||||
bundle_info: &BundleInfo,
|
||||
intersection: bool,
|
||||
) -> Option<ArchetypeId> {
|
||||
// check the archetype graph to see if the Bundle has been removed from this archetype in the past
|
||||
let remove_bundle_result = {
|
||||
// SAFE: entity location is valid and therefore the archetype exists
|
||||
let current_archetype = archetypes.get_unchecked_mut(archetype_id);
|
||||
if intersection {
|
||||
current_archetype
|
||||
.edges()
|
||||
.get_remove_bundle_intersection(bundle_info.id)
|
||||
} else {
|
||||
current_archetype.edges().get_remove_bundle(bundle_info.id)
|
||||
}
|
||||
};
|
||||
let result = if let Some(result) = remove_bundle_result {
|
||||
// this Bundle removal result is cached. just return that!
|
||||
result
|
||||
} else {
|
||||
let mut next_table_components;
|
||||
let mut next_sparse_set_components;
|
||||
let next_table_id;
|
||||
{
|
||||
// SAFE: entity location is valid and therefore the archetype exists
|
||||
let current_archetype = archetypes.get_unchecked_mut(archetype_id);
|
||||
let mut removed_table_components = Vec::new();
|
||||
let mut removed_sparse_set_components = Vec::new();
|
||||
for component_id in bundle_info.component_ids.iter().cloned() {
|
||||
if current_archetype.contains(component_id) {
|
||||
// SAFE: bundle components were already initialized by bundles.get_info
|
||||
let component_info = components.get_info_unchecked(component_id);
|
||||
match component_info.storage_type() {
|
||||
StorageType::Table => removed_table_components.push(component_id),
|
||||
StorageType::SparseSet => removed_sparse_set_components.push(component_id),
|
||||
}
|
||||
} else if !intersection {
|
||||
// a component in the bundle was not present in the entity's archetype, so this removal is invalid
|
||||
// cache the result in the archetype graph
|
||||
current_archetype
|
||||
.edges_mut()
|
||||
.set_remove_bundle(bundle_info.id, None);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
// sort removed components so we can do an efficient "sorted remove". archetype components are already sorted
|
||||
removed_table_components.sort();
|
||||
removed_sparse_set_components.sort();
|
||||
next_table_components = current_archetype.table_components().to_vec();
|
||||
next_sparse_set_components = current_archetype.sparse_set_components().to_vec();
|
||||
sorted_remove(&mut next_table_components, &removed_table_components);
|
||||
sorted_remove(
|
||||
&mut next_sparse_set_components,
|
||||
&removed_sparse_set_components,
|
||||
);
|
||||
|
||||
next_table_id = if removed_table_components.is_empty() {
|
||||
current_archetype.table_id()
|
||||
} else {
|
||||
// SAFE: all components in next_table_components exist
|
||||
storages
|
||||
.tables
|
||||
.get_id_or_insert(&next_table_components, components)
|
||||
};
|
||||
}
|
||||
|
||||
let new_archetype_id = archetypes.get_id_or_insert(
|
||||
next_table_id,
|
||||
next_table_components,
|
||||
next_sparse_set_components,
|
||||
);
|
||||
Some(new_archetype_id)
|
||||
};
|
||||
// SAFE: entity location is valid and therefore the archetype exists
|
||||
let current_archetype = archetypes.get_unchecked_mut(archetype_id);
|
||||
// cache the result in an edge
|
||||
if intersection {
|
||||
current_archetype
|
||||
.edges_mut()
|
||||
.set_remove_bundle_intersection(bundle_info.id, result);
|
||||
} else {
|
||||
current_archetype
|
||||
.edges_mut()
|
||||
.set_remove_bundle(bundle_info.id, result);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn sorted_remove<T: Eq + Ord + Copy>(source: &mut Vec<T>, remove: &[T]) {
|
||||
let mut remove_index = 0;
|
||||
source.retain(|value| {
|
||||
while remove_index < remove.len() && *value > remove[remove_index] {
|
||||
remove_index += 1;
|
||||
}
|
||||
|
||||
if remove_index < remove.len() {
|
||||
*value != remove[remove_index]
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn sorted_remove() {
|
||||
let mut a = vec![1, 2, 3, 4, 5, 6, 7];
|
||||
let b = vec![1, 2, 3, 5, 7];
|
||||
super::sorted_remove(&mut a, &b);
|
||||
|
||||
assert_eq!(a, vec![4, 6]);
|
||||
|
||||
let mut a = vec![1];
|
||||
let b = vec![1];
|
||||
super::sorted_remove(&mut a, &b);
|
||||
|
||||
assert_eq!(a, vec![]);
|
||||
|
||||
let mut a = vec![1];
|
||||
let b = vec![2];
|
||||
super::sorted_remove(&mut a, &b);
|
||||
|
||||
assert_eq!(a, vec![1]);
|
||||
}
|
||||
}
|
857
crates/bevy_ecs/src/world/mod.rs
Normal file
857
crates/bevy_ecs/src/world/mod.rs
Normal file
|
@ -0,0 +1,857 @@
|
|||
mod entity_ref;
|
||||
mod pointer;
|
||||
mod spawn_batch;
|
||||
mod world_cell;
|
||||
|
||||
pub use entity_ref::*;
|
||||
pub use pointer::*;
|
||||
pub use spawn_batch::*;
|
||||
pub use world_cell::*;
|
||||
|
||||
use crate::{
|
||||
archetype::{ArchetypeComponentId, ArchetypeComponentInfo, ArchetypeId, Archetypes},
|
||||
bundle::{Bundle, Bundles},
|
||||
component::{
|
||||
Component, ComponentDescriptor, ComponentFlags, ComponentId, Components, ComponentsError,
|
||||
StorageType,
|
||||
},
|
||||
entity::{Entities, Entity},
|
||||
query::{FilterFetch, QueryState, WorldQuery},
|
||||
storage::{Column, SparseSet, Storages},
|
||||
};
|
||||
use std::{any::TypeId, fmt};
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub struct WorldId(u64);
|
||||
|
||||
impl Default for WorldId {
|
||||
fn default() -> Self {
|
||||
WorldId(rand::random())
|
||||
}
|
||||
}
|
||||
|
||||
/// [World] stores and exposes operations on [entities](Entity), [components](Component), and their associated metadata.
|
||||
/// Each [Entity] has a set of components. Each component can have up to one instance of each component type.
|
||||
/// Entity components can be created, updated, removed, and queried using a given [World].
|
||||
#[derive(Default)]
|
||||
pub struct World {
|
||||
id: WorldId,
|
||||
pub(crate) entities: Entities,
|
||||
pub(crate) components: Components,
|
||||
pub(crate) archetypes: Archetypes,
|
||||
pub(crate) storages: Storages,
|
||||
pub(crate) bundles: Bundles,
|
||||
pub(crate) removed_components: SparseSet<ComponentId, Vec<Entity>>,
|
||||
/// Access cache used by [WorldCell].
|
||||
pub(crate) archetype_component_access: ArchetypeComponentAccess,
|
||||
main_thread_validator: MainThreadValidator,
|
||||
}
|
||||
|
||||
impl World {
|
||||
/// Creates a new empty [World]
|
||||
#[inline]
|
||||
pub fn new() -> World {
|
||||
World::default()
|
||||
}
|
||||
|
||||
/// Retrieves this world's unique ID
|
||||
#[inline]
|
||||
pub fn id(&self) -> WorldId {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// Retrieves this world's [Entities] collection
|
||||
#[inline]
|
||||
pub fn entities(&self) -> &Entities {
|
||||
&self.entities
|
||||
}
|
||||
|
||||
/// Retrieves this world's [Archetypes] collection
|
||||
#[inline]
|
||||
pub fn archetypes(&self) -> &Archetypes {
|
||||
&self.archetypes
|
||||
}
|
||||
|
||||
/// Retrieves this world's [Components] collection
|
||||
#[inline]
|
||||
pub fn components(&self) -> &Components {
|
||||
&self.components
|
||||
}
|
||||
|
||||
/// Retrieves a mutable reference to this world's [Components] collection
|
||||
#[inline]
|
||||
pub fn components_mut(&mut self) -> &mut Components {
|
||||
&mut self.components
|
||||
}
|
||||
|
||||
/// Retrieves this world's [Storages] collection
|
||||
#[inline]
|
||||
pub fn storages(&self) -> &Storages {
|
||||
&self.storages
|
||||
}
|
||||
|
||||
/// Retrieves this world's [Bundles] collection
|
||||
#[inline]
|
||||
pub fn bundles(&self) -> &Bundles {
|
||||
&self.bundles
|
||||
}
|
||||
|
||||
/// Retrieves a [WorldCell], which safely enables multiple mutable World accesses at the same time,
|
||||
/// provided those accesses do not conflict with each other.
|
||||
#[inline]
|
||||
pub fn cell(&mut self) -> WorldCell<'_> {
|
||||
WorldCell::new(self)
|
||||
}
|
||||
|
||||
/// Registers a new component using the given [ComponentDescriptor]. Components do not need to be manually
|
||||
/// registered. This just provides a way to override default configuration. Attempting to register a component
|
||||
/// with a type that has already been used by [World] will result in an error.
|
||||
///
|
||||
/// The default component storage type can be overridden like this:
|
||||
///
|
||||
/// ```
|
||||
/// use bevy_ecs::{component::{ComponentDescriptor, StorageType}, world::World};
|
||||
///
|
||||
/// struct Position {
|
||||
/// x: f32,
|
||||
/// y: f32,
|
||||
/// }
|
||||
///
|
||||
/// let mut world = World::new();
|
||||
/// world.register_component(ComponentDescriptor::new::<Position>(StorageType::SparseSet)).unwrap();
|
||||
/// ```
|
||||
pub fn register_component(
|
||||
&mut self,
|
||||
descriptor: ComponentDescriptor,
|
||||
) -> Result<ComponentId, ComponentsError> {
|
||||
let storage_type = descriptor.storage_type();
|
||||
let component_id = self.components.add(descriptor)?;
|
||||
// ensure sparse set is created for SparseSet components
|
||||
if storage_type == StorageType::SparseSet {
|
||||
// SAFE: just created
|
||||
let info = unsafe { self.components.get_info_unchecked(component_id) };
|
||||
self.storages.sparse_sets.get_or_insert(info);
|
||||
}
|
||||
|
||||
Ok(component_id)
|
||||
}
|
||||
|
||||
/// Retrieves an [EntityRef] that exposes read-only operations for the given `entity`.
|
||||
/// This will panic if the `entity` does not exist. Use [World::get_entity] if you want
|
||||
/// to check for entity existence instead of implicitly panic-ing.
|
||||
///
|
||||
/// ```
|
||||
/// use bevy_ecs::world::World;
|
||||
///
|
||||
/// struct Position {
|
||||
/// x: f32,
|
||||
/// y: f32,
|
||||
/// }
|
||||
///
|
||||
/// let mut world = World::new();
|
||||
/// let entity = world.spawn()
|
||||
/// .insert(Position { x: 0.0, y: 0.0 })
|
||||
/// .id();
|
||||
///
|
||||
/// let position = world.entity(entity).get::<Position>().unwrap();
|
||||
/// assert_eq!(position.x, 0.0);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn entity(&self, entity: Entity) -> EntityRef {
|
||||
self.get_entity(entity).expect("Entity does not exist")
|
||||
}
|
||||
|
||||
/// Retrieves an [EntityMut] that exposes read and write operations for the given `entity`.
|
||||
/// This will panic if the `entity` does not exist. Use [World::get_entity_mut] if you want
|
||||
/// to check for entity existence instead of implicitly panic-ing.
|
||||
///
|
||||
/// ```
|
||||
/// use bevy_ecs::world::World;
|
||||
///
|
||||
/// struct Position {
|
||||
/// x: f32,
|
||||
/// y: f32,
|
||||
/// }
|
||||
///
|
||||
/// let mut world = World::new();
|
||||
/// let entity = world.spawn()
|
||||
/// .insert(Position { x: 0.0, y: 0.0 })
|
||||
/// .id();
|
||||
///
|
||||
/// let mut position = world.entity_mut(entity).get_mut::<Position>().unwrap();
|
||||
/// position.x = 1.0;
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn entity_mut(&mut self, entity: Entity) -> EntityMut {
|
||||
self.get_entity_mut(entity).expect("Entity does not exist")
|
||||
}
|
||||
|
||||
/// Retrieves an [EntityRef] that exposes read-only operations for the given `entity`.
|
||||
/// Returns [None] if the `entity` does not exist. Use [World::entity] if you don't want
|
||||
/// to unwrap the [EntityRef] yourself.
|
||||
///
|
||||
/// ```
|
||||
/// use bevy_ecs::world::World;
|
||||
///
|
||||
/// struct Position {
|
||||
/// x: f32,
|
||||
/// y: f32,
|
||||
/// }
|
||||
///
|
||||
/// let mut world = World::new();
|
||||
/// let entity = world.spawn()
|
||||
/// .insert(Position { x: 0.0, y: 0.0 })
|
||||
/// .id();
|
||||
///
|
||||
/// let entity_ref = world.get_entity(entity).unwrap();
|
||||
/// let position = entity_ref.get::<Position>().unwrap();
|
||||
/// assert_eq!(position.x, 0.0);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn get_entity(&self, entity: Entity) -> Option<EntityRef> {
|
||||
let location = self.entities.get(entity)?;
|
||||
Some(EntityRef::new(self, entity, location))
|
||||
}
|
||||
|
||||
/// Retrieves an [EntityMut] that exposes read and write operations for the given `entity`.
|
||||
/// Returns [None] if the `entity` does not exist. Use [World::entity_mut] if you don't want
|
||||
/// to unwrap the [EntityMut] yourself.
|
||||
///
|
||||
/// ```
|
||||
/// use bevy_ecs::world::World;
|
||||
///
|
||||
/// struct Position {
|
||||
/// x: f32,
|
||||
/// y: f32,
|
||||
/// }
|
||||
///
|
||||
/// let mut world = World::new();
|
||||
/// let entity = world.spawn()
|
||||
/// .insert(Position { x: 0.0, y: 0.0 })
|
||||
/// .id();
|
||||
///
|
||||
/// let mut entity_mut = world.get_entity_mut(entity).unwrap();
|
||||
/// let mut position = entity_mut.get_mut::<Position>().unwrap();
|
||||
/// position.x = 1.0;
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn get_entity_mut(&mut self, entity: Entity) -> Option<EntityMut> {
|
||||
let location = self.entities.get(entity)?;
|
||||
// SAFE: `entity` exists and `location` is that entity's location
|
||||
Some(unsafe { EntityMut::new(self, entity, location) })
|
||||
}
|
||||
|
||||
/// Spawns a new [Entity] and returns a corresponding [EntityMut], which can be used
|
||||
/// to add components to the entity or retrieve its id.
|
||||
///
|
||||
/// ```
|
||||
/// use bevy_ecs::world::World;
|
||||
///
|
||||
/// struct Position {
|
||||
/// x: f32,
|
||||
/// y: f32,
|
||||
/// }
|
||||
///
|
||||
/// let mut world = World::new();
|
||||
/// let entity = world.spawn()
|
||||
/// .insert(Position { x: 0.0, y: 0.0 }) // add a single component
|
||||
/// .insert_bundle((1, 2.0, "hello")) // add a bundle of components
|
||||
/// .id();
|
||||
///
|
||||
/// let position = world.entity(entity).get::<Position>().unwrap();
|
||||
/// assert_eq!(position.x, 0.0);
|
||||
/// ```
|
||||
pub fn spawn(&mut self) -> EntityMut {
|
||||
self.flush();
|
||||
let entity = self.entities.alloc();
|
||||
let archetype = self.archetypes.empty_mut();
|
||||
unsafe {
|
||||
// PERF: consider avoiding allocating entities in the empty archetype unless needed
|
||||
// SAFE: archetype tables always exist
|
||||
let table = self.storages.tables.get_unchecked_mut(archetype.table_id());
|
||||
// SAFE: no components are allocated by archetype.allocate() because the archetype is empty
|
||||
let location = archetype.allocate(entity, table.allocate(entity));
|
||||
// SAFE: entity index was just allocated
|
||||
self.entities
|
||||
.meta
|
||||
.get_unchecked_mut(entity.id() as usize)
|
||||
.location = location;
|
||||
EntityMut::new(self, entity, location)
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawns a batch of entities with the same component [Bundle] type. Takes a given [Bundle]
|
||||
/// iterator and returns a corresponding [Entity] iterator.
|
||||
/// This is more efficient than spawning entities and adding components to them individually,
|
||||
/// but it is limited to spawning entities with the same [Bundle] type, whereas spawning
|
||||
/// individually is more flexible.
|
||||
///
|
||||
/// ```
|
||||
/// use bevy_ecs::{entity::Entity, world::World};
|
||||
///
|
||||
/// let mut world = World::new();
|
||||
/// let entities = world.spawn_batch(vec![
|
||||
/// ("a", 0.0), // the first entity
|
||||
/// ("b", 1.0), // the second entity
|
||||
/// ]).collect::<Vec<Entity>>();
|
||||
///
|
||||
/// assert_eq!(entities.len(), 2);
|
||||
/// ```
|
||||
pub fn spawn_batch<I>(&mut self, iter: I) -> SpawnBatchIter<'_, I::IntoIter>
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: Bundle,
|
||||
{
|
||||
SpawnBatchIter::new(self, iter.into_iter())
|
||||
}
|
||||
|
||||
/// Retrieves a reference to the given `entity`'s [Component] of the given type.
|
||||
/// Returns [None] if the `entity` does not have a [Component] of the given type.
|
||||
/// ```
|
||||
/// use bevy_ecs::world::World;
|
||||
///
|
||||
/// struct Position {
|
||||
/// x: f32,
|
||||
/// y: f32,
|
||||
/// }
|
||||
///
|
||||
/// let mut world = World::new();
|
||||
/// let entity = world.spawn()
|
||||
/// .insert(Position { x: 0.0, y: 0.0 })
|
||||
/// .id();
|
||||
/// let position = world.get::<Position>(entity).unwrap();
|
||||
/// assert_eq!(position.x, 0.0);
|
||||
#[inline]
|
||||
pub fn get<T: Component>(&self, entity: Entity) -> Option<&T> {
|
||||
self.get_entity(entity)?.get()
|
||||
}
|
||||
|
||||
/// Retrieves a mutable reference to the given `entity`'s [Component] of the given type.
|
||||
/// Returns [None] if the `entity` does not have a [Component] of the given type.
|
||||
/// ```
|
||||
/// use bevy_ecs::world::World;
|
||||
///
|
||||
/// struct Position {
|
||||
/// x: f32,
|
||||
/// y: f32,
|
||||
/// }
|
||||
///
|
||||
/// let mut world = World::new();
|
||||
/// let entity = world.spawn()
|
||||
/// .insert(Position { x: 0.0, y: 0.0 })
|
||||
/// .id();
|
||||
/// let mut position = world.get_mut::<Position>(entity).unwrap();
|
||||
/// position.x = 1.0;
|
||||
#[inline]
|
||||
pub fn get_mut<T: Component>(&mut self, entity: Entity) -> Option<Mut<T>> {
|
||||
self.get_entity_mut(entity)?.get_mut()
|
||||
}
|
||||
|
||||
/// Despawns the given `entity`, if it exists. This will also remove all of the entity's [Component]s.
|
||||
/// Returns `true` if the `entity` is successfully despawned and `false` if the `entity` does not exist.
|
||||
/// ```
|
||||
/// use bevy_ecs::world::World;
|
||||
///
|
||||
/// struct Position {
|
||||
/// x: f32,
|
||||
/// y: f32,
|
||||
/// }
|
||||
///
|
||||
/// let mut world = World::new();
|
||||
/// let entity = world.spawn()
|
||||
/// .insert(Position { x: 0.0, y: 0.0 })
|
||||
/// .id();
|
||||
/// assert!(world.despawn(entity));
|
||||
/// assert!(world.get_entity(entity).is_none());
|
||||
/// assert!(world.get::<Position>(entity).is_none());
|
||||
#[inline]
|
||||
pub fn despawn(&mut self, entity: Entity) -> bool {
|
||||
self.get_entity_mut(entity)
|
||||
.map(|e| {
|
||||
e.despawn();
|
||||
true
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Clears all component tracker state, such as "added", "mutated", and "removed".
|
||||
pub fn clear_trackers(&mut self) {
|
||||
self.storages.tables.clear_flags();
|
||||
self.storages.sparse_sets.clear_flags();
|
||||
for entities in self.removed_components.values_mut() {
|
||||
entities.clear();
|
||||
}
|
||||
|
||||
let resource_archetype = self.archetypes.resource_mut();
|
||||
for column in resource_archetype.unique_components.values_mut() {
|
||||
column.clear_flags();
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns [QueryState] for the given [WorldQuery], which is used to efficiently
|
||||
/// run queries on the [World].
|
||||
/// ```
|
||||
///
|
||||
/// use bevy_ecs::{entity::Entity, world::World};
|
||||
///
|
||||
/// #[derive(Debug, PartialEq)]
|
||||
/// struct Position {
|
||||
/// x: f32,
|
||||
/// y: f32,
|
||||
/// }
|
||||
///
|
||||
/// struct Velocity {
|
||||
/// x: f32,
|
||||
/// y: f32,
|
||||
/// }
|
||||
///
|
||||
/// let mut world = World::new();
|
||||
/// let entities = world.spawn_batch(vec![
|
||||
/// (Position { x: 0.0, y: 0.0}, Velocity { x: 1.0, y: 0.0 }),
|
||||
/// (Position { x: 0.0, y: 0.0}, Velocity { x: 0.0, y: 1.0 }),
|
||||
/// ]).collect::<Vec<Entity>>();
|
||||
///
|
||||
/// let mut query = world.query::<(&mut Position, &Velocity)>();
|
||||
/// for (mut position, velocity) in query.iter_mut(&mut world) {
|
||||
/// position.x += velocity.x;
|
||||
/// position.y += velocity.y;
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(world.get::<Position>(entities[0]).unwrap(), &Position { x: 1.0, y: 0.0 });
|
||||
/// assert_eq!(world.get::<Position>(entities[1]).unwrap(), &Position { x: 0.0, y: 1.0 });
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn query<Q: WorldQuery>(&mut self) -> QueryState<Q, ()> {
|
||||
QueryState::new(self)
|
||||
}
|
||||
|
||||
/// Returns [QueryState] for the given [WorldQuery] and filter, which is used to efficiently
|
||||
/// run filtered queries on the [World].
|
||||
/// ```
|
||||
/// use bevy_ecs::{entity::Entity, world::World, query::With};
|
||||
///
|
||||
/// struct A;
|
||||
/// struct B;
|
||||
///
|
||||
/// let mut world = World::new();
|
||||
/// let e1 = world.spawn().insert(A).id();
|
||||
/// let e2 = world.spawn().insert_bundle((A, B)).id();
|
||||
///
|
||||
/// let mut query = world.query_filtered::<Entity, With<B>>();
|
||||
/// let matching_entities = query.iter(&world).collect::<Vec<Entity>>();
|
||||
///
|
||||
/// assert_eq!(matching_entities, vec![e2]);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn query_filtered<Q: WorldQuery, F: WorldQuery>(&mut self) -> QueryState<Q, F>
|
||||
where
|
||||
F::Fetch: FilterFetch,
|
||||
{
|
||||
QueryState::new(self)
|
||||
}
|
||||
|
||||
/// Returns an iterator of entities that had components of type `T` removed since the last call to [World::clear_trackers].
|
||||
pub fn removed<T: Component>(&self) -> std::iter::Cloned<std::slice::Iter<'_, Entity>> {
|
||||
if let Some(component_id) = self.components.get_id(TypeId::of::<T>()) {
|
||||
self.removed_with_id(component_id)
|
||||
} else {
|
||||
[].iter().cloned()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator of entities that had components with the given `component_id` removed since the last call to [World::clear_trackers].
|
||||
pub fn removed_with_id(
|
||||
&self,
|
||||
component_id: ComponentId,
|
||||
) -> std::iter::Cloned<std::slice::Iter<'_, Entity>> {
|
||||
if let Some(removed) = self.removed_components.get(component_id) {
|
||||
removed.iter().cloned()
|
||||
} else {
|
||||
[].iter().cloned()
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts a new resource with the given `value`.
|
||||
/// Resources are "unique" data of a given type.
|
||||
#[inline]
|
||||
pub fn insert_resource<T: Component>(&mut self, value: T) {
|
||||
let component_id = self.components.get_or_insert_resource_id::<T>();
|
||||
// SAFE: component_id just initialized and corresponds to resource of type T
|
||||
unsafe { self.insert_resource_with_id(component_id, value) };
|
||||
}
|
||||
|
||||
/// Inserts a new non-send resource with the given `value`.
|
||||
/// Resources are "unique" data of a given type.
|
||||
#[inline]
|
||||
pub fn insert_non_send<T: 'static>(&mut self, value: T) {
|
||||
self.validate_non_send_access::<T>();
|
||||
let component_id = self.components.get_or_insert_non_send_resource_id::<T>();
|
||||
// SAFE: component_id just initialized and corresponds to resource of type T
|
||||
unsafe { self.insert_resource_with_id(component_id, value) };
|
||||
}
|
||||
|
||||
/// Removes the resource of a given type and returns it, if it exists. Otherwise returns [None].
|
||||
/// Resources are "unique" data of a given type.
|
||||
#[inline]
|
||||
pub fn remove_resource<T: Component>(&mut self) -> Option<T> {
|
||||
let component_id = self.components.get_resource_id(TypeId::of::<T>())?;
|
||||
let resource_archetype = self.archetypes.resource_mut();
|
||||
let unique_components = resource_archetype.unique_components_mut();
|
||||
let column = unique_components.get_mut(component_id)?;
|
||||
if column.is_empty() {
|
||||
return None;
|
||||
}
|
||||
// SAFE: if a resource column exists, row 0 exists as well. caller takes ownership of the ptr value / drop is called when
|
||||
// T is dropped
|
||||
let (ptr, _) = unsafe { column.swap_remove_and_forget_unchecked(0) };
|
||||
// SAFE: column is of type T
|
||||
Some(unsafe { ptr.cast::<T>().read() })
|
||||
}
|
||||
|
||||
/// Returns `true` if a resource of type `T` exists. Otherwise returns `false`.
|
||||
#[inline]
|
||||
pub fn contains_resource<T: Component>(&self) -> bool {
|
||||
let component_id =
|
||||
if let Some(component_id) = self.components.get_resource_id(TypeId::of::<T>()) {
|
||||
component_id
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
self.get_populated_resource_column(component_id).is_some()
|
||||
}
|
||||
|
||||
/// Gets a reference to the resource of the given type, if it exists. Otherwise returns [None]
|
||||
/// Resources are "unique" data of a given type.
|
||||
#[inline]
|
||||
pub fn get_resource<T: Component>(&self) -> Option<&T> {
|
||||
let component_id = self.components.get_resource_id(TypeId::of::<T>())?;
|
||||
unsafe { self.get_resource_with_id(component_id) }
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the resource of the given type, if it exists. Otherwise returns [None]
|
||||
/// Resources are "unique" data of a given type.
|
||||
#[inline]
|
||||
pub fn get_resource_mut<T: Component>(&mut self) -> Option<Mut<'_, T>> {
|
||||
// SAFE: unique world access
|
||||
unsafe { self.get_resource_unchecked_mut() }
|
||||
}
|
||||
|
||||
// PERF: optimize this to avoid redundant lookups
|
||||
/// Gets a resource of type `T` if it exists, otherwise inserts the resource using the result of calling `func`.
|
||||
#[inline]
|
||||
pub fn get_resource_or_insert_with<T: Component>(
|
||||
&mut self,
|
||||
func: impl FnOnce() -> T,
|
||||
) -> Mut<'_, T> {
|
||||
if !self.contains_resource::<T>() {
|
||||
self.insert_resource(func());
|
||||
}
|
||||
self.get_resource_mut().unwrap()
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the resource of the given type, if it exists. Otherwise returns [None]
|
||||
/// Resources are "unique" data of a given type.
|
||||
/// # Safety
|
||||
/// This will allow aliased mutable access to the given resource type. The caller must ensure that only
|
||||
/// one mutable access exists at a time.
|
||||
#[inline]
|
||||
pub unsafe fn get_resource_unchecked_mut<T: Component>(&self) -> Option<Mut<'_, T>> {
|
||||
let component_id = self.components.get_resource_id(TypeId::of::<T>())?;
|
||||
self.get_resource_unchecked_mut_with_id(component_id)
|
||||
}
|
||||
|
||||
/// Gets a reference to the non-send resource of the given type, if it exists. Otherwise returns [None]
|
||||
/// Resources are "unique" data of a given type.
|
||||
#[inline]
|
||||
pub fn get_non_send_resource<T: 'static>(&self) -> Option<&T> {
|
||||
let component_id = self.components.get_resource_id(TypeId::of::<T>())?;
|
||||
// SAFE: component id matches type T
|
||||
unsafe { self.get_non_send_with_id(component_id) }
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the non-send resource of the given type, if it exists. Otherwise returns [None]
|
||||
/// Resources are "unique" data of a given type.
|
||||
#[inline]
|
||||
pub fn get_non_send_resource_mut<T: 'static>(&mut self) -> Option<Mut<'_, T>> {
|
||||
// SAFE: unique world access
|
||||
unsafe { self.get_non_send_resource_unchecked_mut() }
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the non-send resource of the given type, if it exists. Otherwise returns [None]
|
||||
/// Resources are "unique" data of a given type.
|
||||
/// # Safety
|
||||
/// This will allow aliased mutable access to the given non-send resource type. The caller must ensure that only
|
||||
/// one mutable access exists at a time.
|
||||
#[inline]
|
||||
pub unsafe fn get_non_send_resource_unchecked_mut<T: 'static>(&self) -> Option<Mut<'_, T>> {
|
||||
let component_id = self.components.get_resource_id(TypeId::of::<T>())?;
|
||||
self.get_non_send_unchecked_mut_with_id(component_id)
|
||||
}
|
||||
|
||||
/// Temporarily removes the requested resource from this [World], then re-adds it before returning.
|
||||
/// This enables safe mutable access to a resource while still providing mutable world access
|
||||
/// ```
|
||||
/// use bevy_ecs::world::{World, Mut};
|
||||
/// struct A(u32);
|
||||
/// struct B(u32);
|
||||
/// let mut world = World::new();
|
||||
/// world.insert_resource(A(1));
|
||||
/// let entity = world.spawn().insert(B(1)).id();
|
||||
///
|
||||
/// world.resource_scope(|mut a: Mut<A>, world| {
|
||||
/// let b = world.get_mut::<B>(entity).unwrap();
|
||||
/// a.0 += b.0;
|
||||
/// });
|
||||
/// assert_eq!(world.get_resource::<A>().unwrap().0, 2);
|
||||
/// ```
|
||||
pub fn resource_scope<T: Component, U>(
|
||||
&mut self,
|
||||
f: impl FnOnce(Mut<T>, &mut World) -> U,
|
||||
) -> U {
|
||||
let component_id = self
|
||||
.components
|
||||
.get_resource_id(TypeId::of::<T>())
|
||||
.expect("resource does not exist");
|
||||
let (ptr, mut flags) = {
|
||||
let resource_archetype = self.archetypes.resource_mut();
|
||||
let unique_components = resource_archetype.unique_components_mut();
|
||||
let column = unique_components
|
||||
.get_mut(component_id)
|
||||
.expect("resource does not exist");
|
||||
if column.is_empty() {
|
||||
panic!("resource does not exist");
|
||||
}
|
||||
// SAFE: if a resource column exists, row 0 exists as well. caller takes ownership of the ptr value / drop is called when
|
||||
// T is dropped
|
||||
unsafe { column.swap_remove_and_forget_unchecked(0) }
|
||||
};
|
||||
// SAFE: pointer is of type T
|
||||
let value = Mut {
|
||||
value: unsafe { &mut *ptr.cast::<T>() },
|
||||
flags: &mut flags,
|
||||
};
|
||||
let result = f(value, self);
|
||||
let resource_archetype = self.archetypes.resource_mut();
|
||||
let unique_components = resource_archetype.unique_components_mut();
|
||||
let column = unique_components
|
||||
.get_mut(component_id)
|
||||
.expect("resource does not exist");
|
||||
// SAFE: new location is immediately written to below
|
||||
let row = unsafe { column.push_uninit() };
|
||||
// SAFE: row was just allocated above
|
||||
unsafe { column.set_unchecked(row, ptr) };
|
||||
// SAFE: row was just allocated above
|
||||
unsafe { *column.get_flags_unchecked_mut(row) = flags };
|
||||
result
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `component_id` must be assigned to a component of type T
|
||||
#[inline]
|
||||
pub(crate) unsafe fn get_resource_with_id<T: 'static>(
|
||||
&self,
|
||||
component_id: ComponentId,
|
||||
) -> Option<&T> {
|
||||
let column = self.get_populated_resource_column(component_id)?;
|
||||
Some(&*column.get_ptr().as_ptr().cast::<T>())
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `component_id` must be assigned to a component of type T.
|
||||
/// Caller must ensure this doesn't violate Rust mutability rules for the given resource.
|
||||
#[inline]
|
||||
pub(crate) unsafe fn get_resource_unchecked_mut_with_id<T>(
|
||||
&self,
|
||||
component_id: ComponentId,
|
||||
) -> Option<Mut<'_, T>> {
|
||||
let column = self.get_populated_resource_column(component_id)?;
|
||||
Some(Mut {
|
||||
value: &mut *column.get_ptr().as_ptr().cast::<T>(),
|
||||
flags: &mut *column.get_flags_mut_ptr(),
|
||||
})
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `component_id` must be assigned to a component of type T
|
||||
#[inline]
|
||||
pub(crate) unsafe fn get_non_send_with_id<T: 'static>(
|
||||
&self,
|
||||
component_id: ComponentId,
|
||||
) -> Option<&T> {
|
||||
self.validate_non_send_access::<T>();
|
||||
self.get_resource_with_id(component_id)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `component_id` must be assigned to a component of type T.
|
||||
/// Caller must ensure this doesn't violate Rust mutability rules for the given resource.
|
||||
#[inline]
|
||||
pub(crate) unsafe fn get_non_send_unchecked_mut_with_id<T: 'static>(
|
||||
&self,
|
||||
component_id: ComponentId,
|
||||
) -> Option<Mut<'_, T>> {
|
||||
self.validate_non_send_access::<T>();
|
||||
self.get_resource_unchecked_mut_with_id(component_id)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `component_id` must be valid and correspond to a resource component of type T
|
||||
#[inline]
|
||||
unsafe fn insert_resource_with_id<T>(&mut self, component_id: ComponentId, mut value: T) {
|
||||
let column = self.initialize_resource_internal(component_id);
|
||||
if column.is_empty() {
|
||||
// SAFE: column is of type T and has been allocated above
|
||||
let data = (&mut value as *mut T).cast::<u8>();
|
||||
// SAFE: new location is immediately written to below
|
||||
let row = column.push_uninit();
|
||||
// SAFE: index was just allocated above
|
||||
column.set_unchecked(row, data);
|
||||
std::mem::forget(value);
|
||||
column
|
||||
.get_flags_unchecked_mut(row)
|
||||
.set(ComponentFlags::ADDED, true);
|
||||
} else {
|
||||
// SAFE: column is of type T and has already been allocated
|
||||
*column.get_unchecked(0).cast::<T>() = value;
|
||||
column
|
||||
.get_flags_unchecked_mut(0)
|
||||
.set(ComponentFlags::MUTATED, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `component_id` must be valid and correspond to a resource component of type T
|
||||
#[inline]
|
||||
unsafe fn initialize_resource_internal(&mut self, component_id: ComponentId) -> &mut Column {
|
||||
// SAFE: resource archetype always exists
|
||||
let resource_archetype = self
|
||||
.archetypes
|
||||
.archetypes
|
||||
.get_unchecked_mut(ArchetypeId::resource().index());
|
||||
let resource_archetype_components = &mut resource_archetype.components;
|
||||
let archetype_component_count = &mut self.archetypes.archetype_component_count;
|
||||
let components = &self.components;
|
||||
resource_archetype
|
||||
.unique_components
|
||||
.get_or_insert_with(component_id, || {
|
||||
resource_archetype_components.insert(
|
||||
component_id,
|
||||
ArchetypeComponentInfo {
|
||||
archetype_component_id: ArchetypeComponentId::new(
|
||||
*archetype_component_count,
|
||||
),
|
||||
storage_type: StorageType::Table,
|
||||
},
|
||||
);
|
||||
*archetype_component_count += 1;
|
||||
let component_info = components.get_info_unchecked(component_id);
|
||||
Column::with_capacity(component_info, 1)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn initialize_resource<T: Component>(&mut self) -> ComponentId {
|
||||
let component_id = self.components.get_or_insert_resource_id::<T>();
|
||||
// SAFE: resource initialized above
|
||||
unsafe { self.initialize_resource_internal(component_id) };
|
||||
component_id
|
||||
}
|
||||
|
||||
pub(crate) fn initialize_non_send_resource<T: 'static>(&mut self) -> ComponentId {
|
||||
let component_id = self.components.get_or_insert_non_send_resource_id::<T>();
|
||||
// SAFE: resource initialized above
|
||||
unsafe { self.initialize_resource_internal(component_id) };
|
||||
component_id
|
||||
}
|
||||
|
||||
/// returns the resource column if the requested resource exists
|
||||
pub(crate) fn get_populated_resource_column(
|
||||
&self,
|
||||
component_id: ComponentId,
|
||||
) -> Option<&Column> {
|
||||
let resource_archetype = self.archetypes.resource();
|
||||
let unique_components = resource_archetype.unique_components();
|
||||
unique_components.get(component_id).and_then(|column| {
|
||||
if column.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn validate_non_send_access<T: 'static>(&self) {
|
||||
if !self.main_thread_validator.is_main_thread() {
|
||||
panic!(
|
||||
"attempted to access NonSend resource {} off of the main thread",
|
||||
std::any::type_name::<T>()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Empties queued entities and adds them to the empty [Archetype].
|
||||
/// This should be called before doing operations that might operate on queued entities,
|
||||
/// such as inserting a [Component].
|
||||
pub(crate) fn flush(&mut self) {
|
||||
let empty_archetype = self.archetypes.empty_mut();
|
||||
unsafe {
|
||||
// SAFE: archetype tables always exist
|
||||
let table = self
|
||||
.storages
|
||||
.tables
|
||||
.get_unchecked_mut(empty_archetype.table_id());
|
||||
// PERF: consider pre-allocating space for flushed entities
|
||||
self.entities.flush(|entity, location| {
|
||||
// SAFE: no components are allocated by archetype.allocate() because the archetype is empty
|
||||
*location = empty_archetype.allocate(entity, table.allocate(entity));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for World {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("World")
|
||||
.field("id", &self.id)
|
||||
.field("entity_count", &self.entities.len())
|
||||
.field("archetype_count", &self.archetypes.len())
|
||||
.field("component_count", &self.components.len())
|
||||
.field(
|
||||
"resource_count",
|
||||
&self.archetypes.resource().unique_components.len(),
|
||||
)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for World {}
|
||||
unsafe impl Sync for World {}
|
||||
|
||||
/// Creates `Self` using data from the given [World]
|
||||
pub trait FromWorld {
|
||||
/// Creates `Self` using data from the given [World]
|
||||
fn from_world(world: &mut World) -> Self;
|
||||
}
|
||||
|
||||
impl<T: Default> FromWorld for T {
|
||||
fn from_world(_world: &mut World) -> Self {
|
||||
T::default()
|
||||
}
|
||||
}
|
||||
|
||||
struct MainThreadValidator {
|
||||
main_thread: std::thread::ThreadId,
|
||||
}
|
||||
|
||||
impl MainThreadValidator {
|
||||
fn is_main_thread(&self) -> bool {
|
||||
self.main_thread == std::thread::current().id()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MainThreadValidator {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
main_thread: std::thread::current().id(),
|
||||
}
|
||||
}
|
||||
}
|
49
crates/bevy_ecs/src/world/pointer.rs
Normal file
49
crates/bevy_ecs/src/world/pointer.rs
Normal file
|
@ -0,0 +1,49 @@
|
|||
use crate::component::ComponentFlags;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
/// Unique borrow of an entity's component
|
||||
pub struct Mut<'a, T> {
|
||||
pub(crate) value: &'a mut T,
|
||||
pub(crate) flags: &'a mut ComponentFlags,
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for Mut<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &T {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> DerefMut for Mut<'a, T> {
|
||||
#[inline]
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
self.flags.insert(ComponentFlags::MUTATED);
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: core::fmt::Debug> core::fmt::Debug for Mut<'a, T> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
self.value.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w, T> Mut<'w, T> {
|
||||
/// Returns true if (and only if) this component been added since the start of the frame.
|
||||
pub fn added(&self) -> bool {
|
||||
self.flags.contains(ComponentFlags::ADDED)
|
||||
}
|
||||
|
||||
/// Returns true if (and only if) this component been mutated since the start of the frame.
|
||||
pub fn mutated(&self) -> bool {
|
||||
self.flags.contains(ComponentFlags::MUTATED)
|
||||
}
|
||||
|
||||
/// Returns true if (and only if) this component been either mutated or added since the start of the frame.
|
||||
pub fn changed(&self) -> bool {
|
||||
self.flags
|
||||
.intersects(ComponentFlags::ADDED | ComponentFlags::MUTATED)
|
||||
}
|
||||
}
|
126
crates/bevy_ecs/src/world/spawn_batch.rs
Normal file
126
crates/bevy_ecs/src/world/spawn_batch.rs
Normal file
|
@ -0,0 +1,126 @@
|
|||
use crate::{
|
||||
archetype::{Archetype, ArchetypeId},
|
||||
bundle::{Bundle, BundleInfo},
|
||||
entity::{Entities, Entity},
|
||||
storage::{SparseSets, Table},
|
||||
world::{add_bundle_to_archetype, World},
|
||||
};
|
||||
|
||||
pub struct SpawnBatchIter<'w, I>
|
||||
where
|
||||
I: Iterator,
|
||||
I::Item: Bundle,
|
||||
{
|
||||
inner: I,
|
||||
entities: &'w mut Entities,
|
||||
archetype: &'w mut Archetype,
|
||||
table: &'w mut Table,
|
||||
sparse_sets: &'w mut SparseSets,
|
||||
bundle_info: &'w BundleInfo,
|
||||
}
|
||||
|
||||
impl<'w, I> SpawnBatchIter<'w, I>
|
||||
where
|
||||
I: Iterator,
|
||||
I::Item: Bundle,
|
||||
{
|
||||
#[inline]
|
||||
pub(crate) fn new(world: &'w mut World, iter: I) -> Self {
|
||||
// Ensure all entity allocations are accounted for so `self.entities` can realloc if
|
||||
// necessary
|
||||
world.flush();
|
||||
|
||||
let (lower, upper) = iter.size_hint();
|
||||
|
||||
let bundle_info = world.bundles.init_info::<I::Item>(&mut world.components);
|
||||
|
||||
let length = upper.unwrap_or(lower);
|
||||
// SAFE: empty archetype exists and bundle components were initialized above
|
||||
let archetype_id = unsafe {
|
||||
add_bundle_to_archetype(
|
||||
&mut world.archetypes,
|
||||
&mut world.storages,
|
||||
&mut world.components,
|
||||
ArchetypeId::empty(),
|
||||
bundle_info,
|
||||
)
|
||||
};
|
||||
// SAFE: archetype exists
|
||||
let archetype = unsafe { world.archetypes.get_unchecked_mut(archetype_id) };
|
||||
// SAFE: table exists
|
||||
let table = unsafe {
|
||||
world
|
||||
.storages
|
||||
.tables
|
||||
.get_unchecked_mut(archetype.table_id())
|
||||
};
|
||||
archetype.reserve(length);
|
||||
table.reserve(length);
|
||||
world.entities.reserve(length as u32);
|
||||
Self {
|
||||
inner: iter,
|
||||
entities: &mut world.entities,
|
||||
archetype,
|
||||
table,
|
||||
sparse_sets: &mut world.storages.sparse_sets,
|
||||
bundle_info,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Drop for SpawnBatchIter<'_, I>
|
||||
where
|
||||
I: Iterator,
|
||||
I::Item: Bundle,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
for _ in self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Iterator for SpawnBatchIter<'_, I>
|
||||
where
|
||||
I: Iterator,
|
||||
I::Item: Bundle,
|
||||
{
|
||||
type Item = Entity;
|
||||
|
||||
fn next(&mut self) -> Option<Entity> {
|
||||
let bundle = self.inner.next()?;
|
||||
let entity = self.entities.alloc();
|
||||
// SAFE: component values are immediately written to relevant storages (which have been allocated)
|
||||
unsafe {
|
||||
let table_row = self.table.allocate(entity);
|
||||
let location = self.archetype.allocate(entity, table_row);
|
||||
let from_bundle = self
|
||||
.archetype
|
||||
.edges()
|
||||
.get_from_bundle(self.bundle_info.id)
|
||||
.unwrap();
|
||||
self.bundle_info.write_components(
|
||||
self.sparse_sets,
|
||||
entity,
|
||||
self.table,
|
||||
table_row,
|
||||
&from_bundle.bundle_flags,
|
||||
bundle,
|
||||
);
|
||||
self.entities.meta[entity.id as usize].location = location;
|
||||
}
|
||||
Some(entity)
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.inner.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, T> ExactSizeIterator for SpawnBatchIter<'_, I>
|
||||
where
|
||||
I: ExactSizeIterator<Item = T>,
|
||||
T: Bundle,
|
||||
{
|
||||
fn len(&self) -> usize {
|
||||
self.inner.len()
|
||||
}
|
||||
}
|
353
crates/bevy_ecs/src/world/world_cell.rs
Normal file
353
crates/bevy_ecs/src/world/world_cell.rs
Normal file
|
@ -0,0 +1,353 @@
|
|||
use crate::{
|
||||
archetype::ArchetypeComponentId,
|
||||
component::Component,
|
||||
storage::SparseSet,
|
||||
world::{Mut, World},
|
||||
};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
cell::RefCell,
|
||||
ops::{Deref, DerefMut},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
/// Exposes safe mutable access to multiple resources at a time in a World. Attempting to access World in a way that violates
|
||||
/// Rust's mutability rules will panic thanks to runtime checks.
|
||||
pub struct WorldCell<'w> {
|
||||
pub(crate) world: &'w mut World,
|
||||
pub(crate) access: Rc<RefCell<ArchetypeComponentAccess>>,
|
||||
}
|
||||
|
||||
pub(crate) struct ArchetypeComponentAccess {
|
||||
access: SparseSet<ArchetypeComponentId, usize>,
|
||||
}
|
||||
|
||||
impl Default for ArchetypeComponentAccess {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
access: SparseSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const UNIQUE_ACCESS: usize = 0;
|
||||
const BASE_ACCESS: usize = 1;
|
||||
impl ArchetypeComponentAccess {
|
||||
const fn new() -> Self {
|
||||
Self {
|
||||
access: SparseSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn read(&mut self, id: ArchetypeComponentId) -> bool {
|
||||
let id_access = self.access.get_or_insert_with(id, || BASE_ACCESS);
|
||||
if *id_access == UNIQUE_ACCESS {
|
||||
false
|
||||
} else {
|
||||
*id_access += 1;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fn drop_read(&mut self, id: ArchetypeComponentId) {
|
||||
let id_access = self.access.get_or_insert_with(id, || BASE_ACCESS);
|
||||
*id_access -= 1;
|
||||
}
|
||||
|
||||
fn write(&mut self, id: ArchetypeComponentId) -> bool {
|
||||
let id_access = self.access.get_or_insert_with(id, || BASE_ACCESS);
|
||||
if *id_access == BASE_ACCESS {
|
||||
*id_access = UNIQUE_ACCESS;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn drop_write(&mut self, id: ArchetypeComponentId) {
|
||||
let id_access = self.access.get_or_insert_with(id, || BASE_ACCESS);
|
||||
*id_access = BASE_ACCESS;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w> Drop for WorldCell<'w> {
|
||||
fn drop(&mut self) {
|
||||
let mut access = self.access.borrow_mut();
|
||||
// give world ArchetypeComponentAccess back to reuse allocations
|
||||
let _ = std::mem::swap(&mut self.world.archetype_component_access, &mut *access);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WorldBorrow<'w, T> {
|
||||
value: &'w T,
|
||||
archetype_component_id: ArchetypeComponentId,
|
||||
access: Rc<RefCell<ArchetypeComponentAccess>>,
|
||||
}
|
||||
|
||||
impl<'w, T> WorldBorrow<'w, T> {
|
||||
fn new(
|
||||
value: &'w T,
|
||||
archetype_component_id: ArchetypeComponentId,
|
||||
access: Rc<RefCell<ArchetypeComponentAccess>>,
|
||||
) -> Self {
|
||||
if !access.borrow_mut().read(archetype_component_id) {
|
||||
panic!(
|
||||
"Attempted to immutably access {}, but it is already mutably borrowed",
|
||||
std::any::type_name::<T>()
|
||||
)
|
||||
}
|
||||
Self {
|
||||
value,
|
||||
archetype_component_id,
|
||||
access,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w, T> Deref for WorldBorrow<'w, T> {
|
||||
type Target = T;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w, T> Drop for WorldBorrow<'w, T> {
|
||||
fn drop(&mut self) {
|
||||
let mut access = self.access.borrow_mut();
|
||||
access.drop_read(self.archetype_component_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WorldBorrowMut<'w, T> {
|
||||
value: Mut<'w, T>,
|
||||
archetype_component_id: ArchetypeComponentId,
|
||||
access: Rc<RefCell<ArchetypeComponentAccess>>,
|
||||
}
|
||||
|
||||
impl<'w, T> WorldBorrowMut<'w, T> {
|
||||
fn new(
|
||||
value: Mut<'w, T>,
|
||||
archetype_component_id: ArchetypeComponentId,
|
||||
access: Rc<RefCell<ArchetypeComponentAccess>>,
|
||||
) -> Self {
|
||||
if !access.borrow_mut().write(archetype_component_id) {
|
||||
panic!(
|
||||
"Attempted to mutably access {}, but it is already mutably borrowed",
|
||||
std::any::type_name::<T>()
|
||||
)
|
||||
}
|
||||
Self {
|
||||
value,
|
||||
archetype_component_id,
|
||||
access,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w, T> Deref for WorldBorrowMut<'w, T> {
|
||||
type Target = T;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.value.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w, T> DerefMut for WorldBorrowMut<'w, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.value.deref_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w, T> Drop for WorldBorrowMut<'w, T> {
|
||||
fn drop(&mut self) {
|
||||
let mut access = self.access.borrow_mut();
|
||||
access.drop_write(self.archetype_component_id);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w> WorldCell<'w> {
|
||||
pub(crate) fn new(world: &'w mut World) -> Self {
|
||||
// this is cheap because ArchetypeComponentAccess::new() is const / allocation free
|
||||
let access = std::mem::replace(
|
||||
&mut world.archetype_component_access,
|
||||
ArchetypeComponentAccess::new(),
|
||||
);
|
||||
// world's ArchetypeComponentAccess is recycled to cut down on allocations
|
||||
Self {
|
||||
world,
|
||||
access: Rc::new(RefCell::new(access)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_resource<T: Component>(&self) -> Option<WorldBorrow<'_, T>> {
|
||||
let component_id = self.world.components.get_resource_id(TypeId::of::<T>())?;
|
||||
let resource_archetype = self.world.archetypes.resource();
|
||||
let archetype_component_id = resource_archetype.get_archetype_component_id(component_id)?;
|
||||
Some(WorldBorrow::new(
|
||||
// SAFE: ComponentId matches TypeId
|
||||
unsafe { self.world.get_resource_with_id(component_id)? },
|
||||
archetype_component_id,
|
||||
self.access.clone(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn get_resource_mut<T: Component>(&self) -> Option<WorldBorrowMut<'_, T>> {
|
||||
let component_id = self.world.components.get_resource_id(TypeId::of::<T>())?;
|
||||
let resource_archetype = self.world.archetypes.resource();
|
||||
let archetype_component_id = resource_archetype.get_archetype_component_id(component_id)?;
|
||||
Some(WorldBorrowMut::new(
|
||||
// SAFE: ComponentId matches TypeId and access is checked by WorldBorrowMut
|
||||
unsafe {
|
||||
self.world
|
||||
.get_resource_unchecked_mut_with_id(component_id)?
|
||||
},
|
||||
archetype_component_id,
|
||||
self.access.clone(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn get_non_send<T: 'static>(&self) -> Option<WorldBorrow<'_, T>> {
|
||||
let component_id = self.world.components.get_resource_id(TypeId::of::<T>())?;
|
||||
let resource_archetype = self.world.archetypes.resource();
|
||||
let archetype_component_id = resource_archetype.get_archetype_component_id(component_id)?;
|
||||
Some(WorldBorrow::new(
|
||||
// SAFE: ComponentId matches TypeId
|
||||
unsafe { self.world.get_non_send_with_id(component_id)? },
|
||||
archetype_component_id,
|
||||
self.access.clone(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn get_non_send_mut<T: 'static>(&self) -> Option<WorldBorrowMut<'_, T>> {
|
||||
let component_id = self.world.components.get_resource_id(TypeId::of::<T>())?;
|
||||
let resource_archetype = self.world.archetypes.resource();
|
||||
let archetype_component_id = resource_archetype.get_archetype_component_id(component_id)?;
|
||||
Some(WorldBorrowMut::new(
|
||||
// SAFE: ComponentId matches TypeId and access is checked by WorldBorrowMut
|
||||
unsafe {
|
||||
self.world
|
||||
.get_non_send_unchecked_mut_with_id(component_id)?
|
||||
},
|
||||
archetype_component_id,
|
||||
self.access.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::BASE_ACCESS;
|
||||
use crate::{archetype::ArchetypeId, world::World};
|
||||
use std::any::TypeId;
|
||||
|
||||
#[test]
|
||||
fn world_cell() {
|
||||
let mut world = World::default();
|
||||
world.insert_resource(1u32);
|
||||
world.insert_resource(1u64);
|
||||
let cell = world.cell();
|
||||
{
|
||||
let mut a = cell.get_resource_mut::<u32>().unwrap();
|
||||
assert_eq!(1, *a);
|
||||
*a = 2;
|
||||
}
|
||||
{
|
||||
let a = cell.get_resource::<u32>().unwrap();
|
||||
assert_eq!(2, *a, "ensure access is dropped");
|
||||
|
||||
let b = cell.get_resource::<u32>().unwrap();
|
||||
assert_eq!(
|
||||
2, *b,
|
||||
"ensure multiple immutable accesses can occur at the same time"
|
||||
);
|
||||
}
|
||||
{
|
||||
let a = cell.get_resource_mut::<u32>().unwrap();
|
||||
assert_eq!(
|
||||
2, *a,
|
||||
"ensure both immutable accesses are dropped, enabling a new mutable access"
|
||||
);
|
||||
|
||||
let b = cell.get_resource::<u64>().unwrap();
|
||||
assert_eq!(
|
||||
1, *b,
|
||||
"ensure multiple non-conflicting mutable accesses can occur at the same time"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn world_access_reused() {
|
||||
let mut world = World::default();
|
||||
world.insert_resource(1u32);
|
||||
{
|
||||
let cell = world.cell();
|
||||
{
|
||||
let mut a = cell.get_resource_mut::<u32>().unwrap();
|
||||
assert_eq!(1, *a);
|
||||
*a = 2;
|
||||
}
|
||||
}
|
||||
|
||||
let u32_component_id = world
|
||||
.components
|
||||
.get_resource_id(TypeId::of::<u32>())
|
||||
.unwrap();
|
||||
let resource_archetype = world.archetypes.get(ArchetypeId::resource()).unwrap();
|
||||
let u32_archetype_component_id = resource_archetype
|
||||
.get_archetype_component_id(u32_component_id)
|
||||
.unwrap();
|
||||
assert_eq!(world.archetype_component_access.access.len(), 1);
|
||||
assert_eq!(
|
||||
world
|
||||
.archetype_component_access
|
||||
.access
|
||||
.get(u32_archetype_component_id),
|
||||
Some(&BASE_ACCESS),
|
||||
"reused access count is 'base'"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn world_cell_double_mut() {
|
||||
let mut world = World::default();
|
||||
world.insert_resource(1u32);
|
||||
let cell = world.cell();
|
||||
let _value_a = cell.get_resource_mut::<u32>().unwrap();
|
||||
let _value_b = cell.get_resource_mut::<u32>().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn world_cell_ref_and_mut() {
|
||||
let mut world = World::default();
|
||||
world.insert_resource(1u32);
|
||||
let cell = world.cell();
|
||||
let _value_a = cell.get_resource::<u32>().unwrap();
|
||||
let _value_b = cell.get_resource_mut::<u32>().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn world_cell_mut_and_ref() {
|
||||
let mut world = World::default();
|
||||
world.insert_resource(1u32);
|
||||
let cell = world.cell();
|
||||
let _value_a = cell.get_resource_mut::<u32>().unwrap();
|
||||
let _value_b = cell.get_resource::<u32>().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn world_cell_ref_and_ref() {
|
||||
let mut world = World::default();
|
||||
world.insert_resource(1u32);
|
||||
let cell = world.cell();
|
||||
let _value_a = cell.get_resource_mut::<u32>().unwrap();
|
||||
let _value_b = cell.get_resource::<u32>().unwrap();
|
||||
}
|
||||
}
|
|
@ -1,12 +1,13 @@
|
|||
use crate::converter::{convert_axis, convert_button, convert_gamepad_id};
|
||||
use bevy_app::Events;
|
||||
use bevy_ecs::{Resources, World};
|
||||
use bevy_ecs::world::World;
|
||||
use bevy_input::{gamepad::GamepadEventRaw, prelude::*};
|
||||
use gilrs::{EventType, Gilrs};
|
||||
|
||||
pub fn gilrs_event_startup_system(_world: &mut World, resources: &mut Resources) {
|
||||
let gilrs = resources.get_non_send::<Gilrs>().unwrap();
|
||||
let mut event = resources.get_mut::<Events<GamepadEventRaw>>().unwrap();
|
||||
pub fn gilrs_event_startup_system(world: &mut World) {
|
||||
let world = world.cell();
|
||||
let gilrs = world.get_non_send::<Gilrs>().unwrap();
|
||||
let mut event = world.get_resource_mut::<Events<GamepadEventRaw>>().unwrap();
|
||||
for (id, _) in gilrs.gamepads() {
|
||||
event.send(GamepadEventRaw(
|
||||
convert_gamepad_id(id),
|
||||
|
@ -15,9 +16,10 @@ pub fn gilrs_event_startup_system(_world: &mut World, resources: &mut Resources)
|
|||
}
|
||||
}
|
||||
|
||||
pub fn gilrs_event_system(_world: &mut World, resources: &mut Resources) {
|
||||
let mut gilrs = resources.get_non_send_mut::<Gilrs>().unwrap();
|
||||
let mut event = resources.get_mut::<Events<GamepadEventRaw>>().unwrap();
|
||||
pub fn gilrs_event_system(world: &mut World) {
|
||||
let world = world.cell();
|
||||
let mut gilrs = world.get_non_send_mut::<Gilrs>().unwrap();
|
||||
let mut event = world.get_resource_mut::<Events<GamepadEventRaw>>().unwrap();
|
||||
event.update();
|
||||
while let Some(gilrs_event) = gilrs.next_event() {
|
||||
match gilrs_event.event {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
mod converter;
|
||||
mod gilrs_system;
|
||||
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_ecs::IntoExclusiveSystem;
|
||||
use bevy_app::{AppBuilder, CoreStage, Plugin, StartupStage};
|
||||
use bevy_ecs::system::IntoExclusiveSystem;
|
||||
use bevy_utils::tracing::error;
|
||||
use gilrs::GilrsBuilder;
|
||||
use gilrs_system::{gilrs_event_startup_system, gilrs_event_system};
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use anyhow::Result;
|
||||
use bevy_asset::{AssetIoError, AssetLoader, AssetPath, Handle, LoadContext, LoadedAsset};
|
||||
use bevy_asset::{
|
||||
AssetIoError, AssetLoader, AssetPath, BoxedFuture, Handle, LoadContext, LoadedAsset,
|
||||
};
|
||||
use bevy_core::Name;
|
||||
use bevy_ecs::{bevy_utils::BoxedFuture, World, WorldBuilderSource};
|
||||
use bevy_ecs::world::World;
|
||||
use bevy_math::Mat4;
|
||||
use bevy_pbr::prelude::{PbrBundle, StandardMaterial};
|
||||
use bevy_render::{
|
||||
|
@ -207,9 +209,9 @@ async fn load_gltf<'a, 'b>(
|
|||
for scene in gltf.scenes() {
|
||||
let mut err = None;
|
||||
let mut world = World::default();
|
||||
let world_builder = &mut world.build();
|
||||
world_builder
|
||||
.spawn((Transform::default(), GlobalTransform::default()))
|
||||
world
|
||||
.spawn()
|
||||
.insert_bundle((Transform::default(), GlobalTransform::default()))
|
||||
.with_children(|parent| {
|
||||
for node in scene.nodes() {
|
||||
let result = load_node(&node, parent, load_context, &buffer_data);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{Axis, Input};
|
||||
use bevy_app::{EventReader, Events};
|
||||
use bevy_ecs::{Res, ResMut};
|
||||
use bevy_ecs::system::{Res, ResMut};
|
||||
use bevy_utils::HashMap;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue