From 07ed1d053e7946a116ce3eef273fc93dd246f49d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Grabarz?= Date: Sun, 3 Oct 2021 19:23:44 +0000 Subject: [PATCH] Implement and require `#[derive(Component)]` on all component structs (#2254) This implements the most minimal variant of #1843 - a derive for marker trait. This is a prerequisite to more complicated features like statically defined storage type or opt-out component reflection. In order to make component struct's purpose explicit and avoid misuse, it must be annotated with `#[derive(Component)]` (manual impl is discouraged for compatibility). Right now this is just a marker trait, but in the future it might be expanded. Making this change early allows us to make further changes later without breaking backward compatibility for derive macro users. This already prevents a lot of issues, like using bundles in `insert` calls. Primitive types are no longer valid components as well. This can be easily worked around by adding newtype wrappers and deriving `Component` for them. One funny example of prevented bad code (from our own tests) is when an newtype struct or enum variant is used. Previously, it was possible to write `insert(Newtype)` instead of `insert(Newtype(value))`. That code compiled, because function pointers (in this case newtype struct constructor) implement `Send + Sync + 'static`, so we allowed them to be used as components. This is no longer the case and such invalid code will trigger a compile error. Co-authored-by: = <=> Co-authored-by: TheRawMeatball Co-authored-by: Carter Anderson --- Cargo.toml | 2 +- benches/benches/bevy_ecs/commands.rs | 16 +- benches/benches/bevy_ecs/stages.rs | 8 +- benches/benches/bevy_ecs/world_get.rs | 183 +++-- crates/bevy_app/src/app.rs | 27 +- crates/bevy_asset/src/handle.rs | 4 +- crates/bevy_asset/src/loader.rs | 13 +- crates/bevy_core/src/label.rs | 3 +- crates/bevy_core/src/name.rs | 4 +- crates/bevy_core/src/time/stopwatch.rs | 4 +- crates/bevy_core/src/time/timer.rs | 4 +- crates/bevy_ecs/Cargo.toml | 4 - crates/bevy_ecs/README.md | 12 +- crates/bevy_ecs/examples/change_detection.rs | 2 +- crates/bevy_ecs/examples/component_storage.rs | 39 - crates/bevy_ecs/macros/src/component.rs | 102 +++ crates/bevy_ecs/macros/src/lib.rs | 16 +- crates/bevy_ecs/src/bundle.rs | 34 +- crates/bevy_ecs/src/change_detection.rs | 10 +- crates/bevy_ecs/src/component.rs | 154 ++-- crates/bevy_ecs/src/event.rs | 17 +- crates/bevy_ecs/src/lib.rs | 759 ++++++++++-------- crates/bevy_ecs/src/query/fetch.rs | 93 +-- crates/bevy_ecs/src/query/filter.rs | 105 ++- crates/bevy_ecs/src/query/iter.rs | 13 +- crates/bevy_ecs/src/query/mod.rs | 48 +- crates/bevy_ecs/src/query/state.rs | 9 +- .../src/schedule/executor_parallel.rs | 17 +- crates/bevy_ecs/src/schedule/stage.rs | 24 +- crates/bevy_ecs/src/schedule/state.rs | 23 +- crates/bevy_ecs/src/schedule/system_set.rs | 24 +- crates/bevy_ecs/src/storage/table.rs | 8 +- crates/bevy_ecs/src/system/commands/mod.rs | 110 ++- .../bevy_ecs/src/system/exclusive_system.rs | 18 +- crates/bevy_ecs/src/system/mod.rs | 37 +- crates/bevy_ecs/src/system/query.rs | 68 +- crates/bevy_ecs/src/system/system_param.rs | 69 +- crates/bevy_ecs/src/world/entity_ref.rs | 6 +- crates/bevy_ecs/src/world/mod.rs | 167 ++-- crates/bevy_ecs/src/world/spawn_batch.rs | 4 +- crates/bevy_ecs/src/world/world_cell.rs | 6 +- crates/bevy_macro_utils/src/attrs.rs | 50 ++ crates/bevy_macro_utils/src/lib.rs | 6 + crates/bevy_macro_utils/src/symbol.rs | 35 + crates/bevy_pbr/src/light.rs | 6 +- .../bevy_render/src/camera/active_cameras.rs | 3 +- crates/bevy_render/src/camera/camera.rs | 2 +- crates/bevy_render/src/camera/projection.rs | 6 +- .../src/camera/visible_entities.rs | 8 +- crates/bevy_render/src/draw.rs | 8 +- .../src/pipeline/render_pipelines.rs | 3 +- crates/bevy_render/src/render_graph/base.rs | 4 +- .../nodes/render_resources_node.rs | 5 +- crates/bevy_render/src/shader/shader_defs.rs | 3 +- crates/bevy_render/src/wireframe/mod.rs | 3 +- crates/bevy_sprite/src/lib.rs | 8 - crates/bevy_sprite/src/sprite.rs | 3 +- crates/bevy_sprite/src/texture_atlas.rs | 3 +- crates/bevy_text/src/text.rs | 5 +- .../bevy_transform/src/components/children.rs | 3 +- .../src/components/global_transform.rs | 4 +- .../bevy_transform/src/components/parent.rs | 5 +- .../src/components/transform.rs | 4 +- .../src/hierarchy/child_builder.rs | 16 +- .../bevy_transform/src/hierarchy/hierarchy.rs | 37 +- crates/bevy_ui/src/focus.rs | 5 +- crates/bevy_ui/src/ui_node.rs | 8 +- crates/bevy_ui/src/update.rs | 46 +- crates/bevy_ui/src/widget/button.rs | 4 +- crates/bevy_ui/src/widget/image.rs | 3 +- examples/2d/contributors.rs | 4 + examples/2d/text2d.rs | 3 + examples/3d/load_gltf.rs | 1 + examples/3d/parenting.rs | 1 + examples/3d/render_to_texture.rs | 3 + examples/3d/update_gltf_scene.rs | 1 + examples/3d/z_sort_debug.rs | 1 + examples/ecs/component_change_detection.rs | 2 +- examples/ecs/ecs_guide.rs | 2 + examples/ecs/iter_combinations.rs | 6 +- examples/ecs/parallel_query.rs | 1 + examples/ecs/removal_detection.rs | 1 + examples/ecs/system_param.rs | 2 + examples/game/breakout.rs | 11 +- examples/scene/scene.rs | 6 +- examples/shader/animate_shader.rs | 2 +- examples/shader/array_texture.rs | 2 +- examples/tools/bevymark.rs | 1 + examples/ui/text.rs | 2 + examples/ui/text_debug.rs | 1 + tests/how_to_test_systems.rs | 2 +- 91 files changed, 1532 insertions(+), 1085 deletions(-) delete mode 100644 crates/bevy_ecs/examples/component_storage.rs create mode 100644 crates/bevy_ecs/macros/src/component.rs create mode 100644 crates/bevy_macro_utils/src/attrs.rs create mode 100644 crates/bevy_macro_utils/src/symbol.rs diff --git a/Cargo.toml b/Cargo.toml index 7a0eec40f6455..51565942113cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ bevy_audio = ["bevy_internal/bevy_audio"] bevy_dynamic_plugin = ["bevy_internal/bevy_dynamic_plugin"] bevy_gilrs = ["bevy_internal/bevy_gilrs"] bevy_gltf = ["bevy_internal/bevy_gltf"] -bevy_wgpu = ["bevy_internal/bevy_wgpu"] +bevy_wgpu = ["bevy_internal/bevy_wgpu"] bevy_winit = ["bevy_internal/bevy_winit"] trace_chrome = ["bevy_internal/trace_chrome"] diff --git a/benches/benches/bevy_ecs/commands.rs b/benches/benches/bevy_ecs/commands.rs index 620b1915f9aa1..c4d974238dadb 100644 --- a/benches/benches/bevy_ecs/commands.rs +++ b/benches/benches/bevy_ecs/commands.rs @@ -1,4 +1,5 @@ use bevy::ecs::{ + component::Component, entity::Entity, system::{Command, CommandQueue, Commands}, world::World, @@ -18,8 +19,11 @@ criterion_group!( ); criterion_main!(benches); +#[derive(Component)] struct A; +#[derive(Component)] struct B; +#[derive(Component)] struct C; fn empty_commands(criterion: &mut Criterion) { @@ -79,10 +83,10 @@ fn spawn_commands(criterion: &mut Criterion) { group.finish(); } -#[derive(Default)] +#[derive(Default, Component)] struct Matrix([[f32; 4]; 4]); -#[derive(Default)] +#[derive(Default, Component)] struct Vec3([f32; 3]); fn insert_commands(criterion: &mut Criterion) { @@ -95,14 +99,16 @@ fn insert_commands(criterion: &mut Criterion) { let mut world = World::default(); let mut command_queue = CommandQueue::default(); let mut entities = Vec::new(); - for i in 0..entity_count { + for _ in 0..entity_count { entities.push(world.spawn().id()); } bencher.iter(|| { let mut commands = Commands::new(&mut command_queue, &world); for entity in entities.iter() { - commands.entity(*entity).insert_bundle((Matrix::default(), Vec3::default())); + commands + .entity(*entity) + .insert_bundle((Matrix::default(), Vec3::default())); } drop(commands); command_queue.apply(&mut world); @@ -112,7 +118,7 @@ fn insert_commands(criterion: &mut Criterion) { let mut world = World::default(); let mut command_queue = CommandQueue::default(); let mut entities = Vec::new(); - for i in 0..entity_count { + for _ in 0..entity_count { entities.push(world.spawn().id()); } diff --git a/benches/benches/bevy_ecs/stages.rs b/benches/benches/bevy_ecs/stages.rs index e4fce66f0fea3..8e572acff131c 100644 --- a/benches/benches/bevy_ecs/stages.rs +++ b/benches/benches/bevy_ecs/stages.rs @@ -1,6 +1,7 @@ use bevy::ecs::{ + component::Component, schedule::{Stage, SystemStage}, - system::{IntoSystem, Query}, + system::Query, world::World, }; use criterion::{criterion_group, criterion_main, Criterion}; @@ -12,10 +13,15 @@ fn run_stage(stage: &mut SystemStage, world: &mut World) { stage.run(world); } +#[derive(Component)] struct A(f32); +#[derive(Component)] struct B(f32); +#[derive(Component)] struct C(f32); +#[derive(Component)] struct D(f32); +#[derive(Component)] struct E(f32); const ENTITY_BUNCH: usize = 5000; diff --git a/benches/benches/bevy_ecs/world_get.rs b/benches/benches/bevy_ecs/world_get.rs index 21aae5a8f40c2..fc943ede4742f 100644 --- a/benches/benches/bevy_ecs/world_get.rs +++ b/benches/benches/bevy_ecs/world_get.rs @@ -1,8 +1,4 @@ -use bevy::ecs::{ - component::{ComponentDescriptor, StorageType}, - entity::Entity, - world::World, -}; +use bevy::ecs::{component::Component, entity::Entity, world::World}; use criterion::{black_box, criterion_group, criterion_main, Criterion}; criterion_group!( @@ -15,16 +11,18 @@ criterion_group!( ); criterion_main!(benches); -struct A(f32); +#[derive(Component, Default)] +#[component(storage = "Table")] +struct Table(f32); +#[derive(Component, Default)] +#[component(storage = "SparseSet")] +struct Sparse(f32); const RANGE: std::ops::Range = 5..6; -fn setup(entity_count: u32, storage: StorageType) -> World { +fn setup(entity_count: u32) -> World { let mut world = World::default(); - world - .register_component(ComponentDescriptor::new::(storage)) - .unwrap(); - world.spawn_batch((0..entity_count).map(|_| (A(0.0),))); + world.spawn_batch((0..entity_count).map(|_| (T::default(),))); world } @@ -35,7 +33,7 @@ fn world_entity(criterion: &mut Criterion) { for entity_count in RANGE.map(|i| i * 10_000) { group.bench_function(format!("{}_entities", entity_count), |bencher| { - let world = setup(entity_count, StorageType::Table); + let world = setup::(entity_count); bencher.iter(|| { for i in 0..entity_count { @@ -55,21 +53,26 @@ fn world_get(criterion: &mut Criterion) { group.measurement_time(std::time::Duration::from_secs(4)); for entity_count in RANGE.map(|i| i * 10_000) { - for storage in [StorageType::Table, StorageType::SparseSet] { - group.bench_function( - format!("{}_entities_{:?}", entity_count, storage), - |bencher| { - let world = setup(entity_count, storage); - - bencher.iter(|| { - for i in 0..entity_count { - let entity = Entity::new(i); - assert!(world.get::(entity).is_some()); - } - }); - }, - ); - } + group.bench_function(format!("{}_entities_table", entity_count), |bencher| { + let world = setup::
(entity_count); + + bencher.iter(|| { + for i in 0..entity_count { + let entity = Entity::new(i); + assert!(world.get::
(entity).is_some()); + } + }); + }); + group.bench_function(format!("{}_entities_sparse", entity_count), |bencher| { + let world = setup::(entity_count); + + bencher.iter(|| { + for i in 0..entity_count { + let entity = Entity::new(i); + assert!(world.get::(entity).is_some()); + } + }); + }); } group.finish(); @@ -81,22 +84,28 @@ fn world_query_get(criterion: &mut Criterion) { group.measurement_time(std::time::Duration::from_secs(4)); for entity_count in RANGE.map(|i| i * 10_000) { - for storage in [StorageType::Table, StorageType::SparseSet] { - group.bench_function( - format!("{}_entities_{:?}", entity_count, storage), - |bencher| { - let mut world = setup(entity_count, storage); - let mut query = world.query::<&A>(); - - bencher.iter(|| { - for i in 0..entity_count { - let entity = Entity::new(i); - assert!(query.get(&world, entity).is_ok()); - } - }); - }, - ); - } + group.bench_function(format!("{}_entities_table", entity_count), |bencher| { + let mut world = setup::
(entity_count); + let mut query = world.query::<&Table>(); + + bencher.iter(|| { + for i in 0..entity_count { + let entity = Entity::new(i); + assert!(query.get(&world, entity).is_ok()); + } + }); + }); + group.bench_function(format!("{}_entities_sparse", entity_count), |bencher| { + let mut world = setup::(entity_count); + let mut query = world.query::<&Sparse>(); + + bencher.iter(|| { + for i in 0..entity_count { + let entity = Entity::new(i); + assert!(query.get(&world, entity).is_ok()); + } + }); + }); } group.finish(); @@ -108,24 +117,32 @@ fn world_query_iter(criterion: &mut Criterion) { group.measurement_time(std::time::Duration::from_secs(4)); for entity_count in RANGE.map(|i| i * 10_000) { - for storage in [StorageType::Table, StorageType::SparseSet] { - group.bench_function( - format!("{}_entities_{:?}", entity_count, storage), - |bencher| { - let mut world = setup(entity_count, storage); - let mut query = world.query::<&A>(); - - bencher.iter(|| { - let mut count = 0; - for comp in query.iter(&world) { - black_box(comp); - count += 1; - } - assert_eq!(black_box(count), entity_count); - }); - }, - ); - } + group.bench_function(format!("{}_entities_table", entity_count), |bencher| { + let mut world = setup::
(entity_count); + let mut query = world.query::<&Table>(); + + bencher.iter(|| { + let mut count = 0; + for comp in query.iter(&world) { + black_box(comp); + count += 1; + } + assert_eq!(black_box(count), entity_count); + }); + }); + group.bench_function(format!("{}_entities_sparse", entity_count), |bencher| { + let mut world = setup::(entity_count); + let mut query = world.query::<&Sparse>(); + + bencher.iter(|| { + let mut count = 0; + for comp in query.iter(&world) { + black_box(comp); + count += 1; + } + assert_eq!(black_box(count), entity_count); + }); + }); } group.finish(); @@ -137,24 +154,32 @@ fn world_query_for_each(criterion: &mut Criterion) { group.measurement_time(std::time::Duration::from_secs(4)); for entity_count in RANGE.map(|i| i * 10_000) { - for storage in [StorageType::Table, StorageType::SparseSet] { - group.bench_function( - format!("{}_entities_{:?}", entity_count, storage), - |bencher| { - let mut world = setup(entity_count, storage); - let mut query = world.query::<&A>(); - - bencher.iter(|| { - let mut count = 0; - query.for_each(&world, |comp| { - black_box(comp); - count += 1; - }); - assert_eq!(black_box(count), entity_count); - }); - }, - ); - } + group.bench_function(format!("{}_entities_table", entity_count), |bencher| { + let mut world = setup::
(entity_count); + let mut query = world.query::<&Table>(); + + bencher.iter(|| { + let mut count = 0; + query.for_each(&world, |comp| { + black_box(comp); + count += 1; + }); + assert_eq!(black_box(count), entity_count); + }); + }); + group.bench_function(format!("{}_entities_sparse", entity_count), |bencher| { + let mut world = setup::(entity_count); + let mut query = world.query::<&Sparse>(); + + bencher.iter(|| { + let mut count = 0; + query.for_each(&world, |comp| { + black_box(comp); + count += 1; + }); + assert_eq!(black_box(count), entity_count); + }); + }); } group.finish(); diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 41462689053bf..4887bb5917367 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -1,14 +1,15 @@ use crate::{CoreStage, Events, Plugin, PluginGroup, PluginGroupBuilder, StartupStage}; use bevy_ecs::{ - component::{Component, ComponentDescriptor}, prelude::{FromWorld, IntoExclusiveSystem}, schedule::{ - IntoSystemDescriptor, RunOnce, Schedule, Stage, StageLabel, State, SystemSet, SystemStage, + IntoSystemDescriptor, RunOnce, Schedule, Stage, StageLabel, State, StateData, SystemSet, + SystemStage, }, + system::Resource, world::World, }; use bevy_utils::tracing::debug; -use std::{fmt::Debug, hash::Hash}; +use std::fmt::Debug; #[cfg(feature = "trace")] use bevy_utils::tracing::info_span; @@ -493,7 +494,7 @@ impl App { /// adding [State::get_driver] to additional stages you need it in. pub fn add_state(&mut self, initial: T) -> &mut Self where - T: Component + Debug + Clone + Eq + Hash, + T: StateData, { self.add_state_to_stage(CoreStage::Update, initial) } @@ -505,7 +506,7 @@ impl App { /// stages you need it in. pub fn add_state_to_stage(&mut self, stage: impl StageLabel, initial: T) -> &mut Self where - T: Component + Debug + Clone + Eq + Hash, + T: StateData, { self.insert_resource(State::new(initial)) .add_system_set_to_stage(stage, State::::get_driver()) @@ -582,7 +583,7 @@ impl App { /// ``` pub fn add_event(&mut self) -> &mut Self where - T: Component, + T: Resource, { self.init_resource::>() .add_system_to_stage(CoreStage::First, Events::::update_system) @@ -608,7 +609,7 @@ impl App { /// ``` pub fn insert_resource(&mut self, resource: T) -> &mut Self where - T: Component, + T: Resource, { self.world.insert_resource(resource); self @@ -810,18 +811,6 @@ impl App { 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. - /// - /// See [World::register_component] - pub fn register_component(&mut self, descriptor: ComponentDescriptor) -> &mut Self { - self.world.register_component(descriptor).unwrap(); - self - } - /// Adds the type `T` to the type registry resource. #[cfg(feature = "bevy_reflect")] pub fn register_type(&mut self) -> &mut Self { diff --git a/crates/bevy_asset/src/handle.rs b/crates/bevy_asset/src/handle.rs index ae2bb45c0837c..1a606c1a62c80 100644 --- a/crates/bevy_asset/src/handle.rs +++ b/crates/bevy_asset/src/handle.rs @@ -9,7 +9,7 @@ use crate::{ path::{AssetPath, AssetPathId}, Asset, Assets, }; -use bevy_ecs::reflect::ReflectComponent; +use bevy_ecs::{component::Component, reflect::ReflectComponent}; use bevy_reflect::{Reflect, ReflectDeserialize}; use bevy_utils::Uuid; use crossbeam_channel::{Receiver, Sender}; @@ -58,7 +58,7 @@ impl HandleId { /// /// Handles contain a unique id that corresponds to a specific asset in the [Assets](crate::Assets) /// collection. -#[derive(Reflect)] +#[derive(Component, Reflect)] #[reflect(Component)] pub struct Handle where diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index 0ca85fa74738e..960208c77bcfd 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -3,10 +3,7 @@ use crate::{ RefChangeChannel, }; use anyhow::Result; -use bevy_ecs::{ - component::Component, - system::{Res, ResMut}, -}; +use bevy_ecs::system::{Res, ResMut}; use bevy_reflect::{TypeUuid, TypeUuidDynamic}; use bevy_tasks::TaskPool; use bevy_utils::{BoxedFuture, HashMap}; @@ -146,7 +143,7 @@ impl<'a> LoadContext<'a> { /// The result of loading an asset of type `T` #[derive(Debug)] -pub struct AssetResult { +pub struct AssetResult { pub asset: Box, pub id: HandleId, pub version: usize, @@ -154,12 +151,12 @@ pub struct AssetResult { /// A channel to send and receive [AssetResult]s #[derive(Debug)] -pub struct AssetLifecycleChannel { +pub struct AssetLifecycleChannel { pub sender: Sender>, pub receiver: Receiver>, } -pub enum AssetLifecycleEvent { +pub enum AssetLifecycleEvent { Create(AssetResult), Free(HandleId), } @@ -193,7 +190,7 @@ impl AssetLifecycle for AssetLifecycleChannel { } } -impl Default for AssetLifecycleChannel { +impl Default for AssetLifecycleChannel { fn default() -> Self { let (sender, receiver) = crossbeam_channel::unbounded(); AssetLifecycleChannel { sender, receiver } diff --git a/crates/bevy_core/src/label.rs b/crates/bevy_core/src/label.rs index 6c99bc76d8eea..4b805641ef770 100644 --- a/crates/bevy_core/src/label.rs +++ b/crates/bevy_core/src/label.rs @@ -1,4 +1,5 @@ use bevy_ecs::{ + component::Component, entity::Entity, query::Changed, reflect::ReflectComponent, @@ -13,7 +14,7 @@ use std::{ }; /// A collection of labels -#[derive(Default, Reflect)] +#[derive(Component, Default, Reflect)] #[reflect(Component)] pub struct Labels { labels: HashSet>, diff --git a/crates/bevy_core/src/name.rs b/crates/bevy_core/src/name.rs index 7390fd8b18688..593c7ae202baf 100644 --- a/crates/bevy_core/src/name.rs +++ b/crates/bevy_core/src/name.rs @@ -1,4 +1,4 @@ -use bevy_ecs::reflect::ReflectComponent; +use bevy_ecs::{component::Component, reflect::ReflectComponent}; use bevy_reflect::Reflect; use bevy_utils::AHasher; use std::{ @@ -8,7 +8,7 @@ use std::{ }; /// Component used to identify an entity. Stores a hash for faster comparisons -#[derive(Debug, Clone, Reflect)] +#[derive(Component, Debug, Clone, Reflect)] #[reflect(Component)] pub struct Name { hash: u64, // TODO: Shouldn't be serialized diff --git a/crates/bevy_core/src/time/stopwatch.rs b/crates/bevy_core/src/time/stopwatch.rs index 8aa1e974c9647..d03da20824f39 100644 --- a/crates/bevy_core/src/time/stopwatch.rs +++ b/crates/bevy_core/src/time/stopwatch.rs @@ -1,4 +1,4 @@ -use bevy_ecs::reflect::ReflectComponent; +use bevy_ecs::{component::Component, reflect::ReflectComponent}; use bevy_reflect::Reflect; use bevy_utils::Duration; @@ -23,7 +23,7 @@ use bevy_utils::Duration; /// assert!(stopwatch.paused()); /// assert_eq!(stopwatch.elapsed_secs(), 0.0); /// ``` -#[derive(Clone, Debug, Default, Reflect)] +#[derive(Component, Clone, Debug, Default, Reflect)] #[reflect(Component)] pub struct Stopwatch { elapsed: Duration, diff --git a/crates/bevy_core/src/time/timer.rs b/crates/bevy_core/src/time/timer.rs index f09f3f950441e..b9bd0800de38e 100644 --- a/crates/bevy_core/src/time/timer.rs +++ b/crates/bevy_core/src/time/timer.rs @@ -1,5 +1,5 @@ use crate::Stopwatch; -use bevy_ecs::reflect::ReflectComponent; +use bevy_ecs::{component::Component, reflect::ReflectComponent}; use bevy_reflect::Reflect; use bevy_utils::Duration; @@ -10,7 +10,7 @@ use bevy_utils::Duration; /// exceeded, and can still be reset at any given point. /// /// Paused timers will not have elapsed time increased. -#[derive(Clone, Debug, Default, Reflect)] +#[derive(Component, Clone, Debug, Default, Reflect)] #[reflect(Component)] pub struct Timer { stopwatch: Stopwatch, diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index 8f01bc61db490..9c39a46e62d1c 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -34,10 +34,6 @@ rand = "0.8" name = "events" path = "examples/events.rs" -[[example]] -name = "component_storage" -path = "examples/component_storage.rs" - [[example]] name = "resources" path = "examples/resources.rs" diff --git a/crates/bevy_ecs/README.md b/crates/bevy_ecs/README.md index 478b23b915a1f..8bdb28fa59cd4 100644 --- a/crates/bevy_ecs/README.md +++ b/crates/bevy_ecs/README.md @@ -193,11 +193,15 @@ Components can be stored in: * **Tables**: Fast and cache friendly iteration, but slower adding and removing of components. This is the default storage type. * **Sparse Sets**: Fast adding and removing of components, but slower iteration. -Component storage types are configurable, and they default to table storage if the storage is not manually defined. The [`component_storage.rs`](examples/component_storage.rs) example shows how to configure the storage type for a component. +Component storage types are configurable, and they default to table storage if the storage is not manually defined. -```rust -// store Position components in Sparse Sets -world.register_component(ComponentDescriptor::new::(StorageType::SparseSet)); +```rs +#[derive(Component)] +struct TableStoredComponent; + +#[derive(Component)] +#[component(storage = "SparseSet")] +struct SparseStoredComponent; ``` ### Component Bundles diff --git a/crates/bevy_ecs/examples/change_detection.rs b/crates/bevy_ecs/examples/change_detection.rs index ec80dcb9393aa..1a3e135b654e2 100644 --- a/crates/bevy_ecs/examples/change_detection.rs +++ b/crates/bevy_ecs/examples/change_detection.rs @@ -46,7 +46,7 @@ struct EntityCounter { } // This struct represents a Component and holds the age in frames of the entity it gets assigned to -#[derive(Default, Debug)] +#[derive(Component, Default, Debug)] struct Age { frames: i32, } diff --git a/crates/bevy_ecs/examples/component_storage.rs b/crates/bevy_ecs/examples/component_storage.rs deleted file mode 100644 index 018f7afd4750c..0000000000000 --- a/crates/bevy_ecs/examples/component_storage.rs +++ /dev/null @@ -1,39 +0,0 @@ -use bevy_ecs::{ - component::{ComponentDescriptor, StorageType}, - prelude::*, -}; - -// This example shows how to configure the storage of Components. -// A system demonstrates that querying for components is independent of their storage type. -fn main() { - let mut world = World::new(); - - // Store components of type `i32` in a Sparse set - world - .register_component(ComponentDescriptor::new::(StorageType::SparseSet)) - .expect("The component of type i32 is already in use"); - - // Components of type i32 will have the above configured Sparse set storage, - // while f64 components will have the default table storage - world.spawn().insert(1).insert(0.1); - world.spawn().insert(2); - world.spawn().insert(0.2); - - // Setup a schedule and stage to add a system querying for the just spawned entities - let mut schedule = Schedule::default(); - let mut update = SystemStage::parallel(); - update.add_system(query_entities); - schedule.add_stage("update", update); - - schedule.run(&mut world); -} - -// The storage type does not matter for how to query in systems -fn query_entities(entities_with_i32: Query<&i32>, entities_with_f64: Query<&f64>) { - for value in entities_with_i32.iter() { - println!("Got entity with i32: {}", value); - } - for value in entities_with_f64.iter() { - println!("Got entity with f64: {}", value); - } -} diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs new file mode 100644 index 0000000000000..446210d0667ea --- /dev/null +++ b/crates/bevy_ecs/macros/src/component.rs @@ -0,0 +1,102 @@ +use bevy_macro_utils::{get_lit_str, Symbol}; +use proc_macro::TokenStream; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::{quote, ToTokens}; +use syn::{parse_macro_input, parse_quote, DeriveInput, Error, Ident, Path, Result}; + +pub fn derive_component(input: TokenStream) -> TokenStream { + let mut ast = parse_macro_input!(input as DeriveInput); + let bevy_ecs_path: Path = crate::bevy_ecs_path(); + + let attrs = match parse_component_attr(&ast) { + Ok(attrs) => attrs, + Err(e) => return e.into_compile_error().into(), + }; + + let storage = storage_path(bevy_ecs_path.clone(), attrs.storage); + + ast.generics + .make_where_clause() + .predicates + .push(parse_quote! { Self: Send + Sync + 'static }); + + let struct_name = &ast.ident; + let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); + + TokenStream::from(quote! { + impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause { + type Storage = #storage; + } + }) +} + +pub const COMPONENT: Symbol = Symbol("component"); +pub const STORAGE: Symbol = Symbol("storage"); + +struct Attrs { + storage: StorageTy, +} + +#[derive(Clone, Copy)] +enum StorageTy { + Table, + SparseSet, +} + +fn parse_component_attr(ast: &DeriveInput) -> Result { + let meta_items = bevy_macro_utils::parse_attrs(ast, COMPONENT)?; + + let mut attrs = Attrs { + storage: StorageTy::Table, + }; + + for meta in meta_items { + use syn::{ + Meta::NameValue, + NestedMeta::{Lit, Meta}, + }; + match meta { + Meta(NameValue(m)) if m.path == STORAGE => { + attrs.storage = match get_lit_str(STORAGE, &m.lit)?.value().as_str() { + "Table" => StorageTy::Table, + "SparseSet" => StorageTy::SparseSet, + s => { + return Err(Error::new_spanned( + m.lit, + format!( + "Invalid storage type `{}`, expected 'table' or 'sparse'.", + s + ), + )) + } + }; + } + Meta(meta_item) => { + return Err(Error::new_spanned( + meta_item.path(), + format!( + "unknown component attribute `{}`", + meta_item.path().into_token_stream().to_string() + ), + )); + } + Lit(lit) => { + return Err(Error::new_spanned( + lit, + "unexpected literal in component attribute", + )) + } + } + } + + Ok(attrs) +} + +fn storage_path(bevy_ecs_path: Path, ty: StorageTy) -> TokenStream2 { + let typename = match ty { + StorageTy::Table => Ident::new("TableStorage", Span::call_site()), + StorageTy::SparseSet => Ident::new("SparseStorage", Span::call_site()), + }; + + quote! { #bevy_ecs_path::component::#typename } +} diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 8371de3e4b160..e508c7ac2c6e5 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -1,5 +1,7 @@ extern crate proc_macro; +mod component; + use bevy_macro_utils::BevyManifest; use proc_macro::TokenStream; use proc_macro2::{Span, TokenStream as TokenStream2}; @@ -118,7 +120,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { { if *is_bundle { field_component_ids.push(quote! { - component_ids.extend(<#field_type as #ecs_path::bundle::Bundle>::component_ids(components)); + component_ids.extend(<#field_type as #ecs_path::bundle::Bundle>::component_ids(components, storages)); }); field_get_components.push(quote! { self.#field.get_components(&mut func); @@ -128,7 +130,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { }); } else { field_component_ids.push(quote! { - component_ids.push(components.get_or_insert_id::<#field_type>()); + component_ids.push(components.init_component::<#field_type>(storages)); }); field_get_components.push(quote! { func((&mut self.#field as *mut #field_type).cast::()); @@ -145,10 +147,11 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { 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 + /// SAFE: ComponentId 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 #where_clause { fn component_ids( components: &mut #ecs_path::component::Components, + storages: &mut #ecs_path::storage::Storages, ) -> Vec<#ecs_path::component::ComponentId> { let mut component_ids = Vec::with_capacity(#field_len); #(#field_component_ids)* @@ -468,6 +471,11 @@ fn derive_label(input: DeriveInput, label_type: Ident) -> TokenStream2 { } } -fn bevy_ecs_path() -> syn::Path { +pub(crate) fn bevy_ecs_path() -> syn::Path { BevyManifest::default().get_path("bevy_ecs") } + +#[proc_macro_derive(Component, attributes(component))] +pub fn derive_component(input: TokenStream) -> TokenStream { + component::derive_component(input) +} diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index cfb9f96c10fef..463a0483cf5e4 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -29,8 +29,11 @@ use std::{any::TypeId, collections::HashMap}; /// /// ``` /// # use bevy_ecs::prelude::*; +/// # #[derive(Component)] /// # struct ComponentA; +/// # #[derive(Component)] /// # struct ComponentB; +/// # #[derive(Component)] /// # struct ComponentC; /// # /// #[derive(Bundle)] @@ -43,19 +46,26 @@ use std::{any::TypeId, collections::HashMap}; /// /// You can nest bundles using the `#[bundle]` attribute: /// ``` -/// # use bevy_ecs::bundle::Bundle; +/// # use bevy_ecs::{component::Component, bundle::Bundle}; +/// +/// #[derive(Component)] +/// struct X(i32); +/// #[derive(Component)] +/// struct Y(u64); +/// #[derive(Component)] +/// struct Z(String); /// /// #[derive(Bundle)] /// struct A { -/// x: i32, -/// y: u64, +/// x: X, +/// y: Y, /// } /// /// #[derive(Bundle)] /// struct B { /// #[bundle] /// a: A, -/// z: String, +/// z: Z, /// } /// ``` /// @@ -67,7 +77,7 @@ use std::{any::TypeId, collections::HashMap}; /// [Bundle::component_ids]. pub unsafe trait Bundle: Send + Sync + 'static { /// Gets this [Bundle]'s component ids, in the order of this bundle's Components - fn component_ids(components: &mut Components) -> Vec; + fn component_ids(components: &mut Components, storages: &mut Storages) -> Vec; /// Calls `func`, which should return data for each component in the bundle, in the order of /// this bundle's Components @@ -87,11 +97,11 @@ pub unsafe trait Bundle: Send + Sync + 'static { macro_rules! tuple_impl { ($($name: ident),*) => { - /// SAFE: TypeInfo is returned in tuple-order. [Bundle::from_components] and [Bundle::get_components] use tuple-order + /// SAFE: Component is returned in tuple-order. [Bundle::from_components] and [Bundle::get_components] use tuple-order unsafe impl<$($name: Component),*> Bundle for ($($name,)*) { #[allow(unused_variables)] - fn component_ids(components: &mut Components) -> Vec { - vec![$(components.get_or_insert_id::<$name>()),*] + fn component_ids(components: &mut Components, storages: &mut Storages) -> Vec { + vec![$(components.init_component::<$name>(storages)),*] } #[allow(unused_variables, unused_mut)] @@ -318,10 +328,7 @@ impl BundleInfo { let component_info = unsafe { 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) - } + StorageType::SparseSet => new_sparse_set_components.push(component_id), } } } @@ -585,10 +592,11 @@ impl Bundles { pub(crate) fn init_info<'a, T: Bundle>( &'a mut self, components: &mut Components, + storages: &mut Storages, ) -> &'a BundleInfo { let bundle_infos = &mut self.bundle_infos; let id = self.bundle_ids.entry(TypeId::of::()).or_insert_with(|| { - let component_ids = T::component_ids(components); + let component_ids = T::component_ids(components, storages); let id = BundleId(bundle_infos.len()); // SAFE: T::component_id ensures info was created let bundle_info = unsafe { diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index 3cf2fecc49d00..3b59303de4196 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -1,6 +1,6 @@ //! Types that detect when their internal data mutate. -use crate::component::{Component, ComponentTicks}; +use crate::{component::ComponentTicks, system::Resource}; use bevy_reflect::Reflect; use std::ops::{Deref, DerefMut}; @@ -147,14 +147,14 @@ pub(crate) struct Ticks<'a> { /// Panics when used as a [`SystemParam`](crate::system::SystemParam) if the resource does not exist. /// /// Use `Option>` instead if the resource might not always exist. -pub struct ResMut<'a, T: Component> { +pub struct ResMut<'a, T: Resource> { pub(crate) value: &'a mut T, pub(crate) ticks: Ticks<'a>, } -change_detection_impl!(ResMut<'a, T>, T, Component); -impl_into_inner!(ResMut<'a, T>, T, Component); -impl_debug!(ResMut<'a, T>, Component); +change_detection_impl!(ResMut<'a, T>, T, Resource); +impl_into_inner!(ResMut<'a, T>, T, Resource); +impl_debug!(ResMut<'a, T>, Resource); /// Unique borrow of a non-[`Send`] resource. /// diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index a633ff620e403..5ece927418713 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -1,18 +1,28 @@ //! Types for declaring and storing [`Component`]s. -use crate::storage::SparseSetIndex; +use crate::{ + storage::{SparseSetIndex, Storages}, + system::Resource, +}; +pub use bevy_ecs_macros::Component; use std::{ alloc::Layout, any::{Any, TypeId}, - collections::hash_map::Entry, }; use thiserror::Error; /// A component is data associated with an [`Entity`](crate::entity::Entity). Each entity can have /// multiple different types of components, but only one of them per type. /// -/// Any type that is `Send + Sync + 'static` automatically implements `Component`. +/// Any type that is `Send + Sync + 'static` can implement `Component` using `#[derive(Component)]`. /// +/// In order to use foreign types as components, wrap them using a newtype pattern. +/// ``` +/// # use bevy_ecs::component::Component; +/// use std::time::Duration; +/// #[derive(Component)] +/// struct Cooldown(Duration); +/// ``` /// Components are added with new entities using [`Commands::spawn`](crate::system::Commands::spawn), /// or to existing entities with [`EntityCommands::insert`](crate::system::EntityCommands::insert), /// or their [`World`](crate::world::World) equivalents. @@ -21,21 +31,49 @@ use thiserror::Error; /// as one of the arguments. /// /// Components can be grouped together into a [`Bundle`](crate::bundle::Bundle). -pub trait Component: Send + Sync + 'static {} -impl Component for T {} +pub trait Component: Send + Sync + 'static { + type Storage: ComponentStorage; +} + +pub struct TableStorage; +pub struct SparseStorage; + +pub trait ComponentStorage: sealed::Sealed { + // because the trait is selaed, those items are private API. + const STORAGE_TYPE: StorageType; +} + +impl ComponentStorage for TableStorage { + const STORAGE_TYPE: StorageType = StorageType::Table; +} +impl ComponentStorage for SparseStorage { + const STORAGE_TYPE: StorageType = StorageType::SparseSet; +} + +mod sealed { + pub trait Sealed {} + impl Sealed for super::TableStorage {} + impl Sealed for super::SparseStorage {} +} + +// ECS dependencies cannot derive Component, so we must implement it manually for relevant structs. +impl Component for bevy_tasks::Task +where + Self: Send + Sync + 'static, +{ + type Storage = TableStorage; +} /// The storage used for a specific component type. /// /// # Examples -/// The [`StorageType`] for a component is normally configured via `World::register_component`. +/// The [`StorageType`] for a component is configured via the derive attribute /// /// ``` /// # use bevy_ecs::{prelude::*, component::*}; -/// +/// #[derive(Component)] +/// #[component(storage = "SparseSet")] /// struct A; -/// -/// let mut world = World::default(); -/// world.register_component(ComponentDescriptor::new::(StorageType::SparseSet)); /// ``` #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum StorageType { @@ -128,6 +166,8 @@ impl SparseSetIndex for ComponentId { #[derive(Debug)] pub struct ComponentDescriptor { name: String, + // SAFETY: This must remain private. It must match the statically known StorageType of the + // associated rust component type if one exists. storage_type: StorageType, // SAFETY: This must remain private. It must only be set to "true" if this component is // actually Send + Sync @@ -143,7 +183,18 @@ impl ComponentDescriptor { x.cast::().drop_in_place() } - pub fn new(storage_type: StorageType) -> Self { + pub fn new() -> Self { + Self { + name: std::any::type_name::().to_string(), + storage_type: T::Storage::STORAGE_TYPE, + is_send_and_sync: true, + type_id: Some(TypeId::of::()), + layout: Layout::new::(), + drop: Self::drop_ptr::, + } + } + + pub fn new_resource(storage_type: StorageType) -> Self { Self { name: std::any::type_name::().to_string(), storage_type, @@ -191,46 +242,29 @@ pub struct Components { #[derive(Debug, Error)] pub enum ComponentsError { #[error("A component of type {name:?} ({type_id:?}) already exists")] - ComponentAlreadyExists { type_id: TypeId, name: String }, + ComponentAlreadyExists { + type_id: TypeId, + name: String, + existing_id: ComponentId, + }, } impl Components { - pub(crate) fn add( - &mut self, - descriptor: ComponentDescriptor, - ) -> Result { - 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, - name: descriptor.name, - }); - } - self.indices.insert(type_id, index); - } - self.components - .push(ComponentInfo::new(ComponentId(index), descriptor)); - - Ok(ComponentId(index)) - } - #[inline] - pub fn get_or_insert_id(&mut self) -> ComponentId { - // SAFE: The [`ComponentDescriptor`] matches the [`TypeId`] - unsafe { - self.get_or_insert_with(TypeId::of::(), || { - ComponentDescriptor::new::(StorageType::default()) - }) - } - } - - #[inline] - pub fn get_or_insert_info(&mut self) -> &ComponentInfo { - let id = self.get_or_insert_id::(); - // SAFE: component_info with the given `id` initialized above - unsafe { self.get_info_unchecked(id) } + pub fn init_component(&mut self, storages: &mut Storages) -> ComponentId { + let type_id = TypeId::of::(); + let components = &mut self.components; + let index = self.indices.entry(type_id).or_insert_with(|| { + let index = components.len(); + let descriptor = ComponentDescriptor::new::(); + let info = ComponentInfo::new(ComponentId(index), descriptor); + if T::Storage::STORAGE_TYPE == StorageType::SparseSet { + storages.sparse_sets.get_or_insert(&info); + } + components.push(info); + index + }); + ComponentId(*index) } #[inline] @@ -270,17 +304,17 @@ impl Components { } #[inline] - pub fn get_or_insert_resource_id(&mut self) -> ComponentId { + pub fn init_resource(&mut self) -> ComponentId { // SAFE: The [`ComponentDescriptor`] matches the [`TypeId`] unsafe { self.get_or_insert_resource_with(TypeId::of::(), || { - ComponentDescriptor::new::(StorageType::default()) + ComponentDescriptor::new_resource::(StorageType::default()) }) } } #[inline] - pub fn get_or_insert_non_send_resource_id(&mut self) -> ComponentId { + pub fn init_non_send(&mut self) -> ComponentId { // SAFE: The [`ComponentDescriptor`] matches the [`TypeId`] unsafe { self.get_or_insert_resource_with(TypeId::of::(), || { @@ -308,26 +342,6 @@ impl Components { ComponentId(*index) } - - /// # Safety - /// - /// The [`ComponentDescriptor`] must match the [`TypeId`] - #[inline] - pub(crate) unsafe fn get_or_insert_with( - &mut self, - type_id: TypeId, - func: impl FnOnce() -> ComponentDescriptor, - ) -> ComponentId { - let components = &mut self.components; - let index = self.indices.entry(type_id).or_insert_with(|| { - let descriptor = func(); - let index = components.len(); - components.push(ComponentInfo::new(ComponentId(index), descriptor)); - index - }); - - ComponentId(*index) - } } #[derive(Clone, Debug)] diff --git a/crates/bevy_ecs/src/event.rs b/crates/bevy_ecs/src/event.rs index d627c68e69ee0..d28ce5ad3ad1a 100644 --- a/crates/bevy_ecs/src/event.rs +++ b/crates/bevy_ecs/src/event.rs @@ -1,10 +1,7 @@ //! Event handling types. -use crate as bevy_ecs; -use crate::{ - component::Component, - system::{Local, Res, ResMut, SystemParam}, -}; +use crate::system::{Local, Res, ResMut, SystemParam}; +use crate::{self as bevy_ecs, system::Resource}; use bevy_utils::tracing::trace; use std::{ fmt::{self}, @@ -153,20 +150,20 @@ fn map_instance_event(event_instance: &EventInstance) -> &T { /// Reads events of type `T` in order and tracks which events have already been read. #[derive(SystemParam)] -pub struct EventReader<'w, 's, T: Component> { +pub struct EventReader<'w, 's, T: Resource> { last_event_count: Local<'s, (usize, PhantomData)>, events: Res<'w, Events>, } /// Sends events of type `T`. #[derive(SystemParam)] -pub struct EventWriter<'w, 's, T: Component> { +pub struct EventWriter<'w, 's, T: Resource> { events: ResMut<'w, Events>, #[system_param(ignore)] marker: PhantomData<&'s usize>, } -impl<'w, 's, T: Component> EventWriter<'w, 's, T> { +impl<'w, 's, T: Resource> EventWriter<'w, 's, T> { pub fn send(&mut self, event: T) { self.events.send(event); } @@ -256,7 +253,7 @@ fn internal_event_reader<'a, T>( } } -impl<'w, 's, T: Component> EventReader<'w, 's, T> { +impl<'w, 's, T: Resource> EventReader<'w, 's, 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. @@ -273,7 +270,7 @@ impl<'w, 's, T: Component> EventReader<'w, 's, T> { } } -impl Events { +impl Events { /// "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) { diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index ffe8c068049c2..6fb347a2a7e8f 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -21,6 +21,7 @@ pub mod prelude { pub use crate::{ bundle::Bundle, change_detection::DetectChanges, + component::Component, entity::Entity, event::{EventReader, EventWriter}, query::{Added, ChangeTrackers, Changed, Or, QueryState, With, Without}, @@ -42,7 +43,7 @@ mod tests { use crate as bevy_ecs; use crate::{ bundle::Bundle, - component::{Component, ComponentDescriptor, ComponentId, StorageType}, + component::{Component, ComponentId}, entity::Entity, query::{ Added, ChangeTrackers, Changed, FilterFetch, FilteredAccess, With, Without, WorldQuery, @@ -59,14 +60,14 @@ mod tests { }, }; - #[derive(Debug, PartialEq, Eq)] + #[derive(Component, Debug, PartialEq, Eq, Clone, Copy)] struct A(usize); - #[derive(Debug, PartialEq, Eq)] + #[derive(Component, Debug, PartialEq, Eq, Clone, Copy)] struct B(usize); - #[derive(Debug, PartialEq, Eq)] + #[derive(Component, Debug, PartialEq, Eq, Clone, Copy)] struct C; - #[derive(Clone, Debug)] + #[derive(Component, Clone, Debug)] struct DropCk(Arc); impl DropCk { fn new_pair() -> (Self, Arc) { @@ -81,26 +82,41 @@ mod tests { } } + #[derive(Component, Clone, Debug)] + #[component(storage = "SparseSet")] + struct DropCkSparse(DropCk); + + #[derive(Component, Copy, Clone, PartialEq, Eq, Debug)] + #[component(storage = "Table")] + struct TableStored(&'static str); + #[derive(Component, Copy, Clone, PartialEq, Eq, Debug)] + #[component(storage = "SparseSet")] + struct SparseStored(u32); + #[test] fn random_access() { let mut world = World::new(); - world - .register_component(ComponentDescriptor::new::(StorageType::SparseSet)) - .unwrap(); - let e = world.spawn().insert_bundle(("abc", 123)).id(); - let f = world.spawn().insert_bundle(("def", 456, true)).id(); - assert_eq!(*world.get::<&str>(e).unwrap(), "abc"); - assert_eq!(*world.get::(e).unwrap(), 123); - assert_eq!(*world.get::<&str>(f).unwrap(), "def"); - assert_eq!(*world.get::(f).unwrap(), 456); + + let e = world + .spawn() + .insert_bundle((TableStored("abc"), SparseStored(123))) + .id(); + let f = world + .spawn() + .insert_bundle((TableStored("def"), SparseStored(456), A(1))) + .id(); + assert_eq!(world.get::(e).unwrap().0, "abc"); + assert_eq!(world.get::(e).unwrap().0, 123); + assert_eq!(world.get::(f).unwrap().0, "def"); + assert_eq!(world.get::(f).unwrap().0, 456); // test archetype get_mut() - *world.get_mut::<&'static str>(e).unwrap() = "xyz"; - assert_eq!(*world.get::<&'static str>(e).unwrap(), "xyz"); + world.get_mut::(e).unwrap().0 = "xyz"; + assert_eq!(world.get::(e).unwrap().0, "xyz"); // test sparse set get_mut() - *world.get_mut::(f).unwrap() = 42; - assert_eq!(*world.get::(f).unwrap(), 42); + world.get_mut::(f).unwrap().0 = 42; + assert_eq!(world.get::(f).unwrap().0, 42); } #[test] @@ -109,79 +125,93 @@ mod tests { #[derive(Bundle, PartialEq, Debug)] struct Foo { - x: &'static str, - y: i32, + x: TableStored, + y: SparseStored, } - world - .register_component(ComponentDescriptor::new::(StorageType::SparseSet)) - .unwrap(); - assert_eq!( - ::component_ids(world.components_mut()), + ::component_ids(&mut world.components, &mut world.storages), vec![ - world.components_mut().get_or_insert_id::<&'static str>(), - world.components_mut().get_or_insert_id::(), + world.init_component::(), + world.init_component::(), ] ); - let e1 = world.spawn().insert_bundle(Foo { x: "abc", y: 123 }).id(); - let e2 = world.spawn().insert_bundle(("def", 456, true)).id(); - assert_eq!(*world.get::<&str>(e1).unwrap(), "abc"); - assert_eq!(*world.get::(e1).unwrap(), 123); - assert_eq!(*world.get::<&str>(e2).unwrap(), "def"); - assert_eq!(*world.get::(e2).unwrap(), 456); + let e1 = world + .spawn() + .insert_bundle(Foo { + x: TableStored("abc"), + y: SparseStored(123), + }) + .id(); + let e2 = world + .spawn() + .insert_bundle((TableStored("def"), SparseStored(456), A(1))) + .id(); + assert_eq!(world.get::(e1).unwrap().0, "abc"); + assert_eq!(world.get::(e1).unwrap().0, 123); + assert_eq!(world.get::(e2).unwrap().0, "def"); + assert_eq!(world.get::(e2).unwrap().0, 456); // test archetype get_mut() - *world.get_mut::<&'static str>(e1).unwrap() = "xyz"; - assert_eq!(*world.get::<&'static str>(e1).unwrap(), "xyz"); + world.get_mut::(e1).unwrap().0 = "xyz"; + assert_eq!(world.get::(e1).unwrap().0, "xyz"); // test sparse set get_mut() - *world.get_mut::(e2).unwrap() = 42; - assert_eq!(*world.get::(e2).unwrap(), 42); + world.get_mut::(e2).unwrap().0 = 42; + assert_eq!(world.get::(e2).unwrap().0, 42); assert_eq!( world.entity_mut(e1).remove_bundle::().unwrap(), - Foo { x: "xyz", y: 123 } + Foo { + x: TableStored("xyz"), + y: SparseStored(123), + } ); #[derive(Bundle, PartialEq, Debug)] struct Nested { - a: usize, + a: A, #[bundle] foo: Foo, - b: u8, + b: B, } assert_eq!( - ::component_ids(world.components_mut()), + ::component_ids(&mut world.components, &mut world.storages), vec![ - world.components_mut().get_or_insert_id::(), - world.components_mut().get_or_insert_id::<&'static str>(), - world.components_mut().get_or_insert_id::(), - world.components_mut().get_or_insert_id::(), + world.init_component::(), + world.init_component::(), + world.init_component::(), + world.init_component::(), ] ); let e3 = world .spawn() .insert_bundle(Nested { - a: 1, - foo: Foo { x: "ghi", y: 789 }, - b: 2, + a: A(1), + foo: Foo { + x: TableStored("ghi"), + y: SparseStored(789), + }, + b: B(2), }) .id(); - assert_eq!(*world.get::<&str>(e3).unwrap(), "ghi"); - assert_eq!(*world.get::(e3).unwrap(), 789); - assert_eq!(*world.get::(e3).unwrap(), 1); - assert_eq!(*world.get::(e3).unwrap(), 2); + assert_eq!(world.get::(e3).unwrap().0, "ghi"); + assert_eq!(world.get::(e3).unwrap().0, 789); + assert_eq!(world.get::(e3).unwrap().0, 1); + assert_eq!(world.get::(e3).unwrap().0, 2); assert_eq!( world.entity_mut(e3).remove_bundle::().unwrap(), Nested { - a: 1, - foo: Foo { x: "ghi", y: 789 }, - b: 2, + a: A(1), + foo: Foo { + x: TableStored("ghi"), + y: SparseStored(789), + }, + b: B(2), } ); } @@ -189,113 +219,167 @@ mod tests { #[test] fn despawn_table_storage() { let mut world = World::new(); - let e = world.spawn().insert_bundle(("abc", 123)).id(); - let f = world.spawn().insert_bundle(("def", 456)).id(); + let e = world + .spawn() + .insert_bundle((TableStored("abc"), A(123))) + .id(); + let f = world + .spawn() + .insert_bundle((TableStored("def"), A(456))) + .id(); assert_eq!(world.entities.len(), 2); assert!(world.despawn(e)); assert_eq!(world.entities.len(), 1); - assert!(world.get::<&str>(e).is_none()); - assert!(world.get::(e).is_none()); - assert_eq!(*world.get::<&str>(f).unwrap(), "def"); - assert_eq!(*world.get::(f).unwrap(), 456); + assert!(world.get::(e).is_none()); + assert!(world.get::(e).is_none()); + assert_eq!(world.get::(f).unwrap().0, "def"); + assert_eq!(world.get::(f).unwrap().0, 456); } #[test] fn despawn_mixed_storage() { let mut world = World::new(); - world - .register_component(ComponentDescriptor::new::(StorageType::SparseSet)) - .unwrap(); - let e = world.spawn().insert_bundle(("abc", 123)).id(); - let f = world.spawn().insert_bundle(("def", 456)).id(); + + let e = world + .spawn() + .insert_bundle((TableStored("abc"), SparseStored(123))) + .id(); + let f = world + .spawn() + .insert_bundle((TableStored("def"), SparseStored(456))) + .id(); assert_eq!(world.entities.len(), 2); assert!(world.despawn(e)); assert_eq!(world.entities.len(), 1); - assert!(world.get::<&str>(e).is_none()); - assert!(world.get::(e).is_none()); - assert_eq!(*world.get::<&str>(f).unwrap(), "def"); - assert_eq!(*world.get::(f).unwrap(), 456); + assert!(world.get::(e).is_none()); + assert!(world.get::(e).is_none()); + assert_eq!(world.get::(f).unwrap().0, "def"); + assert_eq!(world.get::(f).unwrap().0, 456); } #[test] fn query_all() { let mut world = World::new(); - let e = world.spawn().insert_bundle(("abc", 123)).id(); - let f = world.spawn().insert_bundle(("def", 456)).id(); + let e = world + .spawn() + .insert_bundle((TableStored("abc"), A(123))) + .id(); + let f = world + .spawn() + .insert_bundle((TableStored("def"), A(456))) + .id(); let ents = world - .query::<(Entity, &i32, &&str)>() + .query::<(Entity, &A, &TableStored)>() .iter(&world) .map(|(e, &i, &s)| (e, i, s)) .collect::>(); - assert_eq!(ents, &[(e, 123, "abc"), (f, 456, "def")]); + assert_eq!( + ents, + &[ + (e, A(123), TableStored("abc")), + (f, A(456), TableStored("def")) + ] + ); } #[test] fn query_all_for_each() { let mut world = World::new(); - let e = world.spawn().insert_bundle(("abc", 123)).id(); - let f = world.spawn().insert_bundle(("def", 456)).id(); + let e = world + .spawn() + .insert_bundle((TableStored("abc"), A(123))) + .id(); + let f = world + .spawn() + .insert_bundle((TableStored("def"), A(456))) + .id(); let mut results = Vec::new(); world - .query::<(Entity, &i32, &&str)>() + .query::<(Entity, &A, &TableStored)>() .for_each(&world, |(e, &i, &s)| results.push((e, i, s))); - assert_eq!(results, &[(e, 123, "abc"), (f, 456, "def")]); + assert_eq!( + results, + &[ + (e, A(123), TableStored("abc")), + (f, A(456), TableStored("def")) + ] + ); } #[test] fn query_single_component() { let mut world = World::new(); - let e = world.spawn().insert_bundle(("abc", 123)).id(); - let f = world.spawn().insert_bundle(("def", 456, true)).id(); + let e = world + .spawn() + .insert_bundle((TableStored("abc"), A(123))) + .id(); + let f = world + .spawn() + .insert_bundle((TableStored("def"), A(456), B(1))) + .id(); let ents = world - .query::<(Entity, &i32)>() + .query::<(Entity, &A)>() .iter(&world) .map(|(e, &i)| (e, i)) .collect::>(); - assert_eq!(ents, &[(e, 123), (f, 456)]); + assert_eq!(ents, &[(e, A(123)), (f, A(456))]); } #[test] fn stateful_query_handles_new_archetype() { let mut world = World::new(); - let e = world.spawn().insert_bundle(("abc", 123)).id(); - let mut query = world.query::<(Entity, &i32)>(); + let e = world + .spawn() + .insert_bundle((TableStored("abc"), A(123))) + .id(); + let mut query = world.query::<(Entity, &A)>(); let ents = query.iter(&world).map(|(e, &i)| (e, i)).collect::>(); - assert_eq!(ents, &[(e, 123)]); + assert_eq!(ents, &[(e, A(123))]); - let f = world.spawn().insert_bundle(("def", 456, true)).id(); + let f = world + .spawn() + .insert_bundle((TableStored("def"), A(456), B(1))) + .id(); let ents = query.iter(&world).map(|(e, &i)| (e, i)).collect::>(); - assert_eq!(ents, &[(e, 123), (f, 456)]); + assert_eq!(ents, &[(e, A(123)), (f, A(456))]); } #[test] fn query_single_component_for_each() { let mut world = World::new(); - let e = world.spawn().insert_bundle(("abc", 123)).id(); - let f = world.spawn().insert_bundle(("def", 456, true)).id(); + let e = world + .spawn() + .insert_bundle((TableStored("abc"), A(123))) + .id(); + let f = world + .spawn() + .insert_bundle((TableStored("def"), A(456), B(1))) + .id(); let mut results = Vec::new(); world - .query::<(Entity, &i32)>() + .query::<(Entity, &A)>() .for_each(&world, |(e, &i)| results.push((e, i))); - assert_eq!(results, &[(e, 123), (f, 456)]); + assert_eq!(results, &[(e, A(123)), (f, A(456))]); } #[test] fn par_for_each_dense() { let mut world = World::new(); let task_pool = TaskPool::default(); - let e1 = world.spawn().insert(1).id(); - let e2 = world.spawn().insert(2).id(); - let e3 = world.spawn().insert(3).id(); - let e4 = world.spawn().insert_bundle((4, true)).id(); - let e5 = world.spawn().insert_bundle((5, true)).id(); + let e1 = world.spawn().insert(A(1)).id(); + let e2 = world.spawn().insert(A(2)).id(); + let e3 = world.spawn().insert(A(3)).id(); + let e4 = world.spawn().insert_bundle((A(4), B(1))).id(); + let e5 = world.spawn().insert_bundle((A(5), B(1))).id(); let results = Arc::new(Mutex::new(Vec::new())); world - .query::<(Entity, &i32)>() - .par_for_each(&world, &task_pool, 2, |(e, &i)| results.lock().push((e, i))); + .query::<(Entity, &A)>() + .par_for_each(&world, &task_pool, 2, |(e, &A(i))| { + results.lock().push((e, i)) + }); results.lock().sort(); assert_eq!( &*results.lock(), @@ -306,19 +390,20 @@ mod tests { #[test] fn par_for_each_sparse() { let mut world = World::new(); - world - .register_component(ComponentDescriptor::new::(StorageType::SparseSet)) - .unwrap(); + let task_pool = TaskPool::default(); - let e1 = world.spawn().insert(1).id(); - let e2 = world.spawn().insert(2).id(); - let e3 = world.spawn().insert(3).id(); - let e4 = world.spawn().insert_bundle((4, true)).id(); - let e5 = world.spawn().insert_bundle((5, true)).id(); + let e1 = world.spawn().insert(SparseStored(1)).id(); + let e2 = world.spawn().insert(SparseStored(2)).id(); + let e3 = world.spawn().insert(SparseStored(3)).id(); + let e4 = world.spawn().insert_bundle((SparseStored(4), A(1))).id(); + let e5 = world.spawn().insert_bundle((SparseStored(5), A(1))).id(); let results = Arc::new(Mutex::new(Vec::new())); - world - .query::<(Entity, &i32)>() - .par_for_each(&world, &task_pool, 2, |(e, &i)| results.lock().push((e, i))); + world.query::<(Entity, &SparseStored)>().par_for_each( + &world, + &task_pool, + 2, + |(e, &SparseStored(i))| results.lock().push((e, i)), + ); results.lock().sort(); assert_eq!( &*results.lock(), @@ -329,194 +414,218 @@ mod tests { #[test] fn query_missing_component() { let mut world = World::new(); - world.spawn().insert_bundle(("abc", 123)); - world.spawn().insert_bundle(("def", 456)); - assert!(world.query::<(&bool, &i32)>().iter(&world).next().is_none()); + world.spawn().insert_bundle((TableStored("abc"), A(123))); + world.spawn().insert_bundle((TableStored("def"), A(456))); + assert!(world.query::<(&B, &A)>().iter(&world).next().is_none()); } #[test] fn query_sparse_component() { let mut world = World::new(); - world.spawn().insert_bundle(("abc", 123)); - let f = world.spawn().insert_bundle(("def", 456, true)).id(); + world.spawn().insert_bundle((TableStored("abc"), A(123))); + let f = world + .spawn() + .insert_bundle((TableStored("def"), A(456), B(1))) + .id(); let ents = world - .query::<(Entity, &bool)>() + .query::<(Entity, &B)>() .iter(&world) .map(|(e, &b)| (e, b)) .collect::>(); - assert_eq!(ents, &[(f, true)]); + assert_eq!(ents, &[(f, B(1))]); } #[test] fn query_filter_with() { let mut world = World::new(); - world.spawn().insert_bundle((123u32, 1.0f32)); - world.spawn().insert(456u32); + world.spawn().insert_bundle((A(123), B(1))); + world.spawn().insert(A(456)); let result = world - .query_filtered::<&u32, With>() + .query_filtered::<&A, With>() .iter(&world) .cloned() .collect::>(); - assert_eq!(result, vec![123]); + assert_eq!(result, vec![A(123)]); } #[test] fn query_filter_with_for_each() { let mut world = World::new(); - world.spawn().insert_bundle((123u32, 1.0f32)); - world.spawn().insert(456u32); + world.spawn().insert_bundle((A(123), B(1))); + world.spawn().insert(A(456)); let mut results = Vec::new(); world - .query_filtered::<&u32, With>() + .query_filtered::<&A, With>() .for_each(&world, |i| results.push(*i)); - assert_eq!(results, vec![123]); + assert_eq!(results, vec![A(123)]); } #[test] fn query_filter_with_sparse() { let mut world = World::new(); - world - .register_component(ComponentDescriptor::new::(StorageType::SparseSet)) - .unwrap(); - world.spawn().insert_bundle((123u32, 1.0f32)); - world.spawn().insert(456u32); + + world.spawn().insert_bundle((A(123), SparseStored(321))); + world.spawn().insert(A(456)); let result = world - .query_filtered::<&u32, With>() + .query_filtered::<&A, With>() .iter(&world) .cloned() .collect::>(); - assert_eq!(result, vec![123]); + assert_eq!(result, vec![A(123)]); } #[test] fn query_filter_with_sparse_for_each() { let mut world = World::new(); - world - .register_component(ComponentDescriptor::new::(StorageType::SparseSet)) - .unwrap(); - world.spawn().insert_bundle((123u32, 1.0f32)); - world.spawn().insert(456u32); + + world.spawn().insert_bundle((A(123), SparseStored(321))); + world.spawn().insert(A(456)); let mut results = Vec::new(); world - .query_filtered::<&u32, With>() + .query_filtered::<&A, With>() .for_each(&world, |i| results.push(*i)); - assert_eq!(results, vec![123]); + assert_eq!(results, vec![A(123)]); } #[test] fn query_filter_without() { let mut world = World::new(); - world.spawn().insert_bundle((123u32, 1.0f32)); - world.spawn().insert(456u32); + world.spawn().insert_bundle((A(123), B(321))); + world.spawn().insert(A(456)); let result = world - .query_filtered::<&u32, Without>() + .query_filtered::<&A, Without>() .iter(&world) .cloned() .collect::>(); - assert_eq!(result, vec![456]); + assert_eq!(result, vec![A(456)]); } #[test] fn query_optional_component_table() { let mut world = World::new(); - let e = world.spawn().insert_bundle(("abc", 123)).id(); - let f = world.spawn().insert_bundle(("def", 456, true)).id(); + let e = world + .spawn() + .insert_bundle((TableStored("abc"), A(123))) + .id(); + let f = world + .spawn() + .insert_bundle((TableStored("def"), A(456), B(1))) + .id(); // this should be skipped - world.spawn().insert("abc"); + world.spawn().insert(TableStored("abc")); let ents = world - .query::<(Entity, Option<&bool>, &i32)>() + .query::<(Entity, Option<&B>, &A)>() .iter(&world) .map(|(e, b, &i)| (e, b.copied(), i)) .collect::>(); - assert_eq!(ents, &[(e, None, 123), (f, Some(true), 456)]); + assert_eq!(ents, &[(e, None, A(123)), (f, Some(B(1)), A(456))]); } #[test] fn query_optional_component_sparse() { let mut world = World::new(); - world - .register_component(ComponentDescriptor::new::(StorageType::SparseSet)) - .unwrap(); - let e = world.spawn().insert_bundle(("abc", 123)).id(); - let f = world.spawn().insert_bundle(("def", 456, true)).id(); + + let e = world + .spawn() + .insert_bundle((TableStored("abc"), A(123))) + .id(); + let f = world + .spawn() + .insert_bundle((TableStored("def"), A(456), SparseStored(1))) + .id(); // // this should be skipped - // world.spawn().insert("abc"); + // SparseStored(1).spawn().insert("abc"); let ents = world - .query::<(Entity, Option<&bool>, &i32)>() + .query::<(Entity, Option<&SparseStored>, &A)>() .iter(&world) .map(|(e, b, &i)| (e, b.copied(), i)) .collect::>(); - assert_eq!(ents, &[(e, None, 123), (f, Some(true), 456)]); + assert_eq!( + ents, + &[(e, None, A(123)), (f, Some(SparseStored(1)), A(456))] + ); } #[test] fn query_optional_component_sparse_no_match() { let mut world = World::new(); - world - .register_component(ComponentDescriptor::new::(StorageType::SparseSet)) - .unwrap(); - let e = world.spawn().insert_bundle(("abc", 123)).id(); - let f = world.spawn().insert_bundle(("def", 456)).id(); + + let e = world + .spawn() + .insert_bundle((TableStored("abc"), A(123))) + .id(); + let f = world + .spawn() + .insert_bundle((TableStored("def"), A(456))) + .id(); // // this should be skipped - world.spawn().insert("abc"); + world.spawn().insert(TableStored("abc")); let ents = world - .query::<(Entity, Option<&bool>, &i32)>() + .query::<(Entity, Option<&SparseStored>, &A)>() .iter(&world) .map(|(e, b, &i)| (e, b.copied(), i)) .collect::>(); - assert_eq!(ents, &[(e, None, 123), (f, None, 456)]); + assert_eq!(ents, &[(e, None, A(123)), (f, None, A(456))]); } #[test] fn add_remove_components() { let mut world = World::new(); - let e1 = world.spawn().insert(42).insert_bundle((true, "abc")).id(); - let e2 = world.spawn().insert(0).insert_bundle((false, "xyz")).id(); + let e1 = world + .spawn() + .insert(A(1)) + .insert_bundle((B(3), TableStored("abc"))) + .id(); + let e2 = world + .spawn() + .insert(A(2)) + .insert_bundle((B(4), TableStored("xyz"))) + .id(); assert_eq!( world - .query::<(Entity, &i32, &bool)>() + .query::<(Entity, &A, &B)>() .iter(&world) .map(|(e, &i, &b)| (e, i, b)) .collect::>(), - &[(e1, 42, true), (e2, 0, false)] + &[(e1, A(1), B(3)), (e2, A(2), B(4))] ); - assert_eq!(world.entity_mut(e1).remove::(), Some(42)); + assert_eq!(world.entity_mut(e1).remove::(), Some(A(1))); assert_eq!( world - .query::<(Entity, &i32, &bool)>() + .query::<(Entity, &A, &B)>() .iter(&world) .map(|(e, &i, &b)| (e, i, b)) .collect::>(), - &[(e2, 0, false)] + &[(e2, A(2), B(4))] ); assert_eq!( world - .query::<(Entity, &bool, &&str)>() + .query::<(Entity, &B, &TableStored)>() .iter(&world) - .map(|(e, &b, &s)| (e, b, s)) + .map(|(e, &B(b), &TableStored(s))| (e, b, s)) .collect::>(), - &[(e2, false, "xyz"), (e1, true, "abc")] + &[(e2, 4, "xyz"), (e1, 3, "abc")] ); - world.entity_mut(e1).insert(43); + world.entity_mut(e1).insert(A(43)); assert_eq!( world - .query::<(Entity, &i32, &bool)>() + .query::<(Entity, &A, &B)>() .iter(&world) .map(|(e, &i, &b)| (e, i, b)) .collect::>(), - &[(e2, 0, false), (e1, 43, true)] + &[(e2, A(2), B(4)), (e1, A(43), B(3))] ); - world.entity_mut(e1).insert(1.0f32); + world.entity_mut(e1).insert(C); assert_eq!( world - .query::<(Entity, &f32)>() + .query::<(Entity, &C)>() .iter(&world) .map(|(e, &f)| (e, f)) .collect::>(), - &[(e1, 1.0)] + &[(e1, C)] ); } @@ -525,53 +634,57 @@ mod tests { let mut world = World::default(); let mut entities = Vec::with_capacity(10_000); for _ in 0..1000 { - entities.push(world.spawn().insert(0.0f32).id()); + entities.push(world.spawn().insert(B(0)).id()); } for (i, entity) in entities.iter().cloned().enumerate() { - world.entity_mut(entity).insert(i); + world.entity_mut(entity).insert(A(i)); } for (i, entity) in entities.iter().cloned().enumerate() { - assert_eq!(world.entity_mut(entity).remove::(), Some(i)); + assert_eq!(world.entity_mut(entity).remove::(), Some(A(i))); } } #[test] fn sparse_set_add_remove_many() { let mut world = World::default(); - world - .register_component(ComponentDescriptor::new::(StorageType::SparseSet)) - .unwrap(); + let mut entities = Vec::with_capacity(1000); for _ in 0..4 { - entities.push(world.spawn().insert(0.0f32).id()); + entities.push(world.spawn().insert(A(2)).id()); } for (i, entity) in entities.iter().cloned().enumerate() { - world.entity_mut(entity).insert(i); + world.entity_mut(entity).insert(SparseStored(i as u32)); } for (i, entity) in entities.iter().cloned().enumerate() { - assert_eq!(world.entity_mut(entity).remove::(), Some(i)); + assert_eq!( + world.entity_mut(entity).remove::(), + Some(SparseStored(i as u32)) + ); } } #[test] fn remove_missing() { let mut world = World::new(); - let e = world.spawn().insert_bundle(("abc", 123)).id(); - assert!(world.entity_mut(e).remove::().is_none()); + let e = world + .spawn() + .insert_bundle((TableStored("abc"), A(123))) + .id(); + assert!(world.entity_mut(e).remove::().is_none()); } #[test] fn spawn_batch() { let mut world = World::new(); - world.spawn_batch((0..100).map(|x| (x, "abc"))); + world.spawn_batch((0..100).map(|x| (A(x), TableStored("abc")))); let values = world - .query::<&i32>() + .query::<&A>() .iter(&world) - .copied() + .map(|v| v.0) .collect::>(); let expected = (0..100).collect::>(); assert_eq!(values, expected); @@ -580,17 +693,26 @@ mod tests { #[test] fn query_get() { let mut world = World::new(); - let a = world.spawn().insert_bundle(("abc", 123)).id(); - let b = world.spawn().insert_bundle(("def", 456)).id(); - let c = world.spawn().insert_bundle(("ghi", 789, true)).id(); + let a = world + .spawn() + .insert_bundle((TableStored("abc"), A(123))) + .id(); + let b = world + .spawn() + .insert_bundle((TableStored("def"), A(456))) + .id(); + let c = world + .spawn() + .insert_bundle((TableStored("ghi"), A(789), B(1))) + .id(); - let mut i32_query = world.query::<&i32>(); - assert_eq!(i32_query.get(&world, a).unwrap(), &123); - assert_eq!(i32_query.get(&world, b).unwrap(), &456); + let mut i32_query = world.query::<&A>(); + assert_eq!(i32_query.get(&world, a).unwrap().0, 123); + assert_eq!(i32_query.get(&world, b).unwrap().0, 456); - let mut i32_bool_query = world.query::<(&i32, &bool)>(); + let mut i32_bool_query = world.query::<(&A, &B)>(); assert!(i32_bool_query.get(&world, a).is_err()); - assert_eq!(i32_bool_query.get(&world, c).unwrap(), (&789, &true)); + assert_eq!(i32_bool_query.get(&world, c).unwrap(), (&A(789), &B(1))); assert!(world.despawn(a)); assert!(i32_query.get(&world, a).is_err()); } @@ -598,53 +720,49 @@ mod tests { #[test] fn remove_tracking() { let mut world = World::new(); - world - .register_component(ComponentDescriptor::new::<&'static str>( - StorageType::SparseSet, - )) - .unwrap(); - let a = world.spawn().insert_bundle(("abc", 123)).id(); - let b = world.spawn().insert_bundle(("abc", 123)).id(); + + let a = world.spawn().insert_bundle((SparseStored(0), A(123))).id(); + let b = world.spawn().insert_bundle((SparseStored(1), A(123))).id(); world.entity_mut(a).despawn(); assert_eq!( - world.removed::().collect::>(), + world.removed::().collect::>(), &[a], "despawning results in 'removed component' state for table components" ); assert_eq!( - world.removed::<&'static str>().collect::>(), + world.removed::().collect::>(), &[a], "despawning results in 'removed component' state for sparse set components" ); - world.entity_mut(b).insert(10.0); + world.entity_mut(b).insert(B(1)); assert_eq!( - world.removed::().collect::>(), + world.removed::().collect::>(), &[a], "archetype moves does not result in 'removed component' state" ); - world.entity_mut(b).remove::(); + world.entity_mut(b).remove::(); assert_eq!( - world.removed::().collect::>(), + world.removed::().collect::>(), &[a, b], "removing a component results in a 'removed component' state" ); world.clear_trackers(); assert_eq!( - world.removed::().collect::>(), + world.removed::().collect::>(), &[], "clearning trackers clears removals" ); assert_eq!( - world.removed::<&'static str>().collect::>(), + world.removed::().collect::>(), &[], "clearning trackers clears removals" ); assert_eq!( - world.removed::().collect::>(), + world.removed::().collect::>(), &[], "clearning trackers clears removals" ); @@ -673,61 +791,49 @@ mod tests { #[test] fn added_tracking() { let mut world = World::new(); - let a = world.spawn().insert(123i32).id(); + let a = world.spawn().insert(A(123)).id(); - assert_eq!(world.query::<&i32>().iter(&world).count(), 1); + assert_eq!(world.query::<&A>().iter(&world).count(), 1); assert_eq!( - world - .query_filtered::<(), Added>() - .iter(&world) - .count(), + world.query_filtered::<(), Added>().iter(&world).count(), 1 ); - assert_eq!(world.query::<&i32>().iter(&world).count(), 1); + assert_eq!(world.query::<&A>().iter(&world).count(), 1); assert_eq!( - world - .query_filtered::<(), Added>() - .iter(&world) - .count(), + world.query_filtered::<(), Added>().iter(&world).count(), 1 ); - assert!(world.query::<&i32>().get(&world, a).is_ok()); + assert!(world.query::<&A>().get(&world, a).is_ok()); assert!(world - .query_filtered::<(), Added>() + .query_filtered::<(), Added>() .get(&world, a) .is_ok()); - assert!(world.query::<&i32>().get(&world, a).is_ok()); + assert!(world.query::<&A>().get(&world, a).is_ok()); assert!(world - .query_filtered::<(), Added>() + .query_filtered::<(), Added>() .get(&world, a) .is_ok()); world.clear_trackers(); - assert_eq!(world.query::<&i32>().iter(&world).count(), 1); + assert_eq!(world.query::<&A>().iter(&world).count(), 1); assert_eq!( - world - .query_filtered::<(), Added>() - .iter(&world) - .count(), + world.query_filtered::<(), Added>().iter(&world).count(), 0 ); - assert_eq!(world.query::<&i32>().iter(&world).count(), 1); + assert_eq!(world.query::<&A>().iter(&world).count(), 1); assert_eq!( - world - .query_filtered::<(), Added>() - .iter(&world) - .count(), + world.query_filtered::<(), Added>().iter(&world).count(), 0 ); - assert!(world.query::<&i32>().get(&world, a).is_ok()); + assert!(world.query::<&A>().get(&world, a).is_ok()); assert!(world - .query_filtered::<(), Added>() + .query_filtered::<(), Added>() .get(&world, a) .is_err()); - assert!(world.query::<&i32>().get(&world, a).is_ok()); + assert!(world.query::<&A>().get(&world, a).is_ok()); assert!(world - .query_filtered::<(), Added>() + .query_filtered::<(), Added>() .get(&world, a) .is_err()); } @@ -768,7 +874,7 @@ mod tests { let e1 = world.spawn().insert_bundle((A(0), B(0))).id(); let e2 = world.spawn().insert_bundle((A(0), B(0))).id(); let e3 = world.spawn().insert_bundle((A(0), B(0))).id(); - world.spawn().insert_bundle((A(0), B)); + world.spawn().insert_bundle((A(0), B(0))); world.clear_trackers(); @@ -796,7 +902,7 @@ mod tests { assert_eq!(get_filtered::>(&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 changed state - world.entity_mut(e1).insert_bundle((A(0), B)); + world.entity_mut(e1).insert_bundle((A(0), B(0))); assert_eq!( get_filtered::>(&mut world), vec![e3, e1], @@ -979,36 +1085,39 @@ mod tests { #[test] fn remove_intersection() { let mut world = World::default(); - let e1 = world.spawn().insert_bundle((1, 1.0, "a")).id(); + let e1 = world + .spawn() + .insert_bundle((A(1), B(1), TableStored("a"))) + .id(); let mut e = world.entity_mut(e1); - assert_eq!(e.get::<&'static str>(), Some(&"a")); - assert_eq!(e.get::(), Some(&1)); - assert_eq!(e.get::(), Some(&1.0)); + assert_eq!(e.get::(), Some(&TableStored("a"))); + assert_eq!(e.get::(), Some(&A(1))); + assert_eq!(e.get::(), Some(&B(1))); assert_eq!( - e.get::(), + e.get::(), None, - "usize is not in the entity, so it should not exist" + "C is not in the entity, so it should not exist" ); - e.remove_bundle_intersection::<(i32, f64, usize)>(); + e.remove_bundle_intersection::<(A, B, C)>(); assert_eq!( - e.get::<&'static str>(), - Some(&"a"), - "&'static str is not in the removed bundle, so it should exist" + e.get::(), + Some(&TableStored("a")), + "TableStored is not in the removed bundle, so it should exist" ); assert_eq!( - e.get::(), + e.get::(), None, "i32 is in the removed bundle, so it should not exist" ); assert_eq!( - e.get::(), + e.get::(), None, "f64 is in the removed bundle, so it should not exist" ); assert_eq!( - e.get::(), + e.get::(), None, "usize is in the removed bundle, so it should not exist" ); @@ -1017,48 +1126,57 @@ mod tests { #[test] fn remove_bundle() { let mut world = World::default(); - world.spawn().insert_bundle((1, 1.0, 1usize)).id(); - let e2 = world.spawn().insert_bundle((2, 2.0, 2usize)).id(); - world.spawn().insert_bundle((3, 3.0, 3usize)).id(); + world + .spawn() + .insert_bundle((A(1), B(1), TableStored("1"))) + .id(); + let e2 = world + .spawn() + .insert_bundle((A(2), B(2), TableStored("2"))) + .id(); + world + .spawn() + .insert_bundle((A(3), B(3), TableStored("3"))) + .id(); - let mut query = world.query::<(&f64, &usize)>(); + let mut query = world.query::<(&B, &TableStored)>(); let results = query .iter(&world) - .map(|(a, b)| (*a, *b)) + .map(|(a, b)| (a.0, b.0)) .collect::>(); - assert_eq!(results, vec![(1.0, 1usize), (2.0, 2usize), (3.0, 3usize),]); + assert_eq!(results, vec![(1, "1"), (2, "2"), (3, "3"),]); let removed_bundle = world .entity_mut(e2) - .remove_bundle::<(f64, usize)>() + .remove_bundle::<(B, TableStored)>() .unwrap(); - assert_eq!(removed_bundle, (2.0, 2usize)); + assert_eq!(removed_bundle, (B(2), TableStored("2"))); let results = query .iter(&world) - .map(|(a, b)| (*a, *b)) + .map(|(a, b)| (a.0, b.0)) .collect::>(); - assert_eq!(results, vec![(1.0, 1usize), (3.0, 3usize),]); + assert_eq!(results, vec![(1, "1"), (3, "3"),]); - let mut i32_query = world.query::<&i32>(); - let results = i32_query.iter(&world).cloned().collect::>(); + let mut a_query = world.query::<&A>(); + let results = a_query.iter(&world).map(|a| a.0).collect::>(); assert_eq!(results, vec![1, 3, 2]); let entity_ref = world.entity(e2); assert_eq!( - entity_ref.get::(), - Some(&2), - "i32 is not in the removed bundle, so it should exist" + entity_ref.get::(), + Some(&A(2)), + "A is not in the removed bundle, so it should exist" ); assert_eq!( - entity_ref.get::(), + entity_ref.get::(), None, - "f64 is in the removed bundle, so it should not exist" + "B is in the removed bundle, so it should not exist" ); assert_eq!( - entity_ref.get::(), + entity_ref.get::(), None, - "usize is in the removed bundle, so it should not exist" + "TableStored is in the removed bundle, so it should not exist" ); } @@ -1123,7 +1241,7 @@ mod tests { #[should_panic] fn duplicate_components_panic() { let mut world = World::new(); - world.spawn().insert_bundle((1, 2)); + world.spawn().insert_bundle((A(1), A(2))); } #[test] @@ -1152,7 +1270,7 @@ mod tests { fn multiple_worlds_same_query_iter() { let mut world_a = World::new(); let world_b = World::new(); - let mut query = world_a.query::<&i32>(); + let mut query = world_a.query::<&A>(); query.iter(&world_a); query.iter(&world_b); } @@ -1160,19 +1278,19 @@ mod tests { #[test] fn query_filters_dont_collide_with_fetches() { let mut world = World::new(); - world.query_filtered::<&mut i32, Changed>(); + world.query_filtered::<&mut A, Changed>(); } #[test] fn filtered_query_access() { let mut world = World::new(); - let query = world.query_filtered::<&mut i32, Changed>(); + let query = world.query_filtered::<&mut A, Changed>(); let mut expected = FilteredAccess::::default(); - let i32_id = world.components.get_id(TypeId::of::()).unwrap(); - let f64_id = world.components.get_id(TypeId::of::()).unwrap(); - expected.add_write(i32_id); - expected.add_read(f64_id); + let a_id = world.components.get_id(TypeId::of::()).unwrap(); + let b_id = world.components.get_id(TypeId::of::()).unwrap(); + expected.add_write(a_id); + expected.add_read(b_id); assert!( query.component_access.eq(&expected), "ComponentId access from query fetch and query filter should be combined" @@ -1184,7 +1302,7 @@ mod tests { fn multiple_worlds_same_query_get() { let mut world_a = World::new(); let world_b = World::new(); - let mut query = world_a.query::<&i32>(); + let mut query = world_a.query::<&A>(); let _ = query.get(&world_a, Entity::new(0)); let _ = query.get(&world_b, Entity::new(0)); } @@ -1194,7 +1312,7 @@ mod tests { fn multiple_worlds_same_query_for_each() { let mut world_a = World::new(); let world_b = World::new(); - let mut query = world_a.query::<&i32>(); + let mut query = world_a.query::<&A>(); query.for_each(&world_a, |_| {}); query.for_each(&world_b, |_| {}); } @@ -1228,10 +1346,11 @@ mod tests { let (dropck1, dropped1) = DropCk::new_pair(); let (dropck2, dropped2) = DropCk::new_pair(); let mut world = World::default(); + world - .register_component(ComponentDescriptor::new::(StorageType::SparseSet)) - .unwrap(); - world.spawn().insert(dropck1).insert(dropck2); + .spawn() + .insert(DropCkSparse(dropck1)) + .insert(DropCkSparse(dropck2)); assert_eq!(dropped1.load(Ordering::Relaxed), 1); assert_eq!(dropped2.load(Ordering::Relaxed), 0); drop(world); @@ -1242,15 +1361,13 @@ mod tests { #[test] fn clear_entities() { let mut world = World::default(); - world - .register_component(ComponentDescriptor::new::(StorageType::SparseSet)) - .unwrap(); + world.insert_resource::(0); - world.spawn().insert(1u32); - world.spawn().insert(1.0f32); + world.spawn().insert(A(1)); + world.spawn().insert(SparseStored(1)); - let mut q1 = world.query::<&u32>(); - let mut q2 = world.query::<&f32>(); + let mut q1 = world.query::<&A>(); + let mut q2 = world.query::<&SparseStored>(); assert_eq!(q1.iter(&world).len(), 1); assert_eq!(q2.iter(&world).len(), 1); @@ -1285,8 +1402,8 @@ mod tests { let mut world_a = World::default(); let mut world_b = World::default(); - let e1 = world_a.spawn().insert(1u32).id(); - let e2 = world_a.spawn().insert(2u32).id(); + let e1 = world_a.spawn().insert(A(1)).id(); + let e2 = world_a.spawn().insert(A(2)).id(); let e3 = world_a.entities().reserve_entity(); world_a.flush(); @@ -1296,7 +1413,7 @@ mod tests { .reserve_entities(world_a_max_entities as u32); world_b.entities.flush_as_invalid(); - let e4 = world_b.spawn().insert(4u32).id(); + let e4 = world_b.spawn().insert(A(4)).id(); assert_eq!( e4, Entity { @@ -1305,31 +1422,31 @@ mod tests { }, "new entity is created immediately after world_a's max entity" ); - assert!(world_b.get::(e1).is_none()); + assert!(world_b.get::(e1).is_none()); assert!(world_b.get_entity(e1).is_none()); - assert!(world_b.get::(e2).is_none()); + assert!(world_b.get::(e2).is_none()); assert!(world_b.get_entity(e2).is_none()); - assert!(world_b.get::(e3).is_none()); + assert!(world_b.get::(e3).is_none()); assert!(world_b.get_entity(e3).is_none()); - world_b.get_or_spawn(e1).unwrap().insert(1.0f32); + world_b.get_or_spawn(e1).unwrap().insert(B(1)); assert_eq!( - world_b.get::(e1), - Some(&1.0f32), + world_b.get::(e1), + Some(&B(1)), "spawning into 'world_a' entities works" ); - world_b.get_or_spawn(e4).unwrap().insert(4.0f32); + world_b.get_or_spawn(e4).unwrap().insert(B(4)); assert_eq!( - world_b.get::(e4), - Some(&4.0f32), + world_b.get::(e4), + Some(&B(4)), "spawning into existing `world_b` entities works" ); assert_eq!( - world_b.get::(e4), - Some(&4u32), + world_b.get::(e4), + Some(&A(4)), "spawning into existing `world_b` entities works" ); @@ -1342,13 +1459,13 @@ mod tests { "attempting to spawn on top of an entity with a mismatched entity generation fails" ); assert_eq!( - world_b.get::(e4), - Some(&4.0f32), + world_b.get::(e4), + Some(&B(4)), "failed mismatched spawn doesn't change existing entity" ); assert_eq!( - world_b.get::(e4), - Some(&4u32), + world_b.get::(e4), + Some(&A(4)), "failed mismatched spawn doesn't change existing entity" ); @@ -1359,10 +1476,10 @@ mod tests { world_b .get_or_spawn(high_non_existent_entity) .unwrap() - .insert(10.0f32); + .insert(B(10)); assert_eq!( - world_b.get::(high_non_existent_entity), - Some(&10.0f32), + world_b.get::(high_non_existent_entity), + Some(&B(10)), "inserting into newly allocated high / non-continous entity id works" ); diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 9e20c208761e7..9572a797f1f90 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -1,7 +1,7 @@ use crate::{ archetype::{Archetype, ArchetypeComponentId}, change_detection::Ticks, - component::{Component, ComponentId, ComponentTicks, StorageType}, + component::{Component, ComponentId, ComponentStorage, ComponentTicks, StorageType}, entity::Entity, query::{Access, FilteredAccess}, storage::{ComponentSparseSet, Table, Tables}, @@ -67,7 +67,7 @@ pub trait Fetch<'world, 'state>: Sized { /// 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; + const IS_DENSE: bool; /// Adjusts internal state to account for the next [`Archetype`]. This will always be called on /// archetypes that match this [`Fetch`]. @@ -178,10 +178,7 @@ impl<'w, 's> Fetch<'w, 's> for EntityFetch { type Item = Entity; type State = EntityState; - #[inline] - fn is_dense(&self) -> bool { - true - } + const IS_DENSE: bool = true; unsafe fn init( _world: &World, @@ -228,7 +225,6 @@ impl WorldQuery for &T { /// The [`FetchState`] of `&T`. pub struct ReadState { component_id: ComponentId, - storage_type: StorageType, marker: PhantomData, } @@ -236,10 +232,9 @@ pub struct ReadState { // read unsafe impl FetchState for ReadState { fn init(world: &mut World) -> Self { - let component_info = world.components.get_or_insert_info::(); + let component_id = world.init_component::(); ReadState { - component_id: component_info.id(), - storage_type: component_info.storage_type(), + component_id, marker: PhantomData, } } @@ -275,7 +270,6 @@ unsafe impl FetchState for ReadState { /// The [`Fetch`] of `&T`. pub struct ReadFetch { - storage_type: StorageType, table_components: NonNull, entity_table_rows: *const usize, entities: *const Entity, @@ -285,7 +279,6 @@ pub struct ReadFetch { impl Clone for ReadFetch { fn clone(&self) -> Self { Self { - storage_type: self.storage_type, table_components: self.table_components, entity_table_rows: self.entity_table_rows, entities: self.entities, @@ -301,13 +294,12 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for ReadFetch { type Item = &'w T; type State = ReadState; - #[inline] - fn is_dense(&self) -> bool { - match self.storage_type { + const IS_DENSE: bool = { + match T::Storage::STORAGE_TYPE { StorageType::Table => true, StorageType::SparseSet => false, } - } + }; unsafe fn init( world: &World, @@ -316,13 +308,12 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for ReadFetch { _change_tick: u32, ) -> Self { let mut value = Self { - storage_type: state.storage_type, table_components: NonNull::dangling(), entities: ptr::null::(), entity_table_rows: ptr::null::(), sparse_set: ptr::null::(), }; - if state.storage_type == StorageType::SparseSet { + if T::Storage::STORAGE_TYPE == StorageType::SparseSet { value.sparse_set = world .storages() .sparse_sets @@ -339,7 +330,7 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for ReadFetch { archetype: &Archetype, tables: &Tables, ) { - match state.storage_type { + match T::Storage::STORAGE_TYPE { StorageType::Table => { self.entity_table_rows = archetype.entity_table_rows().as_ptr(); let column = tables[archetype.table_id()] @@ -362,7 +353,7 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for ReadFetch { #[inline] unsafe fn archetype_fetch(&mut self, archetype_index: usize) -> Self::Item { - match self.storage_type { + match T::Storage::STORAGE_TYPE { StorageType::Table => { let table_row = *self.entity_table_rows.add(archetype_index); &*self.table_components.as_ptr().add(table_row) @@ -387,7 +378,6 @@ impl WorldQuery for &mut T { /// The [`Fetch`] of `&mut T`. pub struct WriteFetch { - storage_type: StorageType, table_components: NonNull, table_ticks: *const UnsafeCell, entities: *const Entity, @@ -400,7 +390,6 @@ pub struct WriteFetch { impl Clone for WriteFetch { fn clone(&self) -> Self { Self { - storage_type: self.storage_type, table_components: self.table_components, table_ticks: self.table_ticks, entities: self.entities, @@ -415,7 +404,6 @@ impl Clone for WriteFetch { /// The [`FetchState`] of `&mut T`. pub struct WriteState { component_id: ComponentId, - storage_type: StorageType, marker: PhantomData, } @@ -423,10 +411,9 @@ pub struct WriteState { // written unsafe impl FetchState for WriteState { fn init(world: &mut World) -> Self { - let component_info = world.components.get_or_insert_info::(); + let component_id = world.init_component::(); WriteState { - component_id: component_info.id(), - storage_type: component_info.storage_type(), + component_id, marker: PhantomData, } } @@ -464,13 +451,12 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WriteFetch { type Item = Mut<'w, T>; type State = WriteState; - #[inline] - fn is_dense(&self) -> bool { - match self.storage_type { + const IS_DENSE: bool = { + match T::Storage::STORAGE_TYPE { StorageType::Table => true, StorageType::SparseSet => false, } - } + }; unsafe fn init( world: &World, @@ -479,7 +465,6 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WriteFetch { change_tick: u32, ) -> Self { let mut value = Self { - storage_type: state.storage_type, table_components: NonNull::dangling(), entities: ptr::null::(), entity_table_rows: ptr::null::(), @@ -488,7 +473,7 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WriteFetch { last_change_tick, change_tick, }; - if state.storage_type == StorageType::SparseSet { + if T::Storage::STORAGE_TYPE == StorageType::SparseSet { value.sparse_set = world .storages() .sparse_sets @@ -505,7 +490,7 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WriteFetch { archetype: &Archetype, tables: &Tables, ) { - match state.storage_type { + match T::Storage::STORAGE_TYPE { StorageType::Table => { self.entity_table_rows = archetype.entity_table_rows().as_ptr(); let column = tables[archetype.table_id()] @@ -527,7 +512,7 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WriteFetch { #[inline] unsafe fn archetype_fetch(&mut self, archetype_index: usize) -> Self::Item { - match self.storage_type { + match T::Storage::STORAGE_TYPE { StorageType::Table => { let table_row = *self.entity_table_rows.add(archetype_index); Mut { @@ -625,10 +610,7 @@ impl<'w, 's, T: Fetch<'w, 's>> Fetch<'w, 's> for OptionFetch { type Item = Option; type State = OptionState; - #[inline] - fn is_dense(&self) -> bool { - self.fetch.is_dense() - } + const IS_DENSE: bool = T::IS_DENSE; unsafe fn init( world: &World, @@ -693,12 +675,14 @@ impl<'w, 's, T: Fetch<'w, 's>> Fetch<'w, 's> for OptionFetch { /// # Examples /// /// ``` -/// # use bevy_ecs::system::Query; +/// # use bevy_ecs::component::Component; /// # use bevy_ecs::query::ChangeTrackers; /// # use bevy_ecs::system::IntoSystem; +/// # use bevy_ecs::system::Query; /// # -/// # #[derive(Debug)] +/// # #[derive(Component, Debug)] /// # struct Name {}; +/// # #[derive(Component)] /// # struct Transform {}; /// # /// fn print_moving_objects_system(query: Query<(&Name, ChangeTrackers)>) { @@ -752,7 +736,6 @@ impl WorldQuery for ChangeTrackers { /// The [`FetchState`] of [`ChangeTrackers`]. pub struct ChangeTrackersState { component_id: ComponentId, - storage_type: StorageType, marker: PhantomData, } @@ -760,10 +743,9 @@ pub struct ChangeTrackersState { // read unsafe impl FetchState for ChangeTrackersState { fn init(world: &mut World) -> Self { - let component_info = world.components.get_or_insert_info::(); + let component_id = world.init_component::(); Self { - component_id: component_info.id(), - storage_type: component_info.storage_type(), + component_id, marker: PhantomData, } } @@ -799,7 +781,6 @@ unsafe impl FetchState for ChangeTrackersState { /// The [`Fetch`] of [`ChangeTrackers`]. pub struct ChangeTrackersFetch { - storage_type: StorageType, table_ticks: *const ComponentTicks, entity_table_rows: *const usize, entities: *const Entity, @@ -812,7 +793,6 @@ pub struct ChangeTrackersFetch { impl Clone for ChangeTrackersFetch { fn clone(&self) -> Self { Self { - storage_type: self.storage_type, table_ticks: self.table_ticks, entity_table_rows: self.entity_table_rows, entities: self.entities, @@ -831,13 +811,12 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for ChangeTrackersFetch { type Item = ChangeTrackers; type State = ChangeTrackersState; - #[inline] - fn is_dense(&self) -> bool { - match self.storage_type { + const IS_DENSE: bool = { + match T::Storage::STORAGE_TYPE { StorageType::Table => true, StorageType::SparseSet => false, } - } + }; unsafe fn init( world: &World, @@ -846,7 +825,6 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for ChangeTrackersFetch { change_tick: u32, ) -> Self { let mut value = Self { - storage_type: state.storage_type, table_ticks: ptr::null::(), entities: ptr::null::(), entity_table_rows: ptr::null::(), @@ -855,7 +833,7 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for ChangeTrackersFetch { last_change_tick, change_tick, }; - if state.storage_type == StorageType::SparseSet { + if T::Storage::STORAGE_TYPE == StorageType::SparseSet { value.sparse_set = world .storages() .sparse_sets @@ -872,7 +850,7 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for ChangeTrackersFetch { archetype: &Archetype, tables: &Tables, ) { - match state.storage_type { + match T::Storage::STORAGE_TYPE { StorageType::Table => { self.entity_table_rows = archetype.entity_table_rows().as_ptr(); let column = tables[archetype.table_id()] @@ -894,7 +872,7 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for ChangeTrackersFetch { #[inline] unsafe fn archetype_fetch(&mut self, archetype_index: usize) -> Self::Item { - match self.storage_type { + match T::Storage::STORAGE_TYPE { StorageType::Table => { let table_row = *self.entity_table_rows.add(archetype_index); ChangeTrackers { @@ -940,12 +918,7 @@ macro_rules! impl_tuple_fetch { ($($name::init(_world, $name, _last_change_tick, _change_tick),)*) } - - #[inline] - fn is_dense(&self) -> bool { - let ($($name,)*) = self; - true $(&& $name.is_dense())* - } + const IS_DENSE: bool = true $(&& $name::IS_DENSE)*; #[inline] unsafe fn set_archetype(&mut self, _state: &Self::State, _archetype: &Archetype, _tables: &Tables) { diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index 69dd298cd31e0..bbd8ec567a985 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -1,6 +1,6 @@ use crate::{ archetype::{Archetype, ArchetypeComponentId}, - component::{Component, ComponentId, ComponentTicks, StorageType}, + component::{Component, ComponentId, ComponentStorage, ComponentTicks, StorageType}, entity::Entity, query::{Access, Fetch, FetchState, FilteredAccess, WorldQuery}, storage::{ComponentSparseSet, Table, Tables}, @@ -50,12 +50,14 @@ where /// # Examples /// /// ``` -/// # use bevy_ecs::system::Query; +/// # use bevy_ecs::component::Component; /// # use bevy_ecs::query::With; /// # use bevy_ecs::system::IntoSystem; +/// # use bevy_ecs::system::Query; /// # -/// # #[derive(Debug)] -/// # struct IsBeautiful {}; +/// # #[derive(Component)] +/// # struct IsBeautiful; +/// # #[derive(Component)] /// # struct Name { name: &'static str }; /// # /// fn compliment_entity_system(query: Query<&Name, With>) { @@ -74,24 +76,21 @@ impl WorldQuery for With { /// The [`Fetch`] of [`With`]. pub struct WithFetch { - storage_type: StorageType, marker: PhantomData, } /// The [`FetchState`] of [`With`]. pub struct WithState { component_id: ComponentId, - storage_type: StorageType, marker: PhantomData, } // SAFETY: no component access or archetype component access unsafe impl FetchState for WithState { fn init(world: &mut World) -> Self { - let component_info = world.components.get_or_insert_info::(); + let component_id = world.init_component::(); Self { - component_id: component_info.id(), - storage_type: component_info.storage_type(), + component_id, marker: PhantomData, } } @@ -124,20 +123,21 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WithFetch { unsafe fn init( _world: &World, - state: &Self::State, + _state: &Self::State, _last_change_tick: u32, _change_tick: u32, ) -> Self { Self { - storage_type: state.storage_type, marker: PhantomData, } } - #[inline] - fn is_dense(&self) -> bool { - self.storage_type == StorageType::Table - } + const IS_DENSE: bool = { + match T::Storage::STORAGE_TYPE { + StorageType::Table => true, + StorageType::SparseSet => false, + } + }; #[inline] unsafe fn set_table(&mut self, _state: &Self::State, _table: &Table) {} @@ -169,12 +169,14 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WithFetch { /// # Examples /// /// ``` -/// # use bevy_ecs::system::Query; +/// # use bevy_ecs::component::Component; /// # use bevy_ecs::query::Without; /// # use bevy_ecs::system::IntoSystem; +/// # use bevy_ecs::system::Query; /// # -/// # #[derive(Debug)] +/// # #[derive(Component)] /// # struct Permit; +/// # #[derive(Component)] /// # struct Name { name: &'static str }; /// # /// fn no_permit_system(query: Query<&Name, Without>) { @@ -193,24 +195,21 @@ impl WorldQuery for Without { /// The [`Fetch`] of [`Without`]. pub struct WithoutFetch { - storage_type: StorageType, marker: PhantomData, } /// The [`FetchState`] of [`Without`]. pub struct WithoutState { component_id: ComponentId, - storage_type: StorageType, marker: PhantomData, } // SAFETY: no component access or archetype component access unsafe impl FetchState for WithoutState { fn init(world: &mut World) -> Self { - let component_info = world.components.get_or_insert_info::(); + let component_id = world.init_component::(); Self { - component_id: component_info.id(), - storage_type: component_info.storage_type(), + component_id, marker: PhantomData, } } @@ -243,20 +242,21 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WithoutFetch { unsafe fn init( _world: &World, - state: &Self::State, + _state: &Self::State, _last_change_tick: u32, _change_tick: u32, ) -> Self { Self { - storage_type: state.storage_type, marker: PhantomData, } } - #[inline] - fn is_dense(&self) -> bool { - self.storage_type == StorageType::Table - } + const IS_DENSE: bool = { + match T::Storage::STORAGE_TYPE { + StorageType::Table => true, + StorageType::SparseSet => false, + } + }; #[inline] unsafe fn set_table(&mut self, _state: &Self::State, _table: &Table) {} @@ -292,14 +292,16 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WithoutFetch { /// # Examples /// /// ``` +/// # use bevy_ecs::component::Component; /// # use bevy_ecs::entity::Entity; -/// # use bevy_ecs::system::Query; -/// # use bevy_ecs::system::IntoSystem; /// # use bevy_ecs::query::Changed; /// # use bevy_ecs::query::Or; +/// # use bevy_ecs::system::IntoSystem; +/// # use bevy_ecs::system::Query; /// # -/// # #[derive(Debug)] +/// # #[derive(Component, Debug)] /// # struct Color {}; +/// # #[derive(Component)] /// # struct Style {}; /// # /// fn print_cool_entity_system(query: Query, Changed