From 001f900edfbc73d8ccaf32ef8c5f9c4685fa7392 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Thu, 23 Nov 2023 22:10:46 -0800 Subject: [PATCH 001/109] Initial hooks implementation --- Cargo.toml | 11 + crates/bevy_ecs/src/bundle.rs | 401 ++++++++++------- crates/bevy_ecs/src/component.rs | 48 +- crates/bevy_ecs/src/lib.rs | 14 +- crates/bevy_ecs/src/query/builder.rs | 422 ++++++++++++++++++ crates/bevy_ecs/src/query/fetch.rs | 8 +- crates/bevy_ecs/src/query/filter.rs | 6 +- crates/bevy_ecs/src/schedule/schedule.rs | 2 +- crates/bevy_ecs/src/storage/table.rs | 2 +- .../src/system/commands/command_queue.rs | 6 + crates/bevy_ecs/src/system/mod.rs | 2 +- crates/bevy_ecs/src/world/deferred_world.rs | 241 ++++++++++ crates/bevy_ecs/src/world/entity_ref.rs | 176 ++++---- crates/bevy_ecs/src/world/mod.rs | 92 ++-- crates/bevy_ecs/src/world/spawn_batch.rs | 15 +- examples/ecs/component_hooks.rs | 42 ++ 16 files changed, 1154 insertions(+), 334 deletions(-) create mode 100644 crates/bevy_ecs/src/query/builder.rs create mode 100644 crates/bevy_ecs/src/world/deferred_world.rs create mode 100644 examples/ecs/component_hooks.rs diff --git a/Cargo.toml b/Cargo.toml index d6717b9c51f78..d1a11b330a92b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1289,6 +1289,17 @@ description = "Change detection on components" category = "ECS (Entity Component System)" wasm = false +[[example]] +name = "component_hooks" +path = "examples/ecs/component_hooks.rs" +doc-scrape-examples = true + +[package.metadata.example.component_hooks] +name = "Component Hooks" +description = "Define component hooks to react to ECS events" +category = "ECS (Entity Component System)" +wasm = false + [[example]] name = "custom_query_param" path = "examples/ecs/custom_query_param.rs" diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 361689e5793ad..9979c0c267c30 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -7,11 +7,12 @@ use bevy_utils::{HashMap, HashSet}; use crate::{ archetype::{ - Archetype, ArchetypeId, Archetypes, BundleComponentStatus, ComponentStatus, + AddBundle, Archetype, ArchetypeId, Archetypes, BundleComponentStatus, ComponentStatus, SpawnBundleStatus, }, component::{Component, ComponentId, ComponentStorage, Components, StorageType, Tick}, entity::{Entities, Entity, EntityLocation}, + prelude::World, query::DebugCheckedUnwrap, storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow}, TypeIdMap, @@ -184,7 +185,7 @@ unsafe impl Bundle for C { storages: &mut Storages, ids: &mut impl FnMut(ComponentId), ) { - ids(components.init_component::(storages)); + ids(components.init_component::(storages).id()); } unsafe fn from_components(ctx: &mut T, func: &mut F) -> Self @@ -345,88 +346,6 @@ impl BundleInfo { &self.component_ids } - pub(crate) fn get_bundle_inserter<'a, 'b>( - &'b self, - entities: &'a mut Entities, - archetypes: &'a mut Archetypes, - components: &Components, - storages: &'a mut Storages, - archetype_id: ArchetypeId, - change_tick: Tick, - ) -> BundleInserter<'a, 'b> { - let new_archetype_id = - self.add_bundle_to_archetype(archetypes, storages, components, archetype_id); - let archetypes_ptr = archetypes.archetypes.as_mut_ptr(); - if new_archetype_id == archetype_id { - let archetype = &mut archetypes[archetype_id]; - let table_id = archetype.table_id(); - BundleInserter { - bundle_info: self, - archetype, - entities, - sparse_sets: &mut storages.sparse_sets, - table: &mut storages.tables[table_id], - archetypes_ptr, - change_tick, - result: InsertBundleResult::SameArchetype, - } - } else { - let (archetype, new_archetype) = archetypes.get_2_mut(archetype_id, new_archetype_id); - let table_id = archetype.table_id(); - if table_id == new_archetype.table_id() { - BundleInserter { - bundle_info: self, - archetype, - archetypes_ptr, - entities, - sparse_sets: &mut storages.sparse_sets, - table: &mut storages.tables[table_id], - change_tick, - result: InsertBundleResult::NewArchetypeSameTable { new_archetype }, - } - } else { - let (table, new_table) = storages - .tables - .get_2_mut(table_id, new_archetype.table_id()); - BundleInserter { - bundle_info: self, - archetype, - sparse_sets: &mut storages.sparse_sets, - entities, - archetypes_ptr, - table, - change_tick, - result: InsertBundleResult::NewArchetypeNewTable { - new_archetype, - new_table, - }, - } - } - } - } - - pub(crate) fn get_bundle_spawner<'a, 'b>( - &'b self, - entities: &'a mut Entities, - archetypes: &'a mut Archetypes, - components: &Components, - storages: &'a mut Storages, - change_tick: Tick, - ) -> BundleSpawner<'a, 'b> { - let new_archetype_id = - self.add_bundle_to_archetype(archetypes, storages, components, ArchetypeId::EMPTY); - let archetype = &mut archetypes[new_archetype_id]; - let table = &mut storages.tables[archetype.table_id()]; - BundleSpawner { - archetype, - bundle_info: self, - table, - entities, - sparse_sets: &mut storages.sparse_sets, - change_tick, - } - } - /// This writes components from a given [`Bundle`] to the given entity. /// /// # Safety @@ -572,52 +491,146 @@ impl BundleInfo { } } -pub(crate) struct BundleInserter<'a, 'b> { - pub(crate) archetype: &'a mut Archetype, - pub(crate) entities: &'a mut Entities, - bundle_info: &'b BundleInfo, - table: &'a mut Table, - sparse_sets: &'a mut SparseSets, - result: InsertBundleResult<'a>, - archetypes_ptr: *mut Archetype, +pub(crate) struct BundleInserter<'w> { + world: &'w mut World, + archetype: *mut Archetype, + table: *mut Table, + bundle_info: *const BundleInfo, + add_bundle: *const AddBundle, + result: InsertBundleResult, change_tick: Tick, } -pub(crate) enum InsertBundleResult<'a> { +pub(crate) enum InsertBundleResult { SameArchetype, NewArchetypeSameTable { - new_archetype: &'a mut Archetype, + new_archetype: *mut Archetype, }, NewArchetypeNewTable { - new_archetype: &'a mut Archetype, - new_table: &'a mut Table, + new_archetype: *mut Archetype, + new_table: *mut Table, }, } -impl<'a, 'b> BundleInserter<'a, 'b> { +impl<'w> BundleInserter<'w> { + #[inline] + pub fn new( + world: &'w mut World, + archetype_id: ArchetypeId, + change_tick: Tick, + ) -> Self { + let bundle_info: *const BundleInfo = world + .bundles + .init_info::(&mut world.components, &mut world.storages); + unsafe { Self::new_with_info(world, archetype_id, bundle_info, change_tick) } + } + + #[inline] + pub(crate) unsafe fn new_with_info( + world: &'w mut World, + archetype_id: ArchetypeId, + bundle_info: *const BundleInfo, + change_tick: Tick, + ) -> Self { + let bundle_info = &*bundle_info; + let bundle_id = bundle_info.id(); + let new_archetype_id = bundle_info.add_bundle_to_archetype( + &mut world.archetypes, + &mut world.storages, + &world.components, + archetype_id, + ); + if new_archetype_id == archetype_id { + let archetype = &mut world.archetypes[archetype_id]; + let table_id = archetype.table_id(); + let add_bundle: *const AddBundle = unsafe { + archetype + .edges() + .get_add_bundle_internal(bundle_id) + .debug_checked_unwrap() + }; + let table: *mut Table = &mut world.storages.tables[table_id]; + let archetype: *mut Archetype = archetype; + Self { + world, + archetype, + table, + bundle_info, + add_bundle, + result: InsertBundleResult::SameArchetype, + change_tick, + } + } else { + let (archetype, new_archetype) = + world.archetypes.get_2_mut(archetype_id, new_archetype_id); + let table_id = archetype.table_id(); + let new_table_id = new_archetype.table_id(); + let add_bundle: *const AddBundle = unsafe { + archetype + .edges() + .get_add_bundle_internal(bundle_id) + .debug_checked_unwrap() + }; + let table: *mut Table = &mut world.storages.tables[table_id]; + let archetype: *mut Archetype = archetype; + let new_archetype: *mut Archetype = new_archetype; + if table_id == new_table_id { + Self { + world, + archetype, + table, + bundle_info, + add_bundle, + result: InsertBundleResult::NewArchetypeSameTable { new_archetype }, + change_tick, + } + } else { + let new_table: *mut Table = &mut world.storages.tables[new_table_id]; + Self { + world, + archetype, + table, + bundle_info, + add_bundle, + result: InsertBundleResult::NewArchetypeNewTable { + new_archetype, + new_table, + }, + change_tick, + } + } + } + } /// # Safety /// `entity` must currently exist in the source archetype for this inserter. `archetype_row` /// must be `entity`'s location in the archetype. `T` must match this [`BundleInfo`]'s type #[inline] - pub unsafe fn insert( + pub(crate) unsafe fn insert( &mut self, entity: Entity, location: EntityLocation, bundle: T, ) -> EntityLocation { - match &mut self.result { + let bundle_info = &*self.bundle_info; + let add_bundle: &AddBundle = &*self.add_bundle; + let world = &mut *self.world; + for (i, component_id) in bundle_info.components().iter().cloned().enumerate() { + let hooks = unsafe { world.components.get_info_unchecked(component_id) }.hooks(); + if let ComponentStatus::Added = add_bundle.bundle_status[i] { + if let Some(hook) = hooks.on_add { + hook(unsafe { world.into_deferred() }, entity) + } + } + if let Some(hook) = hooks.on_insert { + hook(unsafe { world.into_deferred() }, entity) + } + } + + match self.result { InsertBundleResult::SameArchetype => { - // PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty) - // SAFETY: The edge is assured to be initialized when creating the BundleInserter - let add_bundle = unsafe { - self.archetype - .edges() - .get_add_bundle_internal(self.bundle_info.id) - .debug_checked_unwrap() - }; - self.bundle_info.write_components( - self.table, - self.sparse_sets, + bundle_info.write_components( + &mut *self.table, + &mut world.storages.sparse_sets, add_bundle, entity, location.table_row, @@ -627,12 +640,15 @@ impl<'a, 'b> BundleInserter<'a, 'b> { location } InsertBundleResult::NewArchetypeSameTable { new_archetype } => { - let result = self.archetype.swap_remove(location.archetype_row); + let table = &mut *self.table; + let archetype = &mut *self.archetype; + let new_archetype = &mut *new_archetype; + let result = archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { let swapped_location = // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; - self.entities.set( + unsafe { world.entities.get(swapped_entity).debug_checked_unwrap() }; + world.entities.set( swapped_entity.index(), EntityLocation { archetype_id: swapped_location.archetype_id, @@ -643,19 +659,10 @@ impl<'a, 'b> BundleInserter<'a, 'b> { ); } let new_location = new_archetype.allocate(entity, result.table_row); - self.entities.set(entity.index(), new_location); - - // PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty) - // SAFETY: The edge is assured to be initialized when creating the BundleInserter - let add_bundle = unsafe { - self.archetype - .edges() - .get_add_bundle_internal(self.bundle_info.id) - .debug_checked_unwrap() - }; - self.bundle_info.write_components( - self.table, - self.sparse_sets, + world.entities.set(entity.index(), new_location); + bundle_info.write_components( + table, + &mut world.storages.sparse_sets, add_bundle, entity, result.table_row, @@ -668,12 +675,16 @@ impl<'a, 'b> BundleInserter<'a, 'b> { new_archetype, new_table, } => { - let result = self.archetype.swap_remove(location.archetype_row); + let table = &mut *self.table; + let new_table = &mut *new_table; + let archetype = &mut *self.archetype; + let new_archetype = &mut *new_archetype; + let result = archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { let swapped_location = // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; - self.entities.set( + unsafe { world.entities.get(swapped_entity).debug_checked_unwrap() }; + world.entities.set( swapped_entity.index(), EntityLocation { archetype_id: swapped_location.archetype_id, @@ -685,30 +696,25 @@ impl<'a, 'b> BundleInserter<'a, 'b> { } // PERF: store "non bundle" components in edge, then just move those to avoid // redundant copies - let move_result = self - .table - .move_to_superset_unchecked(result.table_row, new_table); + let move_result = table.move_to_superset_unchecked(result.table_row, new_table); let new_location = new_archetype.allocate(entity, move_result.new_row); - self.entities.set(entity.index(), new_location); + world.entities.set(entity.index(), new_location); // if an entity was moved into this entity's table spot, update its table row if let Some(swapped_entity) = move_result.swapped_entity { let swapped_location = // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; - let swapped_archetype = if self.archetype.id() == swapped_location.archetype_id - { - &mut *self.archetype + unsafe { world.entities.get(swapped_entity).debug_checked_unwrap() }; + let swapped_archetype = if archetype.id() == swapped_location.archetype_id { + &mut *archetype } else if new_archetype.id() == swapped_location.archetype_id { new_archetype } else { // SAFETY: the only two borrowed archetypes are above and we just did collision checks - &mut *self - .archetypes_ptr - .add(swapped_location.archetype_id.index()) + &mut world.archetypes[swapped_location.archetype_id] }; - self.entities.set( + world.entities.set( swapped_entity.index(), EntityLocation { archetype_id: swapped_location.archetype_id, @@ -721,17 +727,9 @@ impl<'a, 'b> BundleInserter<'a, 'b> { .set_entity_table_row(swapped_location.archetype_row, result.table_row); } - // PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty) - // SAFETY: The edge is assured to be initialized when creating the BundleInserter - let add_bundle = unsafe { - self.archetype - .edges() - .get_add_bundle_internal(self.bundle_info.id) - .debug_checked_unwrap() - }; - self.bundle_info.write_components( + bundle_info.write_components( new_table, - self.sparse_sets, + &mut world.storages.sparse_sets, add_bundle, entity, move_result.new_row, @@ -742,21 +740,59 @@ impl<'a, 'b> BundleInserter<'a, 'b> { } } } + + #[inline] + pub(crate) fn entities(&mut self) -> &mut Entities { + &mut self.world.entities + } } -pub(crate) struct BundleSpawner<'a, 'b> { - pub(crate) archetype: &'a mut Archetype, - pub(crate) entities: &'a mut Entities, - bundle_info: &'b BundleInfo, - table: &'a mut Table, - sparse_sets: &'a mut SparseSets, +pub(crate) struct BundleSpawner<'w> { + world: &'w mut World, + bundle_info: *const BundleInfo, + archetype: *mut Archetype, + table: *mut Table, change_tick: Tick, } -impl<'a, 'b> BundleSpawner<'a, 'b> { +impl<'w> BundleSpawner<'w> { + #[inline] + pub fn new(world: &'w mut World, change_tick: Tick) -> Self { + let bundle_info: *const BundleInfo = world + .bundles + .init_info::(&mut world.components, &mut world.storages); + unsafe { Self::new_with_info(world, bundle_info, change_tick) } + } + + pub(crate) unsafe fn new_with_info( + world: &'w mut World, + bundle_info: *const BundleInfo, + change_tick: Tick, + ) -> Self { + let bundle_info = &*bundle_info; + let new_archetype_id = bundle_info.add_bundle_to_archetype( + &mut world.archetypes, + &mut world.storages, + &world.components, + ArchetypeId::EMPTY, + ); + let archetype = &mut world.archetypes[new_archetype_id]; + let table: *mut Table = &mut world.storages.tables[archetype.table_id()]; + let archetype: *mut Archetype = archetype; + BundleSpawner { + world, + bundle_info, + archetype, + table, + change_tick, + } + } + pub fn reserve_storage(&mut self, additional: usize) { - self.archetype.reserve(additional); - self.table.reserve(additional); + unsafe { + (&mut *self.archetype).reserve(additional); + (&mut *self.table).reserve(additional); + } } /// # Safety /// `entity` must be allocated (but non-existent), `T` must match this [`BundleInfo`]'s type @@ -766,18 +802,36 @@ impl<'a, 'b> BundleSpawner<'a, 'b> { entity: Entity, bundle: T, ) -> EntityLocation { - let table_row = self.table.allocate(entity); - let location = self.archetype.allocate(entity, table_row); - self.bundle_info.write_components( - self.table, - self.sparse_sets, + let bundle_info = &*self.bundle_info; + for component_id in bundle_info.components().iter().cloned() { + let hooks = self + .world + .components + .get_info_unchecked(component_id) + .hooks(); + if let Some(hook) = hooks.on_add { + hook(self.world.into_deferred(), entity); + } + if let Some(hook) = hooks.on_insert { + hook(self.world.into_deferred(), entity); + } + } + + let archetype = &mut *self.archetype; + let table = &mut *self.table; + + let table_row = table.allocate(entity); + let location = archetype.allocate(entity, table_row); + bundle_info.write_components( + table, + &mut self.world.storages.sparse_sets, &SpawnBundleStatus, entity, table_row, self.change_tick, bundle, ); - self.entities.set(entity.index(), location); + self.world.entities.set(entity.index(), location); location } @@ -786,11 +840,16 @@ impl<'a, 'b> BundleSpawner<'a, 'b> { /// `T` must match this [`BundleInfo`]'s type #[inline] pub unsafe fn spawn(&mut self, bundle: T) -> Entity { - let entity = self.entities.alloc(); + let entity = self.world.entities.alloc(); // SAFETY: entity is allocated (but non-existent), `T` matches this BundleInfo's type self.spawn_non_existent(entity, bundle); entity } + + #[inline] + pub(crate) fn entities(&mut self) -> &mut Entities { + &mut self.world.entities + } } /// Metadata for bundles. Stores a [`BundleInfo`] for each type of [`Bundle`] in a given world. @@ -828,7 +887,7 @@ impl Bundles { storages: &mut Storages, ) -> &'a BundleInfo { let bundle_infos = &mut self.bundle_infos; - let id = self.bundle_ids.entry(TypeId::of::()).or_insert_with(|| { + let id = *self.bundle_ids.entry(TypeId::of::()).or_insert_with(|| { let mut component_ids = Vec::new(); T::component_ids(components, storages, &mut |id| component_ids.push(id)); let id = BundleId(bundle_infos.len()); @@ -842,7 +901,11 @@ impl Bundles { id }); // SAFETY: index either exists, or was initialized - unsafe { self.bundle_infos.get_unchecked(id.0) } + unsafe { self.get_unchecked(id) } + } + + pub(crate) unsafe fn get_unchecked<'a>(&'a self, id: BundleId) -> &'a BundleInfo { + self.bundle_infos.get_unchecked(id.0) } /// Initializes a new [`BundleInfo`] for a dynamic [`Bundle`]. diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index bd6cf102cc651..521fe74b959b4 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -3,9 +3,10 @@ use crate::{ self as bevy_ecs, change_detection::MAX_CHANGE_AGE, + entity::Entity, storage::{SparseSetIndex, Storages}, system::{Local, Resource, SystemParam}, - world::{FromWorld, World}, + world::{DeferredWorld, FromWorld, World}, TypeIdMap, }; pub use bevy_ecs_macros::Component; @@ -263,6 +264,25 @@ impl ComponentInfo { pub(crate) fn new(id: ComponentId, descriptor: ComponentDescriptor) -> Self { ComponentInfo { id, descriptor } } + + pub fn on_add(&mut self, hook: ComponentHook) -> &mut Self { + self.descriptor.hooks.on_add = Some(hook); + self + } + + pub fn on_insert(&mut self, hook: ComponentHook) -> &mut Self { + self.descriptor.hooks.on_insert = Some(hook); + self + } + + pub fn on_remove(&mut self, hook: ComponentHook) -> &mut Self { + self.descriptor.hooks.on_remove = Some(hook); + self + } + + pub fn hooks(&self) -> &ComponentHooks { + &self.descriptor.hooks + } } /// A value which uniquely identifies the type of a [`Component`] within a @@ -318,6 +338,15 @@ impl SparseSetIndex for ComponentId { } } +pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, Entity); + +#[derive(Clone, Default)] +pub struct ComponentHooks { + pub(crate) on_add: Option, + pub(crate) on_insert: Option, + pub(crate) on_remove: Option, +} + /// A value describing a component or resource, which may or may not correspond to a Rust type. #[derive(Clone)] pub struct ComponentDescriptor { @@ -330,6 +359,7 @@ pub struct ComponentDescriptor { is_send_and_sync: bool, type_id: Option, layout: Layout, + hooks: ComponentHooks, // SAFETY: this function must be safe to call with pointers pointing to items of the type // this descriptor describes. // None if the underlying type doesn't need to be dropped @@ -363,6 +393,7 @@ impl ComponentDescriptor { is_send_and_sync: true, type_id: Some(TypeId::of::()), layout: Layout::new::(), + hooks: ComponentHooks::default(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), } } @@ -384,6 +415,7 @@ impl ComponentDescriptor { is_send_and_sync: true, type_id: None, layout, + hooks: ComponentHooks::default(), drop, } } @@ -400,6 +432,7 @@ impl ComponentDescriptor { is_send_and_sync: true, type_id: Some(TypeId::of::()), layout: Layout::new::(), + hooks: ComponentHooks::default(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), } } @@ -411,6 +444,7 @@ impl ComponentDescriptor { is_send_and_sync: false, type_id: Some(TypeId::of::()), layout: Layout::new::(), + hooks: ComponentHooks::default(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), } } @@ -453,7 +487,7 @@ impl Components { /// * [`Components::component_id()`] /// * [`Components::init_component_with_descriptor()`] #[inline] - pub fn init_component(&mut self, storages: &mut Storages) -> ComponentId { + pub fn init_component(&mut self, storages: &mut Storages) -> &mut ComponentInfo { let type_id = TypeId::of::(); let Components { @@ -464,7 +498,7 @@ impl Components { let index = indices.entry(type_id).or_insert_with(|| { Components::init_component_inner(components, storages, ComponentDescriptor::new::()) }); - ComponentId(*index) + &mut components[*index] } /// Initializes a component described by `descriptor`. @@ -482,9 +516,9 @@ impl Components { &mut self, storages: &mut Storages, descriptor: ComponentDescriptor, - ) -> ComponentId { + ) -> &mut ComponentInfo { let index = Components::init_component_inner(&mut self.components, storages, descriptor); - ComponentId(index) + &mut self.components[index] } #[inline] @@ -563,7 +597,7 @@ impl Components { /// #[derive(Component)] /// struct ComponentA; /// - /// let component_a_id = world.init_component::(); + /// let component_a_id = world.init_component::().id(); /// /// assert_eq!(component_a_id, world.components().component_id::().unwrap()) /// ``` @@ -872,7 +906,7 @@ struct InitComponentId { impl FromWorld for InitComponentId { fn from_world(world: &mut World) -> Self { Self { - component_id: world.init_component::(), + component_id: world.init_component::().id(), marker: PhantomData, } } diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index ad8ba9b39a51f..57a561451b9b3 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -157,8 +157,8 @@ mod tests { assert_eq!( ids, &[ - world.init_component::(), - world.init_component::(), + world.init_component::().id(), + world.init_component::().id(), ] ); @@ -211,10 +211,10 @@ mod tests { assert_eq!( ids, &[ - world.init_component::(), - world.init_component::(), - world.init_component::(), - world.init_component::(), + world.init_component::().id(), + world.init_component::().id(), + world.init_component::().id(), + world.init_component::().id(), ] ); @@ -264,7 +264,7 @@ mod tests { }, ); - assert_eq!(ids, &[world.init_component::(),]); + assert_eq!(ids, &[world.init_component::().id(),]); let e4 = world .spawn(BundleWithIgnored { diff --git a/crates/bevy_ecs/src/query/builder.rs b/crates/bevy_ecs/src/query/builder.rs new file mode 100644 index 0000000000000..d1beaa513b55f --- /dev/null +++ b/crates/bevy_ecs/src/query/builder.rs @@ -0,0 +1,422 @@ +use std::marker::PhantomData; + +use crate::{component::ComponentId, prelude::*}; + +use super::{FilteredAccess, ReadOnlyWorldQuery, WorldQuery}; + +/// Builder struct to create [`QueryState`] instances at runtime. +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # +/// # #[derive(Component)] +/// # struct A; +/// # +/// # #[derive(Component)] +/// # struct B; +/// # +/// # #[derive(Component)] +/// # struct C; +/// # +/// let mut world = World::new(); +/// let entity_a = world.spawn((A, B)).id(); +/// let entity_b = world.spawn((A, C)).id(); +/// +/// // Instantiate the builder using the type signature of the iterator you will consume +/// let mut query = QueryBuilder::<(Entity, &B)>::new(&mut world) +/// // Add additional terms through builder methods +/// .with::() +/// .without::() +/// .build(); +/// +/// // Consume the QueryState +/// let (entity, b) = query.single(&world); +///``` +pub struct QueryBuilder<'w, Q: WorldQuery = (), F: ReadOnlyWorldQuery = ()> { + access: FilteredAccess, + world: &'w mut World, + or: bool, + first: bool, + _marker: PhantomData<(Q, F)>, +} + +impl<'w, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryBuilder<'w, Q, F> { + /// Creates a new builder with the accesses required for `Q` and `F` + pub fn new(world: &'w mut World) -> Self { + let fetch_state = Q::init_state(world); + let filter_state = F::init_state(world); + + let mut access = FilteredAccess::default(); + Q::update_component_access(&fetch_state, &mut access); + + // Use a temporary empty FilteredAccess for filters. This prevents them from conflicting with the + // main Query's `fetch_state` access. Filters are allowed to conflict with the main query fetch + // because they are evaluated *before* a specific reference is constructed. + let mut filter_access = FilteredAccess::default(); + F::update_component_access(&filter_state, &mut filter_access); + + // Merge the temporary filter access with the main access. This ensures that filter access is + // properly considered in a global "cross-query" context (both within systems and across systems). + access.extend(&filter_access); + + Self { + access, + world, + or: false, + first: false, + _marker: PhantomData, + } + } + + /// Returns a reference to the world passed to [`Self::new`]. + pub fn world(&self) -> &World { + self.world + } + + /// Returns a reference to the world passed to [`Self::new`]. + pub fn world_mut(&mut self) -> &mut World { + self.world + } + + /// Adds access to self's underlying [`FilteredAccess`] respecting [`Self::or`] and [`Self::and`] + pub fn extend_access(&mut self, mut access: FilteredAccess) { + if self.or { + if self.first { + access.required.clear(); + self.access.extend(&access); + self.first = false; + } else { + self.access.append_or(&access); + } + } else { + self.access.extend(&access); + } + } + + /// Adds accesses required for `T` to self. + pub fn push(&mut self) -> &mut Self { + let state = T::init_state(self.world); + let mut access = FilteredAccess::default(); + T::update_component_access(&state, &mut access); + self.extend_access(access); + self + } + + /// Adds [`With`] to the [`FilteredAccess`] of self. + pub fn with(&mut self) -> &mut Self { + self.push::>(); + self + } + + /// Adds [`With`] to the [`FilteredAccess`] of self from a runtime [`ComponentId`]. + pub fn with_id(&mut self, id: ComponentId) -> &mut Self { + let mut access = FilteredAccess::default(); + access.and_with(id); + self.extend_access(access); + self + } + + /// Adds [`Without`] to the [`FilteredAccess`] of self. + pub fn without(&mut self) -> &mut Self { + self.push::>(); + self + } + + /// Adds [`Without`] to the [`FilteredAccess`] of self from a runtime [`ComponentId`]. + pub fn without_id(&mut self, id: ComponentId) -> &mut Self { + let mut access = FilteredAccess::default(); + access.and_without(id); + self.extend_access(access); + self + } + + /// Adds `&T` to the [`FilteredAccess`] of self. + pub fn ref_id(&mut self, id: ComponentId) -> &mut Self { + self.with_id(id); + self.access.add_read(id); + self + } + + /// Adds `&mut T` to the [`FilteredAccess`] of self. + pub fn mut_id(&mut self, id: ComponentId) -> &mut Self { + self.with_id(id); + self.access.add_write(id); + self + } + + /// Takes a function over mutable access to a [`QueryBuilder`], calls that function + /// on an empty builder and then adds all accesses from that builder to self as optional. + pub fn optional(&mut self, f: impl Fn(&mut QueryBuilder)) -> &mut Self { + let mut builder = QueryBuilder::new(self.world); + f(&mut builder); + self.access.extend_access(builder.access()); + self + } + + /// Takes a function over mutable access to a [`QueryBuilder`], calls that function + /// on an empty builder and then adds all accesses from that builder to self. + /// + /// Primarily used when inside a [`Self::or`] closure to group several terms. + pub fn and(&mut self, f: impl Fn(&mut QueryBuilder)) -> &mut Self { + let mut builder = QueryBuilder::new(self.world); + f(&mut builder); + let access = builder.access().clone(); + self.extend_access(access); + self + } + + /// Takes a function over mutable access to a [`QueryBuilder`], calls that function + /// on an empty builder, all accesses added to that builder will become terms in an or expression. + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # + /// # #[derive(Component)] + /// # struct A; + /// # + /// # #[derive(Component)] + /// # struct B; + /// # + /// # let mut world = World::new(); + /// # + /// QueryBuilder::::new(&mut world).or(|builder| { + /// builder.with::(); + /// builder.with::(); + /// }); + /// // is equivalent to + /// QueryBuilder::::new(&mut world).push::, With)>>(); + /// ``` + pub fn or(&mut self, f: impl Fn(&mut QueryBuilder)) -> &mut Self { + let mut builder = QueryBuilder::new(self.world); + builder.or = true; + builder.first = true; + f(&mut builder); + self.access.extend(builder.access()); + self + } + + /// Returns a reference to the the [`FilteredAccess`] that will be provided to the built [`Query`]. + pub fn access(&self) -> &FilteredAccess { + &self.access + } + + /// Transmute the existing builder adding required accesses. + /// This will maintain all exisiting accesses. + /// + /// If including a filter type see [`Self::transmute_filtered`] + pub fn transmute(&mut self) -> &mut QueryBuilder<'w, NewQ> { + self.transmute_filtered::() + } + + /// Transmute the existing builder adding required accesses. + /// This will maintain all existing accesses. + pub fn transmute_filtered( + &mut self, + ) -> &mut QueryBuilder<'w, NewQ, NewF> { + let mut fetch_state = NewQ::init_state(self.world); + let filter_state = NewF::init_state(self.world); + + NewQ::set_access(&mut fetch_state, &self.access); + + let mut access = FilteredAccess::default(); + NewQ::update_component_access(&fetch_state, &mut access); + NewF::update_component_access(&filter_state, &mut access); + + self.extend_access(access); + // SAFETY: + // - We have included all required acceses for NewQ and NewF + // - The layout of all QueryBuilder instances is the same + unsafe { std::mem::transmute(self) } + } + + /// Create a [`QueryState`] with the accesses of the builder. + pub fn build(&mut self) -> QueryState { + QueryState::::from_builder(self) + } +} + +#[cfg(test)] +mod tests { + use crate as bevy_ecs; + use crate::prelude::*; + use crate::world::FilteredEntityRef; + + use super::QueryBuilder; + + #[derive(Component, PartialEq, Debug)] + struct A(usize); + + #[derive(Component, PartialEq, Debug)] + struct B(usize); + + #[derive(Component, PartialEq, Debug)] + struct C(usize); + + #[test] + fn builder_with_without_static() { + let mut world = World::new(); + let entity_a = world.spawn((A(0), B(0))).id(); + let entity_b = world.spawn((A(0), C(0))).id(); + + let mut query_a = QueryBuilder::::new(&mut world) + .with::() + .without::() + .build(); + assert_eq!(entity_a, query_a.single(&world)); + + let mut query_b = QueryBuilder::::new(&mut world) + .with::() + .without::() + .build(); + assert_eq!(entity_b, query_b.single(&world)); + } + + #[test] + fn builder_with_without_dynamic() { + let mut world = World::new(); + let entity_a = world.spawn((A(0), B(0))).id(); + let entity_b = world.spawn((A(0), C(0))).id(); + let component_id_a = world.init_component::().id(); + let component_id_b = world.init_component::().id(); + let component_id_c = world.init_component::().id(); + + let mut query_a = QueryBuilder::::new(&mut world) + .with_id(component_id_a) + .without_id(component_id_c) + .build(); + assert_eq!(entity_a, query_a.single(&world)); + + let mut query_b = QueryBuilder::::new(&mut world) + .with_id(component_id_a) + .without_id(component_id_b) + .build(); + assert_eq!(entity_b, query_b.single(&world)); + } + + #[test] + fn builder_or() { + let mut world = World::new(); + world.spawn((A(0), B(0))); + world.spawn(B(0)); + world.spawn(C(0)); + + let mut query_a = QueryBuilder::::new(&mut world) + .or(|builder| { + builder.with::(); + builder.with::(); + }) + .build(); + assert_eq!(2, query_a.iter(&world).count()); + + let mut query_b = QueryBuilder::::new(&mut world) + .or(|builder| { + builder.with::(); + builder.without::(); + }) + .build(); + dbg!(&query_b.component_access); + assert_eq!(2, query_b.iter(&world).count()); + + let mut query_c = QueryBuilder::::new(&mut world) + .or(|builder| { + builder.with::(); + builder.with::(); + builder.with::(); + }) + .build(); + assert_eq!(3, query_c.iter(&world).count()); + } + + #[test] + fn builder_transmute() { + let mut world = World::new(); + world.spawn(A(0)); + world.spawn((A(1), B(0))); + let mut query = QueryBuilder::<()>::new(&mut world) + .with::() + .transmute::<&A>() + .build(); + + query.iter(&world).for_each(|a| assert_eq!(a.0, 1)); + } + + #[test] + fn builder_static_components() { + let mut world = World::new(); + let entity = world.spawn((A(0), B(1))).id(); + + let mut query = QueryBuilder::::new(&mut world) + .push::<&A>() + .push::<&B>() + .build(); + + let entity_ref = query.single(&world); + + assert_eq!(entity, entity_ref.id()); + + let a = entity_ref.get::().unwrap(); + let b = entity_ref.get::().unwrap(); + + assert_eq!(0, a.0); + assert_eq!(1, b.0); + } + + #[test] + fn builder_dynamic_components() { + let mut world = World::new(); + let entity = world.spawn((A(0), B(1))).id(); + let component_id_a = world.init_component::().id(); + let component_id_b = world.init_component::().id(); + + let mut query = QueryBuilder::::new(&mut world) + .ref_id(component_id_a) + .ref_id(component_id_b) + .build(); + + let entity_ref = query.single(&world); + + assert_eq!(entity, entity_ref.id()); + + let a = entity_ref.get_by_id(component_id_a).unwrap(); + let b = entity_ref.get_by_id(component_id_b).unwrap(); + + // SAFETY: We set these pointers to point to these components + unsafe { + assert_eq!(0, a.deref::().0); + assert_eq!(1, b.deref::().0); + } + } + + #[test] + fn builder_query_system() { + let mut world = World::new(); + world.spawn(A(0)); + let entity = world.spawn((A(1), B(0))).id(); + + let sys = move |query: Query<(Entity, &A)>| { + let (e, a) = query.single(); + assert_eq!(e, entity); + assert_eq!(1, a.0); + }; + + // Add additional terms that don't appear in the original query + let query = QueryBuilder::<(Entity, &A)>::new(&mut world) + .with::() + .build(); + let mut system = IntoSystem::into_system(sys); + system.initialize(&mut world); + + // SAFETY: We know the system param we are modifying has a compatible type signature + unsafe { system.state_mut().0 = query }; + system.run((), &mut world); + + // Alternatively truncate terms from a query to match the system + let query = QueryBuilder::<(Entity, &A, &B)>::new(&mut world).build(); + let mut system = IntoSystem::into_system(sys); + system.initialize(&mut world); + + // SAFETY: We know the system param we are modifying has a compatible type signature + unsafe { system.state_mut().0 = query.transmute(&world) }; + system.run((), &mut world); + } +} diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 1f7fe29ec729f..ea7188fad9bbb 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -812,7 +812,7 @@ unsafe impl WorldQuery for &T { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::() + world.init_component::().id() } fn matches_component_set( @@ -973,7 +973,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::() + world.init_component::().id() } fn matches_component_set( @@ -1134,7 +1134,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::() + world.init_component::().id() } fn matches_component_set( @@ -1381,7 +1381,7 @@ unsafe impl WorldQuery for Has { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::() + world.init_component::().id() } fn matches_component_set( diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index 7d958fe8742c0..8fac8a0b57211 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -102,7 +102,7 @@ unsafe impl WorldQuery for With { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::() + world.init_component::().id() } fn matches_component_set( @@ -203,7 +203,7 @@ unsafe impl WorldQuery for Without { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::() + world.init_component::().id() } fn matches_component_set( @@ -530,7 +530,7 @@ macro_rules! impl_tick_filter { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::() + world.init_component::().id() } fn matches_component_set(&id: &ComponentId, set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index c5012f8f1491b..c169eb55f77ca 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -118,7 +118,7 @@ impl Schedules { /// Ignore system order ambiguities caused by conflicts on [`Component`]s of type `T`. pub fn allow_ambiguous_component(&mut self, world: &mut World) { self.ignored_scheduling_ambiguities - .insert(world.init_component::()); + .insert(world.init_component::().id()); } /// Ignore system order ambiguities caused by conflicts on [`Resource`]s of type `T`. diff --git a/crates/bevy_ecs/src/storage/table.rs b/crates/bevy_ecs/src/storage/table.rs index 6da4504d40add..1406c602fd70d 100644 --- a/crates/bevy_ecs/src/storage/table.rs +++ b/crates/bevy_ecs/src/storage/table.rs @@ -930,7 +930,7 @@ mod tests { fn table() { let mut components = Components::default(); let mut storages = Storages::default(); - let component_id = components.init_component::>(&mut storages); + let component_id = components.init_component::>(&mut storages).id(); let columns = &[component_id]; let mut table = TableBuilder::with_capacity(0, columns.len()) .add_column(components.get_info(component_id).unwrap()) diff --git a/crates/bevy_ecs/src/system/commands/command_queue.rs b/crates/bevy_ecs/src/system/commands/command_queue.rs index dab6a916662ec..dcd6f58ed5066 100644 --- a/crates/bevy_ecs/src/system/commands/command_queue.rs +++ b/crates/bevy_ecs/src/system/commands/command_queue.rs @@ -135,12 +135,18 @@ impl CommandQueue { // or 1 byte past the end, so this addition will not overflow the pointer's allocation. cursor = unsafe { cursor.add(size) }; } + + world.flush_commands(); } /// Take all commands from `other` and append them to `self`, leaving `other` empty pub fn append(&mut self, other: &mut CommandQueue) { self.bytes.append(&mut other.bytes); } + + pub fn is_empty(&self) -> bool { + self.bytes.is_empty() + } } #[cfg(test)] diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index c50798d623097..8144caa51d2b8 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -1443,7 +1443,7 @@ mod tests { let mut world = World::default(); let mut system = IntoSystem::into_system(a_not_b_system); let mut expected_ids = HashSet::::new(); - let a_id = world.init_component::(); + let a_id = world.init_component::().id(); // set up system and verify its access is empty system.initialize(&mut world); diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs new file mode 100644 index 0000000000000..99f6a917e5010 --- /dev/null +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -0,0 +1,241 @@ +use std::ops::Deref; + +use crate::{ + change_detection::MutUntyped, + component::ComponentId, + entity::Entity, + event::{Event, EventId, Events, SendBatchIds}, + prelude::{Component, QueryState}, + query::{ReadOnlyWorldQuery, WorldQuery}, + system::{CommandQueue, Commands, Query, Resource}, +}; + +use super::{unsafe_world_cell::UnsafeWorldCell, Mut, World}; + +pub struct DeferredWorld<'w> { + world: UnsafeWorldCell<'w>, + command_queue: CommandQueue, +} + +impl<'w> Deref for DeferredWorld<'w> { + type Target = World; + + fn deref(&self) -> &'w Self::Target { + unsafe { self.world.world() } + } +} + +impl<'w> DeferredWorld<'w> { + pub fn commands(&mut self) -> Commands { + Commands::new(&mut self.command_queue, unsafe { self.world.world() }) + } + + /// Retrieves a mutable reference to the given `entity`'s [`Component`] of the given type. + /// Returns `None` if the `entity` does not have a [`Component`] of the given type. + #[inline] + pub fn get_mut(&mut self, entity: Entity) -> Option> { + // SAFETY: + // - `as_unsafe_world_cell` is the only thing that is borrowing world + // - `as_unsafe_world_cell` provides mutable permission to everything + // - `&mut self` ensures no other borrows on world data + unsafe { self.world.get_entity(entity)?.get_mut() } + } + + /// Returns [`Query`] for the given [`QueryState`], which is used to efficiently + /// run queries on the [`World`] by storing and reusing the [`QueryState`]. + #[inline] + pub fn query<'s, Q: WorldQuery, F: ReadOnlyWorldQuery>( + &mut self, + state: &'s mut QueryState, + ) -> Query<'_, 's, Q, F> { + unsafe { + state.update_archetypes(self.world.world()); + Query::new( + self.world, + state, + self.world.last_change_tick(), + self.world.change_tick(), + false, + ) + } + } + + /// Gets a mutable reference to the resource of the given type + /// + /// # Panics + /// + /// Panics if the resource does not exist. + /// Use [`get_resource_mut`](World::get_resource_mut) instead if you want to handle this case. + /// + /// If you want to instead insert a value if the resource does not exist, + /// use [`get_resource_or_insert_with`](World::get_resource_or_insert_with). + #[inline] + #[track_caller] + pub fn resource_mut(&mut self) -> Mut<'_, R> { + match self.get_resource_mut() { + Some(x) => x, + None => panic!( + "Requested resource {} does not exist in the `World`. + Did you forget to add it using `app.insert_resource` / `app.init_resource`? + Resources are also implicitly added via `app.add_event`, + and can be added by plugins.", + std::any::type_name::() + ), + } + } + + /// Gets a mutable reference to the resource of the given type if it exists + #[inline] + pub fn get_resource_mut(&mut self) -> Option> { + // SAFETY: + // - `as_unsafe_world_cell` gives permission to access everything mutably + // - `&mut self` ensures nothing in world is borrowed + unsafe { self.world.get_resource_mut() } + } + + /// Gets a mutable reference to the non-send resource of the given type, if it exists. + /// + /// # Panics + /// + /// Panics if the resource does not exist. + /// Use [`get_non_send_resource_mut`](World::get_non_send_resource_mut) instead if you want to handle this case. + /// + /// This function will panic if it isn't called from the same thread that the resource was inserted from. + #[inline] + #[track_caller] + pub fn non_send_resource_mut(&mut self) -> Mut<'_, R> { + match self.get_non_send_resource_mut() { + Some(x) => x, + None => panic!( + "Requested non-send resource {} does not exist in the `World`. + Did you forget to add it using `app.insert_non_send_resource` / `app.init_non_send_resource`? + Non-send resources can also be be added by plugins.", + std::any::type_name::() + ), + } + } + + /// Gets a mutable reference to the non-send resource of the given type, if it exists. + /// Otherwise returns `None`. + /// + /// # Panics + /// This function will panic if it isn't called from the same thread that the resource was inserted from. + #[inline] + pub fn get_non_send_resource_mut(&mut self) -> Option> { + // SAFETY: + // - `as_unsafe_world_cell` gives permission to access the entire world mutably + // - `&mut self` ensures that there are no borrows of world data + unsafe { self.world.get_non_send_resource_mut() } + } + + /// Sends an [`Event`]. + /// This method returns the [ID](`EventId`) of the sent `event`, + /// or [`None`] if the `event` could not be sent. + #[inline] + pub fn send_event(&mut self, event: E) -> Option> { + self.send_event_batch(std::iter::once(event))?.next() + } + + /// Sends the default value of the [`Event`] of type `E`. + /// This method returns the [ID](`EventId`) of the sent `event`, + /// or [`None`] if the `event` could not be sent. + #[inline] + pub fn send_event_default(&mut self) -> Option> { + self.send_event(E::default()) + } + + /// Sends a batch of [`Event`]s from an iterator. + /// This method returns the [IDs](`EventId`) of the sent `events`, + /// or [`None`] if the `event` could not be sent. + #[inline] + pub fn send_event_batch( + &mut self, + events: impl IntoIterator, + ) -> Option> { + let Some(mut events_resource) = self.get_resource_mut::>() else { + bevy_utils::tracing::error!( + "Unable to send event `{}`\n\tEvent must be added to the app with `add_event()`\n\thttps://docs.rs/bevy/*/bevy/app/struct.App.html#method.add_event ", + std::any::type_name::() + ); + return None; + }; + Some(events_resource.send_batch(events)) + } + + /// Gets a pointer to the resource with the id [`ComponentId`] if it exists. + /// The returned pointer may be used to modify the resource, as long as the mutable borrow + /// of the [`World`] is still valid. + /// + /// **You should prefer to use the typed API [`World::get_resource_mut`] where possible and only + /// use this in cases where the actual types are not known at compile time.** + #[inline] + pub fn get_resource_mut_by_id(&mut self, component_id: ComponentId) -> Option> { + // SAFETY: + // - `&mut self` ensures that all accessed data is unaliased + // - `as_unsafe_world_cell` provides mutable permission to the whole world + unsafe { self.world.get_resource_mut_by_id(component_id) } + } + + /// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists. + /// The returned pointer may be used to modify the resource, as long as the mutable borrow + /// of the [`World`] is still valid. + /// + /// **You should prefer to use the typed API [`World::get_resource_mut`] where possible and only + /// use this in cases where the actual types are not known at compile time.** + /// + /// # Panics + /// This function will panic if it isn't called from the same thread that the resource was inserted from. + #[inline] + pub fn get_non_send_mut_by_id(&mut self, component_id: ComponentId) -> Option> { + // SAFETY: + // - `&mut self` ensures that all accessed data is unaliased + // - `as_unsafe_world_cell` provides mutable permission to the whole world + unsafe { self.world.get_non_send_resource_mut_by_id(component_id) } + } + + /// Retrieves a mutable untyped reference to the given `entity`'s [`Component`] of the given [`ComponentId`]. + /// Returns `None` if the `entity` does not have a [`Component`] of the given type. + /// + /// **You should prefer to use the typed API [`World::get_mut`] where possible and only + /// use this in cases where the actual types are not known at compile time.** + #[inline] + pub fn get_mut_by_id( + &mut self, + entity: Entity, + component_id: ComponentId, + ) -> Option> { + // SAFETY: + // - `&mut self` ensures that all accessed data is unaliased + // - `as_unsafe_world_cell` provides mutable permission to the whole world + unsafe { self.world.get_entity(entity)?.get_mut_by_id(component_id) } + } +} + +impl World { + pub unsafe fn into_deferred(&self) -> DeferredWorld<'_> { + DeferredWorld { + world: self.as_unsafe_world_cell_readonly(), + command_queue: CommandQueue::default(), + } + } +} + +impl<'w> Into> for &'w mut World { + fn into(self) -> DeferredWorld<'w> { + DeferredWorld { + world: self.as_unsafe_world_cell(), + command_queue: CommandQueue::default(), + } + } +} + +impl<'w> Drop for DeferredWorld<'w> { + fn drop(&mut self) { + unsafe { + self.world + .world_mut() + .command_queue + .append(&mut self.command_queue) + } + } +} diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index c94b45dad10ee..c24a47505acb3 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -4,7 +4,6 @@ use crate::{ change_detection::MutUntyped, component::{Component, ComponentId, ComponentTicks, Components, StorageType}, entity::{Entities, Entity, EntityLocation}, - removal_detection::RemovedComponentEvents, storage::Storages, world::{Mut, World}, }; @@ -569,23 +568,11 @@ impl<'w> EntityWorldMut<'w> { /// This will overwrite any previous value(s) of the same component type. pub fn insert(&mut self, bundle: T) -> &mut Self { let change_tick = self.world.change_tick(); - let bundle_info = self - .world - .bundles - .init_info::(&mut self.world.components, &mut self.world.storages); - let mut bundle_inserter = bundle_info.get_bundle_inserter( - &mut self.world.entities, - &mut self.world.archetypes, - &self.world.components, - &mut self.world.storages, - self.location.archetype_id, - change_tick, - ); + let mut bundle_inserter = + BundleInserter::new::(self.world, self.location.archetype_id, change_tick); // SAFETY: location matches current entity. `T` matches `bundle_info` - unsafe { - self.location = bundle_inserter.insert(self.entity, self.location, bundle); - } - + self.location = unsafe { bundle_inserter.insert(self.entity, self.location, bundle) }; + self.world.flush_commands(); self } @@ -605,28 +592,28 @@ impl<'w> EntityWorldMut<'w> { component: OwningPtr<'_>, ) -> &mut Self { let change_tick = self.world.change_tick(); - - let bundles = &mut self.world.bundles; - let components = &mut self.world.components; - - let (bundle_info, storage_type) = bundles.init_component_info(components, component_id); - let bundle_inserter = bundle_info.get_bundle_inserter( - &mut self.world.entities, - &mut self.world.archetypes, - &self.world.components, - &mut self.world.storages, - self.location.archetype_id, - change_tick, - ); + let (bundle_info, storage_type) = self + .world + .bundles + .init_component_info(&self.world.components, component_id); + let bundle_info: *const BundleInfo = bundle_info; + let bundle_inserter = unsafe { + BundleInserter::new_with_info( + self.world, + self.location.archetype_id, + bundle_info, + change_tick, + ) + }; self.location = insert_dynamic_bundle( bundle_inserter, self.entity, self.location, Some(component).into_iter(), - Some(storage_type).into_iter(), + Some(storage_type).iter().cloned(), ); - + self.world.flush_commands(); self } @@ -648,28 +635,29 @@ impl<'w> EntityWorldMut<'w> { iter_components: I, ) -> &mut Self { let change_tick = self.world.change_tick(); - - let bundles = &mut self.world.bundles; - let components = &mut self.world.components; - - let (bundle_info, storage_types) = bundles.init_dynamic_info(components, component_ids); - let bundle_inserter = bundle_info.get_bundle_inserter( - &mut self.world.entities, - &mut self.world.archetypes, - &self.world.components, - &mut self.world.storages, - self.location.archetype_id, - change_tick, - ); + let (bundle_info, storage_types) = self + .world + .bundles + .init_dynamic_info(&self.world.components, component_ids); + let bundle_info: *const BundleInfo = bundle_info; + let storage_types: *const Vec = storage_types; + let bundle_inserter = unsafe { + BundleInserter::new_with_info( + self.world, + self.location.archetype_id, + bundle_info, + change_tick, + ) + }; self.location = insert_dynamic_bundle( bundle_inserter, self.entity, self.location, iter_components, - storage_types.iter().cloned(), + (&*storage_types).iter().cloned(), ); - + self.world.flush_commands(); self } @@ -680,19 +668,17 @@ impl<'w> EntityWorldMut<'w> { // TODO: BundleRemover? #[must_use] pub fn take(&mut self) -> Option { - let archetypes = &mut self.world.archetypes; let storages = &mut self.world.storages; let components = &mut self.world.components; - let entities = &mut self.world.entities; - let removed_components = &mut self.world.removed_components; - - let bundle_info = self.world.bundles.init_info::(components, storages); + let bundle_id = self.world.bundles.init_info::(components, storages).id(); + // Reborrow to drop &mut World + let bundle_info = unsafe { self.world.bundles.get_unchecked(bundle_id) }; let old_location = self.location; // SAFETY: `archetype_id` exists because it is referenced in the old `EntityLocation` which is valid, // components exist in `bundle_info` because `Bundles::init_info` initializes a `BundleInfo` containing all components of the bundle type `T` let new_archetype_id = unsafe { remove_bundle_from_archetype( - archetypes, + &mut self.world.archetypes, storages, components, old_location.archetype_id, @@ -705,8 +691,24 @@ impl<'w> EntityWorldMut<'w> { return None; } - let mut bundle_components = bundle_info.components().iter().cloned(); + // Reborrow so world is not mutably borrowed let entity = self.entity; + for component_id in bundle_info.components().iter().cloned() { + self.world.removed_components.send(component_id, entity); + unsafe { + let info = self.world.components.get_info_unchecked(component_id); + if let Some(hook) = info.hooks().on_remove { + hook(self.world.into_deferred(), self.entity) + } + } + } + let archetypes = &mut self.world.archetypes; + let storages = &mut self.world.storages; + let components = &mut self.world.components; + let entities = &mut self.world.entities; + + let entity = self.entity; + let mut bundle_components = bundle_info.components().iter().cloned(); // SAFETY: bundle components are iterated in order, which guarantees that the component type // matches let result = unsafe { @@ -716,14 +718,7 @@ impl<'w> EntityWorldMut<'w> { // - entity location is valid // - table row is removed below, without dropping the contents // - `components` comes from the same world as `storages` - take_component( - storages, - components, - removed_components, - component_id, - entity, - old_location, - ) + take_component(storages, components, component_id, entity, old_location) }) }; @@ -740,7 +735,7 @@ impl<'w> EntityWorldMut<'w> { new_archetype_id, ); } - + self.world.flush_commands(); Some(result) } @@ -831,10 +826,10 @@ impl<'w> EntityWorldMut<'w> { let archetypes = &mut self.world.archetypes; let storages = &mut self.world.storages; let components = &mut self.world.components; - let entities = &mut self.world.entities; - let removed_components = &mut self.world.removed_components; - let bundle_info = self.world.bundles.init_info::(components, storages); + let bundle_id = self.world.bundles.init_info::(components, storages).id(); + // Reborrow to drop &mut World + let bundle_info = unsafe { self.world.bundles.get_unchecked(bundle_id) }; let old_location = self.location; // SAFETY: `archetype_id` exists because it is referenced in the old `EntityLocation` which is valid, @@ -855,16 +850,24 @@ impl<'w> EntityWorldMut<'w> { return self; } - let old_archetype = &mut archetypes[old_location.archetype_id]; + let old_archetype = &self.world.archetypes[old_location.archetype_id]; + // Reborrow so world is not mutably borrowed let entity = self.entity; for component_id in bundle_info.components().iter().cloned() { if old_archetype.contains(component_id) { - removed_components.send(component_id, entity); + self.world.removed_components.send(component_id, entity); + unsafe { + let info = self.world.components.get_info_unchecked(component_id); + if let Some(hook) = info.hooks().on_remove { + hook(self.world.into_deferred(), self.entity) + } + } // Make sure to drop components stored in sparse sets. // Dense components are dropped later in `move_to_and_drop_missing_unchecked`. if let Some(StorageType::SparseSet) = old_archetype.get_storage_type(component_id) { - storages + self.world + .storages .sparse_sets .get_mut(component_id) .unwrap() @@ -880,13 +883,13 @@ impl<'w> EntityWorldMut<'w> { &mut self.location, old_location.archetype_id, old_location, - entities, - archetypes, - storages, + &mut self.world.entities, + &mut self.world.archetypes, + &mut self.world.storages, new_archetype_id, ); } - + self.world.flush_commands(); self } @@ -895,6 +898,17 @@ impl<'w> EntityWorldMut<'w> { debug!("Despawning entity {:?}", self.entity); let world = self.world; world.flush(); + let archetype = &world.archetypes[self.location.archetype_id]; + for component_id in archetype.components() { + world.removed_components.send(component_id, self.entity); + unsafe { + let info = world.components().get_info_unchecked(component_id); + if let Some(hook) = info.hooks().on_remove { + hook(world.into_deferred(), self.entity) + } + } + } + let location = world .entities .free(self.entity) @@ -903,10 +917,7 @@ impl<'w> EntityWorldMut<'w> { let moved_entity; { - let archetype = &mut world.archetypes[location.archetype_id]; - for component_id in archetype.components() { - world.removed_components.send(component_id, self.entity); - } + let archetype = &mut world.archetypes[self.location.archetype_id]; let remove_result = archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = remove_result.swapped_entity { let swapped_location = world.entities.get(swapped_entity).unwrap(); @@ -954,6 +965,7 @@ impl<'w> EntityWorldMut<'w> { world.archetypes[moved_location.archetype_id] .set_entity_table_row(moved_location.archetype_row, table_row); } + world.flush_commands(); } /// Gets read-only access to the world that the current entity belongs to. @@ -1431,7 +1443,7 @@ unsafe fn insert_dynamic_bundle< I: Iterator>, S: Iterator, >( - mut bundle_inserter: BundleInserter<'_, '_>, + mut bundle_inserter: BundleInserter<'_>, entity: Entity, location: EntityLocation, components: I, @@ -1587,14 +1599,12 @@ fn sorted_remove(source: &mut Vec, remove: &[T]) { pub(crate) unsafe fn take_component<'a>( storages: &'a mut Storages, components: &Components, - removed_components: &mut RemovedComponentEvents, component_id: ComponentId, entity: Entity, location: EntityLocation, ) -> OwningPtr<'a> { // SAFETY: caller promises component_id to be valid let component_info = components.get_info_unchecked(component_id); - removed_components.send(component_id, entity); match component_info.storage_type() { StorageType::Table => { let table = &mut storages.tables[location.table_id]; @@ -1897,7 +1907,7 @@ mod tests { #[test] fn entity_mut_insert_by_id() { let mut world = World::new(); - let test_component_id = world.init_component::(); + let test_component_id = world.init_component::().id(); let mut entity = world.spawn_empty(); OwningPtr::make(TestComponent(42), |ptr| { @@ -1925,8 +1935,8 @@ mod tests { #[test] fn entity_mut_insert_bundle_by_id() { let mut world = World::new(); - let test_component_id = world.init_component::(); - let test_component_2_id = world.init_component::(); + let test_component_id = world.init_component::().id(); + let test_component_2_id = world.init_component::().id(); let component_ids = [test_component_id, test_component_2_id]; let test_component_value = TestComponent(42); diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 0de995257fc4d..891ad56a00415 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1,5 +1,6 @@ //! Defines the [`World`] and APIs for accessing it directly. +mod deferred_world; mod entity_ref; pub mod error; mod spawn_batch; @@ -7,13 +8,14 @@ pub mod unsafe_world_cell; mod world_cell; pub use crate::change_detection::{Mut, Ref, CHECK_TICK_THRESHOLD}; +pub use deferred_world::DeferredWorld; pub use entity_ref::{EntityMut, EntityRef, EntityWorldMut, Entry, OccupiedEntry, VacantEntry}; pub use spawn_batch::*; pub use world_cell::*; use crate::{ archetype::{ArchetypeComponentId, ArchetypeId, ArchetypeRow, Archetypes}, - bundle::{Bundle, BundleInserter, BundleSpawner, Bundles}, + bundle::{Bundle, BundleInfo, BundleInserter, BundleSpawner, Bundles}, change_detection::{MutUntyped, TicksMut}, component::{Component, ComponentDescriptor, ComponentId, ComponentInfo, Components, Tick}, entity::{AllocAtWithoutReplacement, Entities, Entity, EntityLocation}, @@ -22,7 +24,7 @@ use crate::{ removal_detection::RemovedComponentEvents, schedule::{Schedule, ScheduleLabel, Schedules}, storage::{ResourceData, Storages}, - system::Resource, + system::{CommandQueue, Resource}, world::error::TryRunScheduleError, }; use bevy_ptr::{OwningPtr, Ptr}; @@ -71,6 +73,8 @@ pub struct World { pub(crate) change_tick: AtomicU32, pub(crate) last_change_tick: Tick, pub(crate) last_check_tick: Tick, + pub(crate) command_queue: CommandQueue, + flushing_commands: bool, } impl Default for World { @@ -89,6 +93,8 @@ impl Default for World { change_tick: AtomicU32::new(1), last_change_tick: Tick::new(0), last_check_tick: Tick::new(0), + command_queue: CommandQueue::default(), + flushing_commands: false, } } } @@ -178,7 +184,7 @@ impl World { } /// Initializes a new [`Component`] type and returns the [`ComponentId`] created for it. - pub fn init_component(&mut self) -> ComponentId { + pub fn init_component(&mut self) -> &mut ComponentInfo { self.components.init_component::(&mut self.storages) } @@ -194,7 +200,7 @@ impl World { pub fn init_component_with_descriptor( &mut self, descriptor: ComponentDescriptor, - ) -> ComponentId { + ) -> &mut ComponentInfo { self.components .init_component_with_descriptor(&mut self.storages, descriptor) } @@ -215,7 +221,7 @@ impl World { /// #[derive(Component)] /// struct ComponentA; /// - /// let component_a_id = world.init_component::(); + /// let component_a_id = world.init_component::().id(); /// /// assert_eq!(component_a_id, world.component_id::().unwrap()) /// ``` @@ -740,19 +746,9 @@ impl World { let change_tick = self.change_tick(); let entity = self.entities.alloc(); let entity_location = { - let bundle_info = self - .bundles - .init_info::(&mut self.components, &mut self.storages); - let mut spawner = bundle_info.get_bundle_spawner( - &mut self.entities, - &mut self.archetypes, - &self.components, - &mut self.storages, - change_tick, - ); - + let mut bundle_spawner = BundleSpawner::new::(self, change_tick); // SAFETY: bundle's type matches `bundle_info`, entity is allocated but non-existent - unsafe { spawner.spawn_non_existent(entity, bundle) } + unsafe { bundle_spawner.spawn_non_existent(entity, bundle) } }; // SAFETY: entity and location are valid, as they were just created above @@ -1438,26 +1434,23 @@ impl World { let bundle_info = self .bundles .init_info::(&mut self.components, &mut self.storages); - enum SpawnOrInsert<'a, 'b> { - Spawn(BundleSpawner<'a, 'b>), - Insert(BundleInserter<'a, 'b>, ArchetypeId), + enum SpawnOrInsert<'w> { + Spawn(BundleSpawner<'w>), + Insert(BundleInserter<'w>, ArchetypeId), } - impl<'a, 'b> SpawnOrInsert<'a, 'b> { + impl<'w> SpawnOrInsert<'w> { fn entities(&mut self) -> &mut Entities { match self { - SpawnOrInsert::Spawn(spawner) => spawner.entities, - SpawnOrInsert::Insert(inserter, _) => inserter.entities, + SpawnOrInsert::Spawn(spawner) => spawner.entities(), + SpawnOrInsert::Insert(inserter, _) => inserter.entities(), } } } - let mut spawn_or_insert = SpawnOrInsert::Spawn(bundle_info.get_bundle_spawner( - &mut self.entities, - &mut self.archetypes, - &self.components, - &mut self.storages, - change_tick, - )); + let bundle_info: *const BundleInfo = bundle_info; + let mut spawn_or_insert = SpawnOrInsert::Spawn(unsafe { + BundleSpawner::new_with_info(self, bundle_info, change_tick) + }); let mut invalid_entities = Vec::new(); for (entity, bundle) in iter { @@ -1474,14 +1467,14 @@ impl World { unsafe { inserter.insert(entity, location, bundle) }; } _ => { - let mut inserter = bundle_info.get_bundle_inserter( - &mut self.entities, - &mut self.archetypes, - &self.components, - &mut self.storages, - location.archetype_id, - change_tick, - ); + let mut inserter = unsafe { + BundleInserter::new_with_info( + self, + location.archetype_id, + bundle_info, + change_tick, + ) + }; // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter unsafe { inserter.insert(entity, location, bundle) }; spawn_or_insert = @@ -1494,13 +1487,8 @@ impl World { // SAFETY: `entity` is allocated (but non existent), bundle matches inserter unsafe { spawner.spawn_non_existent(entity, bundle) }; } else { - let mut spawner = bundle_info.get_bundle_spawner( - &mut self.entities, - &mut self.archetypes, - &self.components, - &mut self.storages, - change_tick, - ); + let mut spawner = + unsafe { BundleSpawner::new_with_info(self, bundle_info, change_tick) }; // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter unsafe { spawner.spawn_non_existent(entity, bundle) }; spawn_or_insert = SpawnOrInsert::Spawn(spawner); @@ -1735,6 +1723,18 @@ impl World { } } + pub fn flush_commands(&mut self) { + if !self.flushing_commands { + self.flushing_commands = true; + while !self.command_queue.is_empty() { + let mut commands = CommandQueue::default(); + std::mem::swap(&mut commands, &mut self.command_queue); + commands.apply(self) + } + self.flushing_commands = false; + } + } + /// Increments the world's current change tick and returns the old value. #[inline] pub fn increment_change_tick(&self) -> Tick { @@ -2347,7 +2347,7 @@ mod tests { ) }; - let component_id = world.init_component_with_descriptor(descriptor); + let component_id = world.init_component_with_descriptor(descriptor).id(); let value: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7]; OwningPtr::make(value, |ptr| { diff --git a/crates/bevy_ecs/src/world/spawn_batch.rs b/crates/bevy_ecs/src/world/spawn_batch.rs index e21199f56444c..ef90947e54d12 100644 --- a/crates/bevy_ecs/src/world/spawn_batch.rs +++ b/crates/bevy_ecs/src/world/spawn_batch.rs @@ -15,7 +15,7 @@ where I::Item: Bundle, { inner: I, - spawner: BundleSpawner<'w, 'w>, + spawner: BundleSpawner<'w>, } impl<'w, I> SpawnBatchIter<'w, I> @@ -33,18 +33,9 @@ where let (lower, upper) = iter.size_hint(); let length = upper.unwrap_or(lower); - - let bundle_info = world - .bundles - .init_info::(&mut world.components, &mut world.storages); world.entities.reserve(length as u32); - let mut spawner = bundle_info.get_bundle_spawner( - &mut world.entities, - &mut world.archetypes, - &world.components, - &mut world.storages, - change_tick, - ); + + let mut spawner = BundleSpawner::new::(world, change_tick); spawner.reserve_storage(length); Self { diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs new file mode 100644 index 0000000000000..510474e455db1 --- /dev/null +++ b/examples/ecs/component_hooks.rs @@ -0,0 +1,42 @@ +use std::collections::HashSet; + +use bevy::prelude::*; + +#[derive(Component, Debug)] +struct MyComponent(usize); + +#[derive(Resource, Default, Debug, Deref, DerefMut)] +struct MyComponentIndex(HashSet); + +fn main() { + App::new() + .add_systems(Startup, (setup, trigger_hooks).chain()) + .init_resource::() + .run(); +} + +fn setup(world: &mut World) { + world + .init_component::() + .on_add(|mut world, entity| { + println!("Added MyComponent to: {:?}", entity); + world.resource_mut::().insert(entity); + }) + .on_remove(|mut world, entity| { + println!( + "Removed MyComponent from: {:?} {:?}", + entity, + world.get::(entity) + ); + let mut index = world.resource_mut::(); + index.remove(&entity); + println!("Current index: {:?}", *index) + }); +} + +fn trigger_hooks(mut commands: Commands) { + let entity_a = commands.spawn(MyComponent(0)).id(); + let entity_b = commands.spawn(MyComponent(1)).id(); + commands.entity(entity_b).despawn(); + commands.entity(entity_a).despawn(); +} From 26edb7341dfdfaa2baf4ced6b813d9d2dbff2bd9 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Fri, 24 Nov 2023 18:20:32 -0800 Subject: [PATCH 002/109] Perf improvements, archetype flags --- crates/bevy_ecs/Cargo.toml | 1 + crates/bevy_ecs/src/archetype.rs | 97 +++++++++++++++++-- crates/bevy_ecs/src/bundle.rs | 86 ++++++++++------ crates/bevy_ecs/src/component.rs | 31 ++++-- crates/bevy_ecs/src/lib.rs | 14 +-- crates/bevy_ecs/src/query/fetch.rs | 8 +- crates/bevy_ecs/src/query/filter.rs | 6 +- crates/bevy_ecs/src/schedule/schedule.rs | 2 +- crates/bevy_ecs/src/storage/table.rs | 2 +- crates/bevy_ecs/src/system/mod.rs | 2 +- crates/bevy_ecs/src/world/deferred_world.rs | 85 +++++++--------- crates/bevy_ecs/src/world/entity_ref.rs | 71 ++++++++------ crates/bevy_ecs/src/world/mod.rs | 23 ++++- .../bevy_ecs/src/world/unsafe_world_cell.rs | 8 +- examples/ecs/component_hooks.rs | 6 +- 15 files changed, 297 insertions(+), 145 deletions(-) diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index 3231e7b4101ff..0746eb72d2f87 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -22,6 +22,7 @@ bevy_utils = { path = "../bevy_utils", version = "0.12.0" } bevy_ecs_macros = { path = "macros", version = "0.12.0" } async-channel = "1.4" +bitflags = "2.3" event-listener = "2.5" thread_local = "1.1.4" fixedbitset = "0.4.2" diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index c9e3a2e4a488e..13cba4df2d1a8 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -21,9 +21,10 @@ use crate::{ bundle::BundleId, - component::{ComponentId, StorageType}, + component::{ComponentId, Components, StorageType}, entity::{Entity, EntityLocation}, storage::{ImmutableSparseSet, SparseArray, SparseSet, SparseSetIndex, TableId, TableRow}, + world::unsafe_world_cell::UnsafeWorldCell, }; use std::{ hash::Hash, @@ -107,7 +108,7 @@ impl ArchetypeId { } } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Eq, PartialEq)] pub(crate) enum ComponentStatus { Added, Mutated, @@ -298,6 +299,15 @@ struct ArchetypeComponentInfo { archetype_component_id: ArchetypeComponentId, } +bitflags::bitflags! { + #[derive(Clone, Copy)] + pub struct ArchetypeFlags: u32 { + const ON_ADD_HOOK = (1 << 0); + const ON_INSERT_HOOK = (1 << 1); + const ON_REMOVE_HOOK = (1 << 2); + } +} + /// Metadata for a single archetype within a [`World`]. /// /// For more information, see the *[module level documentation]*. @@ -310,10 +320,12 @@ pub struct Archetype { edges: Edges, entities: Vec, components: ImmutableSparseSet, + flags: ArchetypeFlags, } impl Archetype { pub(crate) fn new( + components: &Components, id: ArchetypeId, table_id: TableId, table_components: impl Iterator, @@ -321,9 +333,13 @@ impl Archetype { ) -> Self { let (min_table, _) = table_components.size_hint(); let (min_sparse, _) = sparse_set_components.size_hint(); - let mut components = SparseSet::with_capacity(min_table + min_sparse); + let mut flags = ArchetypeFlags::empty(); + let mut archetype_components = SparseSet::with_capacity(min_table + min_sparse); for (component_id, archetype_component_id) in table_components { - components.insert( + // SAFETY: We are creating an archetype that includes this component so it must exist + let info = unsafe { components.get_info_unchecked(component_id) }; + info.update_archetype_flags(&mut flags); + archetype_components.insert( component_id, ArchetypeComponentInfo { storage_type: StorageType::Table, @@ -333,7 +349,10 @@ impl Archetype { } for (component_id, archetype_component_id) in sparse_set_components { - components.insert( + // SAFETY: We are creating an archetype that includes this component so it must exist + let info = unsafe { components.get_info_unchecked(component_id) }; + info.update_archetype_flags(&mut flags); + archetype_components.insert( component_id, ArchetypeComponentInfo { storage_type: StorageType::SparseSet, @@ -345,8 +364,9 @@ impl Archetype { id, table_id, entities: Vec::new(), - components: components.into_immutable(), + components: archetype_components.into_immutable(), edges: Default::default(), + flags, } } @@ -356,6 +376,11 @@ impl Archetype { self.id } + #[inline] + pub fn flags(&self) -> ArchetypeFlags { + self.flags + } + /// Fetches the archetype's [`Table`] ID. /// /// [`Table`]: crate::storage::Table @@ -536,6 +561,57 @@ impl Archetype { pub(crate) fn clear_entities(&mut self) { self.entities.clear(); } + + #[inline] + pub(crate) unsafe fn trigger_on_add( + &self, + world: UnsafeWorldCell, + entity: Entity, + targets: impl Iterator, + ) { + if self.flags().contains(ArchetypeFlags::ON_ADD_HOOK) { + for component_id in targets { + let hooks = unsafe { world.components().get_info_unchecked(component_id) }.hooks(); + if let Some(hook) = hooks.on_add { + hook(world.into_deferred(), entity, component_id) + } + } + } + } + + #[inline] + pub(crate) unsafe fn trigger_on_insert( + &self, + world: UnsafeWorldCell, + entity: Entity, + targets: impl Iterator, + ) { + if self.flags().contains(ArchetypeFlags::ON_INSERT_HOOK) { + for component_id in targets { + let hooks = unsafe { world.components().get_info_unchecked(component_id) }.hooks(); + if let Some(hook) = hooks.on_insert { + hook(world.into_deferred(), entity, component_id) + } + } + } + } + + #[inline] + pub(crate) unsafe fn trigger_on_remove( + &self, + world: UnsafeWorldCell, + entity: Entity, + targets: impl Iterator, + ) { + if self.flags().contains(ArchetypeFlags::ON_REMOVE_HOOK) { + for component_id in targets { + let hooks = unsafe { world.components().get_info_unchecked(component_id) }.hooks(); + if let Some(hook) = hooks.on_remove { + hook(world.into_deferred(), entity, component_id); + } + } + } + } } /// The next [`ArchetypeId`] in an [`Archetypes`] collection. @@ -625,7 +701,12 @@ impl Archetypes { by_components: Default::default(), archetype_component_count: 0, }; - archetypes.get_id_or_insert(TableId::empty(), Vec::new(), Vec::new()); + archetypes.get_id_or_insert( + &Components::default(), + TableId::empty(), + Vec::new(), + Vec::new(), + ); archetypes } @@ -704,6 +785,7 @@ impl Archetypes { /// [`TableId`] must exist in tables pub(crate) fn get_id_or_insert( &mut self, + components: &Components, table_id: TableId, table_components: Vec, sparse_set_components: Vec, @@ -729,6 +811,7 @@ impl Archetypes { let sparse_set_archetype_components = (sparse_start..*archetype_component_count).map(ArchetypeComponentId); archetypes.push(Archetype::new( + components, id, table_id, table_components.into_iter().zip(table_archetype_components), diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 9979c0c267c30..1ea87f76ed6f9 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -185,7 +185,7 @@ unsafe impl Bundle for C { storages: &mut Storages, ids: &mut impl FnMut(ComponentId), ) { - ids(components.init_component::(storages).id()); + ids(components.init_component::(storages)); } unsafe fn from_components(ctx: &mut T, func: &mut F) -> Self @@ -346,6 +346,11 @@ impl BundleInfo { &self.component_ids } + #[inline] + pub fn iter_components(&self) -> impl Iterator + '_ { + self.component_ids.iter().cloned() + } + /// This writes components from a given [`Bundle`] to the given entity. /// /// # Safety @@ -478,8 +483,12 @@ impl BundleInfo { new_sparse_set_components }; }; - let new_archetype_id = - archetypes.get_id_or_insert(table_id, table_components, sparse_set_components); + let new_archetype_id = archetypes.get_id_or_insert( + components, + table_id, + table_components, + sparse_set_components, + ); // add an edge from the old archetype to the new archetype archetypes[archetype_id].edges_mut().insert_add_bundle( self.id, @@ -612,22 +621,17 @@ impl<'w> BundleInserter<'w> { bundle: T, ) -> EntityLocation { let bundle_info = &*self.bundle_info; - let add_bundle: &AddBundle = &*self.add_bundle; + let add_bundle = &*self.add_bundle; let world = &mut *self.world; - for (i, component_id) in bundle_info.components().iter().cloned().enumerate() { - let hooks = unsafe { world.components.get_info_unchecked(component_id) }.hooks(); - if let ComponentStatus::Added = add_bundle.bundle_status[i] { - if let Some(hook) = hooks.on_add { - hook(unsafe { world.into_deferred() }, entity) - } - } - if let Some(hook) = hooks.on_insert { - hook(unsafe { world.into_deferred() }, entity) - } - } match self.result { InsertBundleResult::SameArchetype => { + let archetype = &mut *self.archetype; + archetype.trigger_on_insert( + world.as_unsafe_world_cell_readonly(), + entity, + bundle_info.components().iter().cloned(), + ); bundle_info.write_components( &mut *self.table, &mut world.storages.sparse_sets, @@ -643,6 +647,20 @@ impl<'w> BundleInserter<'w> { let table = &mut *self.table; let archetype = &mut *self.archetype; let new_archetype = &mut *new_archetype; + new_archetype.trigger_on_add( + world.as_unsafe_world_cell_readonly(), + entity, + bundle_info + .components() + .iter() + .cloned() + .filter(|id| add_bundle.get_status(id.index()) == ComponentStatus::Added), + ); + new_archetype.trigger_on_insert( + world.as_unsafe_world_cell_readonly(), + entity, + bundle_info.components().iter().cloned(), + ); let result = archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { let swapped_location = @@ -679,6 +697,20 @@ impl<'w> BundleInserter<'w> { let new_table = &mut *new_table; let archetype = &mut *self.archetype; let new_archetype = &mut *new_archetype; + new_archetype.trigger_on_add( + world.as_unsafe_world_cell_readonly(), + entity, + bundle_info + .components() + .iter() + .cloned() + .filter(|id| add_bundle.get_status(id.index()) == ComponentStatus::Added), + ); + new_archetype.trigger_on_insert( + world.as_unsafe_world_cell_readonly(), + entity, + bundle_info.components().iter().cloned(), + ); let result = archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { let swapped_location = @@ -803,19 +835,17 @@ impl<'w> BundleSpawner<'w> { bundle: T, ) -> EntityLocation { let bundle_info = &*self.bundle_info; - for component_id in bundle_info.components().iter().cloned() { - let hooks = self - .world - .components - .get_info_unchecked(component_id) - .hooks(); - if let Some(hook) = hooks.on_add { - hook(self.world.into_deferred(), entity); - } - if let Some(hook) = hooks.on_insert { - hook(self.world.into_deferred(), entity); - } - } + let archetype = &*self.archetype; + archetype.trigger_on_add( + self.world.as_unsafe_world_cell_readonly(), + entity, + bundle_info.iter_components(), + ); + archetype.trigger_on_insert( + self.world.as_unsafe_world_cell_readonly(), + entity, + bundle_info.iter_components(), + ); let archetype = &mut *self.archetype; let table = &mut *self.table; diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 521fe74b959b4..52b4fdc167ec5 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -2,6 +2,7 @@ use crate::{ self as bevy_ecs, + archetype::ArchetypeFlags, change_detection::MAX_CHANGE_AGE, entity::Entity, storage::{SparseSetIndex, Storages}, @@ -280,6 +281,19 @@ impl ComponentInfo { self } + #[inline] + pub fn update_archetype_flags(&self, flags: &mut ArchetypeFlags) { + if self.hooks().on_add.is_some() { + flags.insert(ArchetypeFlags::ON_ADD_HOOK); + } + if self.hooks().on_insert.is_some() { + flags.insert(ArchetypeFlags::ON_INSERT_HOOK); + } + if self.hooks().on_remove.is_some() { + flags.insert(ArchetypeFlags::ON_REMOVE_HOOK); + } + } + pub fn hooks(&self) -> &ComponentHooks { &self.descriptor.hooks } @@ -338,7 +352,7 @@ impl SparseSetIndex for ComponentId { } } -pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, Entity); +pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, Entity, ComponentId); #[derive(Clone, Default)] pub struct ComponentHooks { @@ -487,7 +501,7 @@ impl Components { /// * [`Components::component_id()`] /// * [`Components::init_component_with_descriptor()`] #[inline] - pub fn init_component(&mut self, storages: &mut Storages) -> &mut ComponentInfo { + pub fn init_component(&mut self, storages: &mut Storages) -> ComponentId { let type_id = TypeId::of::(); let Components { @@ -498,7 +512,7 @@ impl Components { let index = indices.entry(type_id).or_insert_with(|| { Components::init_component_inner(components, storages, ComponentDescriptor::new::()) }); - &mut components[*index] + ComponentId(*index) } /// Initializes a component described by `descriptor`. @@ -516,9 +530,9 @@ impl Components { &mut self, storages: &mut Storages, descriptor: ComponentDescriptor, - ) -> &mut ComponentInfo { + ) -> ComponentId { let index = Components::init_component_inner(&mut self.components, storages, descriptor); - &mut self.components[index] + ComponentId(index) } #[inline] @@ -574,6 +588,11 @@ impl Components { self.components.get_unchecked(id.0) } + #[inline] + pub(crate) unsafe fn get_info_mut(&mut self, id: ComponentId) -> &mut ComponentInfo { + self.components.get_unchecked_mut(id.0) + } + /// Type-erased equivalent of [`Components::component_id()`]. #[inline] pub fn get_id(&self, type_id: TypeId) -> Option { @@ -906,7 +925,7 @@ struct InitComponentId { impl FromWorld for InitComponentId { fn from_world(world: &mut World) -> Self { Self { - component_id: world.init_component::().id(), + component_id: world.init_component::(), marker: PhantomData, } } diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 57a561451b9b3..ad8ba9b39a51f 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -157,8 +157,8 @@ mod tests { assert_eq!( ids, &[ - world.init_component::().id(), - world.init_component::().id(), + world.init_component::(), + world.init_component::(), ] ); @@ -211,10 +211,10 @@ mod tests { assert_eq!( ids, &[ - world.init_component::().id(), - world.init_component::().id(), - world.init_component::().id(), - world.init_component::().id(), + world.init_component::(), + world.init_component::(), + world.init_component::(), + world.init_component::(), ] ); @@ -264,7 +264,7 @@ mod tests { }, ); - assert_eq!(ids, &[world.init_component::().id(),]); + assert_eq!(ids, &[world.init_component::(),]); let e4 = world .spawn(BundleWithIgnored { diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index ea7188fad9bbb..1f7fe29ec729f 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -812,7 +812,7 @@ unsafe impl WorldQuery for &T { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::().id() + world.init_component::() } fn matches_component_set( @@ -973,7 +973,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::().id() + world.init_component::() } fn matches_component_set( @@ -1134,7 +1134,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::().id() + world.init_component::() } fn matches_component_set( @@ -1381,7 +1381,7 @@ unsafe impl WorldQuery for Has { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::().id() + world.init_component::() } fn matches_component_set( diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index 8fac8a0b57211..7d958fe8742c0 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -102,7 +102,7 @@ unsafe impl WorldQuery for With { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::().id() + world.init_component::() } fn matches_component_set( @@ -203,7 +203,7 @@ unsafe impl WorldQuery for Without { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::().id() + world.init_component::() } fn matches_component_set( @@ -530,7 +530,7 @@ macro_rules! impl_tick_filter { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::().id() + world.init_component::() } fn matches_component_set(&id: &ComponentId, set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index c169eb55f77ca..c5012f8f1491b 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -118,7 +118,7 @@ impl Schedules { /// Ignore system order ambiguities caused by conflicts on [`Component`]s of type `T`. pub fn allow_ambiguous_component(&mut self, world: &mut World) { self.ignored_scheduling_ambiguities - .insert(world.init_component::().id()); + .insert(world.init_component::()); } /// Ignore system order ambiguities caused by conflicts on [`Resource`]s of type `T`. diff --git a/crates/bevy_ecs/src/storage/table.rs b/crates/bevy_ecs/src/storage/table.rs index 1406c602fd70d..6da4504d40add 100644 --- a/crates/bevy_ecs/src/storage/table.rs +++ b/crates/bevy_ecs/src/storage/table.rs @@ -930,7 +930,7 @@ mod tests { fn table() { let mut components = Components::default(); let mut storages = Storages::default(); - let component_id = components.init_component::>(&mut storages).id(); + let component_id = components.init_component::>(&mut storages); let columns = &[component_id]; let mut table = TableBuilder::with_capacity(0, columns.len()) .add_column(components.get_info(component_id).unwrap()) diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 8144caa51d2b8..c50798d623097 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -1443,7 +1443,7 @@ mod tests { let mut world = World::default(); let mut system = IntoSystem::into_system(a_not_b_system); let mut expected_ids = HashSet::::new(); - let a_id = world.init_component::().id(); + let a_id = world.init_component::(); // set up system and verify its access is empty system.initialize(&mut world); diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 99f6a917e5010..bf492993ddfd5 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -7,27 +7,27 @@ use crate::{ event::{Event, EventId, Events, SendBatchIds}, prelude::{Component, QueryState}, query::{ReadOnlyWorldQuery, WorldQuery}, - system::{CommandQueue, Commands, Query, Resource}, + system::{Commands, Query, Resource}, }; use super::{unsafe_world_cell::UnsafeWorldCell, Mut, World}; pub struct DeferredWorld<'w> { - world: UnsafeWorldCell<'w>, - command_queue: CommandQueue, + world: &'w mut World, } impl<'w> Deref for DeferredWorld<'w> { - type Target = World; + type Target = &'w mut World; - fn deref(&self) -> &'w Self::Target { - unsafe { self.world.world() } + fn deref(&self) -> &Self::Target { + &self.world } } impl<'w> DeferredWorld<'w> { pub fn commands(&mut self) -> Commands { - Commands::new(&mut self.command_queue, unsafe { self.world.world() }) + let world = self.world.as_unsafe_world_cell(); + unsafe { Commands::new(world.get_command_queue(), world.world()) } } /// Retrieves a mutable reference to the given `entity`'s [`Component`] of the given type. @@ -38,23 +38,29 @@ impl<'w> DeferredWorld<'w> { // - `as_unsafe_world_cell` is the only thing that is borrowing world // - `as_unsafe_world_cell` provides mutable permission to everything // - `&mut self` ensures no other borrows on world data - unsafe { self.world.get_entity(entity)?.get_mut() } + unsafe { + self.world + .as_unsafe_world_cell() + .get_entity(entity)? + .get_mut() + } } /// Returns [`Query`] for the given [`QueryState`], which is used to efficiently /// run queries on the [`World`] by storing and reusing the [`QueryState`]. #[inline] pub fn query<'s, Q: WorldQuery, F: ReadOnlyWorldQuery>( - &mut self, + &'w mut self, state: &'s mut QueryState, - ) -> Query<'_, 's, Q, F> { + ) -> Query<'w, 's, Q, F> { unsafe { - state.update_archetypes(self.world.world()); + state.update_archetypes(self.world); + let world = self.world.as_unsafe_world_cell(); Query::new( - self.world, + world, state, - self.world.last_change_tick(), - self.world.change_tick(), + world.last_change_tick(), + world.change_tick(), false, ) } @@ -87,10 +93,7 @@ impl<'w> DeferredWorld<'w> { /// Gets a mutable reference to the resource of the given type if it exists #[inline] pub fn get_resource_mut(&mut self) -> Option> { - // SAFETY: - // - `as_unsafe_world_cell` gives permission to access everything mutably - // - `&mut self` ensures nothing in world is borrowed - unsafe { self.world.get_resource_mut() } + self.world.get_resource_mut() } /// Gets a mutable reference to the non-send resource of the given type, if it exists. @@ -122,10 +125,7 @@ impl<'w> DeferredWorld<'w> { /// This function will panic if it isn't called from the same thread that the resource was inserted from. #[inline] pub fn get_non_send_resource_mut(&mut self) -> Option> { - // SAFETY: - // - `as_unsafe_world_cell` gives permission to access the entire world mutably - // - `&mut self` ensures that there are no borrows of world data - unsafe { self.world.get_non_send_resource_mut() } + self.world.get_non_send_resource_mut() } /// Sends an [`Event`]. @@ -170,10 +170,7 @@ impl<'w> DeferredWorld<'w> { /// use this in cases where the actual types are not known at compile time.** #[inline] pub fn get_resource_mut_by_id(&mut self, component_id: ComponentId) -> Option> { - // SAFETY: - // - `&mut self` ensures that all accessed data is unaliased - // - `as_unsafe_world_cell` provides mutable permission to the whole world - unsafe { self.world.get_resource_mut_by_id(component_id) } + self.world.get_resource_mut_by_id(component_id) } /// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists. @@ -187,10 +184,7 @@ impl<'w> DeferredWorld<'w> { /// This function will panic if it isn't called from the same thread that the resource was inserted from. #[inline] pub fn get_non_send_mut_by_id(&mut self, component_id: ComponentId) -> Option> { - // SAFETY: - // - `&mut self` ensures that all accessed data is unaliased - // - `as_unsafe_world_cell` provides mutable permission to the whole world - unsafe { self.world.get_non_send_resource_mut_by_id(component_id) } + self.world.get_non_send_mut_by_id(component_id) } /// Retrieves a mutable untyped reference to the given `entity`'s [`Component`] of the given [`ComponentId`]. @@ -207,35 +201,26 @@ impl<'w> DeferredWorld<'w> { // SAFETY: // - `&mut self` ensures that all accessed data is unaliased // - `as_unsafe_world_cell` provides mutable permission to the whole world - unsafe { self.world.get_entity(entity)?.get_mut_by_id(component_id) } + unsafe { + self.world + .as_unsafe_world_cell() + .get_entity(entity)? + .get_mut_by_id(component_id) + } } } -impl World { - pub unsafe fn into_deferred(&self) -> DeferredWorld<'_> { +impl<'w> UnsafeWorldCell<'w> { + pub unsafe fn into_deferred(&self) -> DeferredWorld<'w> { DeferredWorld { - world: self.as_unsafe_world_cell_readonly(), - command_queue: CommandQueue::default(), + // SAFETY: Not + world: self.world_mut(), } } } impl<'w> Into> for &'w mut World { fn into(self) -> DeferredWorld<'w> { - DeferredWorld { - world: self.as_unsafe_world_cell(), - command_queue: CommandQueue::default(), - } - } -} - -impl<'w> Drop for DeferredWorld<'w> { - fn drop(&mut self) { - unsafe { - self.world - .world_mut() - .command_queue - .append(&mut self.command_queue) - } + DeferredWorld { world: self } } } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index c24a47505acb3..2741919e8ffbd 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -693,14 +693,17 @@ impl<'w> EntityWorldMut<'w> { // Reborrow so world is not mutably borrowed let entity = self.entity; - for component_id in bundle_info.components().iter().cloned() { + let new_archetype = &self.world.archetypes[new_archetype_id]; + unsafe { + new_archetype.trigger_on_remove( + // SAFETY: the only outstanding borrow of the world is to an Archetype which cannot be modified through a deferred world + self.world.as_unsafe_world_cell_readonly(), + entity, + bundle_info.iter_components(), + ); + } + for component_id in bundle_info.iter_components() { self.world.removed_components.send(component_id, entity); - unsafe { - let info = self.world.components.get_info_unchecked(component_id); - if let Some(hook) = info.hooks().on_remove { - hook(self.world.into_deferred(), self.entity) - } - } } let archetypes = &mut self.world.archetypes; let storages = &mut self.world.storages; @@ -708,7 +711,7 @@ impl<'w> EntityWorldMut<'w> { let entities = &mut self.world.entities; let entity = self.entity; - let mut bundle_components = bundle_info.components().iter().cloned(); + let mut bundle_components = bundle_info.iter_components(); // SAFETY: bundle components are iterated in order, which guarantees that the component type // matches let result = unsafe { @@ -735,6 +738,7 @@ impl<'w> EntityWorldMut<'w> { new_archetype_id, ); } + drop(bundle_components); self.world.flush_commands(); Some(result) } @@ -850,18 +854,21 @@ impl<'w> EntityWorldMut<'w> { return self; } - let old_archetype = &self.world.archetypes[old_location.archetype_id]; // Reborrow so world is not mutably borrowed - let entity = self.entity; - for component_id in bundle_info.components().iter().cloned() { + let entity: Entity = self.entity; + let old_archetype = &self.world.archetypes[old_location.archetype_id]; + unsafe { + old_archetype.trigger_on_remove( + self.world.as_unsafe_world_cell_readonly(), + entity, + bundle_info + .iter_components() + .filter(|id| old_archetype.contains(*id)), + ); + } + for component_id in bundle_info.iter_components() { if old_archetype.contains(component_id) { self.world.removed_components.send(component_id, entity); - unsafe { - let info = self.world.components.get_info_unchecked(component_id); - if let Some(hook) = info.hooks().on_remove { - hook(self.world.into_deferred(), self.entity) - } - } // Make sure to drop components stored in sparse sets. // Dense components are dropped later in `move_to_and_drop_missing_unchecked`. @@ -896,19 +903,22 @@ impl<'w> EntityWorldMut<'w> { /// Despawns the current entity. pub fn despawn(self) { debug!("Despawning entity {:?}", self.entity); - let world = self.world; - world.flush(); - let archetype = &world.archetypes[self.location.archetype_id]; + self.world.flush(); + let archetype = &self.world.archetypes[self.location.archetype_id]; + unsafe { + archetype.trigger_on_remove( + self.world.as_unsafe_world_cell_readonly(), + self.entity, + archetype.components(), + ); + } for component_id in archetype.components() { - world.removed_components.send(component_id, self.entity); - unsafe { - let info = world.components().get_info_unchecked(component_id); - if let Some(hook) = info.hooks().on_remove { - hook(world.into_deferred(), self.entity) - } - } + self.world + .removed_components + .send(component_id, self.entity); } + let world = self.world; let location = world .entities .free(self.entity) @@ -1549,6 +1559,7 @@ unsafe fn remove_bundle_from_archetype( } let new_archetype_id = archetypes.get_id_or_insert( + components, next_table_id, next_table_components, next_sparse_set_components, @@ -1907,7 +1918,7 @@ mod tests { #[test] fn entity_mut_insert_by_id() { let mut world = World::new(); - let test_component_id = world.init_component::().id(); + let test_component_id = world.init_component::(); let mut entity = world.spawn_empty(); OwningPtr::make(TestComponent(42), |ptr| { @@ -1935,8 +1946,8 @@ mod tests { #[test] fn entity_mut_insert_bundle_by_id() { let mut world = World::new(); - let test_component_id = world.init_component::().id(); - let test_component_2_id = world.init_component::().id(); + let test_component_id = world.init_component::(); + let test_component_2_id = world.init_component::(); let component_ids = [test_component_id, test_component_2_id]; let test_component_value = TestComponent(42); diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 891ad56a00415..977f25bc6e477 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -183,11 +183,28 @@ impl World { WorldCell::new(self) } + pub fn register_component(&mut self) -> &mut ComponentInfo { + let type_id = TypeId::of::(); + assert!(self.components.get_id(type_id).is_none(), "Components cannot be registered twice, use init_component instead if this component may already exist in the world."); + let index = self.init_component::(); + // SAFETY: We just created this component + unsafe { self.components.get_info_mut(index) } + } + /// Initializes a new [`Component`] type and returns the [`ComponentId`] created for it. - pub fn init_component(&mut self) -> &mut ComponentInfo { + pub fn init_component(&mut self) -> ComponentId { self.components.init_component::(&mut self.storages) } + pub fn register_component_with_descriptor( + &mut self, + descriptor: ComponentDescriptor, + ) -> &mut ComponentInfo { + let index = self.init_component_with_descriptor(descriptor); + // SAFETY: We just created this component + unsafe { self.components.get_info_mut(index) } + } + /// Initializes a new [`Component`] type and returns the [`ComponentId`] created for it. /// /// This method differs from [`World::init_component`] in that it uses a [`ComponentDescriptor`] @@ -200,7 +217,7 @@ impl World { pub fn init_component_with_descriptor( &mut self, descriptor: ComponentDescriptor, - ) -> &mut ComponentInfo { + ) -> ComponentId { self.components .init_component_with_descriptor(&mut self.storages, descriptor) } @@ -2347,7 +2364,7 @@ mod tests { ) }; - let component_id = world.init_component_with_descriptor(descriptor).id(); + let component_id = world.init_component_with_descriptor(descriptor); let value: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7]; OwningPtr::make(value, |ptr| { diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index f5dd64c72530b..210da32926b5e 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -14,7 +14,7 @@ use crate::{ prelude::Component, removal_detection::RemovedComponentEvents, storage::{Column, ComponentSparseSet, Storages}, - system::Resource, + system::{CommandQueue, Resource}, }; use bevy_ptr::Ptr; use std::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData}; @@ -566,6 +566,12 @@ impl<'w> UnsafeWorldCell<'w> { .get(component_id)? .get_with_ticks() } + + #[inline] + pub(crate) unsafe fn get_command_queue(self) -> &'w mut CommandQueue { + let world = unsafe { &mut *self.0 }; + &mut world.command_queue + } } impl Debug for UnsafeWorldCell<'_> { diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs index 510474e455db1..9309de94e9fc9 100644 --- a/examples/ecs/component_hooks.rs +++ b/examples/ecs/component_hooks.rs @@ -17,12 +17,12 @@ fn main() { fn setup(world: &mut World) { world - .init_component::() - .on_add(|mut world, entity| { + .register_component::() + .on_add(|mut world, entity, _| { println!("Added MyComponent to: {:?}", entity); world.resource_mut::().insert(entity); }) - .on_remove(|mut world, entity| { + .on_remove(|mut world, entity, _| { println!( "Removed MyComponent from: {:?} {:?}", entity, From 4eb9a801a951243337602062b3bf5471e7582ab4 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Fri, 24 Nov 2023 19:23:02 -0800 Subject: [PATCH 003/109] Refactoring --- crates/bevy_ecs/src/archetype.rs | 49 ++--------- crates/bevy_ecs/src/bundle.rs | 92 +++++++++++---------- crates/bevy_ecs/src/world/deferred_world.rs | 51 +++++++++++- crates/bevy_ecs/src/world/entity_ref.rs | 33 +++----- 4 files changed, 113 insertions(+), 112 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 13cba4df2d1a8..3508668e1c094 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -24,7 +24,6 @@ use crate::{ component::{ComponentId, Components, StorageType}, entity::{Entity, EntityLocation}, storage::{ImmutableSparseSet, SparseArray, SparseSet, SparseSetIndex, TableId, TableRow}, - world::unsafe_world_cell::UnsafeWorldCell, }; use std::{ hash::Hash, @@ -563,54 +562,18 @@ impl Archetype { } #[inline] - pub(crate) unsafe fn trigger_on_add( - &self, - world: UnsafeWorldCell, - entity: Entity, - targets: impl Iterator, - ) { - if self.flags().contains(ArchetypeFlags::ON_ADD_HOOK) { - for component_id in targets { - let hooks = unsafe { world.components().get_info_unchecked(component_id) }.hooks(); - if let Some(hook) = hooks.on_add { - hook(world.into_deferred(), entity, component_id) - } - } - } + pub fn has_on_add(&self) -> bool { + self.flags().contains(ArchetypeFlags::ON_ADD_HOOK) } #[inline] - pub(crate) unsafe fn trigger_on_insert( - &self, - world: UnsafeWorldCell, - entity: Entity, - targets: impl Iterator, - ) { - if self.flags().contains(ArchetypeFlags::ON_INSERT_HOOK) { - for component_id in targets { - let hooks = unsafe { world.components().get_info_unchecked(component_id) }.hooks(); - if let Some(hook) = hooks.on_insert { - hook(world.into_deferred(), entity, component_id) - } - } - } + pub fn has_on_insert(&self) -> bool { + self.flags().contains(ArchetypeFlags::ON_INSERT_HOOK) } #[inline] - pub(crate) unsafe fn trigger_on_remove( - &self, - world: UnsafeWorldCell, - entity: Entity, - targets: impl Iterator, - ) { - if self.flags().contains(ArchetypeFlags::ON_REMOVE_HOOK) { - for component_id in targets { - let hooks = unsafe { world.components().get_info_unchecked(component_id) }.hooks(); - if let Some(hook) = hooks.on_remove { - hook(world.into_deferred(), entity, component_id); - } - } - } + pub fn has_on_remove(&self) -> bool { + self.flags().contains(ArchetypeFlags::ON_REMOVE_HOOK) } } diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 1ea87f76ed6f9..7ab66d4690859 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -627,11 +627,19 @@ impl<'w> BundleInserter<'w> { match self.result { InsertBundleResult::SameArchetype => { let archetype = &mut *self.archetype; - archetype.trigger_on_insert( - world.as_unsafe_world_cell_readonly(), - entity, - bundle_info.components().iter().cloned(), - ); + if archetype.has_on_add() { + world.into_deferred().trigger_on_add( + entity, + bundle_info.iter_components().filter(|id| { + add_bundle.get_status(id.index()) == ComponentStatus::Added + }), + ); + } + if archetype.has_on_insert() { + world + .into_deferred() + .trigger_on_insert(entity, bundle_info.iter_components()) + } bundle_info.write_components( &mut *self.table, &mut world.storages.sparse_sets, @@ -647,20 +655,19 @@ impl<'w> BundleInserter<'w> { let table = &mut *self.table; let archetype = &mut *self.archetype; let new_archetype = &mut *new_archetype; - new_archetype.trigger_on_add( - world.as_unsafe_world_cell_readonly(), - entity, - bundle_info - .components() - .iter() - .cloned() - .filter(|id| add_bundle.get_status(id.index()) == ComponentStatus::Added), - ); - new_archetype.trigger_on_insert( - world.as_unsafe_world_cell_readonly(), - entity, - bundle_info.components().iter().cloned(), - ); + if new_archetype.has_on_add() { + world.into_deferred().trigger_on_add( + entity, + bundle_info.iter_components().filter(|id| { + add_bundle.get_status(id.index()) == ComponentStatus::Added + }), + ); + } + if new_archetype.has_on_insert() { + world + .into_deferred() + .trigger_on_insert(entity, bundle_info.iter_components()) + } let result = archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { let swapped_location = @@ -697,20 +704,19 @@ impl<'w> BundleInserter<'w> { let new_table = &mut *new_table; let archetype = &mut *self.archetype; let new_archetype = &mut *new_archetype; - new_archetype.trigger_on_add( - world.as_unsafe_world_cell_readonly(), - entity, - bundle_info - .components() - .iter() - .cloned() - .filter(|id| add_bundle.get_status(id.index()) == ComponentStatus::Added), - ); - new_archetype.trigger_on_insert( - world.as_unsafe_world_cell_readonly(), - entity, - bundle_info.components().iter().cloned(), - ); + if new_archetype.has_on_add() { + world.into_deferred().trigger_on_add( + entity, + bundle_info.iter_components().filter(|id| { + add_bundle.get_status(id.index()) == ComponentStatus::Added + }), + ); + } + if new_archetype.has_on_insert() { + world + .into_deferred() + .trigger_on_insert(entity, bundle_info.iter_components()) + } let result = archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { let swapped_location = @@ -836,16 +842,16 @@ impl<'w> BundleSpawner<'w> { ) -> EntityLocation { let bundle_info = &*self.bundle_info; let archetype = &*self.archetype; - archetype.trigger_on_add( - self.world.as_unsafe_world_cell_readonly(), - entity, - bundle_info.iter_components(), - ); - archetype.trigger_on_insert( - self.world.as_unsafe_world_cell_readonly(), - entity, - bundle_info.iter_components(), - ); + if archetype.has_on_add() { + self.world + .into_deferred() + .trigger_on_add(entity, bundle_info.iter_components()); + } + if archetype.has_on_insert() { + self.world + .into_deferred() + .trigger_on_insert(entity, bundle_info.iter_components()) + } let archetype = &mut *self.archetype; let table = &mut *self.table; diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index bf492993ddfd5..932b9a66a3a82 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -10,7 +10,7 @@ use crate::{ system::{Commands, Query, Resource}, }; -use super::{unsafe_world_cell::UnsafeWorldCell, Mut, World}; +use super::{Mut, World}; pub struct DeferredWorld<'w> { world: &'w mut World, @@ -208,13 +208,56 @@ impl<'w> DeferredWorld<'w> { .get_mut_by_id(component_id) } } + + #[inline] + pub(crate) fn trigger_on_add( + &mut self, + entity: Entity, + targets: impl Iterator, + ) { + for component_id in targets { + let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks(); + if let Some(hook) = hooks.on_add { + hook(DeferredWorld { world: self.world }, entity, component_id) + } + } + } + + #[inline] + pub(crate) fn trigger_on_insert( + &mut self, + entity: Entity, + targets: impl Iterator, + ) { + for component_id in targets { + let hooks = unsafe { self.world.components().get_info_unchecked(component_id) }.hooks(); + if let Some(hook) = hooks.on_insert { + hook(DeferredWorld { world: self.world }, entity, component_id) + } + } + } + + #[inline] + pub(crate) fn trigger_on_remove( + &mut self, + entity: Entity, + targets: impl Iterator, + ) { + for component_id in targets { + let hooks = unsafe { self.world.components().get_info_unchecked(component_id) }.hooks(); + if let Some(hook) = hooks.on_remove { + hook(DeferredWorld { world: self.world }, entity, component_id) + } + } + } } -impl<'w> UnsafeWorldCell<'w> { - pub unsafe fn into_deferred(&self) -> DeferredWorld<'w> { +impl World { + #[inline] + pub unsafe fn into_deferred(&self) -> DeferredWorld { DeferredWorld { // SAFETY: Not - world: self.world_mut(), + world: self.as_unsafe_world_cell_readonly().world_mut(), } } } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 2741919e8ffbd..50ddf83797a93 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -693,14 +693,10 @@ impl<'w> EntityWorldMut<'w> { // Reborrow so world is not mutably borrowed let entity = self.entity; - let new_archetype = &self.world.archetypes[new_archetype_id]; - unsafe { - new_archetype.trigger_on_remove( - // SAFETY: the only outstanding borrow of the world is to an Archetype which cannot be modified through a deferred world - self.world.as_unsafe_world_cell_readonly(), - entity, - bundle_info.iter_components(), - ); + let old_archetype = &self.world.archetypes[old_location.archetype_id]; + if old_archetype.has_on_remove() { + unsafe { self.world.into_deferred() } + .trigger_on_remove(entity, bundle_info.iter_components()); } for component_id in bundle_info.iter_components() { self.world.removed_components.send(component_id, entity); @@ -857,14 +853,9 @@ impl<'w> EntityWorldMut<'w> { // Reborrow so world is not mutably borrowed let entity: Entity = self.entity; let old_archetype = &self.world.archetypes[old_location.archetype_id]; - unsafe { - old_archetype.trigger_on_remove( - self.world.as_unsafe_world_cell_readonly(), - entity, - bundle_info - .iter_components() - .filter(|id| old_archetype.contains(*id)), - ); + if old_archetype.has_on_remove() { + unsafe { self.world.into_deferred() } + .trigger_on_remove(entity, bundle_info.iter_components()) } for component_id in bundle_info.iter_components() { if old_archetype.contains(component_id) { @@ -905,13 +896,11 @@ impl<'w> EntityWorldMut<'w> { debug!("Despawning entity {:?}", self.entity); self.world.flush(); let archetype = &self.world.archetypes[self.location.archetype_id]; - unsafe { - archetype.trigger_on_remove( - self.world.as_unsafe_world_cell_readonly(), - self.entity, - archetype.components(), - ); + if archetype.has_on_remove() { + unsafe { self.world.into_deferred() } + .trigger_on_remove(self.entity, archetype.components()); } + for component_id in archetype.components() { self.world .removed_components From e174cd865ea5ee88298f85bcd6eb0e82e5e619c8 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sat, 25 Nov 2023 00:43:31 -0800 Subject: [PATCH 004/109] Improve command application logic --- .../src/system/commands/command_queue.rs | 3 ++ crates/bevy_ecs/src/world/deferred_world.rs | 6 +++ crates/bevy_ecs/src/world/mod.rs | 53 +++++++++++-------- 3 files changed, 41 insertions(+), 21 deletions(-) diff --git a/crates/bevy_ecs/src/system/commands/command_queue.rs b/crates/bevy_ecs/src/system/commands/command_queue.rs index dcd6f58ed5066..8e67ac9a8092a 100644 --- a/crates/bevy_ecs/src/system/commands/command_queue.rs +++ b/crates/bevy_ecs/src/system/commands/command_queue.rs @@ -96,6 +96,7 @@ impl CommandQueue { pub fn apply(&mut self, world: &mut World) { // flush the previously queued entities world.flush(); + world.set_flushing(true); // The range of pointers of the filled portion of `self.bytes`. let bytes_range = self.bytes.as_mut_ptr_range(); @@ -136,6 +137,7 @@ impl CommandQueue { cursor = unsafe { cursor.add(size) }; } + world.set_flushing(false); world.flush_commands(); } @@ -144,6 +146,7 @@ impl CommandQueue { self.bytes.append(&mut other.bytes); } + /// Returns false if there are any commands in the queue pub fn is_empty(&self) -> bool { self.bytes.is_empty() } diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 932b9a66a3a82..ea6293ac25e20 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -12,6 +12,8 @@ use crate::{ use super::{Mut, World}; +/// An [`World`] reference that prevents structural ECS changes. +/// This includes creating tables, registering components or spawning entities. pub struct DeferredWorld<'w> { world: &'w mut World, } @@ -25,6 +27,7 @@ impl<'w> Deref for DeferredWorld<'w> { } impl<'w> DeferredWorld<'w> { + /// Creates a [`Commands`] instance that pushes to the world's command queue pub fn commands(&mut self) -> Commands { let world = self.world.as_unsafe_world_cell(); unsafe { Commands::new(world.get_command_queue(), world.world()) } @@ -253,6 +256,9 @@ impl<'w> DeferredWorld<'w> { } impl World { + /// Turn a [`World`] reference into a [`DeferredWorld`] + /// + /// Caller must ensure there are no outstanding references to the world's command queue, resource or component data #[inline] pub unsafe fn into_deferred(&self) -> DeferredWorld { DeferredWorld { diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 977f25bc6e477..9d3235e5eb8ee 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -183,24 +183,19 @@ impl World { WorldCell::new(self) } - pub fn register_component(&mut self) -> &mut ComponentInfo { - let type_id = TypeId::of::(); - assert!(self.components.get_id(type_id).is_none(), "Components cannot be registered twice, use init_component instead if this component may already exist in the world."); - let index = self.init_component::(); - // SAFETY: We just created this component - unsafe { self.components.get_info_mut(index) } - } - /// Initializes a new [`Component`] type and returns the [`ComponentId`] created for it. pub fn init_component(&mut self) -> ComponentId { self.components.init_component::(&mut self.storages) } - pub fn register_component_with_descriptor( - &mut self, - descriptor: ComponentDescriptor, - ) -> &mut ComponentInfo { - let index = self.init_component_with_descriptor(descriptor); + /// Initializes a new [`Component`] type and returns a mutable reference to the [`ComponentInfo`] created for it. + /// Primarily used for registering hooks. + /// + /// Will panic if a component for `T`` already exists. + pub fn register_component(&mut self) -> &mut ComponentInfo { + let type_id = TypeId::of::(); + assert!(self.components.get_id(type_id).is_none(), "Components cannot be registered twice, use init_component instead if this component may already exist in the world."); + let index = self.init_component::(); // SAFETY: We just created this component unsafe { self.components.get_info_mut(index) } } @@ -222,6 +217,19 @@ impl World { .init_component_with_descriptor(&mut self.storages, descriptor) } + /// Initializes a new [`Component`] type and returns a mutable reference to the [`ComponentInfo`] created for it. + /// Primarily used for registering hooks. + /// + /// Will panic if a component for `T`` already exists. + pub fn register_component_with_descriptor( + &mut self, + descriptor: ComponentDescriptor, + ) -> &mut ComponentInfo { + let index = self.init_component_with_descriptor(descriptor); + // SAFETY: We just created this component + unsafe { self.components.get_info_mut(index) } + } + /// Returns the [`ComponentId`] of the given [`Component`] type `T`. /// /// The returned `ComponentId` is specific to the `World` instance @@ -1740,18 +1748,21 @@ impl World { } } + /// Attempts to apply the internal [`CommandQueue`] to self + /// Will do nothing if already flushing commands. + #[inline] pub fn flush_commands(&mut self) { - if !self.flushing_commands { - self.flushing_commands = true; - while !self.command_queue.is_empty() { - let mut commands = CommandQueue::default(); - std::mem::swap(&mut commands, &mut self.command_queue); - commands.apply(self) - } - self.flushing_commands = false; + if !self.flushing_commands && !self.command_queue.is_empty() { + let mut commands = std::mem::take(&mut self.command_queue); + commands.apply(self); } } + #[inline] + pub(crate) fn set_flushing(&mut self, state: bool) { + self.flushing_commands = state; + } + /// Increments the world's current change tick and returns the old value. #[inline] pub fn increment_change_tick(&self) -> Tick { From abc728b50156fa83e32a73893d0d3b12b3c8c154 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sat, 25 Nov 2023 15:44:22 -0800 Subject: [PATCH 005/109] Simplify command application to get recursive ordering --- crates/bevy_ecs/src/lib.rs | 4 ++-- .../src/system/commands/command_queue.rs | 9 ++++----- crates/bevy_ecs/src/world/entity_ref.rs | 20 +++++++++---------- crates/bevy_ecs/src/world/mod.rs | 19 ++++++------------ crates/bevy_ecs/src/world/spawn_batch.rs | 2 +- 5 files changed, 23 insertions(+), 31 deletions(-) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index ad8ba9b39a51f..e298b7cefa317 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -1063,7 +1063,7 @@ mod tests { fn reserve_and_spawn() { let mut world = World::default(); let e = world.entities().reserve_entity(); - world.flush(); + world.flush_entities(); let mut e_mut = world.entity_mut(e); e_mut.insert(A(0)); assert_eq!(e_mut.get::().unwrap(), &A(0)); @@ -1546,7 +1546,7 @@ mod tests { let e1 = world_a.spawn(A(1)).id(); let e2 = world_a.spawn(A(2)).id(); let e3 = world_a.entities().reserve_entity(); - world_a.flush(); + world_a.flush_entities(); let world_a_max_entities = world_a.entities().len(); world_b.entities.reserve_entities(world_a_max_entities); diff --git a/crates/bevy_ecs/src/system/commands/command_queue.rs b/crates/bevy_ecs/src/system/commands/command_queue.rs index 8e67ac9a8092a..a0eb445bb60c1 100644 --- a/crates/bevy_ecs/src/system/commands/command_queue.rs +++ b/crates/bevy_ecs/src/system/commands/command_queue.rs @@ -95,8 +95,7 @@ impl CommandQueue { #[inline] pub fn apply(&mut self, world: &mut World) { // flush the previously queued entities - world.flush(); - world.set_flushing(true); + world.flush_entities(); // The range of pointers of the filled portion of `self.bytes`. let bytes_range = self.bytes.as_mut_ptr_range(); @@ -135,10 +134,9 @@ impl CommandQueue { // SAFETY: The address just past the command is either within the buffer, // or 1 byte past the end, so this addition will not overflow the pointer's allocation. cursor = unsafe { cursor.add(size) }; - } - world.set_flushing(false); - world.flush_commands(); + world.flush_commands(); + } } /// Take all commands from `other` and append them to `self`, leaving `other` empty @@ -147,6 +145,7 @@ impl CommandQueue { } /// Returns false if there are any commands in the queue + #[inline] pub fn is_empty(&self) -> bool { self.bytes.is_empty() } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 50ddf83797a93..e3349fb23282e 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -698,9 +698,9 @@ impl<'w> EntityWorldMut<'w> { unsafe { self.world.into_deferred() } .trigger_on_remove(entity, bundle_info.iter_components()); } - for component_id in bundle_info.iter_components() { - self.world.removed_components.send(component_id, entity); - } + // for component_id in bundle_info.iter_components() { + // self.world.removed_components.send(component_id, entity); + // } let archetypes = &mut self.world.archetypes; let storages = &mut self.world.storages; let components = &mut self.world.components; @@ -859,7 +859,7 @@ impl<'w> EntityWorldMut<'w> { } for component_id in bundle_info.iter_components() { if old_archetype.contains(component_id) { - self.world.removed_components.send(component_id, entity); + // self.world.removed_components.send(component_id, entity); // Make sure to drop components stored in sparse sets. // Dense components are dropped later in `move_to_and_drop_missing_unchecked`. @@ -894,18 +894,18 @@ impl<'w> EntityWorldMut<'w> { /// Despawns the current entity. pub fn despawn(self) { debug!("Despawning entity {:?}", self.entity); - self.world.flush(); + self.world.flush_entities(); let archetype = &self.world.archetypes[self.location.archetype_id]; if archetype.has_on_remove() { unsafe { self.world.into_deferred() } .trigger_on_remove(self.entity, archetype.components()); } - for component_id in archetype.components() { - self.world - .removed_components - .send(component_id, self.entity); - } + // for component_id in archetype.components() { + // self.world + // .removed_components + // .send(component_id, self.entity); + // } let world = self.world; let location = world diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 9d3235e5eb8ee..daa98d9fce9f6 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -74,7 +74,6 @@ pub struct World { pub(crate) last_change_tick: Tick, pub(crate) last_check_tick: Tick, pub(crate) command_queue: CommandQueue, - flushing_commands: bool, } impl Default for World { @@ -94,7 +93,6 @@ impl Default for World { last_change_tick: Tick::new(0), last_check_tick: Tick::new(0), command_queue: CommandQueue::default(), - flushing_commands: false, } } } @@ -447,7 +445,7 @@ impl World { /// scheme worked out to share an ID space (which doesn't happen by default). #[inline] pub fn get_or_spawn(&mut self, entity: Entity) -> Option { - self.flush(); + self.flush_entities(); match self.entities.alloc_at_without_replacement(entity) { AllocAtWithoutReplacement::Exists(location) => { // SAFETY: `entity` exists and `location` is that entity's location @@ -701,7 +699,7 @@ impl World { /// assert_eq!(position.x, 0.0); /// ``` pub fn spawn_empty(&mut self) -> EntityWorldMut { - self.flush(); + self.flush_entities(); let entity = self.entities.alloc(); // SAFETY: entity was just allocated unsafe { self.spawn_at_empty_internal(entity) } @@ -767,7 +765,7 @@ impl World { /// assert_eq!(position.x, 2.0); /// ``` pub fn spawn(&mut self, bundle: B) -> EntityWorldMut { - self.flush(); + self.flush_entities(); let change_tick = self.change_tick(); let entity = self.entities.alloc(); let entity_location = { @@ -1452,7 +1450,7 @@ impl World { I::IntoIter: Iterator, B: Bundle, { - self.flush(); + self.flush_entities(); let change_tick = self.change_tick(); @@ -1734,7 +1732,7 @@ impl World { /// Empties queued entities and adds them to the empty [`Archetype`](crate::archetype::Archetype). /// This should be called before doing operations that might operate on queued entities, /// such as inserting a [`Component`]. - pub(crate) fn flush(&mut self) { + pub(crate) fn flush_entities(&mut self) { let empty_archetype = self.archetypes.empty_mut(); let table = &mut self.storages.tables[empty_archetype.table_id()]; // PERF: consider pre-allocating space for flushed entities @@ -1752,17 +1750,12 @@ impl World { /// Will do nothing if already flushing commands. #[inline] pub fn flush_commands(&mut self) { - if !self.flushing_commands && !self.command_queue.is_empty() { + if !self.command_queue.is_empty() { let mut commands = std::mem::take(&mut self.command_queue); commands.apply(self); } } - #[inline] - pub(crate) fn set_flushing(&mut self, state: bool) { - self.flushing_commands = state; - } - /// Increments the world's current change tick and returns the old value. #[inline] pub fn increment_change_tick(&self) -> Tick { diff --git a/crates/bevy_ecs/src/world/spawn_batch.rs b/crates/bevy_ecs/src/world/spawn_batch.rs index ef90947e54d12..b0586e4c7c8f8 100644 --- a/crates/bevy_ecs/src/world/spawn_batch.rs +++ b/crates/bevy_ecs/src/world/spawn_batch.rs @@ -27,7 +27,7 @@ where pub(crate) fn new(world: &'w mut World, iter: I) -> Self { // Ensure all entity allocations are accounted for so `self.entities` can realloc if // necessary - world.flush(); + world.flush_entities(); let change_tick = world.change_tick(); From 06d4db65c13b30fec221e7bed5ee5c2d7a953991 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sat, 25 Nov 2023 20:27:48 -0800 Subject: [PATCH 006/109] First docs and safety comment pass --- crates/bevy_ecs/src/archetype.rs | 11 +- crates/bevy_ecs/src/bundle.rs | 359 ++++++++++-------- crates/bevy_ecs/src/component.rs | 47 ++- crates/bevy_ecs/src/world/deferred_world.rs | 112 +++--- crates/bevy_ecs/src/world/entity_ref.rs | 99 +++-- crates/bevy_ecs/src/world/mod.rs | 40 +- crates/bevy_ecs/src/world/spawn_batch.rs | 4 +- .../bevy_ecs/src/world/unsafe_world_cell.rs | 4 + examples/ecs/component_hooks.rs | 2 +- 9 files changed, 398 insertions(+), 280 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 3508668e1c094..309b2fc185593 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -299,8 +299,11 @@ struct ArchetypeComponentInfo { } bitflags::bitflags! { + /// Flags used to keep track of metadata about the component in this [`Archetype`] + /// + /// Used primarily to early-out when there are no [`ComponentHook`] registered for any contained components. #[derive(Clone, Copy)] - pub struct ArchetypeFlags: u32 { + pub(crate) struct ArchetypeFlags: u32 { const ON_ADD_HOOK = (1 << 0); const ON_INSERT_HOOK = (1 << 1); const ON_REMOVE_HOOK = (1 << 2); @@ -375,8 +378,9 @@ impl Archetype { self.id } + /// Fetches the flags for the archetype. #[inline] - pub fn flags(&self) -> ArchetypeFlags { + pub(crate) fn flags(&self) -> ArchetypeFlags { self.flags } @@ -561,16 +565,19 @@ impl Archetype { self.entities.clear(); } + /// Returns true if any of the components in this archetype have `on_add` hooks #[inline] pub fn has_on_add(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_ADD_HOOK) } + /// Returns true if any of the components in this archetype have `on_insert` hooks #[inline] pub fn has_on_insert(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_INSERT_HOOK) } + /// Returns true if any of the components in this archetype have `on_remove` hooks #[inline] pub fn has_on_remove(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_REMOVE_HOOK) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 7ab66d4690859..fe57cc96af184 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -15,6 +15,7 @@ use crate::{ prelude::World, query::DebugCheckedUnwrap, storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow}, + world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld}, TypeIdMap, }; use bevy_ptr::OwningPtr; @@ -346,6 +347,7 @@ impl BundleInfo { &self.component_ids } + /// Returns an iterator over the the [ID](ComponentId) of each component stored in this bundle. #[inline] pub fn iter_components(&self) -> impl Iterator + '_ { self.component_ids.iter().cloned() @@ -501,23 +503,26 @@ impl BundleInfo { } pub(crate) struct BundleInserter<'w> { - world: &'w mut World, - archetype: *mut Archetype, - table: *mut Table, - bundle_info: *const BundleInfo, - add_bundle: *const AddBundle, - result: InsertBundleResult, + world: DeferredWorld<'w>, + archetype: &'w mut Archetype, + entities: &'w mut Entities, + bundle_info: &'w BundleInfo, + table: &'w mut Table, + sparse_sets: &'w mut SparseSets, + result: InsertBundleResult<'w>, + add_bundle_ptr: *const AddBundle, + archetypes_ptr: *mut Archetype, change_tick: Tick, } -pub(crate) enum InsertBundleResult { +pub(crate) enum InsertBundleResult<'w> { SameArchetype, NewArchetypeSameTable { - new_archetype: *mut Archetype, + new_archetype: &'w mut Archetype, }, NewArchetypeNewTable { - new_archetype: *mut Archetype, - new_table: *mut Table, + new_archetype: &'w mut Archetype, + new_table: &'w mut Table, }, } @@ -528,20 +533,31 @@ impl<'w> BundleInserter<'w> { archetype_id: ArchetypeId, change_tick: Tick, ) -> Self { - let bundle_info: *const BundleInfo = world + let bundle_id = world .bundles .init_info::(&mut world.components, &mut world.storages); - unsafe { Self::new_with_info(world, archetype_id, bundle_info, change_tick) } + // SAFETY: We just ensured this bundle exists, re-borrow to drop &mut World + let bundle_info = unsafe { world.bundles.get_unchecked(bundle_id) }; + Self::new_with_info( + world.as_unsafe_world_cell_readonly(), + archetype_id, + bundle_info, + change_tick, + ) } + /// Creates a new [`BundleInserter`]. + /// + /// Implementation guarantees it will not alias `bundle_info`. #[inline] - pub(crate) unsafe fn new_with_info( - world: &'w mut World, + pub(crate) fn new_with_info( + world: UnsafeWorldCell<'w>, archetype_id: ArchetypeId, - bundle_info: *const BundleInfo, + bundle_info: &'w BundleInfo, change_tick: Tick, ) -> Self { - let bundle_info = &*bundle_info; + // SAFETY: We will not make any accesses to the command queue, component or resource data of this world + let (world, deferred_world) = unsafe { world.split_deferred() }; let bundle_id = bundle_info.id(); let new_archetype_id = bundle_info.add_bundle_to_archetype( &mut world.archetypes, @@ -549,62 +565,71 @@ impl<'w> BundleInserter<'w> { &world.components, archetype_id, ); + let archetypes_ptr = world.archetypes.archetypes.as_mut_ptr(); if new_archetype_id == archetype_id { let archetype = &mut world.archetypes[archetype_id]; - let table_id = archetype.table_id(); - let add_bundle: *const AddBundle = unsafe { + // SAFETY: The edge is assured to be initialized when we called add_bundle_to_archetype + let add_bundle_ptr: *const AddBundle = unsafe { archetype .edges() .get_add_bundle_internal(bundle_id) .debug_checked_unwrap() }; - let table: *mut Table = &mut world.storages.tables[table_id]; - let archetype: *mut Archetype = archetype; + let table_id = archetype.table_id(); + let table = &mut world.storages.tables[table_id]; Self { - world, + world: deferred_world, archetype, - table, + entities: &mut world.entities, bundle_info, - add_bundle, + table, + sparse_sets: &mut world.storages.sparse_sets, result: InsertBundleResult::SameArchetype, + archetypes_ptr, + add_bundle_ptr, change_tick, } } else { let (archetype, new_archetype) = world.archetypes.get_2_mut(archetype_id, new_archetype_id); - let table_id = archetype.table_id(); - let new_table_id = new_archetype.table_id(); - let add_bundle: *const AddBundle = unsafe { + // SAFETY: The edge is assured to be initialized when we called add_bundle_to_archetype + let add_bundle_ptr: *const AddBundle = unsafe { archetype .edges() .get_add_bundle_internal(bundle_id) .debug_checked_unwrap() }; - let table: *mut Table = &mut world.storages.tables[table_id]; - let archetype: *mut Archetype = archetype; - let new_archetype: *mut Archetype = new_archetype; + let table_id = archetype.table_id(); + let new_table_id = new_archetype.table_id(); if table_id == new_table_id { + let table = &mut world.storages.tables[table_id]; Self { - world, + world: deferred_world, archetype, - table, + entities: &mut world.entities, bundle_info, - add_bundle, + table, + sparse_sets: &mut world.storages.sparse_sets, result: InsertBundleResult::NewArchetypeSameTable { new_archetype }, + archetypes_ptr, + add_bundle_ptr, change_tick, } } else { - let new_table: *mut Table = &mut world.storages.tables[new_table_id]; + let (table, new_table) = world.storages.tables.get_2_mut(table_id, new_table_id); Self { - world, + world: deferred_world, archetype, - table, + entities: &mut world.entities, bundle_info, - add_bundle, + table, + sparse_sets: &mut world.storages.sparse_sets, result: InsertBundleResult::NewArchetypeNewTable { new_archetype, new_table, }, + archetypes_ptr, + add_bundle_ptr, change_tick, } } @@ -620,29 +645,33 @@ impl<'w> BundleInserter<'w> { location: EntityLocation, bundle: T, ) -> EntityLocation { - let bundle_info = &*self.bundle_info; - let add_bundle = &*self.add_bundle; - let world = &mut *self.world; - - match self.result { + // SAFETY: We must ensure we do not use self.archetype to invalidate this reference + let add_bundle = &*self.add_bundle_ptr; + match &mut self.result { InsertBundleResult::SameArchetype => { - let archetype = &mut *self.archetype; - if archetype.has_on_add() { - world.into_deferred().trigger_on_add( + if self.archetype.has_on_add() { + self.world.trigger_on_add( entity, - bundle_info.iter_components().filter(|id| { + self.bundle_info.iter_components().filter(|id| { add_bundle.get_status(id.index()) == ComponentStatus::Added }), ); } - if archetype.has_on_insert() { - world - .into_deferred() - .trigger_on_insert(entity, bundle_info.iter_components()) + if self.archetype.has_on_insert() { + self.world + .trigger_on_insert(entity, self.bundle_info.iter_components()); } - bundle_info.write_components( - &mut *self.table, - &mut world.storages.sparse_sets, + // PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty) + // SAFETY: The edge is assured to be initialized when creating the BundleInserter + let add_bundle = unsafe { + self.archetype + .edges() + .get_add_bundle_internal(self.bundle_info.id) + .debug_checked_unwrap() + }; + self.bundle_info.write_components( + self.table, + self.sparse_sets, add_bundle, entity, location.table_row, @@ -652,28 +681,24 @@ impl<'w> BundleInserter<'w> { location } InsertBundleResult::NewArchetypeSameTable { new_archetype } => { - let table = &mut *self.table; - let archetype = &mut *self.archetype; - let new_archetype = &mut *new_archetype; if new_archetype.has_on_add() { - world.into_deferred().trigger_on_add( + self.world.trigger_on_add( entity, - bundle_info.iter_components().filter(|id| { + self.bundle_info.iter_components().filter(|id| { add_bundle.get_status(id.index()) == ComponentStatus::Added }), ); } if new_archetype.has_on_insert() { - world - .into_deferred() - .trigger_on_insert(entity, bundle_info.iter_components()) + self.world + .trigger_on_insert(entity, self.bundle_info.iter_components()); } - let result = archetype.swap_remove(location.archetype_row); + let result = self.archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { let swapped_location = // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { world.entities.get(swapped_entity).debug_checked_unwrap() }; - world.entities.set( + unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; + self.entities.set( swapped_entity.index(), EntityLocation { archetype_id: swapped_location.archetype_id, @@ -684,10 +709,10 @@ impl<'w> BundleInserter<'w> { ); } let new_location = new_archetype.allocate(entity, result.table_row); - world.entities.set(entity.index(), new_location); - bundle_info.write_components( - table, - &mut world.storages.sparse_sets, + self.entities.set(entity.index(), new_location); + self.bundle_info.write_components( + self.table, + self.sparse_sets, add_bundle, entity, result.table_row, @@ -700,29 +725,24 @@ impl<'w> BundleInserter<'w> { new_archetype, new_table, } => { - let table = &mut *self.table; - let new_table = &mut *new_table; - let archetype = &mut *self.archetype; - let new_archetype = &mut *new_archetype; if new_archetype.has_on_add() { - world.into_deferred().trigger_on_add( + self.world.trigger_on_add( entity, - bundle_info.iter_components().filter(|id| { + self.bundle_info.iter_components().filter(|id| { add_bundle.get_status(id.index()) == ComponentStatus::Added }), ); } if new_archetype.has_on_insert() { - world - .into_deferred() - .trigger_on_insert(entity, bundle_info.iter_components()) + self.world + .trigger_on_insert(entity, self.bundle_info.iter_components()); } - let result = archetype.swap_remove(location.archetype_row); + let result = self.archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { let swapped_location = // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { world.entities.get(swapped_entity).debug_checked_unwrap() }; - world.entities.set( + unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; + self.entities.set( swapped_entity.index(), EntityLocation { archetype_id: swapped_location.archetype_id, @@ -734,25 +754,31 @@ impl<'w> BundleInserter<'w> { } // PERF: store "non bundle" components in edge, then just move those to avoid // redundant copies - let move_result = table.move_to_superset_unchecked(result.table_row, new_table); + let move_result = self + .table + .move_to_superset_unchecked(result.table_row, new_table); let new_location = new_archetype.allocate(entity, move_result.new_row); - world.entities.set(entity.index(), new_location); + self.entities.set(entity.index(), new_location); // if an entity was moved into this entity's table spot, update its table row if let Some(swapped_entity) = move_result.swapped_entity { let swapped_location = // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { world.entities.get(swapped_entity).debug_checked_unwrap() }; - let swapped_archetype = if archetype.id() == swapped_location.archetype_id { + unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; + let swapped_archetype = if self.archetype.id() == swapped_location.archetype_id + { + let archetype: *mut Archetype = self.archetype; &mut *archetype } else if new_archetype.id() == swapped_location.archetype_id { new_archetype } else { // SAFETY: the only two borrowed archetypes are above and we just did collision checks - &mut world.archetypes[swapped_location.archetype_id] + &mut *self + .archetypes_ptr + .add(swapped_location.archetype_id.index()) }; - world.entities.set( + self.entities.set( swapped_entity.index(), EntityLocation { archetype_id: swapped_location.archetype_id, @@ -765,9 +791,9 @@ impl<'w> BundleInserter<'w> { .set_entity_table_row(swapped_location.archetype_row, result.table_row); } - bundle_info.write_components( + self.bundle_info.write_components( new_table, - &mut world.storages.sparse_sets, + self.sparse_sets, add_bundle, entity, move_result.new_row, @@ -781,33 +807,43 @@ impl<'w> BundleInserter<'w> { #[inline] pub(crate) fn entities(&mut self) -> &mut Entities { - &mut self.world.entities + self.entities } } pub(crate) struct BundleSpawner<'w> { - world: &'w mut World, - bundle_info: *const BundleInfo, - archetype: *mut Archetype, - table: *mut Table, + world: DeferredWorld<'w>, + bundle_info: &'w BundleInfo, + archetype: &'w mut Archetype, + table: &'w mut Table, + sparse_sets: &'w mut SparseSets, + entities: &'w mut Entities, change_tick: Tick, } impl<'w> BundleSpawner<'w> { #[inline] pub fn new(world: &'w mut World, change_tick: Tick) -> Self { - let bundle_info: *const BundleInfo = world + let bundle_id = world .bundles .init_info::(&mut world.components, &mut world.storages); - unsafe { Self::new_with_info(world, bundle_info, change_tick) } + // SAFETY: We just ensured this bundle exists, re-borrow to drop &mut World + let bundle_info = unsafe { world.bundles.get_unchecked(bundle_id) }; + Self::new_with_info( + world.as_unsafe_world_cell_readonly(), + bundle_info, + change_tick, + ) } - pub(crate) unsafe fn new_with_info( - world: &'w mut World, - bundle_info: *const BundleInfo, + #[inline] + pub(crate) fn new_with_info( + world: UnsafeWorldCell<'w>, + bundle_info: &'w BundleInfo, change_tick: Tick, ) -> Self { - let bundle_info = &*bundle_info; + // SAFETY: We will not make any accesses to the command queue, component or resource data of this world + let (world, deferred_world) = unsafe { world.split_deferred() }; let new_archetype_id = bundle_info.add_bundle_to_archetype( &mut world.archetypes, &mut world.storages, @@ -815,23 +851,24 @@ impl<'w> BundleSpawner<'w> { ArchetypeId::EMPTY, ); let archetype = &mut world.archetypes[new_archetype_id]; - let table: *mut Table = &mut world.storages.tables[archetype.table_id()]; - let archetype: *mut Archetype = archetype; - BundleSpawner { - world, + let table = &mut world.storages.tables[archetype.table_id()]; + Self { + world: deferred_world, bundle_info, archetype, table, + sparse_sets: &mut world.storages.sparse_sets, + entities: &mut world.entities, change_tick, } } + #[inline] pub fn reserve_storage(&mut self, additional: usize) { - unsafe { - (&mut *self.archetype).reserve(additional); - (&mut *self.table).reserve(additional); - } + self.archetype.reserve(additional); + self.table.reserve(additional); } + /// # Safety /// `entity` must be allocated (but non-existent), `T` must match this [`BundleInfo`]'s type #[inline] @@ -840,34 +877,28 @@ impl<'w> BundleSpawner<'w> { entity: Entity, bundle: T, ) -> EntityLocation { - let bundle_info = &*self.bundle_info; - let archetype = &*self.archetype; - if archetype.has_on_add() { + if self.archetype.has_on_add() { self.world - .into_deferred() - .trigger_on_add(entity, bundle_info.iter_components()); + .trigger_on_add(entity, self.bundle_info.iter_components()); } - if archetype.has_on_insert() { + if self.archetype.has_on_insert() { self.world - .into_deferred() - .trigger_on_insert(entity, bundle_info.iter_components()) + .trigger_on_insert(entity, self.bundle_info.iter_components()); } - let archetype = &mut *self.archetype; let table = &mut *self.table; - let table_row = table.allocate(entity); - let location = archetype.allocate(entity, table_row); - bundle_info.write_components( + let location = self.archetype.allocate(entity, table_row); + self.bundle_info.write_components( table, - &mut self.world.storages.sparse_sets, + self.sparse_sets, &SpawnBundleStatus, entity, table_row, self.change_tick, bundle, ); - self.world.entities.set(entity.index(), location); + self.entities.set(entity.index(), location); location } @@ -876,7 +907,7 @@ impl<'w> BundleSpawner<'w> { /// `T` must match this [`BundleInfo`]'s type #[inline] pub unsafe fn spawn(&mut self, bundle: T) -> Entity { - let entity = self.world.entities.alloc(); + let entity = self.entities.alloc(); // SAFETY: entity is allocated (but non-existent), `T` matches this BundleInfo's type self.spawn_non_existent(entity, bundle); entity @@ -884,7 +915,19 @@ impl<'w> BundleSpawner<'w> { #[inline] pub(crate) fn entities(&mut self) -> &mut Entities { - &mut self.world.entities + self.entities + } + + /// # Safety: + /// - `Self` must be dropped after running this function as it may invalidate internal pointers. + /// - Caller must ensure that no there are no outstanding access to `self.world` + #[inline] + pub(crate) unsafe fn flush_commands(&mut self) { + // SAFETY: pointers on self can be invalidated, + self.world + .as_unsafe_world_cell_readonly() + .world_mut() + .flush_commands(); } } @@ -895,9 +938,11 @@ pub struct Bundles { /// Cache static [`BundleId`] bundle_ids: TypeIdMap, /// Cache dynamic [`BundleId`] with multiple components - dynamic_bundle_ids: HashMap, (BundleId, Vec)>, + dynamic_bundle_ids: HashMap, BundleId>, + dynamic_bundle_storages: HashMap>, /// Cache optimized dynamic [`BundleId`] with single component - dynamic_component_bundle_ids: HashMap, + dynamic_component_bundle_ids: HashMap, + dynamic_component_storages: HashMap, } impl Bundles { @@ -917,11 +962,11 @@ impl Bundles { } /// Initializes a new [`BundleInfo`] for a statically known type. - pub(crate) fn init_info<'a, T: Bundle>( - &'a mut self, + pub(crate) fn init_info( + &mut self, components: &mut Components, storages: &mut Storages, - ) -> &'a BundleInfo { + ) -> BundleId { let bundle_infos = &mut self.bundle_infos; let id = *self.bundle_ids.entry(TypeId::of::()).or_insert_with(|| { let mut component_ids = Vec::new(); @@ -936,14 +981,35 @@ impl Bundles { bundle_infos.push(bundle_info); id }); - // SAFETY: index either exists, or was initialized - unsafe { self.get_unchecked(id) } + id } - pub(crate) unsafe fn get_unchecked<'a>(&'a self, id: BundleId) -> &'a BundleInfo { + pub(crate) unsafe fn get_unchecked(&self, id: BundleId) -> &BundleInfo { self.bundle_infos.get_unchecked(id.0) } + pub(crate) unsafe fn get_with_storage_unchecked( + &self, + id: BundleId, + ) -> (&BundleInfo, &StorageType) { + ( + self.bundle_infos.get_unchecked(id.0), + self.dynamic_component_storages + .get(&id) + .debug_checked_unwrap(), + ) + } + + pub(crate) unsafe fn get_with_storages_unchecked( + &self, + id: BundleId, + ) -> (&BundleInfo, &Vec) { + ( + self.bundle_infos.get_unchecked(id.0), + self.dynamic_bundle_storages.get(&id).debug_checked_unwrap(), + ) + } + /// Initializes a new [`BundleInfo`] for a dynamic [`Bundle`]. /// /// # Panics @@ -954,25 +1020,22 @@ impl Bundles { &mut self, components: &Components, component_ids: &[ComponentId], - ) -> (&BundleInfo, &Vec) { + ) -> BundleId { let bundle_infos = &mut self.bundle_infos; // Use `raw_entry_mut` to avoid cloning `component_ids` to access `Entry` - let (_, (bundle_id, storage_types)) = self + let (_, bundle_id) = self .dynamic_bundle_ids .raw_entry_mut() .from_key(component_ids) .or_insert_with(|| { - ( - Vec::from(component_ids), - initialize_dynamic_bundle(bundle_infos, components, Vec::from(component_ids)), - ) + let (id, storages) = + initialize_dynamic_bundle(bundle_infos, components, Vec::from(component_ids)); + self.dynamic_bundle_storages + .insert_unique_unchecked(id, storages); + (Vec::from(component_ids), id) }); - - // SAFETY: index either exists, or was initialized - let bundle_info = unsafe { bundle_infos.get_unchecked(bundle_id.0) }; - - (bundle_info, storage_types) + *bundle_id } /// Initializes a new [`BundleInfo`] for a dynamic [`Bundle`] with single component. @@ -984,22 +1047,18 @@ impl Bundles { &mut self, components: &Components, component_id: ComponentId, - ) -> (&BundleInfo, StorageType) { + ) -> BundleId { let bundle_infos = &mut self.bundle_infos; - let (bundle_id, storage_types) = self + let bundle_id = self .dynamic_component_bundle_ids .entry(component_id) .or_insert_with(|| { let (id, storage_type) = initialize_dynamic_bundle(bundle_infos, components, vec![component_id]); - // SAFETY: `storage_type` guaranteed to have length 1 - (id, storage_type[0]) + self.dynamic_component_storages.insert(id, storage_type[0]); + id }); - - // SAFETY: index either exists, or was initialized - let bundle_info = unsafe { bundle_infos.get_unchecked(bundle_id.0) }; - - (bundle_info, *storage_types) + *bundle_id } } diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 52b4fdc167ec5..3a89a8791468f 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -203,11 +203,23 @@ pub enum StorageType { SparseSet, } +/// The type used for [`Component`] lifecycle hooks such as `on_add`, `on_insert` or `on_remove` +pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, Entity, ComponentId); + +/// Lifecycle hooks for a given [`Component`], stored in it's [`ComponentInfo`] +#[derive(Debug, Clone, Default)] +pub struct ComponentHooks { + pub(crate) on_add: Option, + pub(crate) on_insert: Option, + pub(crate) on_remove: Option, +} + /// Stores metadata for a type of component or resource stored in a specific [`World`]. #[derive(Debug, Clone)] pub struct ComponentInfo { id: ComponentId, descriptor: ComponentDescriptor, + hooks: ComponentHooks, } impl ComponentInfo { @@ -263,26 +275,34 @@ impl ComponentInfo { /// Create a new [`ComponentInfo`]. pub(crate) fn new(id: ComponentId, descriptor: ComponentDescriptor) -> Self { - ComponentInfo { id, descriptor } + ComponentInfo { + id, + descriptor, + hooks: ComponentHooks::default(), + } } + /// Register a [`ComponentHook`] that will be run when this component is added to an entity pub fn on_add(&mut self, hook: ComponentHook) -> &mut Self { - self.descriptor.hooks.on_add = Some(hook); + self.hooks.on_add = Some(hook); self } + /// Register a [`ComponentHook`] that will be run when this component is added or set by `.insert` pub fn on_insert(&mut self, hook: ComponentHook) -> &mut Self { - self.descriptor.hooks.on_insert = Some(hook); + self.hooks.on_insert = Some(hook); self } + /// Register a [`ComponentHook`] that will be run when this component is removed from an entity. pub fn on_remove(&mut self, hook: ComponentHook) -> &mut Self { - self.descriptor.hooks.on_remove = Some(hook); + self.hooks.on_remove = Some(hook); self } + /// Update the given flags to include any [`ComponentHook`] registered to self #[inline] - pub fn update_archetype_flags(&self, flags: &mut ArchetypeFlags) { + pub(crate) fn update_archetype_flags(&self, flags: &mut ArchetypeFlags) { if self.hooks().on_add.is_some() { flags.insert(ArchetypeFlags::ON_ADD_HOOK); } @@ -294,8 +314,9 @@ impl ComponentInfo { } } + /// Provides a reference to the collection of hooks associated with this [`Component`] pub fn hooks(&self) -> &ComponentHooks { - &self.descriptor.hooks + &self.hooks } } @@ -352,15 +373,6 @@ impl SparseSetIndex for ComponentId { } } -pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, Entity, ComponentId); - -#[derive(Clone, Default)] -pub struct ComponentHooks { - pub(crate) on_add: Option, - pub(crate) on_insert: Option, - pub(crate) on_remove: Option, -} - /// A value describing a component or resource, which may or may not correspond to a Rust type. #[derive(Clone)] pub struct ComponentDescriptor { @@ -373,7 +385,6 @@ pub struct ComponentDescriptor { is_send_and_sync: bool, type_id: Option, layout: Layout, - hooks: ComponentHooks, // SAFETY: this function must be safe to call with pointers pointing to items of the type // this descriptor describes. // None if the underlying type doesn't need to be dropped @@ -407,7 +418,6 @@ impl ComponentDescriptor { is_send_and_sync: true, type_id: Some(TypeId::of::()), layout: Layout::new::(), - hooks: ComponentHooks::default(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), } } @@ -429,7 +439,6 @@ impl ComponentDescriptor { is_send_and_sync: true, type_id: None, layout, - hooks: ComponentHooks::default(), drop, } } @@ -446,7 +455,6 @@ impl ComponentDescriptor { is_send_and_sync: true, type_id: Some(TypeId::of::()), layout: Layout::new::(), - hooks: ComponentHooks::default(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), } } @@ -458,7 +466,6 @@ impl ComponentDescriptor { is_send_and_sync: false, type_id: Some(TypeId::of::()), layout: Layout::new::(), - hooks: ComponentHooks::default(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), } } diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index ea6293ac25e20..d12ba0b4248b2 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -10,27 +10,28 @@ use crate::{ system::{Commands, Query, Resource}, }; -use super::{Mut, World}; +use super::{unsafe_world_cell::UnsafeWorldCell, Mut, World}; /// An [`World`] reference that prevents structural ECS changes. /// This includes creating tables, registering components or spawning entities. pub struct DeferredWorld<'w> { - world: &'w mut World, + world: UnsafeWorldCell<'w>, } impl<'w> Deref for DeferredWorld<'w> { - type Target = &'w mut World; + type Target = World; fn deref(&self) -> &Self::Target { - &self.world + // SAFETY: &self ensures ther are no active mutable borrows + unsafe { self.world.world() } } } impl<'w> DeferredWorld<'w> { /// Creates a [`Commands`] instance that pushes to the world's command queue pub fn commands(&mut self) -> Commands { - let world = self.world.as_unsafe_world_cell(); - unsafe { Commands::new(world.get_command_queue(), world.world()) } + // SAFETY: Commands::new only take &World which has no way to access the command queue + unsafe { Commands::new(self.world.get_command_queue(), self.world.world()) } } /// Retrieves a mutable reference to the given `entity`'s [`Component`] of the given type. @@ -41,29 +42,28 @@ impl<'w> DeferredWorld<'w> { // - `as_unsafe_world_cell` is the only thing that is borrowing world // - `as_unsafe_world_cell` provides mutable permission to everything // - `&mut self` ensures no other borrows on world data - unsafe { - self.world - .as_unsafe_world_cell() - .get_entity(entity)? - .get_mut() - } + unsafe { self.world.get_entity(entity)?.get_mut() } } /// Returns [`Query`] for the given [`QueryState`], which is used to efficiently /// run queries on the [`World`] by storing and reusing the [`QueryState`]. + /// + /// # Panics + /// If state is from a different world then self #[inline] pub fn query<'s, Q: WorldQuery, F: ReadOnlyWorldQuery>( &'w mut self, state: &'s mut QueryState, ) -> Query<'w, 's, Q, F> { + state.validate_world(self.world.id()); + state.update_archetypes(self); + // SAFETY: We ran validate_world to ensure our state matches unsafe { - state.update_archetypes(self.world); - let world = self.world.as_unsafe_world_cell(); Query::new( - world, + self.world, state, - world.last_change_tick(), - world.change_tick(), + self.world.last_change_tick(), + self.world.change_tick(), false, ) } @@ -96,7 +96,8 @@ impl<'w> DeferredWorld<'w> { /// Gets a mutable reference to the resource of the given type if it exists #[inline] pub fn get_resource_mut(&mut self) -> Option> { - self.world.get_resource_mut() + // SAFETY: `&mut self` ensures that all accessed data is unaliased + unsafe { self.world.get_resource_mut() } } /// Gets a mutable reference to the non-send resource of the given type, if it exists. @@ -128,7 +129,8 @@ impl<'w> DeferredWorld<'w> { /// This function will panic if it isn't called from the same thread that the resource was inserted from. #[inline] pub fn get_non_send_resource_mut(&mut self) -> Option> { - self.world.get_non_send_resource_mut() + // SAFETY: `&mut self` ensures that all accessed data is unaliased + unsafe { self.world.get_non_send_resource_mut() } } /// Sends an [`Event`]. @@ -173,7 +175,8 @@ impl<'w> DeferredWorld<'w> { /// use this in cases where the actual types are not known at compile time.** #[inline] pub fn get_resource_mut_by_id(&mut self, component_id: ComponentId) -> Option> { - self.world.get_resource_mut_by_id(component_id) + // SAFETY: `&mut self` ensures that all accessed data is unaliased + unsafe { self.world.get_resource_mut_by_id(component_id) } } /// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists. @@ -187,7 +190,8 @@ impl<'w> DeferredWorld<'w> { /// This function will panic if it isn't called from the same thread that the resource was inserted from. #[inline] pub fn get_non_send_mut_by_id(&mut self, component_id: ComponentId) -> Option> { - self.world.get_non_send_mut_by_id(component_id) + // SAFETY: `&mut self` ensures that all accessed data is unaliased + unsafe { self.world.get_non_send_resource_mut_by_id(component_id) } } /// Retrieves a mutable untyped reference to the given `entity`'s [`Component`] of the given [`ComponentId`]. @@ -201,75 +205,91 @@ impl<'w> DeferredWorld<'w> { entity: Entity, component_id: ComponentId, ) -> Option> { - // SAFETY: - // - `&mut self` ensures that all accessed data is unaliased - // - `as_unsafe_world_cell` provides mutable permission to the whole world - unsafe { - self.world - .as_unsafe_world_cell() - .get_entity(entity)? - .get_mut_by_id(component_id) - } + // SAFETY: `&mut self` ensures that all accessed data is unaliased + unsafe { self.world.get_entity(entity)?.get_mut_by_id(component_id) } } + /// Triggers all `on_add` hooks for [`ComponentId`] in target. + /// + /// # Safety + /// Caller must ensure [`ComponentId`] in target exist in self. #[inline] - pub(crate) fn trigger_on_add( + pub(crate) unsafe fn trigger_on_add( &mut self, entity: Entity, targets: impl Iterator, ) { for component_id in targets { + // SAFETY: Caller ensures that these components exist let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks(); if let Some(hook) = hooks.on_add { - hook(DeferredWorld { world: self.world }, entity, component_id) + hook(DeferredWorld { world: self.world }, entity, component_id); } } } + /// Triggers all `on_insert` hooks for [`ComponentId`] in target. + /// + /// # Safety + /// Caller must ensure [`ComponentId`] in target exist in self. #[inline] - pub(crate) fn trigger_on_insert( + pub(crate) unsafe fn trigger_on_insert( &mut self, entity: Entity, targets: impl Iterator, ) { for component_id in targets { + // SAFETY: Caller ensures that these components exist let hooks = unsafe { self.world.components().get_info_unchecked(component_id) }.hooks(); if let Some(hook) = hooks.on_insert { - hook(DeferredWorld { world: self.world }, entity, component_id) + hook(DeferredWorld { world: self.world }, entity, component_id); } } } + /// Triggers all `on_remove` hooks for [`ComponentId`] in target. + /// + /// # Safety + /// Caller must ensure [`ComponentId`] in target exist in self. #[inline] - pub(crate) fn trigger_on_remove( + pub(crate) unsafe fn trigger_on_remove( &mut self, entity: Entity, targets: impl Iterator, ) { for component_id in targets { + // SAFETY: Caller ensures that these components exist let hooks = unsafe { self.world.components().get_info_unchecked(component_id) }.hooks(); if let Some(hook) = hooks.on_remove { - hook(DeferredWorld { world: self.world }, entity, component_id) + hook(DeferredWorld { world: self.world }, entity, component_id); } } } } -impl World { - /// Turn a [`World`] reference into a [`DeferredWorld`] +impl<'w> UnsafeWorldCell<'w> { + /// Turn self into a [`DeferredWorld`] /// + /// # Safety /// Caller must ensure there are no outstanding references to the world's command queue, resource or component data #[inline] - pub unsafe fn into_deferred(&self) -> DeferredWorld { - DeferredWorld { - // SAFETY: Not - world: self.as_unsafe_world_cell_readonly().world_mut(), - } + pub unsafe fn into_deferred(self) -> DeferredWorld<'w> { + DeferredWorld { world: self } + } + + /// Turn self into a mutable world reference and a [`DeferredWorld`] + /// + /// # Safety + /// Caller must ensure that the world reference is not used to construct any references to the world's command queue, resource or component data + pub unsafe fn split_deferred(self) -> (&'w mut World, DeferredWorld<'w>) { + (self.world_mut(), self.into_deferred()) } } -impl<'w> Into> for &'w mut World { - fn into(self) -> DeferredWorld<'w> { - DeferredWorld { world: self } +impl<'w> From<&'w mut World> for DeferredWorld<'w> { + fn from(world: &'w mut World) -> DeferredWorld<'w> { + DeferredWorld { + world: world.as_unsafe_world_cell(), + } } } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index e3349fb23282e..c7763d7c87c82 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -592,26 +592,26 @@ impl<'w> EntityWorldMut<'w> { component: OwningPtr<'_>, ) -> &mut Self { let change_tick = self.world.change_tick(); - let (bundle_info, storage_type) = self + let bundle_id = self .world .bundles .init_component_info(&self.world.components, component_id); - let bundle_info: *const BundleInfo = bundle_info; - let bundle_inserter = unsafe { - BundleInserter::new_with_info( - self.world, - self.location.archetype_id, - bundle_info, - change_tick, - ) - }; + let (bundle_info, storage_type) = self.world.bundles.get_with_storage_unchecked(bundle_id); + + let bundle_inserter = BundleInserter::new_with_info( + // SAFETY: The only outstanding reference to world is bundle_info + self.world.as_unsafe_world_cell_readonly(), + self.location.archetype_id, + bundle_info, + change_tick, + ); self.location = insert_dynamic_bundle( bundle_inserter, self.entity, self.location, Some(component).into_iter(), - Some(storage_type).iter().cloned(), + Some(*storage_type).iter().cloned(), ); self.world.flush_commands(); self @@ -635,27 +635,27 @@ impl<'w> EntityWorldMut<'w> { iter_components: I, ) -> &mut Self { let change_tick = self.world.change_tick(); - let (bundle_info, storage_types) = self + let bundle_id = self .world .bundles .init_dynamic_info(&self.world.components, component_ids); - let bundle_info: *const BundleInfo = bundle_info; - let storage_types: *const Vec = storage_types; - let bundle_inserter = unsafe { - BundleInserter::new_with_info( - self.world, - self.location.archetype_id, - bundle_info, - change_tick, - ) - }; + let (bundle_info, storage_types) = + self.world.bundles.get_with_storages_unchecked(bundle_id); + + let bundle_inserter = BundleInserter::new_with_info( + // SAFETY: The only outstanding reference to world is bundle_info + self.world.as_unsafe_world_cell_readonly(), + self.location.archetype_id, + bundle_info, + change_tick, + ); self.location = insert_dynamic_bundle( bundle_inserter, self.entity, self.location, iter_components, - (&*storage_types).iter().cloned(), + (*storage_types).iter().cloned(), ); self.world.flush_commands(); self @@ -670,8 +670,8 @@ impl<'w> EntityWorldMut<'w> { pub fn take(&mut self) -> Option { let storages = &mut self.world.storages; let components = &mut self.world.components; - let bundle_id = self.world.bundles.init_info::(components, storages).id(); - // Reborrow to drop &mut World + let bundle_id = self.world.bundles.init_info::(components, storages); + // SAFETY: We just ensured this bundle exists let bundle_info = unsafe { self.world.bundles.get_unchecked(bundle_id) }; let old_location = self.location; // SAFETY: `archetype_id` exists because it is referenced in the old `EntityLocation` which is valid, @@ -695,12 +695,17 @@ impl<'w> EntityWorldMut<'w> { let entity = self.entity; let old_archetype = &self.world.archetypes[old_location.archetype_id]; if old_archetype.has_on_remove() { - unsafe { self.world.into_deferred() } - .trigger_on_remove(entity, bundle_info.iter_components()); + // SAFETY: None of our oustanding references can be modified through DeferredWorld + unsafe { + self.world + .as_unsafe_world_cell_readonly() + .into_deferred() + .trigger_on_remove(entity, bundle_info.iter_components()); + }; + } + for component_id in bundle_info.iter_components() { + self.world.removed_components.send(component_id, entity); } - // for component_id in bundle_info.iter_components() { - // self.world.removed_components.send(component_id, entity); - // } let archetypes = &mut self.world.archetypes; let storages = &mut self.world.storages; let components = &mut self.world.components; @@ -827,8 +832,8 @@ impl<'w> EntityWorldMut<'w> { let storages = &mut self.world.storages; let components = &mut self.world.components; - let bundle_id = self.world.bundles.init_info::(components, storages).id(); - // Reborrow to drop &mut World + let bundle_id = self.world.bundles.init_info::(components, storages); + // SAFETY: We just ensured this bundle exists let bundle_info = unsafe { self.world.bundles.get_unchecked(bundle_id) }; let old_location = self.location; @@ -854,12 +859,17 @@ impl<'w> EntityWorldMut<'w> { let entity: Entity = self.entity; let old_archetype = &self.world.archetypes[old_location.archetype_id]; if old_archetype.has_on_remove() { - unsafe { self.world.into_deferred() } - .trigger_on_remove(entity, bundle_info.iter_components()) + // SAFETY: None of our oustanding references can be modified through DeferredWorld + unsafe { + self.world + .as_unsafe_world_cell_readonly() + .into_deferred() + .trigger_on_remove(entity, bundle_info.iter_components()); + }; } for component_id in bundle_info.iter_components() { if old_archetype.contains(component_id) { - // self.world.removed_components.send(component_id, entity); + self.world.removed_components.send(component_id, entity); // Make sure to drop components stored in sparse sets. // Dense components are dropped later in `move_to_and_drop_missing_unchecked`. @@ -897,15 +907,20 @@ impl<'w> EntityWorldMut<'w> { self.world.flush_entities(); let archetype = &self.world.archetypes[self.location.archetype_id]; if archetype.has_on_remove() { - unsafe { self.world.into_deferred() } - .trigger_on_remove(self.entity, archetype.components()); + // SAFETY: None of our oustanding references can be modified through DeferredWorld + unsafe { + self.world + .as_unsafe_world_cell_readonly() + .into_deferred() + .trigger_on_remove(self.entity, archetype.components()); + }; } - // for component_id in archetype.components() { - // self.world - // .removed_components - // .send(component_id, self.entity); - // } + for component_id in archetype.components() { + self.world + .removed_components + .send(component_id, self.entity); + } let world = self.world; let location = world diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index daa98d9fce9f6..b46e262a4add0 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -15,7 +15,7 @@ pub use world_cell::*; use crate::{ archetype::{ArchetypeComponentId, ArchetypeId, ArchetypeRow, Archetypes}, - bundle::{Bundle, BundleInfo, BundleInserter, BundleSpawner, Bundles}, + bundle::{Bundle, BundleInserter, BundleSpawner, Bundles}, change_detection::{MutUntyped, TicksMut}, component::{Component, ComponentDescriptor, ComponentId, ComponentInfo, Components, Tick}, entity::{AllocAtWithoutReplacement, Entities, Entity, EntityLocation}, @@ -189,7 +189,7 @@ impl World { /// Initializes a new [`Component`] type and returns a mutable reference to the [`ComponentInfo`] created for it. /// Primarily used for registering hooks. /// - /// Will panic if a component for `T`` already exists. + /// Will panic if a component for `T` already exists. pub fn register_component(&mut self) -> &mut ComponentInfo { let type_id = TypeId::of::(); assert!(self.components.get_id(type_id).is_none(), "Components cannot be registered twice, use init_component instead if this component may already exist in the world."); @@ -218,7 +218,7 @@ impl World { /// Initializes a new [`Component`] type and returns a mutable reference to the [`ComponentInfo`] created for it. /// Primarily used for registering hooks. /// - /// Will panic if a component for `T`` already exists. + /// Will panic if a component for `T` already exists. pub fn register_component_with_descriptor( &mut self, descriptor: ComponentDescriptor, @@ -1454,9 +1454,11 @@ impl World { let change_tick = self.change_tick(); - let bundle_info = self + let bundle_id = self .bundles .init_info::(&mut self.components, &mut self.storages); + // SAFETY: We just ensured this bundle exists + let bundle_info = unsafe { self.bundles.get_unchecked(bundle_id) }; enum SpawnOrInsert<'w> { Spawn(BundleSpawner<'w>), Insert(BundleInserter<'w>, ArchetypeId), @@ -1470,10 +1472,11 @@ impl World { } } } - let bundle_info: *const BundleInfo = bundle_info; - let mut spawn_or_insert = SpawnOrInsert::Spawn(unsafe { - BundleSpawner::new_with_info(self, bundle_info, change_tick) - }); + let mut spawn_or_insert = SpawnOrInsert::Spawn(BundleSpawner::new_with_info( + self.as_unsafe_world_cell_readonly(), + bundle_info, + change_tick, + )); let mut invalid_entities = Vec::new(); for (entity, bundle) in iter { @@ -1490,14 +1493,12 @@ impl World { unsafe { inserter.insert(entity, location, bundle) }; } _ => { - let mut inserter = unsafe { - BundleInserter::new_with_info( - self, - location.archetype_id, - bundle_info, - change_tick, - ) - }; + let mut inserter = BundleInserter::new_with_info( + self.as_unsafe_world_cell_readonly(), + location.archetype_id, + bundle_info, + change_tick, + ); // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter unsafe { inserter.insert(entity, location, bundle) }; spawn_or_insert = @@ -1510,8 +1511,11 @@ impl World { // SAFETY: `entity` is allocated (but non existent), bundle matches inserter unsafe { spawner.spawn_non_existent(entity, bundle) }; } else { - let mut spawner = - unsafe { BundleSpawner::new_with_info(self, bundle_info, change_tick) }; + let mut spawner = BundleSpawner::new_with_info( + self.as_unsafe_world_cell_readonly(), + bundle_info, + change_tick, + ); // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter unsafe { spawner.spawn_non_existent(entity, bundle) }; spawn_or_insert = SpawnOrInsert::Spawn(spawner); diff --git a/crates/bevy_ecs/src/world/spawn_batch.rs b/crates/bevy_ecs/src/world/spawn_batch.rs index b0586e4c7c8f8..71e367fd1a86f 100644 --- a/crates/bevy_ecs/src/world/spawn_batch.rs +++ b/crates/bevy_ecs/src/world/spawn_batch.rs @@ -51,7 +51,9 @@ where I::Item: Bundle, { fn drop(&mut self) { - for _ in self {} + for _ in &mut *self {} + // SAFETY: `self.spawner` will be dropped immediately after this call. + unsafe { self.spawner.flush_commands() }; } } diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 210da32926b5e..173d56576e444 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -567,8 +567,12 @@ impl<'w> UnsafeWorldCell<'w> { .get_with_ticks() } + // Returns a mutable reference to worlds internal [`CommandQueue`] + /// # Safety + /// It is the callers responsibility to ensure that no mutable references exist to the command queue at the same time #[inline] pub(crate) unsafe fn get_command_queue(self) -> &'w mut CommandQueue { + // SAFETY: caller ensures there is no `&mut World` let world = unsafe { &mut *self.0 }; &mut world.command_queue } diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs index 9309de94e9fc9..8e8e63de0322f 100644 --- a/examples/ecs/component_hooks.rs +++ b/examples/ecs/component_hooks.rs @@ -30,7 +30,7 @@ fn setup(world: &mut World) { ); let mut index = world.resource_mut::(); index.remove(&entity); - println!("Current index: {:?}", *index) + println!("Current index: {:?}", *index); }); } From dec4cecb45e313e36fea0433dc369cf255dffe0d Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sun, 26 Nov 2023 16:32:37 -0800 Subject: [PATCH 007/109] Miri pass --- crates/bevy_ecs/src/bundle.rs | 653 +++++++++++------- crates/bevy_ecs/src/system/system_registry.rs | 4 + crates/bevy_ecs/src/world/deferred_world.rs | 17 +- crates/bevy_ecs/src/world/entity_ref.rs | 146 ++-- crates/bevy_ecs/src/world/mod.rs | 34 +- .../bevy_ecs/src/world/unsafe_world_cell.rs | 12 +- examples/ecs/component_hooks.rs | 77 ++- 7 files changed, 577 insertions(+), 366 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index fe57cc96af184..a32f98a9b0ce6 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -15,7 +15,7 @@ use crate::{ prelude::World, query::DebugCheckedUnwrap, storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow}, - world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld}, + world::unsafe_world_cell::UnsafeWorldCell, TypeIdMap, }; use bevy_ptr::OwningPtr; @@ -502,27 +502,25 @@ impl BundleInfo { } } +// SAFETY: We have exclusive world access so our pointers can't be invalidated externally pub(crate) struct BundleInserter<'w> { - world: DeferredWorld<'w>, - archetype: &'w mut Archetype, - entities: &'w mut Entities, - bundle_info: &'w BundleInfo, - table: &'w mut Table, - sparse_sets: &'w mut SparseSets, - result: InsertBundleResult<'w>, - add_bundle_ptr: *const AddBundle, - archetypes_ptr: *mut Archetype, + world: UnsafeWorldCell<'w>, + bundle_info: *const BundleInfo, + add_bundle: *const AddBundle, + table: *mut Table, + archetype: *mut Archetype, + result: InsertBundleResult, change_tick: Tick, } -pub(crate) enum InsertBundleResult<'w> { +pub(crate) enum InsertBundleResult { SameArchetype, NewArchetypeSameTable { - new_archetype: &'w mut Archetype, + new_archetype: *mut Archetype, }, NewArchetypeNewTable { - new_archetype: &'w mut Archetype, - new_table: &'w mut Table, + new_archetype: *mut Archetype, + new_table: *mut Table, }, } @@ -536,28 +534,23 @@ impl<'w> BundleInserter<'w> { let bundle_id = world .bundles .init_info::(&mut world.components, &mut world.storages); - // SAFETY: We just ensured this bundle exists, re-borrow to drop &mut World - let bundle_info = unsafe { world.bundles.get_unchecked(bundle_id) }; - Self::new_with_info( - world.as_unsafe_world_cell_readonly(), - archetype_id, - bundle_info, - change_tick, - ) + // SAFETY: We just ensured this bundle exists + unsafe { Self::new_with_id(world, archetype_id, bundle_id, change_tick) } } /// Creates a new [`BundleInserter`]. /// - /// Implementation guarantees it will not alias `bundle_info`. + /// # Safety + /// Caller must ensure that BundleId exists in `world.bundles` #[inline] - pub(crate) fn new_with_info( - world: UnsafeWorldCell<'w>, + pub(crate) unsafe fn new_with_id( + world: &'w mut World, archetype_id: ArchetypeId, - bundle_info: &'w BundleInfo, + bundle_id: BundleId, change_tick: Tick, ) -> Self { // SAFETY: We will not make any accesses to the command queue, component or resource data of this world - let (world, deferred_world) = unsafe { world.split_deferred() }; + let bundle_info = world.bundles.get_unchecked(bundle_id); let bundle_id = bundle_info.id(); let new_archetype_id = bundle_info.add_bundle_to_archetype( &mut world.archetypes, @@ -565,11 +558,10 @@ impl<'w> BundleInserter<'w> { &world.components, archetype_id, ); - let archetypes_ptr = world.archetypes.archetypes.as_mut_ptr(); if new_archetype_id == archetype_id { let archetype = &mut world.archetypes[archetype_id]; // SAFETY: The edge is assured to be initialized when we called add_bundle_to_archetype - let add_bundle_ptr: *const AddBundle = unsafe { + let add_bundle = unsafe { archetype .edges() .get_add_bundle_internal(bundle_id) @@ -578,22 +570,19 @@ impl<'w> BundleInserter<'w> { let table_id = archetype.table_id(); let table = &mut world.storages.tables[table_id]; Self { - world: deferred_world, + add_bundle, archetype, - entities: &mut world.entities, bundle_info, table, - sparse_sets: &mut world.storages.sparse_sets, result: InsertBundleResult::SameArchetype, - archetypes_ptr, - add_bundle_ptr, change_tick, + world: world.as_unsafe_world_cell(), } } else { let (archetype, new_archetype) = world.archetypes.get_2_mut(archetype_id, new_archetype_id); // SAFETY: The edge is assured to be initialized when we called add_bundle_to_archetype - let add_bundle_ptr: *const AddBundle = unsafe { + let add_bundle = unsafe { archetype .edges() .get_add_bundle_internal(bundle_id) @@ -604,33 +593,27 @@ impl<'w> BundleInserter<'w> { if table_id == new_table_id { let table = &mut world.storages.tables[table_id]; Self { - world: deferred_world, + add_bundle, archetype, - entities: &mut world.entities, bundle_info, table, - sparse_sets: &mut world.storages.sparse_sets, result: InsertBundleResult::NewArchetypeSameTable { new_archetype }, - archetypes_ptr, - add_bundle_ptr, change_tick, + world: world.as_unsafe_world_cell(), } } else { let (table, new_table) = world.storages.tables.get_2_mut(table_id, new_table_id); Self { - world: deferred_world, + add_bundle, archetype, - entities: &mut world.entities, bundle_info, table, - sparse_sets: &mut world.storages.sparse_sets, result: InsertBundleResult::NewArchetypeNewTable { new_archetype, new_table, }, - archetypes_ptr, - add_bundle_ptr, change_tick, + world: world.as_unsafe_world_cell(), } } } @@ -645,161 +628,205 @@ impl<'w> BundleInserter<'w> { location: EntityLocation, bundle: T, ) -> EntityLocation { - // SAFETY: We must ensure we do not use self.archetype to invalidate this reference - let add_bundle = &*self.add_bundle_ptr; + // SAFETY: We do not make any structural changes to the archetype graph through self.world so these pointers always remain valid + let bundle_info = &*self.bundle_info; + let add_bundle = &*self.add_bundle; + let archetype = &*self.archetype; match &mut self.result { InsertBundleResult::SameArchetype => { - if self.archetype.has_on_add() { - self.world.trigger_on_add( + { + // SAFETY: Mutable references do not alias and will be dropped after this block + let sparse_sets = { + let world = self.world.world_mut(); + &mut world.storages.sparse_sets + }; + let table = &mut *self.table; + bundle_info.write_components( + table, + sparse_sets, + add_bundle, + entity, + location.table_row, + self.change_tick, + bundle, + ); + } + // SAFETY: We have no oustanding mutable references to world as they were dropped + let mut world = self.world.into_deferred(); + if archetype.has_on_add() { + world.trigger_on_add( entity, - self.bundle_info.iter_components().filter(|id| { - add_bundle.get_status(id.index()) == ComponentStatus::Added - }), + bundle_info + .iter_components() + .enumerate() + .filter(|(index, _)| { + add_bundle.get_status(*index) == ComponentStatus::Added + }) + .map(|(_, id)| id), ); } - if self.archetype.has_on_insert() { - self.world - .trigger_on_insert(entity, self.bundle_info.iter_components()); + if archetype.has_on_insert() { + world.trigger_on_insert(entity, bundle_info.iter_components()); } - // PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty) - // SAFETY: The edge is assured to be initialized when creating the BundleInserter - let add_bundle = unsafe { - self.archetype - .edges() - .get_add_bundle_internal(self.bundle_info.id) - .debug_checked_unwrap() - }; - self.bundle_info.write_components( - self.table, - self.sparse_sets, - add_bundle, - entity, - location.table_row, - self.change_tick, - bundle, - ); location } InsertBundleResult::NewArchetypeSameTable { new_archetype } => { + let new_location = { + // SAFETY: Mutable references do not alias and will be dropped after this block + let table = &mut *self.table; + let archetype = &mut *self.archetype; + let new_archetype = &mut **new_archetype; + let (sparse_sets, entities) = { + let world = self.world.world_mut(); + (&mut world.storages.sparse_sets, &mut world.entities) + }; + + let result = archetype.swap_remove(location.archetype_row); + if let Some(swapped_entity) = result.swapped_entity { + let swapped_location = + // SAFETY: If the swap was successful, swapped_entity must be valid. + unsafe { entities.get(swapped_entity).debug_checked_unwrap() }; + entities.set( + swapped_entity.index(), + EntityLocation { + archetype_id: swapped_location.archetype_id, + archetype_row: location.archetype_row, + table_id: swapped_location.table_id, + table_row: swapped_location.table_row, + }, + ); + } + let new_location = new_archetype.allocate(entity, result.table_row); + entities.set(entity.index(), new_location); + bundle_info.write_components( + table, + sparse_sets, + add_bundle, + entity, + result.table_row, + self.change_tick, + bundle, + ); + new_location + }; + + // SAFETY: We have no oustanding mutable references to world as they were dropped + let new_archetype = &**new_archetype; + let mut world = self.world.into_deferred(); if new_archetype.has_on_add() { - self.world.trigger_on_add( + world.trigger_on_add( entity, - self.bundle_info.iter_components().filter(|id| { - add_bundle.get_status(id.index()) == ComponentStatus::Added - }), + bundle_info + .iter_components() + .enumerate() + .filter(|(index, _)| { + add_bundle.get_status(*index) == ComponentStatus::Added + }) + .map(|(_, id)| id), ); } if new_archetype.has_on_insert() { - self.world - .trigger_on_insert(entity, self.bundle_info.iter_components()); - } - let result = self.archetype.swap_remove(location.archetype_row); - if let Some(swapped_entity) = result.swapped_entity { - let swapped_location = - // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; - self.entities.set( - swapped_entity.index(), - EntityLocation { - archetype_id: swapped_location.archetype_id, - archetype_row: location.archetype_row, - table_id: swapped_location.table_id, - table_row: swapped_location.table_row, - }, - ); + world.trigger_on_insert(entity, bundle_info.iter_components()); } - let new_location = new_archetype.allocate(entity, result.table_row); - self.entities.set(entity.index(), new_location); - self.bundle_info.write_components( - self.table, - self.sparse_sets, - add_bundle, - entity, - result.table_row, - self.change_tick, - bundle, - ); new_location } InsertBundleResult::NewArchetypeNewTable { new_archetype, new_table, } => { + let new_location = { + // SAFETY: Mutable references do not alias and will be dropped after this block + let table = &mut *self.table; + let new_table = &mut **new_table; + let archetype = &mut *self.archetype; + let new_archetype = &mut **new_archetype; + let (archetypes, sparse_sets, entities) = { + let world = self.world.world_mut(); + ( + &mut world.archetypes, + &mut world.storages.sparse_sets, + &mut world.entities, + ) + }; + let result = archetype.swap_remove(location.archetype_row); + if let Some(swapped_entity) = result.swapped_entity { + let swapped_location = + // SAFETY: If the swap was successful, swapped_entity must be valid. + unsafe { entities.get(swapped_entity).debug_checked_unwrap() }; + entities.set( + swapped_entity.index(), + EntityLocation { + archetype_id: swapped_location.archetype_id, + archetype_row: location.archetype_row, + table_id: swapped_location.table_id, + table_row: swapped_location.table_row, + }, + ); + } + // PERF: store "non bundle" components in edge, then just move those to avoid + // redundant copies + let move_result = table.move_to_superset_unchecked(result.table_row, new_table); + let new_location = new_archetype.allocate(entity, move_result.new_row); + entities.set(entity.index(), new_location); + + // if an entity was moved into this entity's table spot, update its table row + if let Some(swapped_entity) = move_result.swapped_entity { + let swapped_location = + // SAFETY: If the swap was successful, swapped_entity must be valid. + unsafe { entities.get(swapped_entity).debug_checked_unwrap() }; + let swapped_archetype = if archetype.id() == swapped_location.archetype_id { + archetype + } else if new_archetype.id() == swapped_location.archetype_id { + new_archetype + } else { + // SAFETY: the only two borrowed archetypes are above and we just did collision checks + &mut archetypes.archetypes[swapped_location.archetype_id.index()] + }; + + entities.set( + swapped_entity.index(), + EntityLocation { + archetype_id: swapped_location.archetype_id, + archetype_row: swapped_location.archetype_row, + table_id: swapped_location.table_id, + table_row: result.table_row, + }, + ); + swapped_archetype + .set_entity_table_row(swapped_location.archetype_row, result.table_row); + } + + bundle_info.write_components( + new_table, + sparse_sets, + add_bundle, + entity, + move_result.new_row, + self.change_tick, + bundle, + ); + + new_location + }; + + // SAFETY: We have no oustanding mutable references to world as they were dropped + let new_archetype = &**new_archetype; + let mut world = self.world.into_deferred(); if new_archetype.has_on_add() { - self.world.trigger_on_add( + world.trigger_on_add( entity, - self.bundle_info.iter_components().filter(|id| { - add_bundle.get_status(id.index()) == ComponentStatus::Added - }), + bundle_info + .iter_components() + .enumerate() + .filter(|(index, _)| { + add_bundle.get_status(*index) == ComponentStatus::Added + }) + .map(|(_, id)| id), ); } if new_archetype.has_on_insert() { - self.world - .trigger_on_insert(entity, self.bundle_info.iter_components()); - } - let result = self.archetype.swap_remove(location.archetype_row); - if let Some(swapped_entity) = result.swapped_entity { - let swapped_location = - // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; - self.entities.set( - swapped_entity.index(), - EntityLocation { - archetype_id: swapped_location.archetype_id, - archetype_row: location.archetype_row, - table_id: swapped_location.table_id, - table_row: swapped_location.table_row, - }, - ); + world.trigger_on_insert(entity, bundle_info.iter_components()); } - // PERF: store "non bundle" components in edge, then just move those to avoid - // redundant copies - let move_result = self - .table - .move_to_superset_unchecked(result.table_row, new_table); - let new_location = new_archetype.allocate(entity, move_result.new_row); - self.entities.set(entity.index(), new_location); - - // if an entity was moved into this entity's table spot, update its table row - if let Some(swapped_entity) = move_result.swapped_entity { - let swapped_location = - // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; - let swapped_archetype = if self.archetype.id() == swapped_location.archetype_id - { - let archetype: *mut Archetype = self.archetype; - &mut *archetype - } else if new_archetype.id() == swapped_location.archetype_id { - new_archetype - } else { - // SAFETY: the only two borrowed archetypes are above and we just did collision checks - &mut *self - .archetypes_ptr - .add(swapped_location.archetype_id.index()) - }; - - self.entities.set( - swapped_entity.index(), - EntityLocation { - archetype_id: swapped_location.archetype_id, - archetype_row: swapped_location.archetype_row, - table_id: swapped_location.table_id, - table_row: result.table_row, - }, - ); - swapped_archetype - .set_entity_table_row(swapped_location.archetype_row, result.table_row); - } - - self.bundle_info.write_components( - new_table, - self.sparse_sets, - add_bundle, - entity, - move_result.new_row, - self.change_tick, - bundle, - ); new_location } } @@ -807,17 +834,17 @@ impl<'w> BundleInserter<'w> { #[inline] pub(crate) fn entities(&mut self) -> &mut Entities { - self.entities + // SAFETY: No outstanding references to self.world, changes to entities cannot invalidate our internal pointers + unsafe { &mut self.world.world_mut().entities } } } +// SAFETY: We have exclusive world access so our pointers can't be invalidated externally pub(crate) struct BundleSpawner<'w> { - world: DeferredWorld<'w>, - bundle_info: &'w BundleInfo, - archetype: &'w mut Archetype, - table: &'w mut Table, - sparse_sets: &'w mut SparseSets, - entities: &'w mut Entities, + world: UnsafeWorldCell<'w>, + bundle_info: *const BundleInfo, + table: *mut Table, + archetype: *mut Archetype, change_tick: Tick, } @@ -827,23 +854,21 @@ impl<'w> BundleSpawner<'w> { let bundle_id = world .bundles .init_info::(&mut world.components, &mut world.storages); - // SAFETY: We just ensured this bundle exists, re-borrow to drop &mut World - let bundle_info = unsafe { world.bundles.get_unchecked(bundle_id) }; - Self::new_with_info( - world.as_unsafe_world_cell_readonly(), - bundle_info, - change_tick, - ) + // SAFETY: we initialized this bundle_id in `init_info` + unsafe { Self::new_with_id(world, bundle_id, change_tick) } } + /// Creates a new [`BundleSpawner`]. + /// + /// # Safety + /// Caller must ensure that BundleId exists in `world.bundles` #[inline] - pub(crate) fn new_with_info( - world: UnsafeWorldCell<'w>, - bundle_info: &'w BundleInfo, + pub(crate) unsafe fn new_with_id( + world: &'w mut World, + bundle_id: BundleId, change_tick: Tick, ) -> Self { - // SAFETY: We will not make any accesses to the command queue, component or resource data of this world - let (world, deferred_world) = unsafe { world.split_deferred() }; + let bundle_info = world.bundles.get_unchecked(bundle_id); let new_archetype_id = bundle_info.add_bundle_to_archetype( &mut world.archetypes, &mut world.storages, @@ -853,20 +878,20 @@ impl<'w> BundleSpawner<'w> { let archetype = &mut world.archetypes[new_archetype_id]; let table = &mut world.storages.tables[archetype.table_id()]; Self { - world: deferred_world, bundle_info, - archetype, table, - sparse_sets: &mut world.storages.sparse_sets, - entities: &mut world.entities, + archetype, change_tick, + world: world.as_unsafe_world_cell(), } } #[inline] pub fn reserve_storage(&mut self, additional: usize) { - self.archetype.reserve(additional); - self.table.reserve(additional); + // SAFETY: There are no oustanding world references + let (archetype, table) = unsafe { (&mut *self.archetype, &mut *self.table) }; + archetype.reserve(additional); + table.reserve(additional); } /// # Safety @@ -877,29 +902,41 @@ impl<'w> BundleSpawner<'w> { entity: Entity, bundle: T, ) -> EntityLocation { - if self.archetype.has_on_add() { - self.world - .trigger_on_add(entity, self.bundle_info.iter_components()); + // SAFETY: We do not make any structural changes to the archetype graph through self.world so this pointer always remain valid + let bundle_info = &*self.bundle_info; + let location = { + // SAFETY: Mutable references do not alias and will be dropped after this block + let table = &mut *self.table; + let archetype = &mut *self.archetype; + let (sparse_sets, entities) = { + let world = self.world.world_mut(); + (&mut world.storages.sparse_sets, &mut world.entities) + }; + let table_row = table.allocate(entity); + let location = archetype.allocate(entity, table_row); + bundle_info.write_components( + table, + sparse_sets, + &SpawnBundleStatus, + entity, + table_row, + self.change_tick, + bundle, + ); + entities.set(entity.index(), location); + location + }; + + // SAFETY: We have no oustanding mutable references to world as they were dropped + let archetype = &*self.archetype; + let mut world = self.world.into_deferred(); + if archetype.has_on_add() { + world.trigger_on_add(entity, bundle_info.iter_components()); } - if self.archetype.has_on_insert() { - self.world - .trigger_on_insert(entity, self.bundle_info.iter_components()); + if archetype.has_on_insert() { + world.trigger_on_insert(entity, bundle_info.iter_components()); } - let table = &mut *self.table; - let table_row = table.allocate(entity); - let location = self.archetype.allocate(entity, table_row); - self.bundle_info.write_components( - table, - self.sparse_sets, - &SpawnBundleStatus, - entity, - table_row, - self.change_tick, - bundle, - ); - self.entities.set(entity.index(), location); - location } @@ -907,7 +944,7 @@ impl<'w> BundleSpawner<'w> { /// `T` must match this [`BundleInfo`]'s type #[inline] pub unsafe fn spawn(&mut self, bundle: T) -> Entity { - let entity = self.entities.alloc(); + let entity = self.entities().alloc(); // SAFETY: entity is allocated (but non-existent), `T` matches this BundleInfo's type self.spawn_non_existent(entity, bundle); entity @@ -915,19 +952,16 @@ impl<'w> BundleSpawner<'w> { #[inline] pub(crate) fn entities(&mut self) -> &mut Entities { - self.entities + // SAFETY: No outstanding references to self.world, changes to entities cannot invalidate our internal pointers + unsafe { &mut self.world.world_mut().entities } } /// # Safety: /// - `Self` must be dropped after running this function as it may invalidate internal pointers. - /// - Caller must ensure that no there are no outstanding access to `self.world` #[inline] pub(crate) unsafe fn flush_commands(&mut self) { // SAFETY: pointers on self can be invalidated, - self.world - .as_unsafe_world_cell_readonly() - .world_mut() - .flush_commands(); + self.world.world_mut().flush_commands(); } } @@ -988,26 +1022,17 @@ impl Bundles { self.bundle_infos.get_unchecked(id.0) } - pub(crate) unsafe fn get_with_storage_unchecked( - &self, - id: BundleId, - ) -> (&BundleInfo, &StorageType) { - ( - self.bundle_infos.get_unchecked(id.0), - self.dynamic_component_storages - .get(&id) - .debug_checked_unwrap(), - ) + pub(crate) unsafe fn get_storage_unchecked(&self, id: BundleId) -> StorageType { + *self + .dynamic_component_storages + .get(&id) + .debug_checked_unwrap() } - pub(crate) unsafe fn get_with_storages_unchecked( - &self, - id: BundleId, - ) -> (&BundleInfo, &Vec) { - ( - self.bundle_infos.get_unchecked(id.0), - self.dynamic_bundle_storages.get(&id).debug_checked_unwrap(), - ) + pub(crate) unsafe fn get_storages_unchecked(&mut self, id: BundleId) -> &mut Vec { + self.dynamic_bundle_storages + .get_mut(&id) + .debug_checked_unwrap() } /// Initializes a new [`BundleInfo`] for a dynamic [`Bundle`]. @@ -1086,3 +1111,145 @@ fn initialize_dynamic_bundle( (id, storage_types) } + +mod tests { + use crate as bevy_ecs; + use crate::prelude::*; + + #[derive(Component)] + struct A; + + #[derive(Component)] + struct B; + + #[derive(Component)] + struct C; + + #[derive(Component)] + struct D; + + #[derive(Resource, Default)] + struct R(usize); + + impl R { + fn assert_order(&mut self, count: usize) { + assert_eq!(count, self.0); + self.0 += 1; + } + } + + #[test] + fn component_hook_order_spawn_despawn() { + let mut world = World::new(); + world.init_resource::(); + world + .register_component::() + .on_add(|mut world, _, _| { + world.resource_mut::().assert_order(0); + }) + .on_insert(|mut world, _, _| world.resource_mut::().assert_order(1)) + .on_remove(|mut world, _, _| world.resource_mut::().assert_order(2)); + + let entity = world.spawn(A).id(); + world.despawn(entity); + assert_eq!(3, world.resource::().0); + } + + #[test] + fn component_hook_order_insert_remove() { + let mut world = World::new(); + world.init_resource::(); + world + .register_component::() + .on_add(|mut world, _, _| { + world.resource_mut::().assert_order(0); + }) + .on_insert(|mut world, _, _| { + world.resource_mut::().assert_order(1); + }) + .on_remove(|mut world, _, _| { + world.resource_mut::().assert_order(2); + }); + + let mut entity = world.spawn_empty(); + entity.insert(A); + entity.remove::(); + entity.flush(); + assert_eq!(3, world.resource::().0); + } + + #[test] + fn component_hook_order_recursive() { + let mut world = World::new(); + world.init_resource::(); + world + .register_component::() + .on_add(|mut world, entity, _| { + world.resource_mut::().assert_order(0); + + world.with_commands(|mut commands| { + commands.entity(entity).insert(B); + }); + }) + .on_remove(|mut world, entity, _| { + world.resource_mut::().assert_order(2); + + world.with_commands(|mut commands| { + commands.entity(entity).remove::(); + }); + }); + + world + .register_component::() + .on_add(|mut world, entity, _| { + world.resource_mut::().assert_order(1); + + world.with_commands(|mut commands| { + commands.entity(entity).remove::(); + }); + }) + .on_remove(|mut world, _, _| { + world.resource_mut::().assert_order(3); + }); + + let entity = world.spawn(A).flush(); + let entity = world.get_entity(entity).unwrap(); + assert_eq!(false, entity.contains::()); + assert_eq!(false, entity.contains::()); + assert_eq!(4, world.resource::().0); + } + + #[test] + fn component_hook_order_recursive_multiple() { + let mut world = World::new(); + world.init_resource::(); + world + .register_component::() + .on_add(|mut world, entity, _| { + world.resource_mut::().assert_order(0); + world.with_commands(|mut commands| { + commands.entity(entity).insert(B).insert(D); + }); + }); + + world + .register_component::() + .on_add(|mut world, entity, _| { + world.resource_mut::().assert_order(1); + world.with_commands(|mut commands| { + commands.entity(entity).insert(C); + }); + }); + + world.register_component::().on_add(|mut world, _, _| { + world.resource_mut::().assert_order(2); + }); + + world.register_component::().on_add(|mut world, _, _| { + world.resource_mut::().assert_order(3); + }); + + world.spawn(A).flush(); + assert_eq!(4, world.resource::().0); + } +} diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index 54e8e6e177dc8..c754a7d77b652 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -280,6 +280,10 @@ impl World { .take::>() .ok_or(RegisteredSystemError::Recursive(id))?; + // Flush commands from removing component + // TODO: Consider refactoring to an RegisteredSystem to an Option to avoid removal + drop(entity); + // run the system if !initialized { system.initialize(self); diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index d12ba0b4248b2..1f688f15e3911 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -29,9 +29,12 @@ impl<'w> Deref for DeferredWorld<'w> { impl<'w> DeferredWorld<'w> { /// Creates a [`Commands`] instance that pushes to the world's command queue - pub fn commands(&mut self) -> Commands { - // SAFETY: Commands::new only take &World which has no way to access the command queue - unsafe { Commands::new(self.world.get_command_queue(), self.world.world()) } + #[inline] + pub fn with_commands(&mut self, f: impl Fn(Commands)) { + let world = unsafe { self.world.world_mut() }; + let mut queue = std::mem::take(&mut world.command_queue); + f(Commands::new(&mut queue, world)); + world.command_queue = std::mem::take(&mut queue); } /// Retrieves a mutable reference to the given `entity`'s [`Component`] of the given type. @@ -276,14 +279,6 @@ impl<'w> UnsafeWorldCell<'w> { pub unsafe fn into_deferred(self) -> DeferredWorld<'w> { DeferredWorld { world: self } } - - /// Turn self into a mutable world reference and a [`DeferredWorld`] - /// - /// # Safety - /// Caller must ensure that the world reference is not used to construct any references to the world's command queue, resource or component data - pub unsafe fn split_deferred(self) -> (&'w mut World, DeferredWorld<'w>) { - (self.world_mut(), self.into_deferred()) - } } impl<'w> From<&'w mut World> for DeferredWorld<'w> { diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index c7763d7c87c82..49a74a0bccf6a 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -8,7 +8,6 @@ use crate::{ world::{Mut, World}, }; use bevy_ptr::{OwningPtr, Ptr}; -use bevy_utils::tracing::debug; use std::{any::TypeId, marker::PhantomData}; use super::{unsafe_world_cell::UnsafeEntityCell, Ref}; @@ -572,7 +571,6 @@ impl<'w> EntityWorldMut<'w> { BundleInserter::new::(self.world, self.location.archetype_id, change_tick); // SAFETY: location matches current entity. `T` matches `bundle_info` self.location = unsafe { bundle_inserter.insert(self.entity, self.location, bundle) }; - self.world.flush_commands(); self } @@ -596,13 +594,12 @@ impl<'w> EntityWorldMut<'w> { .world .bundles .init_component_info(&self.world.components, component_id); - let (bundle_info, storage_type) = self.world.bundles.get_with_storage_unchecked(bundle_id); + let storage_type = self.world.bundles.get_storage_unchecked(bundle_id); - let bundle_inserter = BundleInserter::new_with_info( - // SAFETY: The only outstanding reference to world is bundle_info - self.world.as_unsafe_world_cell_readonly(), + let bundle_inserter = BundleInserter::new_with_id( + self.world, self.location.archetype_id, - bundle_info, + bundle_id, change_tick, ); @@ -611,9 +608,8 @@ impl<'w> EntityWorldMut<'w> { self.entity, self.location, Some(component).into_iter(), - Some(*storage_type).iter().cloned(), + Some(storage_type).iter().cloned(), ); - self.world.flush_commands(); self } @@ -639,14 +635,12 @@ impl<'w> EntityWorldMut<'w> { .world .bundles .init_dynamic_info(&self.world.components, component_ids); - let (bundle_info, storage_types) = - self.world.bundles.get_with_storages_unchecked(bundle_id); - - let bundle_inserter = BundleInserter::new_with_info( - // SAFETY: The only outstanding reference to world is bundle_info - self.world.as_unsafe_world_cell_readonly(), + let mut storage_types = + std::mem::take(self.world.bundles.get_storages_unchecked(bundle_id)); + let bundle_inserter = BundleInserter::new_with_id( + self.world, self.location.archetype_id, - bundle_info, + bundle_id, change_tick, ); @@ -657,7 +651,7 @@ impl<'w> EntityWorldMut<'w> { iter_components, (*storage_types).iter().cloned(), ); - self.world.flush_commands(); + *self.world.bundles.get_storages_unchecked(bundle_id) = std::mem::take(&mut storage_types); self } @@ -668,17 +662,18 @@ impl<'w> EntityWorldMut<'w> { // TODO: BundleRemover? #[must_use] pub fn take(&mut self) -> Option { - let storages = &mut self.world.storages; - let components = &mut self.world.components; - let bundle_id = self.world.bundles.init_info::(components, storages); + let world = &mut self.world; + let storages = &mut world.storages; + let components = &mut world.components; + let bundle_id = world.bundles.init_info::(components, storages); // SAFETY: We just ensured this bundle exists - let bundle_info = unsafe { self.world.bundles.get_unchecked(bundle_id) }; + let bundle_info = unsafe { world.bundles.get_unchecked(bundle_id) }; let old_location = self.location; // SAFETY: `archetype_id` exists because it is referenced in the old `EntityLocation` which is valid, // components exist in `bundle_info` because `Bundles::init_info` initializes a `BundleInfo` containing all components of the bundle type `T` let new_archetype_id = unsafe { remove_bundle_from_archetype( - &mut self.world.archetypes, + &mut world.archetypes, storages, components, old_location.archetype_id, @@ -691,25 +686,32 @@ impl<'w> EntityWorldMut<'w> { return None; } - // Reborrow so world is not mutably borrowed let entity = self.entity; - let old_archetype = &self.world.archetypes[old_location.archetype_id]; + let old_archetype = &world.archetypes[old_location.archetype_id]; + + // Drop borrow on world so it can be turned into DeferredWorld + // SAFETY: Changes to Bundles cannot happen through DeferredWorld + let bundle_info = { + let bundle_info: *const BundleInfo = bundle_info; + unsafe { &*bundle_info } + }; if old_archetype.has_on_remove() { - // SAFETY: None of our oustanding references can be modified through DeferredWorld + // SAFETY: All components in the archetype exist in world unsafe { - self.world - .as_unsafe_world_cell_readonly() + world + .as_unsafe_world_cell() .into_deferred() .trigger_on_remove(entity, bundle_info.iter_components()); - }; + } } + for component_id in bundle_info.iter_components() { - self.world.removed_components.send(component_id, entity); + world.removed_components.send(component_id, entity); } - let archetypes = &mut self.world.archetypes; - let storages = &mut self.world.storages; - let components = &mut self.world.components; - let entities = &mut self.world.entities; + let archetypes = &mut world.archetypes; + let storages = &mut world.storages; + let components = &mut world.components; + let entities = &mut world.entities; let entity = self.entity; let mut bundle_components = bundle_info.iter_components(); @@ -739,8 +741,6 @@ impl<'w> EntityWorldMut<'w> { new_archetype_id, ); } - drop(bundle_components); - self.world.flush_commands(); Some(result) } @@ -828,13 +828,14 @@ impl<'w> EntityWorldMut<'w> { /// Removes any components in the [`Bundle`] from the entity. // TODO: BundleRemover? pub fn remove(&mut self) -> &mut Self { - let archetypes = &mut self.world.archetypes; - let storages = &mut self.world.storages; - let components = &mut self.world.components; + let world = &mut self.world; + let archetypes = &mut world.archetypes; + let storages = &mut world.storages; + let components = &mut world.components; - let bundle_id = self.world.bundles.init_info::(components, storages); + let bundle_id = world.bundles.init_info::(components, storages); // SAFETY: We just ensured this bundle exists - let bundle_info = unsafe { self.world.bundles.get_unchecked(bundle_id) }; + let bundle_info = unsafe { world.bundles.get_unchecked(bundle_id) }; let old_location = self.location; // SAFETY: `archetype_id` exists because it is referenced in the old `EntityLocation` which is valid, @@ -855,26 +856,34 @@ impl<'w> EntityWorldMut<'w> { return self; } - // Reborrow so world is not mutably borrowed let entity: Entity = self.entity; - let old_archetype = &self.world.archetypes[old_location.archetype_id]; + let old_archetype = &world.archetypes[old_location.archetype_id]; + + // Drop borrow on world so it can be turned into DeferredWorld + // SAFETY: Changes to Bundles cannot happen through DeferredWorld + let bundle_info = { + let bundle_info: *const BundleInfo = bundle_info; + unsafe { &*bundle_info } + }; if old_archetype.has_on_remove() { - // SAFETY: None of our oustanding references can be modified through DeferredWorld + // SAFETY: All components in the archetype exist in world unsafe { - self.world - .as_unsafe_world_cell_readonly() + world + .as_unsafe_world_cell() .into_deferred() .trigger_on_remove(entity, bundle_info.iter_components()); - }; + } } + + let old_archetype = &world.archetypes[old_location.archetype_id]; for component_id in bundle_info.iter_components() { if old_archetype.contains(component_id) { - self.world.removed_components.send(component_id, entity); + world.removed_components.send(component_id, entity); // Make sure to drop components stored in sparse sets. // Dense components are dropped later in `move_to_and_drop_missing_unchecked`. if let Some(StorageType::SparseSet) = old_archetype.get_storage_type(component_id) { - self.world + world .storages .sparse_sets .get_mut(component_id) @@ -891,38 +900,41 @@ impl<'w> EntityWorldMut<'w> { &mut self.location, old_location.archetype_id, old_location, - &mut self.world.entities, - &mut self.world.archetypes, - &mut self.world.storages, + &mut world.entities, + &mut world.archetypes, + &mut world.storages, new_archetype_id, ); } - self.world.flush_commands(); self } /// Despawns the current entity. pub fn despawn(self) { - debug!("Despawning entity {:?}", self.entity); - self.world.flush_entities(); - let archetype = &self.world.archetypes[self.location.archetype_id]; + let world = self.world; + world.flush_entities(); + let archetype = &world.archetypes[self.location.archetype_id]; if archetype.has_on_remove() { - // SAFETY: None of our oustanding references can be modified through DeferredWorld + // Drop borrow on world so it can be turned into DeferredWorld + // SAFETY: Changes to Archetypes cannot happpen through DeferredWorld + let archetype = { + let archetype: *const Archetype = archetype; + unsafe { &*archetype } + }; + // SAFETY: All components in the archetype exist in world unsafe { - self.world - .as_unsafe_world_cell_readonly() + world + .as_unsafe_world_cell() .into_deferred() .trigger_on_remove(self.entity, archetype.components()); - }; + } } + let archetype = &world.archetypes[self.location.archetype_id]; for component_id in archetype.components() { - self.world - .removed_components - .send(component_id, self.entity); + world.removed_components.send(component_id, self.entity); } - let world = self.world; let location = world .entities .free(self.entity) @@ -979,7 +991,13 @@ impl<'w> EntityWorldMut<'w> { world.archetypes[moved_location.archetype_id] .set_entity_table_row(moved_location.archetype_row, table_row); } - world.flush_commands(); + world.flush_commands() + } + + /// Ensures any commands triggered by the actions of Self are applied, equivalent to [`World::flush_commands`] + pub fn flush(self) -> Entity { + self.world.flush_commands(); + self.entity } /// Gets read-only access to the world that the current entity belongs to. diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index b46e262a4add0..e03a20d525dc6 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1457,8 +1457,6 @@ impl World { let bundle_id = self .bundles .init_info::(&mut self.components, &mut self.storages); - // SAFETY: We just ensured this bundle exists - let bundle_info = unsafe { self.bundles.get_unchecked(bundle_id) }; enum SpawnOrInsert<'w> { Spawn(BundleSpawner<'w>), Insert(BundleInserter<'w>, ArchetypeId), @@ -1472,11 +1470,10 @@ impl World { } } } - let mut spawn_or_insert = SpawnOrInsert::Spawn(BundleSpawner::new_with_info( - self.as_unsafe_world_cell_readonly(), - bundle_info, - change_tick, - )); + // SAFETY: we initialized this bundle_id in `init_info` + let mut spawn_or_insert = SpawnOrInsert::Spawn(unsafe { + BundleSpawner::new_with_id(self, bundle_id, change_tick) + }); let mut invalid_entities = Vec::new(); for (entity, bundle) in iter { @@ -1493,12 +1490,15 @@ impl World { unsafe { inserter.insert(entity, location, bundle) }; } _ => { - let mut inserter = BundleInserter::new_with_info( - self.as_unsafe_world_cell_readonly(), - location.archetype_id, - bundle_info, - change_tick, - ); + // SAFETY: we initialized this bundle_id in `init_info` + let mut inserter = unsafe { + BundleInserter::new_with_id( + self, + location.archetype_id, + bundle_id, + change_tick, + ) + }; // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter unsafe { inserter.insert(entity, location, bundle) }; spawn_or_insert = @@ -1511,11 +1511,9 @@ impl World { // SAFETY: `entity` is allocated (but non existent), bundle matches inserter unsafe { spawner.spawn_non_existent(entity, bundle) }; } else { - let mut spawner = BundleSpawner::new_with_info( - self.as_unsafe_world_cell_readonly(), - bundle_info, - change_tick, - ); + // SAFETY: we initialized this bundle_id in `init_info` + let mut spawner = + unsafe { BundleSpawner::new_with_id(self, bundle_id, change_tick) }; // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter unsafe { spawner.spawn_non_existent(entity, bundle) }; spawn_or_insert = SpawnOrInsert::Spawn(spawner); diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 173d56576e444..f5dd64c72530b 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -14,7 +14,7 @@ use crate::{ prelude::Component, removal_detection::RemovedComponentEvents, storage::{Column, ComponentSparseSet, Storages}, - system::{CommandQueue, Resource}, + system::Resource, }; use bevy_ptr::Ptr; use std::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData}; @@ -566,16 +566,6 @@ impl<'w> UnsafeWorldCell<'w> { .get(component_id)? .get_with_ticks() } - - // Returns a mutable reference to worlds internal [`CommandQueue`] - /// # Safety - /// It is the callers responsibility to ensure that no mutable references exist to the command queue at the same time - #[inline] - pub(crate) unsafe fn get_command_queue(self) -> &'w mut CommandQueue { - // SAFETY: caller ensures there is no `&mut World` - let world = unsafe { &mut *self.0 }; - &mut world.command_queue - } } impl Debug for UnsafeWorldCell<'_> { diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs index 8e8e63de0322f..78deb6f2f5966 100644 --- a/examples/ecs/component_hooks.rs +++ b/examples/ecs/component_hooks.rs @@ -1,42 +1,81 @@ -use std::collections::HashSet; +//! This examples illustrates the different ways you can employ component lifecycle hooks use bevy::prelude::*; +use std::collections::HashMap; #[derive(Component, Debug)] -struct MyComponent(usize); +struct MyComponent(KeyCode); #[derive(Resource, Default, Debug, Deref, DerefMut)] -struct MyComponentIndex(HashSet); +struct MyComponentIndex(HashMap); + +#[derive(Event)] +struct MyEvent; fn main() { App::new() - .add_systems(Startup, (setup, trigger_hooks).chain()) + .add_plugins(DefaultPlugins) + .add_systems(Startup, setup) + .add_systems(Update, trigger_hooks) .init_resource::() + .add_event::() .run(); } fn setup(world: &mut World) { + // In order to register component hooks it must be the first time you register the component + // This is to prevent users from overwriting hooks that may be used internally in foreign crates world .register_component::() - .on_add(|mut world, entity, _| { - println!("Added MyComponent to: {:?}", entity); - world.resource_mut::().insert(entity); + // There are 3 component lifecyle hooks: `on_add`, `on_insert` and `on_remove` + // A hook has 3 arguments: + // - a `DeferredWorld`, this allows access to resource and component data as well as `Commands` + // - the entity that triggered the hook + // - the component id of the triggering component, this is mostly used for dynamic components + // + // `on_add` will trigger when a component is inserted onto an entity without it + .on_add(|mut world, entity, component_id| { + // You can access component data from within the hook + let value = world.get::(entity).unwrap().0; + println!( + "Component: {:?} added to: {:?} with value {:?}", + component_id, entity, value + ); + // Or access resources + world + .resource_mut::() + .insert(value, entity); + // Or send events + world.send_event(MyEvent); + }) + // `on_insert` will trigger when a component is inserted onto an entity, + // regardless of whether or not it already had it and after `on_add` if it ran + .on_insert(|world, _, _| { + println!("Current Index: {:?}", world.resource::()); }) - .on_remove(|mut world, entity, _| { + // `on_remove` will trigger when a component is removed from an entity, + // since it runs before the component is removed you can still access the component data + .on_remove(|mut world, entity, component_id| { + let value = world.get::(entity).unwrap().0; println!( - "Removed MyComponent from: {:?} {:?}", - entity, - world.get::(entity) + "Component: {:?} removed from: {:?} with value {:?}", + component_id, entity, value ); - let mut index = world.resource_mut::(); - index.remove(&entity); - println!("Current index: {:?}", *index); + world.resource_mut::().remove(&value); + // You can also issue commands through `.with_commands` + world.with_commands(|mut commands| { + commands.entity(entity).despawn(); + }); }); } -fn trigger_hooks(mut commands: Commands) { - let entity_a = commands.spawn(MyComponent(0)).id(); - let entity_b = commands.spawn(MyComponent(1)).id(); - commands.entity(entity_b).despawn(); - commands.entity(entity_a).despawn(); +fn trigger_hooks(mut commands: Commands, keys: Res>, index: Res) { + for (key, entity) in index.iter() { + if !keys.pressed(*key) { + commands.entity(*entity).remove::(); + } + } + for key in keys.get_just_pressed() { + commands.spawn(MyComponent(*key)); + } } From 6589b9f8950901daea2a5d23640ddce39d595728 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sun, 26 Nov 2023 16:38:13 -0800 Subject: [PATCH 008/109] Add missing cfg(test) --- crates/bevy_ecs/src/bundle.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index a32f98a9b0ce6..5a7c793f2663c 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -1112,6 +1112,7 @@ fn initialize_dynamic_bundle( (id, storage_types) } +#[cfg(test)] mod tests { use crate as bevy_ecs; use crate::prelude::*; From 43d7c744b1b3a5cffc323ddef54821d6da4b96cb Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sun, 26 Nov 2023 16:43:28 -0800 Subject: [PATCH 009/109] Tidy iterator chains --- crates/bevy_ecs/src/bundle.rs | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 5a7c793f2663c..9ee95c210567b 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -658,11 +658,9 @@ impl<'w> BundleInserter<'w> { entity, bundle_info .iter_components() - .enumerate() - .filter(|(index, _)| { - add_bundle.get_status(*index) == ComponentStatus::Added - }) - .map(|(_, id)| id), + .zip(add_bundle.bundle_status.iter()) + .filter(|(_, &status)| status == ComponentStatus::Added) + .map(|(id, _)| id), ); } if archetype.has_on_insert() { @@ -718,11 +716,9 @@ impl<'w> BundleInserter<'w> { entity, bundle_info .iter_components() - .enumerate() - .filter(|(index, _)| { - add_bundle.get_status(*index) == ComponentStatus::Added - }) - .map(|(_, id)| id), + .zip(add_bundle.bundle_status.iter()) + .filter(|(_, &status)| status == ComponentStatus::Added) + .map(|(id, _)| id), ); } if new_archetype.has_on_insert() { @@ -817,11 +813,9 @@ impl<'w> BundleInserter<'w> { entity, bundle_info .iter_components() - .enumerate() - .filter(|(index, _)| { - add_bundle.get_status(*index) == ComponentStatus::Added - }) - .map(|(_, id)| id), + .zip(add_bundle.bundle_status.iter()) + .filter(|(_, &status)| status == ComponentStatus::Added) + .map(|(id, _)| id), ); } if new_archetype.has_on_insert() { From 3d5503799e83d5f71095bde25ee9fbdba1d35078 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sun, 26 Nov 2023 17:00:51 -0800 Subject: [PATCH 010/109] Cleanup --- Cargo.toml | 2 +- crates/bevy_ecs/src/bundle.rs | 2 +- crates/bevy_ecs/src/query/builder.rs | 422 ------------------ crates/bevy_ecs/src/system/system_registry.rs | 4 - crates/bevy_ecs/src/world/deferred_world.rs | 6 +- crates/bevy_ecs/src/world/entity_ref.rs | 13 +- crates/bevy_ecs/src/world/mod.rs | 3 +- 7 files changed, 18 insertions(+), 434 deletions(-) delete mode 100644 crates/bevy_ecs/src/query/builder.rs diff --git a/Cargo.toml b/Cargo.toml index d1a11b330a92b..16d931449ecb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1296,7 +1296,7 @@ doc-scrape-examples = true [package.metadata.example.component_hooks] name = "Component Hooks" -description = "Define component hooks to react to ECS events" +description = "Define component hooks to manage component lifecycle events" category = "ECS (Entity Component System)" wasm = false diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 9ee95c210567b..f8b563bdf9325 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -619,7 +619,7 @@ impl<'w> BundleInserter<'w> { } } /// # Safety - /// `entity` must currently exist in the source archetype for this inserter. `archetype_row` + /// `entity` must currently exist in the source archetype for this inserter. `location` /// must be `entity`'s location in the archetype. `T` must match this [`BundleInfo`]'s type #[inline] pub(crate) unsafe fn insert( diff --git a/crates/bevy_ecs/src/query/builder.rs b/crates/bevy_ecs/src/query/builder.rs deleted file mode 100644 index d1beaa513b55f..0000000000000 --- a/crates/bevy_ecs/src/query/builder.rs +++ /dev/null @@ -1,422 +0,0 @@ -use std::marker::PhantomData; - -use crate::{component::ComponentId, prelude::*}; - -use super::{FilteredAccess, ReadOnlyWorldQuery, WorldQuery}; - -/// Builder struct to create [`QueryState`] instances at runtime. -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # -/// # #[derive(Component)] -/// # struct A; -/// # -/// # #[derive(Component)] -/// # struct B; -/// # -/// # #[derive(Component)] -/// # struct C; -/// # -/// let mut world = World::new(); -/// let entity_a = world.spawn((A, B)).id(); -/// let entity_b = world.spawn((A, C)).id(); -/// -/// // Instantiate the builder using the type signature of the iterator you will consume -/// let mut query = QueryBuilder::<(Entity, &B)>::new(&mut world) -/// // Add additional terms through builder methods -/// .with::() -/// .without::() -/// .build(); -/// -/// // Consume the QueryState -/// let (entity, b) = query.single(&world); -///``` -pub struct QueryBuilder<'w, Q: WorldQuery = (), F: ReadOnlyWorldQuery = ()> { - access: FilteredAccess, - world: &'w mut World, - or: bool, - first: bool, - _marker: PhantomData<(Q, F)>, -} - -impl<'w, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryBuilder<'w, Q, F> { - /// Creates a new builder with the accesses required for `Q` and `F` - pub fn new(world: &'w mut World) -> Self { - let fetch_state = Q::init_state(world); - let filter_state = F::init_state(world); - - let mut access = FilteredAccess::default(); - Q::update_component_access(&fetch_state, &mut access); - - // Use a temporary empty FilteredAccess for filters. This prevents them from conflicting with the - // main Query's `fetch_state` access. Filters are allowed to conflict with the main query fetch - // because they are evaluated *before* a specific reference is constructed. - let mut filter_access = FilteredAccess::default(); - F::update_component_access(&filter_state, &mut filter_access); - - // Merge the temporary filter access with the main access. This ensures that filter access is - // properly considered in a global "cross-query" context (both within systems and across systems). - access.extend(&filter_access); - - Self { - access, - world, - or: false, - first: false, - _marker: PhantomData, - } - } - - /// Returns a reference to the world passed to [`Self::new`]. - pub fn world(&self) -> &World { - self.world - } - - /// Returns a reference to the world passed to [`Self::new`]. - pub fn world_mut(&mut self) -> &mut World { - self.world - } - - /// Adds access to self's underlying [`FilteredAccess`] respecting [`Self::or`] and [`Self::and`] - pub fn extend_access(&mut self, mut access: FilteredAccess) { - if self.or { - if self.first { - access.required.clear(); - self.access.extend(&access); - self.first = false; - } else { - self.access.append_or(&access); - } - } else { - self.access.extend(&access); - } - } - - /// Adds accesses required for `T` to self. - pub fn push(&mut self) -> &mut Self { - let state = T::init_state(self.world); - let mut access = FilteredAccess::default(); - T::update_component_access(&state, &mut access); - self.extend_access(access); - self - } - - /// Adds [`With`] to the [`FilteredAccess`] of self. - pub fn with(&mut self) -> &mut Self { - self.push::>(); - self - } - - /// Adds [`With`] to the [`FilteredAccess`] of self from a runtime [`ComponentId`]. - pub fn with_id(&mut self, id: ComponentId) -> &mut Self { - let mut access = FilteredAccess::default(); - access.and_with(id); - self.extend_access(access); - self - } - - /// Adds [`Without`] to the [`FilteredAccess`] of self. - pub fn without(&mut self) -> &mut Self { - self.push::>(); - self - } - - /// Adds [`Without`] to the [`FilteredAccess`] of self from a runtime [`ComponentId`]. - pub fn without_id(&mut self, id: ComponentId) -> &mut Self { - let mut access = FilteredAccess::default(); - access.and_without(id); - self.extend_access(access); - self - } - - /// Adds `&T` to the [`FilteredAccess`] of self. - pub fn ref_id(&mut self, id: ComponentId) -> &mut Self { - self.with_id(id); - self.access.add_read(id); - self - } - - /// Adds `&mut T` to the [`FilteredAccess`] of self. - pub fn mut_id(&mut self, id: ComponentId) -> &mut Self { - self.with_id(id); - self.access.add_write(id); - self - } - - /// Takes a function over mutable access to a [`QueryBuilder`], calls that function - /// on an empty builder and then adds all accesses from that builder to self as optional. - pub fn optional(&mut self, f: impl Fn(&mut QueryBuilder)) -> &mut Self { - let mut builder = QueryBuilder::new(self.world); - f(&mut builder); - self.access.extend_access(builder.access()); - self - } - - /// Takes a function over mutable access to a [`QueryBuilder`], calls that function - /// on an empty builder and then adds all accesses from that builder to self. - /// - /// Primarily used when inside a [`Self::or`] closure to group several terms. - pub fn and(&mut self, f: impl Fn(&mut QueryBuilder)) -> &mut Self { - let mut builder = QueryBuilder::new(self.world); - f(&mut builder); - let access = builder.access().clone(); - self.extend_access(access); - self - } - - /// Takes a function over mutable access to a [`QueryBuilder`], calls that function - /// on an empty builder, all accesses added to that builder will become terms in an or expression. - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # #[derive(Component)] - /// # struct A; - /// # - /// # #[derive(Component)] - /// # struct B; - /// # - /// # let mut world = World::new(); - /// # - /// QueryBuilder::::new(&mut world).or(|builder| { - /// builder.with::(); - /// builder.with::(); - /// }); - /// // is equivalent to - /// QueryBuilder::::new(&mut world).push::, With)>>(); - /// ``` - pub fn or(&mut self, f: impl Fn(&mut QueryBuilder)) -> &mut Self { - let mut builder = QueryBuilder::new(self.world); - builder.or = true; - builder.first = true; - f(&mut builder); - self.access.extend(builder.access()); - self - } - - /// Returns a reference to the the [`FilteredAccess`] that will be provided to the built [`Query`]. - pub fn access(&self) -> &FilteredAccess { - &self.access - } - - /// Transmute the existing builder adding required accesses. - /// This will maintain all exisiting accesses. - /// - /// If including a filter type see [`Self::transmute_filtered`] - pub fn transmute(&mut self) -> &mut QueryBuilder<'w, NewQ> { - self.transmute_filtered::() - } - - /// Transmute the existing builder adding required accesses. - /// This will maintain all existing accesses. - pub fn transmute_filtered( - &mut self, - ) -> &mut QueryBuilder<'w, NewQ, NewF> { - let mut fetch_state = NewQ::init_state(self.world); - let filter_state = NewF::init_state(self.world); - - NewQ::set_access(&mut fetch_state, &self.access); - - let mut access = FilteredAccess::default(); - NewQ::update_component_access(&fetch_state, &mut access); - NewF::update_component_access(&filter_state, &mut access); - - self.extend_access(access); - // SAFETY: - // - We have included all required acceses for NewQ and NewF - // - The layout of all QueryBuilder instances is the same - unsafe { std::mem::transmute(self) } - } - - /// Create a [`QueryState`] with the accesses of the builder. - pub fn build(&mut self) -> QueryState { - QueryState::::from_builder(self) - } -} - -#[cfg(test)] -mod tests { - use crate as bevy_ecs; - use crate::prelude::*; - use crate::world::FilteredEntityRef; - - use super::QueryBuilder; - - #[derive(Component, PartialEq, Debug)] - struct A(usize); - - #[derive(Component, PartialEq, Debug)] - struct B(usize); - - #[derive(Component, PartialEq, Debug)] - struct C(usize); - - #[test] - fn builder_with_without_static() { - let mut world = World::new(); - let entity_a = world.spawn((A(0), B(0))).id(); - let entity_b = world.spawn((A(0), C(0))).id(); - - let mut query_a = QueryBuilder::::new(&mut world) - .with::() - .without::() - .build(); - assert_eq!(entity_a, query_a.single(&world)); - - let mut query_b = QueryBuilder::::new(&mut world) - .with::() - .without::() - .build(); - assert_eq!(entity_b, query_b.single(&world)); - } - - #[test] - fn builder_with_without_dynamic() { - let mut world = World::new(); - let entity_a = world.spawn((A(0), B(0))).id(); - let entity_b = world.spawn((A(0), C(0))).id(); - let component_id_a = world.init_component::().id(); - let component_id_b = world.init_component::().id(); - let component_id_c = world.init_component::().id(); - - let mut query_a = QueryBuilder::::new(&mut world) - .with_id(component_id_a) - .without_id(component_id_c) - .build(); - assert_eq!(entity_a, query_a.single(&world)); - - let mut query_b = QueryBuilder::::new(&mut world) - .with_id(component_id_a) - .without_id(component_id_b) - .build(); - assert_eq!(entity_b, query_b.single(&world)); - } - - #[test] - fn builder_or() { - let mut world = World::new(); - world.spawn((A(0), B(0))); - world.spawn(B(0)); - world.spawn(C(0)); - - let mut query_a = QueryBuilder::::new(&mut world) - .or(|builder| { - builder.with::(); - builder.with::(); - }) - .build(); - assert_eq!(2, query_a.iter(&world).count()); - - let mut query_b = QueryBuilder::::new(&mut world) - .or(|builder| { - builder.with::(); - builder.without::(); - }) - .build(); - dbg!(&query_b.component_access); - assert_eq!(2, query_b.iter(&world).count()); - - let mut query_c = QueryBuilder::::new(&mut world) - .or(|builder| { - builder.with::(); - builder.with::(); - builder.with::(); - }) - .build(); - assert_eq!(3, query_c.iter(&world).count()); - } - - #[test] - fn builder_transmute() { - let mut world = World::new(); - world.spawn(A(0)); - world.spawn((A(1), B(0))); - let mut query = QueryBuilder::<()>::new(&mut world) - .with::() - .transmute::<&A>() - .build(); - - query.iter(&world).for_each(|a| assert_eq!(a.0, 1)); - } - - #[test] - fn builder_static_components() { - let mut world = World::new(); - let entity = world.spawn((A(0), B(1))).id(); - - let mut query = QueryBuilder::::new(&mut world) - .push::<&A>() - .push::<&B>() - .build(); - - let entity_ref = query.single(&world); - - assert_eq!(entity, entity_ref.id()); - - let a = entity_ref.get::().unwrap(); - let b = entity_ref.get::().unwrap(); - - assert_eq!(0, a.0); - assert_eq!(1, b.0); - } - - #[test] - fn builder_dynamic_components() { - let mut world = World::new(); - let entity = world.spawn((A(0), B(1))).id(); - let component_id_a = world.init_component::().id(); - let component_id_b = world.init_component::().id(); - - let mut query = QueryBuilder::::new(&mut world) - .ref_id(component_id_a) - .ref_id(component_id_b) - .build(); - - let entity_ref = query.single(&world); - - assert_eq!(entity, entity_ref.id()); - - let a = entity_ref.get_by_id(component_id_a).unwrap(); - let b = entity_ref.get_by_id(component_id_b).unwrap(); - - // SAFETY: We set these pointers to point to these components - unsafe { - assert_eq!(0, a.deref::().0); - assert_eq!(1, b.deref::().0); - } - } - - #[test] - fn builder_query_system() { - let mut world = World::new(); - world.spawn(A(0)); - let entity = world.spawn((A(1), B(0))).id(); - - let sys = move |query: Query<(Entity, &A)>| { - let (e, a) = query.single(); - assert_eq!(e, entity); - assert_eq!(1, a.0); - }; - - // Add additional terms that don't appear in the original query - let query = QueryBuilder::<(Entity, &A)>::new(&mut world) - .with::() - .build(); - let mut system = IntoSystem::into_system(sys); - system.initialize(&mut world); - - // SAFETY: We know the system param we are modifying has a compatible type signature - unsafe { system.state_mut().0 = query }; - system.run((), &mut world); - - // Alternatively truncate terms from a query to match the system - let query = QueryBuilder::<(Entity, &A, &B)>::new(&mut world).build(); - let mut system = IntoSystem::into_system(sys); - system.initialize(&mut world); - - // SAFETY: We know the system param we are modifying has a compatible type signature - unsafe { system.state_mut().0 = query.transmute(&world) }; - system.run((), &mut world); - } -} diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index c754a7d77b652..54e8e6e177dc8 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -280,10 +280,6 @@ impl World { .take::>() .ok_or(RegisteredSystemError::Recursive(id))?; - // Flush commands from removing component - // TODO: Consider refactoring to an RegisteredSystem to an Option to avoid removal - drop(entity); - // run the system if !initialized { system.initialize(self); diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 1f688f15e3911..da1d30e6f67d4 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -12,8 +12,8 @@ use crate::{ use super::{unsafe_world_cell::UnsafeWorldCell, Mut, World}; -/// An [`World`] reference that prevents structural ECS changes. -/// This includes creating tables, registering components or spawning entities. +/// A [`World`] reference that disallows structural ECS changes. +/// This includes initializing resources, registering components or spawning entities. pub struct DeferredWorld<'w> { world: UnsafeWorldCell<'w>, } @@ -22,7 +22,7 @@ impl<'w> Deref for DeferredWorld<'w> { type Target = World; fn deref(&self) -> &Self::Target { - // SAFETY: &self ensures ther are no active mutable borrows + // SAFETY: &self ensures there are no active mutable borrows unsafe { self.world.world() } } } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 49a74a0bccf6a..b115c943e09a3 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -4,6 +4,7 @@ use crate::{ change_detection::MutUntyped, component::{Component, ComponentId, ComponentTicks, Components, StorageType}, entity::{Entities, Entity, EntityLocation}, + removal_detection::RemovedComponentEvents, storage::Storages, world::{Mut, World}, }; @@ -712,6 +713,7 @@ impl<'w> EntityWorldMut<'w> { let storages = &mut world.storages; let components = &mut world.components; let entities = &mut world.entities; + let removed_components = &mut world.removed_components; let entity = self.entity; let mut bundle_components = bundle_info.iter_components(); @@ -724,7 +726,14 @@ impl<'w> EntityWorldMut<'w> { // - entity location is valid // - table row is removed below, without dropping the contents // - `components` comes from the same world as `storages` - take_component(storages, components, component_id, entity, old_location) + take_component( + storages, + components, + removed_components, + component_id, + entity, + old_location, + ) }) }; @@ -1632,12 +1641,14 @@ fn sorted_remove(source: &mut Vec, remove: &[T]) { pub(crate) unsafe fn take_component<'a>( storages: &'a mut Storages, components: &Components, + removed_components: &mut RemovedComponentEvents, component_id: ComponentId, entity: Entity, location: EntityLocation, ) -> OwningPtr<'a> { // SAFETY: caller promises component_id to be valid let component_info = components.get_info_unchecked(component_id); + removed_components.send(component_id, entity); match component_info.storage_type() { StorageType::Table => { let table = &mut storages.tables[location.table_id]; diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index e03a20d525dc6..d2f87b347c8b6 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1748,8 +1748,7 @@ impl World { } } - /// Attempts to apply the internal [`CommandQueue`] to self - /// Will do nothing if already flushing commands. + /// Applies the internal [`CommandQueue`] to self #[inline] pub fn flush_commands(&mut self) { if !self.command_queue.is_empty() { From 9754ae9bf9062d734d9718ce915233f7ffd3056f Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sun, 26 Nov 2023 17:42:10 -0800 Subject: [PATCH 011/109] CI pass --- crates/bevy_ecs/src/bundle.rs | 4 ++-- crates/bevy_ecs/src/component.rs | 2 +- crates/bevy_ecs/src/world/deferred_world.rs | 1 + crates/bevy_ecs/src/world/entity_ref.rs | 4 ++-- crates/bevy_ecs/src/world/mod.rs | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index f8b563bdf9325..67587f18599d7 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -541,7 +541,7 @@ impl<'w> BundleInserter<'w> { /// Creates a new [`BundleInserter`]. /// /// # Safety - /// Caller must ensure that BundleId exists in `world.bundles` + /// Caller must ensure that `bundle_id` exists in `world.bundles` #[inline] pub(crate) unsafe fn new_with_id( world: &'w mut World, @@ -855,7 +855,7 @@ impl<'w> BundleSpawner<'w> { /// Creates a new [`BundleSpawner`]. /// /// # Safety - /// Caller must ensure that BundleId exists in `world.bundles` + /// Caller must ensure that `bundle_id` exists in `world.bundles` #[inline] pub(crate) unsafe fn new_with_id( world: &'w mut World, diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 3a89a8791468f..814238f702772 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -623,7 +623,7 @@ impl Components { /// #[derive(Component)] /// struct ComponentA; /// - /// let component_a_id = world.init_component::().id(); + /// let component_a_id = world.init_component::(); /// /// assert_eq!(component_a_id, world.components().component_id::().unwrap()) /// ``` diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index da1d30e6f67d4..e3e241a6d43a3 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -31,6 +31,7 @@ impl<'w> DeferredWorld<'w> { /// Creates a [`Commands`] instance that pushes to the world's command queue #[inline] pub fn with_commands(&mut self, f: impl Fn(Commands)) { + // SAFETY: We have exclusive access to the world's command queue let world = unsafe { self.world.world_mut() }; let mut queue = std::mem::take(&mut world.command_queue); f(Commands::new(&mut queue, world)); diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index b115c943e09a3..187effc55d88d 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -691,9 +691,9 @@ impl<'w> EntityWorldMut<'w> { let old_archetype = &world.archetypes[old_location.archetype_id]; // Drop borrow on world so it can be turned into DeferredWorld - // SAFETY: Changes to Bundles cannot happen through DeferredWorld let bundle_info = { let bundle_info: *const BundleInfo = bundle_info; + // SAFETY: Changes to Bundles cannot happen through DeferredWorld unsafe { &*bundle_info } }; if old_archetype.has_on_remove() { @@ -869,9 +869,9 @@ impl<'w> EntityWorldMut<'w> { let old_archetype = &world.archetypes[old_location.archetype_id]; // Drop borrow on world so it can be turned into DeferredWorld - // SAFETY: Changes to Bundles cannot happen through DeferredWorld let bundle_info = { let bundle_info: *const BundleInfo = bundle_info; + // SAFETY: Changes to Bundles cannot happen through DeferredWorld unsafe { &*bundle_info } }; if old_archetype.has_on_remove() { diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index d2f87b347c8b6..403dc1428d57b 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -244,7 +244,7 @@ impl World { /// #[derive(Component)] /// struct ComponentA; /// - /// let component_a_id = world.init_component::().id(); + /// let component_a_id = world.init_component::(); /// /// assert_eq!(component_a_id, world.component_id::().unwrap()) /// ``` From d237d0bc6469fc9ce213bfc6a7134f0e6f5d9c97 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sun, 26 Nov 2023 17:57:56 -0800 Subject: [PATCH 012/109] Re-generate README.md --- crates/bevy_ecs/src/bundle.rs | 4 ++-- examples/README.md | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 67587f18599d7..b61b55983f8ce 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -361,10 +361,10 @@ impl BundleInfo { /// in the [`Bundle`], with respect to the entity's original archetype (prior to the bundle being added) /// For example, if the original archetype already has `ComponentA` and `T` also has `ComponentA`, the status /// should be `Mutated`. If the original archetype does not have `ComponentA`, the status should be `Added`. - /// When "inserting" a bundle into an existing entity, [`AddBundle`](crate::archetype::AddBundle) + /// When "inserting" a bundle into an existing entity, [`AddBundle`] /// should be used, which will report `Added` vs `Mutated` status based on the current archetype's structure. /// When spawning a bundle, [`SpawnBundleStatus`] can be used instead, which removes the need - /// to look up the [`AddBundle`](crate::archetype::AddBundle) in the archetype graph, which requires + /// to look up the [`AddBundle`] in the archetype graph, which requires /// ownership of the entity's current archetype. /// /// `table` must be the "new" table for `entity`. `table_row` must have space allocated for the diff --git a/examples/README.md b/examples/README.md index 0450ef88159b8..fa7b2cc7f727c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -219,6 +219,7 @@ Example | Description --- | --- [Apply System Buffers](../examples/ecs/apply_deferred.rs) | Show how to use `apply_deferred` system [Component Change Detection](../examples/ecs/component_change_detection.rs) | Change detection on components +[Component Hooks](../examples/ecs/component_hooks.rs) | Define component hooks to manage component lifecycle events [Custom Query Parameters](../examples/ecs/custom_query_param.rs) | Groups commonly used compound queries and query filters into a single type [ECS Guide](../examples/ecs/ecs_guide.rs) | Full guide to Bevy's ECS [Event](../examples/ecs/event.rs) | Illustrates event creation, activation, and reception From 79bbe6791001a480e060434f4f68f716b3a2a0bd Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sun, 26 Nov 2023 18:08:13 -0800 Subject: [PATCH 013/109] Add missing safety comment --- crates/bevy_ecs/src/world/entity_ref.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 187effc55d88d..93d47d3e7ae8d 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -925,9 +925,9 @@ impl<'w> EntityWorldMut<'w> { let archetype = &world.archetypes[self.location.archetype_id]; if archetype.has_on_remove() { // Drop borrow on world so it can be turned into DeferredWorld - // SAFETY: Changes to Archetypes cannot happpen through DeferredWorld let archetype = { let archetype: *const Archetype = archetype; + // SAFETY: Changes to Archetypes cannot happpen through DeferredWorld unsafe { &*archetype } }; // SAFETY: All components in the archetype exist in world @@ -1000,7 +1000,7 @@ impl<'w> EntityWorldMut<'w> { world.archetypes[moved_location.archetype_id] .set_entity_table_row(moved_location.archetype_row, table_row); } - world.flush_commands() + world.flush_commands(); } /// Ensures any commands triggered by the actions of Self are applied, equivalent to [`World::flush_commands`] From ce371fc1ccc30bb0c52109264d5fa069f5fbf329 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sun, 26 Nov 2023 18:13:48 -0800 Subject: [PATCH 014/109] Simplify asserts --- crates/bevy_ecs/src/bundle.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index b61b55983f8ce..9e989b3c45e43 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -1209,8 +1209,8 @@ mod tests { let entity = world.spawn(A).flush(); let entity = world.get_entity(entity).unwrap(); - assert_eq!(false, entity.contains::()); - assert_eq!(false, entity.contains::()); + assert!(!entity.contains::()); + assert!(!entity.contains::()); assert_eq!(4, world.resource::().0); } From bbecffa76267c2007a20edb555bbb1da7761d5ad Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Mon, 27 Nov 2023 09:17:30 -0800 Subject: [PATCH 015/109] Relax restrictions on register_component --- crates/bevy_ecs/src/component.rs | 21 +++++++++++++++++++++ crates/bevy_ecs/src/world/mod.rs | 7 ++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 814238f702772..17ceab9780a65 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -283,19 +283,40 @@ impl ComponentInfo { } /// Register a [`ComponentHook`] that will be run when this component is added to an entity + /// + /// Will panic if the component already has an `on_add` hook pub fn on_add(&mut self, hook: ComponentHook) -> &mut Self { + assert!( + self.hooks.on_add.is_none(), + "Component id: {:?}, already has an on_add hook", + self.id() + ); self.hooks.on_add = Some(hook); self } /// Register a [`ComponentHook`] that will be run when this component is added or set by `.insert` + /// + /// Will panic if the component already has an `on_insert` hook pub fn on_insert(&mut self, hook: ComponentHook) -> &mut Self { + assert!( + self.hooks.on_insert.is_none(), + "Component id: {:?}, already has an on_insert hook", + self.id() + ); self.hooks.on_insert = Some(hook); self } /// Register a [`ComponentHook`] that will be run when this component is removed from an entity. + /// + /// Will panic if the component already has an `on_remove` hook pub fn on_remove(&mut self, hook: ComponentHook) -> &mut Self { + assert!( + self.hooks.on_remove.is_none(), + "Component id: {:?}, already has an on_remove hook", + self.id() + ); self.hooks.on_remove = Some(hook); self } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 403dc1428d57b..944c2a076141e 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -189,11 +189,10 @@ impl World { /// Initializes a new [`Component`] type and returns a mutable reference to the [`ComponentInfo`] created for it. /// Primarily used for registering hooks. /// - /// Will panic if a component for `T` already exists. + /// Will panic if `T` exists in any archetypes. pub fn register_component(&mut self) -> &mut ComponentInfo { - let type_id = TypeId::of::(); - assert!(self.components.get_id(type_id).is_none(), "Components cannot be registered twice, use init_component instead if this component may already exist in the world."); let index = self.init_component::(); + assert!(!self.archetypes.archetypes.iter().any(|a| a.contains(index)), "Components cannot be registered if they already exist in an archetype, use init_component if {} may already be in use", std::any::type_name::()); // SAFETY: We just created this component unsafe { self.components.get_info_mut(index) } } @@ -217,8 +216,6 @@ impl World { /// Initializes a new [`Component`] type and returns a mutable reference to the [`ComponentInfo`] created for it. /// Primarily used for registering hooks. - /// - /// Will panic if a component for `T` already exists. pub fn register_component_with_descriptor( &mut self, descriptor: ComponentDescriptor, From 3600bee5f23e8d98c370ebf2ab106b92425d6126 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Mon, 27 Nov 2023 14:29:29 -0800 Subject: [PATCH 016/109] Update example comments --- examples/ecs/component_hooks.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs index 78deb6f2f5966..5158e1497d82b 100644 --- a/examples/ecs/component_hooks.rs +++ b/examples/ecs/component_hooks.rs @@ -23,8 +23,10 @@ fn main() { } fn setup(world: &mut World) { - // In order to register component hooks it must be the first time you register the component - // This is to prevent users from overwriting hooks that may be used internally in foreign crates + // In order to register component hooks the component must: + // - not belong to any created archetypes + // - not already have a hook of that kind registered + // This is to prevent overriding hooks defined in plugins and other crates as well as keeping things fast world .register_component::() // There are 3 component lifecyle hooks: `on_add`, `on_insert` and `on_remove` From 1ffe76c7531996fa3bd4730378907f3b187a1627 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Wed, 29 Nov 2023 16:18:13 -0800 Subject: [PATCH 017/109] Initial implementation --- Cargo.toml | 11 + crates/bevy_ecs/src/archetype.rs | 50 +- crates/bevy_ecs/src/bundle.rs | 75 +-- crates/bevy_ecs/src/entity/mod.rs | 17 +- crates/bevy_ecs/src/lib.rs | 2 + crates/bevy_ecs/src/observer.rs | 602 ++++++++++++++++++ crates/bevy_ecs/src/world/deferred_world.rs | 126 +++- crates/bevy_ecs/src/world/entity_constants.rs | 6 + crates/bevy_ecs/src/world/entity_ref.rs | 98 ++- crates/bevy_ecs/src/world/mod.rs | 18 +- .../bevy_ecs/src/world/unsafe_world_cell.rs | 9 + crates/bevy_render/src/lib.rs | 2 +- examples/ecs/observers.rs | 52 ++ 13 files changed, 969 insertions(+), 99 deletions(-) create mode 100644 crates/bevy_ecs/src/observer.rs create mode 100644 crates/bevy_ecs/src/world/entity_constants.rs create mode 100644 examples/ecs/observers.rs diff --git a/Cargo.toml b/Cargo.toml index 16d931449ecb5..4db9c9748a5ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2075,6 +2075,17 @@ description = "Systems run in parallel, but their order isn't always determinist category = "ECS (Entity Component System)" wasm = false +[[example]] +name = "observers" +path = "examples/ecs/observers.rs" +doc-scrape-examples = true + +[package.metadata.example.observers] +name = "Observers" +description = "Define observers to react to ECS events" +category = "ECS (Entity Component System)" +wasm = false + [[example]] name = "3d_rotation" path = "examples/transforms/3d_rotation.rs" diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 309b2fc185593..15e1bc8732bc4 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -23,6 +23,7 @@ use crate::{ bundle::BundleId, component::{ComponentId, Components, StorageType}, entity::{Entity, EntityLocation}, + observer::Observers, storage::{ImmutableSparseSet, SparseArray, SparseSet, SparseSetIndex, TableId, TableRow}, }; use std::{ @@ -116,6 +117,7 @@ pub(crate) enum ComponentStatus { pub(crate) struct AddBundle { pub archetype_id: ArchetypeId, pub bundle_status: Vec, + pub added: Vec, } /// This trait is used to report the status of [`Bundle`](crate::bundle::Bundle) components @@ -199,12 +201,14 @@ impl Edges { bundle_id: BundleId, archetype_id: ArchetypeId, bundle_status: Vec, + added: Vec, ) { self.add_bundle.insert( bundle_id, AddBundle { archetype_id, bundle_status, + added, }, ); } @@ -307,6 +311,9 @@ bitflags::bitflags! { const ON_ADD_HOOK = (1 << 0); const ON_INSERT_HOOK = (1 << 1); const ON_REMOVE_HOOK = (1 << 2); + const ON_ADD_OBSERVER = (1 << 3); + const ON_INSERT_OBSERVER = (1 << 4); + const ON_REMOVE_OBSERVER = (1 << 5); } } @@ -328,6 +335,7 @@ pub struct Archetype { impl Archetype { pub(crate) fn new( components: &Components, + observers: &Observers, id: ArchetypeId, table_id: TableId, table_components: impl Iterator, @@ -341,6 +349,7 @@ impl Archetype { // SAFETY: We are creating an archetype that includes this component so it must exist let info = unsafe { components.get_info_unchecked(component_id) }; info.update_archetype_flags(&mut flags); + observers.update_archetype_flags(component_id, &mut flags); archetype_components.insert( component_id, ArchetypeComponentInfo { @@ -567,21 +576,39 @@ impl Archetype { /// Returns true if any of the components in this archetype have `on_add` hooks #[inline] - pub fn has_on_add(&self) -> bool { + pub fn has_add_hook(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_ADD_HOOK) } /// Returns true if any of the components in this archetype have `on_insert` hooks #[inline] - pub fn has_on_insert(&self) -> bool { + pub fn has_insert_hook(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_INSERT_HOOK) } /// Returns true if any of the components in this archetype have `on_remove` hooks #[inline] - pub fn has_on_remove(&self) -> bool { + pub fn has_remove_hook(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_REMOVE_HOOK) } + + /// Returns true if any of the components in this archetype have `OnAdd` observer + #[inline] + pub fn has_add_observer(&self) -> bool { + self.flags().contains(ArchetypeFlags::ON_ADD_OBSERVER) + } + + /// Returns true if any of the components in this archetype have `OnInsert` observer + #[inline] + pub fn has_insert_observer(&self) -> bool { + self.flags().contains(ArchetypeFlags::ON_INSERT_OBSERVER) + } + + /// Returns true if any of the components in this archetype have `OnRemove` observer + #[inline] + pub fn has_remove_observer(&self) -> bool { + self.flags().contains(ArchetypeFlags::ON_REMOVE_OBSERVER) + } } /// The next [`ArchetypeId`] in an [`Archetypes`] collection. @@ -673,6 +700,7 @@ impl Archetypes { }; archetypes.get_id_or_insert( &Components::default(), + &Observers::default(), TableId::empty(), Vec::new(), Vec::new(), @@ -756,6 +784,7 @@ impl Archetypes { pub(crate) fn get_id_or_insert( &mut self, components: &Components, + observers: &Observers, table_id: TableId, table_components: Vec, sparse_set_components: Vec, @@ -782,6 +811,7 @@ impl Archetypes { (sparse_start..*archetype_component_count).map(ArchetypeComponentId); archetypes.push(Archetype::new( components, + observers, id, table_id, table_components.into_iter().zip(table_archetype_components), @@ -806,6 +836,20 @@ impl Archetypes { archetype.clear_entities(); } } + + pub(crate) fn update_flags( + &mut self, + component_id: ComponentId, + flags: ArchetypeFlags, + set: bool, + ) { + // This is terrible, we need to refactor the component index + for archetype in &mut self.archetypes { + if archetype.contains(component_id) { + archetype.flags.set(flags, set) + } + } + } } impl Index> for Archetypes { diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 9e989b3c45e43..f15c609767a2d 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -12,10 +12,11 @@ use crate::{ }, component::{Component, ComponentId, ComponentStorage, Components, StorageType, Tick}, entity::{Entities, Entity, EntityLocation}, + observer::Observers, prelude::World, query::DebugCheckedUnwrap, storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow}, - world::unsafe_world_cell::UnsafeWorldCell, + world::{unsafe_world_cell::UnsafeWorldCell, ON_ADD, ON_INSERT}, TypeIdMap, }; use bevy_ptr::OwningPtr; @@ -422,6 +423,7 @@ impl BundleInfo { archetypes: &mut Archetypes, storages: &mut Storages, components: &Components, + observers: &Observers, archetype_id: ArchetypeId, ) -> ArchetypeId { if let Some(add_bundle_id) = archetypes[archetype_id].edges().get_add_bundle(self.id) { @@ -430,6 +432,7 @@ impl BundleInfo { let mut new_table_components = Vec::new(); let mut new_sparse_set_components = Vec::new(); let mut bundle_status = Vec::with_capacity(self.component_ids.len()); + let mut added = Vec::new(); let current_archetype = &mut archetypes[archetype_id]; for component_id in self.component_ids.iter().cloned() { @@ -437,6 +440,7 @@ impl BundleInfo { bundle_status.push(ComponentStatus::Mutated); } else { bundle_status.push(ComponentStatus::Added); + added.push(component_id); // SAFETY: component_id exists let component_info = unsafe { components.get_info_unchecked(component_id) }; match component_info.storage_type() { @@ -449,7 +453,7 @@ impl BundleInfo { if new_table_components.is_empty() && new_sparse_set_components.is_empty() { let edges = current_archetype.edges_mut(); // the archetype does not change when we add this bundle - edges.insert_add_bundle(self.id, archetype_id, bundle_status); + edges.insert_add_bundle(self.id, archetype_id, bundle_status, added); archetype_id } else { let table_id; @@ -487,6 +491,7 @@ impl BundleInfo { }; let new_archetype_id = archetypes.get_id_or_insert( components, + observers, table_id, table_components, sparse_set_components, @@ -496,6 +501,7 @@ impl BundleInfo { self.id, new_archetype_id, bundle_status, + added, ); new_archetype_id } @@ -556,6 +562,7 @@ impl<'w> BundleInserter<'w> { &mut world.archetypes, &mut world.storages, &world.components, + &world.observers, archetype_id, ); if new_archetype_id == archetype_id { @@ -653,19 +660,15 @@ impl<'w> BundleInserter<'w> { } // SAFETY: We have no oustanding mutable references to world as they were dropped let mut world = self.world.into_deferred(); - if archetype.has_on_add() { - world.trigger_on_add( - entity, - bundle_info - .iter_components() - .zip(add_bundle.bundle_status.iter()) - .filter(|(_, &status)| status == ComponentStatus::Added) - .map(|(id, _)| id), - ); + world.trigger_on_add(archetype, entity, add_bundle.added.iter().cloned()); + if archetype.has_add_observer() { + world.trigger_observers(ON_ADD, entity, add_bundle.added.iter().cloned()); } - if archetype.has_on_insert() { - world.trigger_on_insert(entity, bundle_info.iter_components()); + world.trigger_on_insert(archetype, entity, bundle_info.iter_components()); + if archetype.has_insert_observer() { + world.trigger_observers(ON_INSERT, entity, bundle_info.iter_components()); } + location } InsertBundleResult::NewArchetypeSameTable { new_archetype } => { @@ -711,18 +714,13 @@ impl<'w> BundleInserter<'w> { // SAFETY: We have no oustanding mutable references to world as they were dropped let new_archetype = &**new_archetype; let mut world = self.world.into_deferred(); - if new_archetype.has_on_add() { - world.trigger_on_add( - entity, - bundle_info - .iter_components() - .zip(add_bundle.bundle_status.iter()) - .filter(|(_, &status)| status == ComponentStatus::Added) - .map(|(id, _)| id), - ); + world.trigger_on_add(archetype, entity, add_bundle.added.iter().cloned()); + if archetype.has_add_observer() { + world.trigger_observers(ON_ADD, entity, add_bundle.added.iter().cloned()); } - if new_archetype.has_on_insert() { - world.trigger_on_insert(entity, bundle_info.iter_components()); + world.trigger_on_insert(new_archetype, entity, bundle_info.iter_components()); + if archetype.has_insert_observer() { + world.trigger_observers(ON_INSERT, entity, bundle_info.iter_components()); } new_location } @@ -808,18 +806,13 @@ impl<'w> BundleInserter<'w> { // SAFETY: We have no oustanding mutable references to world as they were dropped let new_archetype = &**new_archetype; let mut world = self.world.into_deferred(); - if new_archetype.has_on_add() { - world.trigger_on_add( - entity, - bundle_info - .iter_components() - .zip(add_bundle.bundle_status.iter()) - .filter(|(_, &status)| status == ComponentStatus::Added) - .map(|(id, _)| id), - ); + world.trigger_on_add(new_archetype, entity, add_bundle.added.iter().cloned()); + if new_archetype.has_add_observer() { + world.trigger_observers(ON_ADD, entity, add_bundle.added.iter().cloned()) } - if new_archetype.has_on_insert() { - world.trigger_on_insert(entity, bundle_info.iter_components()); + world.trigger_on_insert(new_archetype, entity, bundle_info.iter_components()); + if new_archetype.has_insert_observer() { + world.trigger_observers(ON_INSERT, entity, bundle_info.iter_components()) } new_location } @@ -867,6 +860,7 @@ impl<'w> BundleSpawner<'w> { &mut world.archetypes, &mut world.storages, &world.components, + &world.observers, ArchetypeId::EMPTY, ); let archetype = &mut world.archetypes[new_archetype_id]; @@ -924,13 +918,14 @@ impl<'w> BundleSpawner<'w> { // SAFETY: We have no oustanding mutable references to world as they were dropped let archetype = &*self.archetype; let mut world = self.world.into_deferred(); - if archetype.has_on_add() { - world.trigger_on_add(entity, bundle_info.iter_components()); + world.trigger_on_add(archetype, entity, bundle_info.iter_components()); + if archetype.has_add_observer() { + world.trigger_observers(ON_ADD, entity, bundle_info.iter_components()) } - if archetype.has_on_insert() { - world.trigger_on_insert(entity, bundle_info.iter_components()); + world.trigger_on_insert(archetype, entity, bundle_info.iter_components()); + if archetype.has_insert_observer() { + world.trigger_observers(ON_INSERT, entity, bundle_info.iter_components()) } - location } diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index 0723204d70dd0..9513a2949fdda 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -422,6 +422,8 @@ pub struct Entities { free_cursor: AtomicIdCursor, /// Stores the number of free entities for [`len`](Entities::len) len: u32, + /// Count of entities marked as constant, will cause panics on despawn + constant_count: u32, } impl Entities { @@ -431,6 +433,7 @@ impl Entities { pending: Vec::new(), free_cursor: AtomicIdCursor::new(0), len: 0, + constant_count: 0, } } @@ -609,6 +612,9 @@ impl Entities { /// /// Must not be called while reserved entities are awaiting `flush()`. pub fn free(&mut self, entity: Entity) -> Option { + if entity.index < self.constant_count { + panic!("Cannot despawn entities marked as internal"); + } self.verify_flushed(); let meta = &mut self.meta[entity.index as usize]; @@ -650,7 +656,7 @@ impl Entities { /// Clears all [`Entity`] from the World. pub fn clear(&mut self) { - self.meta.clear(); + self.meta.truncate(self.constant_count as usize); self.pending.clear(); *self.free_cursor.get_mut() = 0; self.len = 0; @@ -704,6 +710,10 @@ impl Entities { } } + pub(crate) fn set_constant(&mut self) { + self.constant_count = self.total_count() as u32; + } + /// Get the [`Entity`] with a given id, if it exists in this [`Entities`] collection /// Returns `None` if this [`Entity`] is outside of the range of currently reserved Entities /// @@ -823,6 +833,11 @@ impl Entities { self.len } + #[inline] + pub fn constant_count(&self) -> u32 { + self.constant_count + } + /// Checks if any entity is currently active. #[inline] pub fn is_empty(&self) -> bool { diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index e298b7cefa317..ebd84305ac784 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -10,6 +10,7 @@ pub mod change_detection; pub mod component; pub mod entity; pub mod event; +pub mod observer; pub mod query; #[cfg(feature = "bevy_reflect")] pub mod reflect; @@ -35,6 +36,7 @@ pub mod prelude { component::Component, entity::Entity, event::{Event, EventReader, EventWriter, Events}, + observer::{Observer, OnAdd, OnInsert, OnRemove}, query::{Added, AnyOf, Changed, Has, Or, QueryState, With, Without}, removal_detection::RemovedComponents, schedule::{ diff --git a/crates/bevy_ecs/src/observer.rs b/crates/bevy_ecs/src/observer.rs new file mode 100644 index 0000000000000..38e6db9ab3d31 --- /dev/null +++ b/crates/bevy_ecs/src/observer.rs @@ -0,0 +1,602 @@ +//! Types for creating and storing [`Observer`]s + +use std::{any::TypeId, marker::PhantomData}; + +use crate::{ + self as bevy_ecs, + archetype::{ArchetypeFlags, Archetypes}, + query::{DebugCheckedUnwrap, FilteredAccess}, + system::Insert, + world::*, +}; + +use bevy_ptr::PtrMut; +use bevy_utils::{EntityHashMap, HashMap}; + +use crate::{ + component::ComponentId, + prelude::*, + query::{ReadOnlyWorldQuery, WorldQuery}, + world::DeferredWorld, +}; + +pub struct Observer<'w, E, Q: WorldQuery, F: ReadOnlyWorldQuery = ()> { + world: DeferredWorld<'w>, + state: &'w mut ObserverState, + data: &'w mut E, + trigger: ObserverTrigger, +} + +impl<'w, E, Q: WorldQuery, F: ReadOnlyWorldQuery> Observer<'w, E, Q, F> { + pub(crate) fn new( + world: DeferredWorld<'w>, + state: &'w mut ObserverState, + data: &'w mut E, + trigger: ObserverTrigger, + ) -> Self { + Self { + world, + state, + data, + trigger, + } + } + + pub fn event(&self) -> ComponentId { + self.trigger.event + } + + pub fn fetch(&mut self) -> Q::Item<'_> { + let location = self.world.entities.get(self.trigger.source).unwrap(); + let world = self.world.as_unsafe_world_cell(); + unsafe { + let mut fetch = Q::init_fetch( + world, + &self.state.fetch_state, + world.last_change_tick(), + world.change_tick(), + ); + let archetype = world.archetypes().get(location.archetype_id).unwrap(); + let table = world.storages().tables.get(location.table_id).unwrap(); + Q::set_archetype(&mut fetch, &self.state.fetch_state, archetype, table); + Q::fetch(&mut fetch, self.trigger.source, location.table_row) + } + } + + pub fn data(&self) -> &E { + &self.data + } + + pub fn source(&self) -> Entity { + self.trigger.source + } + + pub fn world(&self) -> &DeferredWorld { + &self.world + } + + pub fn world_mut(&mut self) -> &mut DeferredWorld<'w> { + &mut self.world + } +} + +#[derive(Component)] +struct ObserverState { + fetch_state: Q::State, + filter_state: F::State, + component_access: FilteredAccess, + last_event_id: u32, +} + +impl ObserverState { + pub fn new(world: &mut World) -> Self { + let fetch_state = Q::init_state(world); + let filter_state = F::init_state(world); + + let mut component_access = FilteredAccess::default(); + Q::update_component_access(&fetch_state, &mut component_access); + + // Use a temporary empty FilteredAccess for filters. This prevents them from conflicting with the + // main Query's `fetch_state` access. Filters are allowed to conflict with the main query fetch + // because they are evaluated *before* a specific reference is constructed. + let mut filter_component_access = FilteredAccess::default(); + F::update_component_access(&filter_state, &mut filter_component_access); + + // Merge the temporary filter access with the main access. This ensures that filter access is + // properly considered in a global "cross-query" context (both within systems and across systems). + component_access.extend(&filter_component_access); + + Self { + fetch_state, + filter_state, + component_access, + last_event_id: 0, + } + } +} + +pub trait EcsEvent: Component {} + +impl EcsEvent for C {} + +#[derive(Default, Clone, Component)] +pub(crate) struct ObserverDescriptor { + events: Vec, + components: Vec, + sources: Vec, +} + +pub struct ObserverBuilder<'w, E: EcsEvent = NoEvent> { + world: &'w mut World, + descriptor: ObserverDescriptor, + _marker: PhantomData, +} + +impl<'w, E: EcsEvent> ObserverBuilder<'w, E> { + pub fn new(world: &'w mut World) -> Self { + let mut descriptor = ObserverDescriptor::default(); + let event = world.init_component::(); + if event != NO_EVENT { + descriptor.events.push(event); + } + Self { + world, + descriptor, + _marker: PhantomData::default(), + } + } + + // Allows listening for multiple types of events but without passing typed data + pub fn on_event(&mut self) -> &mut ObserverBuilder<'w, NoEvent> { + let type_id = TypeId::of::(); + let event = self.world.init_component::(); + self.descriptor.events.push(event); + // SAFETY: () type will not allow bad memory access as it has no size + unsafe { std::mem::transmute(self) } + } + + pub fn on_event_ids( + &mut self, + events: impl IntoIterator, + ) -> &mut ObserverBuilder<'w, NoEvent> { + self.descriptor.events.extend(events); + // SAFETY: () type will not allow bad memory access as it has no size + unsafe { std::mem::transmute(self) } + } + + pub fn components(&mut self) -> &mut Self { + T::component_ids( + &mut self.world.components, + &mut self.world.storages, + &mut |id| self.descriptor.components.push(id), + ); + self + } + + pub fn component_ids( + &mut self, + ids: impl IntoIterator, + ) -> &mut Self { + self.descriptor.components.extend(ids); + self + } + + pub fn source(&mut self, source: Entity) -> &mut Self { + self.descriptor.sources.push(source); + self + } + + pub fn run( + &mut self, + callback: fn(Observer), + ) -> Entity { + let entity = self.world.spawn_observer(&self.descriptor, callback); + self.world.flush_commands(); + entity + } + + pub fn enqueue( + &mut self, + callback: fn(Observer), + ) -> Entity { + self.world.spawn_observer(&self.descriptor, callback) + } +} + +pub struct ObserverTrigger { + observer: Entity, + event: ComponentId, + source: Entity, +} + +#[derive(Copy, Clone, Debug)] +struct ObserverCallback { + run: fn(DeferredWorld, ObserverTrigger, PtrMut, Option)>), + callback: Option)>, +} + +#[derive(Component)] +pub(crate) struct ObserverComponent { + descriptor: ObserverDescriptor, + runner: ObserverCallback, +} + +impl ObserverComponent { + fn from( + descriptor: ObserverDescriptor, + value: fn(Observer), + ) -> Self { + Self { + descriptor, + runner: ObserverCallback { + run: |mut world, trigger, ptr, callback| { + let callback: fn(Observer) = + unsafe { std::mem::transmute(callback.debug_checked_unwrap()) }; + // let last_event = world.last_event_id; + let mut state = unsafe { + world + .get_mut::>(trigger.observer) + .debug_checked_unwrap() + }; + // if state.last_event_id == last_event { + // return; + // } + + let state: *mut ObserverState = state.as_mut(); + // SAFETY: Pointer is valid as we just created it, ObserverState is a private type and so will not be aliased + let observer = Observer::new( + world, + unsafe { &mut *state }, + unsafe { ptr.deref_mut() }, + trigger, + ); + callback(observer); + }, + callback: Some(unsafe { std::mem::transmute(value) }), + }, + } + } +} + +#[derive(Default, Debug)] +struct CachedObservers { + component_observers: HashMap>, + entity_observers: EntityHashMap>, +} + +#[derive(Default, Debug)] +pub struct Observers { + on_add: CachedObservers, + on_insert: CachedObservers, + on_remove: CachedObservers, + // Map from event type to set of observers + cache: HashMap, +} + +impl Observers { + pub(crate) fn get_observers(&mut self, event: ComponentId) -> &mut CachedObservers { + match event { + ON_ADD => &mut self.on_add, + ON_INSERT => &mut self.on_insert, + ON_REMOVE => &mut self.on_remove, + _ => self.cache.entry(event).or_default(), + } + } + + pub(crate) fn try_get_observers(&self, event: ComponentId) -> Option<&CachedObservers> { + match event { + ON_ADD => Some(&self.on_add), + ON_INSERT => Some(&self.on_insert), + ON_REMOVE => Some(&self.on_remove), + _ => self.cache.get(&event), + } + } + + pub(crate) fn try_get_observers_mut( + &mut self, + event: ComponentId, + ) -> Option<&mut CachedObservers> { + match event { + ON_ADD => Some(&mut self.on_add), + ON_INSERT => Some(&mut self.on_insert), + ON_REMOVE => Some(&mut self.on_remove), + _ => self.cache.get_mut(&event), + } + } + + pub(crate) fn register( + &mut self, + archetypes: &mut Archetypes, + entity: Entity, + observer: &ObserverComponent, + ) { + for &event in &observer.descriptor.events { + let cache = self.get_observers(event); + for &component in &observer.descriptor.components { + let observers = cache.component_observers.entry(component).or_default(); + observers.insert(entity, observer.runner); + if observers.len() == 1 { + if let Some(flag) = Self::is_archetype_cached(event) { + archetypes.update_flags(component, flag, true); + } + } + } + for &source in &observer.descriptor.sources { + let observers = cache.entity_observers.entry(source).or_default(); + observers.insert(entity, observer.runner); + } + } + } + + pub(crate) fn unregister( + &mut self, + archetypes: &mut Archetypes, + entity: Entity, + observer: &ObserverComponent, + ) { + for &event in &observer.descriptor.events { + let Some(cache) = self.try_get_observers_mut(event) else { + continue; + }; + for component in &observer.descriptor.components { + let Some(observers) = cache.component_observers.get_mut(component) else { + continue; + }; + observers.remove(&entity); + if observers.is_empty() { + cache.component_observers.remove(component); + if let Some(flag) = Self::is_archetype_cached(event) { + archetypes.update_flags(*component, flag, false); + } + } + } + for source in &observer.descriptor.sources { + let Some(observers) = cache.entity_observers.get_mut(source) else { + continue; + }; + observers.remove(&entity); + if observers.is_empty() { + cache.entity_observers.remove(source); + } + } + } + } + + pub(crate) fn invoke( + &self, + event: ComponentId, + source: Entity, + components: impl Iterator, + mut world: DeferredWorld, + data: &mut E, + ) { + let Some(observers) = self.try_get_observers(event) else { + return; + }; + if let Some(observers) = observers.entity_observers.get(&source) { + observers.iter().for_each(|(&observer, runner)| { + (runner.run)( + world.clone(), + ObserverTrigger { + observer, + event, + source, + }, + data.into(), + runner.callback, + ); + }); + } + for component in components { + if let Some(observers) = observers.component_observers.get(&component) { + observers.iter().for_each(|(&observer, runner)| { + (runner.run)( + world.clone(), + ObserverTrigger { + observer, + event, + source, + }, + data.into(), + runner.callback, + ); + }); + } + } + } + + pub(crate) fn is_archetype_cached(event: ComponentId) -> Option { + match event { + ON_ADD => Some(ArchetypeFlags::ON_ADD_OBSERVER), + ON_INSERT => Some(ArchetypeFlags::ON_INSERT_OBSERVER), + ON_REMOVE => Some(ArchetypeFlags::ON_REMOVE_OBSERVER), + _ => None, + } + } + + pub(crate) fn update_archetype_flags( + &self, + component_id: ComponentId, + flags: &mut ArchetypeFlags, + ) { + if self.on_add.component_observers.contains_key(&component_id) { + flags.insert(ArchetypeFlags::ON_ADD_OBSERVER); + } + if self + .on_insert + .component_observers + .contains_key(&component_id) + { + flags.insert(ArchetypeFlags::ON_INSERT_OBSERVER); + } + if self + .on_remove + .component_observers + .contains_key(&component_id) + { + flags.insert(ArchetypeFlags::ON_REMOVE_OBSERVER); + } + } +} + +#[derive(Component)] +pub struct OnAdd; + +#[derive(Component)] +pub struct OnInsert; + +#[derive(Component)] +pub struct OnRemove; + +#[derive(Component)] +pub struct NoEvent; + +#[derive(Component)] +pub(crate) struct AttachObserver(pub(crate) Entity); + +#[derive(Component, Default)] +pub(crate) struct ObservedBy(Vec); + +pub struct EventBuilder<'w, E> { + event: ComponentId, + world: &'w mut World, + data: E, + targets: Vec, + components: Vec, +} + +impl<'w, E: EcsEvent> EventBuilder<'w, E> { + pub fn new(world: &'w mut World, data: E) -> Self { + let event = world.init_component::(); + Self { + event, + world, + data, + targets: Vec::new(), + components: Vec::new(), + } + } + + pub fn target(&mut self, target: Entity) -> &mut Self { + self.targets.push(target); + self + } + + pub fn emit(&mut self) { + let mut world = unsafe { self.world.as_unsafe_world_cell().into_deferred() }; + for &target in &self.targets { + unsafe { + world.trigger_observers_with_data( + self.event, + target, + self.components.iter().cloned(), + &mut self.data, + ) + } + } + } +} + +impl World { + pub(crate) fn bootstrap_observers(&mut self) { + assert_eq!(NO_EVENT, self.init_component::()); + assert_eq!(ON_ADD, self.init_component::()); + assert_eq!(ON_INSERT, self.init_component::()); + assert_eq!(ON_REMOVE, self.init_component::()); + self.register_component::() + .on_add(|mut world, entity, _| { + let (world, archetypes, observers) = unsafe { + let world = world.as_unsafe_world_cell(); + ( + world.into_deferred(), + world.archetypes_mut(), + world.observers_mut(), + ) + }; + + let observer = world.get::(entity).unwrap(); + observers.register(archetypes, entity, observer); + }) + .on_remove(|mut world, entity, _| { + let (world, archetypes, observers) = unsafe { + let world = world.as_unsafe_world_cell(); + ( + world.into_deferred(), + world.archetypes_mut(), + world.observers_mut(), + ) + }; + + let observer = world.get::(entity).unwrap(); + observers.unregister(archetypes, entity, observer); + }); + + self.register_component::() + .on_add(|mut world, entity, _| { + let observer = world.get::(entity).unwrap().0; + world.with_commands(|mut commands| { + commands.entity(entity).remove::(); + }); + match world.get_mut::(entity) { + Some(mut o) => o.0.push(observer), + None => world.with_commands(|mut commands| { + commands.entity(entity).insert(ObservedBy(vec![observer])); + }), + } + }); + + self.register_component::() + .on_remove(|mut world, entity, _| { + let observed_by = + std::mem::take(world.get_mut::(entity).unwrap().as_mut()); + world.with_commands(|mut commands| { + observed_by.0.iter().for_each(|&e| { + commands.entity(e).despawn(); + }) + }) + }); + } + + pub fn observer_builder(&mut self) -> ObserverBuilder { + ObserverBuilder::new(self) + } + + pub fn observer( + &mut self, + callback: fn(Observer), + ) -> Entity { + ObserverBuilder::new(self).run(callback) + } + + pub fn ecs_event(&mut self, event: E) -> EventBuilder { + EventBuilder::new(self, event) + } + + pub(crate) fn spawn_observer< + E: EcsEvent, + Q: WorldQuery + 'static, + F: ReadOnlyWorldQuery + 'static, + >( + &mut self, + descriptor: &ObserverDescriptor, + callback: fn(Observer), + ) -> Entity { + let mut descriptor = descriptor.clone(); + let iterator_state = ObserverState::::new(self); + if descriptor.components.is_empty() && descriptor.sources.is_empty() { + descriptor + .components + .extend(iterator_state.component_access.access().reads_and_writes()); + } + let entity = self.entities.reserve_entity(); + self.command_queue.push(Insert { + entity, + bundle: ( + iterator_state, + ObserverComponent::from(descriptor, callback), + ), + }); + + entity + } +} diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index e3e241a6d43a3..8c501e3dc8055 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -1,6 +1,7 @@ use std::ops::Deref; use crate::{ + archetype::Archetype, change_detection::MutUntyped, component::ComponentId, entity::Entity, @@ -10,7 +11,10 @@ use crate::{ system::{Commands, Query, Resource}, }; -use super::{unsafe_world_cell::UnsafeWorldCell, Mut, World}; +use super::{ + unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell}, + EntityMut, Mut, World, +}; /// A [`World`] reference that disallows structural ECS changes. /// This includes initializing resources, registering components or spawning entities. @@ -28,6 +32,11 @@ impl<'w> Deref for DeferredWorld<'w> { } impl<'w> DeferredWorld<'w> { + #[inline] + pub fn clone(&mut self) -> DeferredWorld { + DeferredWorld { world: self.world } + } + /// Creates a [`Commands`] instance that pushes to the world's command queue #[inline] pub fn with_commands(&mut self, f: impl Fn(Commands)) { @@ -49,6 +58,31 @@ impl<'w> DeferredWorld<'w> { unsafe { self.world.get_entity(entity)?.get_mut() } } + #[inline] + pub fn get_entity_mut(&mut self, entity: Entity) -> Option { + let location = self.entities.get(entity)?; + // SAFETY: if the Entity is invalid, the function returns early. + // Additionally, Entities::get(entity) returns the correct EntityLocation if the entity exists. + let entity_cell = UnsafeEntityCell::new(self.as_unsafe_world_cell(), entity, location); + // SAFETY: The UnsafeEntityCell has read access to the entire world. + let entity_ref = unsafe { EntityMut::new(entity_cell) }; + Some(entity_ref) + } + + #[inline] + pub fn entity_mut(&mut self, entity: Entity) -> EntityMut { + #[inline(never)] + #[cold] + fn panic_no_entity(entity: Entity) -> ! { + panic!("Entity {entity:?} does not exist"); + } + + match self.get_entity_mut(entity) { + Some(entity) => entity, + None => panic_no_entity(entity), + } + } + /// Returns [`Query`] for the given [`QueryState`], which is used to efficiently /// run queries on the [`World`] by storing and reusing the [`QueryState`]. /// @@ -213,62 +247,116 @@ impl<'w> DeferredWorld<'w> { unsafe { self.world.get_entity(entity)?.get_mut_by_id(component_id) } } - /// Triggers all `on_add` hooks for [`ComponentId`] in target. + /// Triggers all `OnAdd` hooks and observers for [`ComponentId`] in target. /// /// # Safety /// Caller must ensure [`ComponentId`] in target exist in self. #[inline] pub(crate) unsafe fn trigger_on_add( &mut self, + archetype: &Archetype, entity: Entity, targets: impl Iterator, ) { - for component_id in targets { - // SAFETY: Caller ensures that these components exist - let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks(); - if let Some(hook) = hooks.on_add { - hook(DeferredWorld { world: self.world }, entity, component_id); + if archetype.has_add_hook() { + for component_id in targets { + // SAFETY: Caller ensures that these components exist + let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks(); + if let Some(hook) = hooks.on_add { + hook(DeferredWorld { world: self.world }, entity, component_id); + } } } } - /// Triggers all `on_insert` hooks for [`ComponentId`] in target. + /// Triggers all `OnInsert` hooks and observers for [`ComponentId`] in target. /// /// # Safety /// Caller must ensure [`ComponentId`] in target exist in self. #[inline] pub(crate) unsafe fn trigger_on_insert( &mut self, + archetype: &Archetype, entity: Entity, targets: impl Iterator, ) { - for component_id in targets { - // SAFETY: Caller ensures that these components exist - let hooks = unsafe { self.world.components().get_info_unchecked(component_id) }.hooks(); - if let Some(hook) = hooks.on_insert { - hook(DeferredWorld { world: self.world }, entity, component_id); + if archetype.has_insert_hook() { + for component_id in targets { + // SAFETY: Caller ensures that these components exist + let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks(); + if let Some(hook) = hooks.on_insert { + hook(DeferredWorld { world: self.world }, entity, component_id); + } } } } - /// Triggers all `on_remove` hooks for [`ComponentId`] in target. + /// Triggers all `OnRemove` hooks for [`ComponentId`] in target. /// /// # Safety /// Caller must ensure [`ComponentId`] in target exist in self. #[inline] pub(crate) unsafe fn trigger_on_remove( &mut self, + archetype: &Archetype, entity: Entity, targets: impl Iterator, ) { - for component_id in targets { - // SAFETY: Caller ensures that these components exist - let hooks = unsafe { self.world.components().get_info_unchecked(component_id) }.hooks(); - if let Some(hook) = hooks.on_remove { - hook(DeferredWorld { world: self.world }, entity, component_id); + if archetype.has_remove_hook() { + for component_id in targets { + // SAFETY: Caller ensures that these components exist + let hooks = + unsafe { self.world.components().get_info_unchecked(component_id) }.hooks(); + if let Some(hook) = hooks.on_remove { + hook(DeferredWorld { world: self.world }, entity, component_id); + } } } } + + /// Triggers all event observers for [`ComponentId`] in target. + /// + /// # Safety + /// Caller must ensure [`ComponentId`] in target exist in self. + #[inline] + pub(crate) unsafe fn trigger_observers( + &mut self, + event: ComponentId, + target: Entity, + components: impl Iterator, + ) { + let (world, observers) = unsafe { + let world = self.as_unsafe_world_cell(); + world.world_mut().last_event_id += 1; + (world.into_deferred(), &world.world().observers) + }; + observers.invoke(event, target, components, world, &mut ()); + } + + /// Triggers all event observers for [`ComponentId`] in target. + /// + /// # Safety + /// Caller must ensure [`ComponentId`] in target exist in self. + #[inline] + pub(crate) unsafe fn trigger_observers_with_data( + &mut self, + event: ComponentId, + target: Entity, + components: impl Iterator, + data: &mut E, + ) { + let (world, observers) = unsafe { + let world = self.as_unsafe_world_cell(); + world.world_mut().last_event_id += 1; + (world.into_deferred(), &world.world().observers) + }; + observers.invoke(event, target, components, world, data); + } + + #[inline] + pub(crate) fn as_unsafe_world_cell(&mut self) -> UnsafeWorldCell { + self.world + } } impl<'w> UnsafeWorldCell<'w> { diff --git a/crates/bevy_ecs/src/world/entity_constants.rs b/crates/bevy_ecs/src/world/entity_constants.rs new file mode 100644 index 0000000000000..70cc6e2ebf5d8 --- /dev/null +++ b/crates/bevy_ecs/src/world/entity_constants.rs @@ -0,0 +1,6 @@ +use crate::component::ComponentId; + +pub const NO_EVENT: ComponentId = ComponentId::new(0); +pub const ON_ADD: ComponentId = ComponentId::new(1); +pub const ON_INSERT: ComponentId = ComponentId::new(2); +pub const ON_REMOVE: ComponentId = ComponentId::new(3); diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 93d47d3e7ae8d..f357e9994219d 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -4,6 +4,9 @@ use crate::{ change_detection::MutUntyped, component::{Component, ComponentId, ComponentTicks, Components, StorageType}, entity::{Entities, Entity, EntityLocation}, + observer::{AttachObserver, EcsEvent, ObserverBuilder, Observers}, + prelude::Observer, + query::{ReadOnlyWorldQuery, WorldQuery}, removal_detection::RemovedComponentEvents, storage::Storages, world::{Mut, World}, @@ -11,7 +14,7 @@ use crate::{ use bevy_ptr::{OwningPtr, Ptr}; use std::{any::TypeId, marker::PhantomData}; -use super::{unsafe_world_cell::UnsafeEntityCell, Ref}; +use super::{unsafe_world_cell::UnsafeEntityCell, Ref, ON_REMOVE}; /// A read-only reference to a particular [`Entity`] and all of its components. /// @@ -677,6 +680,7 @@ impl<'w> EntityWorldMut<'w> { &mut world.archetypes, storages, components, + &world.observers, old_location.archetype_id, bundle_info, false, @@ -688,7 +692,6 @@ impl<'w> EntityWorldMut<'w> { } let entity = self.entity; - let old_archetype = &world.archetypes[old_location.archetype_id]; // Drop borrow on world so it can be turned into DeferredWorld let bundle_info = { @@ -696,13 +699,23 @@ impl<'w> EntityWorldMut<'w> { // SAFETY: Changes to Bundles cannot happen through DeferredWorld unsafe { &*bundle_info } }; - if old_archetype.has_on_remove() { - // SAFETY: All components in the archetype exist in world - unsafe { - world - .as_unsafe_world_cell() - .into_deferred() - .trigger_on_remove(entity, bundle_info.iter_components()); + // SAFETY: archetypes cannot be modified through DeferredWorld + let (old_archetype, mut deferred_world) = unsafe { + let world = world.as_unsafe_world_cell(); + ( + &world.world().archetypes[old_location.archetype_id], + world.into_deferred(), + ) + }; + // SAFETY: All components in the archetype exist in world + unsafe { + deferred_world.trigger_on_remove(old_archetype, entity, bundle_info.iter_components()); + if old_archetype.has_remove_observer() { + deferred_world.trigger_observers( + ON_REMOVE, + self.entity, + bundle_info.iter_components(), + ) } } @@ -854,6 +867,7 @@ impl<'w> EntityWorldMut<'w> { archetypes, storages, components, + &world.observers, old_location.archetype_id, bundle_info, true, @@ -866,21 +880,26 @@ impl<'w> EntityWorldMut<'w> { } let entity: Entity = self.entity; - let old_archetype = &world.archetypes[old_location.archetype_id]; - // Drop borrow on world so it can be turned into DeferredWorld let bundle_info = { let bundle_info: *const BundleInfo = bundle_info; // SAFETY: Changes to Bundles cannot happen through DeferredWorld unsafe { &*bundle_info } }; - if old_archetype.has_on_remove() { - // SAFETY: All components in the archetype exist in world - unsafe { - world - .as_unsafe_world_cell() - .into_deferred() - .trigger_on_remove(entity, bundle_info.iter_components()); + // SAFETY: archetypes cannot be modified through DeferredWorld + let (old_archetype, mut deferred_world) = unsafe { + let world = world.as_unsafe_world_cell(); + ( + &world.world().archetypes[old_location.archetype_id], + world.into_deferred(), + ) + }; + + // SAFETY: All components in the archetype exist in world + unsafe { + deferred_world.trigger_on_remove(old_archetype, entity, bundle_info.iter_components()); + if old_archetype.has_remove_observer() { + deferred_world.trigger_observers(ON_REMOVE, entity, bundle_info.iter_components()) } } @@ -922,20 +941,20 @@ impl<'w> EntityWorldMut<'w> { pub fn despawn(self) { let world = self.world; world.flush_entities(); - let archetype = &world.archetypes[self.location.archetype_id]; - if archetype.has_on_remove() { - // Drop borrow on world so it can be turned into DeferredWorld - let archetype = { - let archetype: *const Archetype = archetype; - // SAFETY: Changes to Archetypes cannot happpen through DeferredWorld - unsafe { &*archetype } - }; - // SAFETY: All components in the archetype exist in world - unsafe { - world - .as_unsafe_world_cell() - .into_deferred() - .trigger_on_remove(self.entity, archetype.components()); + // SAFETY: You cannot modify archetypes through DeferredWorld + let (archetype, mut deferred_world) = unsafe { + let world = world.as_unsafe_world_cell(); + ( + &world.world().archetypes[self.location.archetype_id], + world.into_deferred(), + ) + }; + + // SAFETY: All components in the archetype exist in world + unsafe { + deferred_world.trigger_on_remove(archetype, self.entity, archetype.components()); + if archetype.has_remove_observer() { + deferred_world.trigger_observers(ON_REMOVE, self.entity, archetype.components()) } } @@ -1004,7 +1023,7 @@ impl<'w> EntityWorldMut<'w> { } /// Ensures any commands triggered by the actions of Self are applied, equivalent to [`World::flush_commands`] - pub fn flush(self) -> Entity { + pub fn flush(&mut self) -> Entity { self.world.flush_commands(); self.entity } @@ -1121,6 +1140,17 @@ impl<'w> EntityWorldMut<'w> { }) } } + + pub fn observe( + &mut self, + callback: fn(Observer), + ) -> &mut Self { + let observer = ObserverBuilder::new(self.world) + .source(self.entity) + .enqueue(callback); + self.insert(AttachObserver(observer)); + self + } } /// A view into a single entity and component in a world, which may either be vacant or occupied. @@ -1523,6 +1553,7 @@ unsafe fn remove_bundle_from_archetype( archetypes: &mut Archetypes, storages: &mut Storages, components: &Components, + observers: &Observers, archetype_id: ArchetypeId, bundle_info: &BundleInfo, intersection: bool, @@ -1591,6 +1622,7 @@ unsafe fn remove_bundle_from_archetype( let new_archetype_id = archetypes.get_id_or_insert( components, + observers, next_table_id, next_table_components, next_sparse_set_components, diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 944c2a076141e..14d11c63b4378 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1,6 +1,7 @@ //! Defines the [`World`] and APIs for accessing it directly. mod deferred_world; +mod entity_constants; mod entity_ref; pub mod error; mod spawn_batch; @@ -9,6 +10,7 @@ mod world_cell; pub use crate::change_detection::{Mut, Ref, CHECK_TICK_THRESHOLD}; pub use deferred_world::DeferredWorld; +pub use entity_constants::*; pub use entity_ref::{EntityMut, EntityRef, EntityWorldMut, Entry, OccupiedEntry, VacantEntry}; pub use spawn_batch::*; pub use world_cell::*; @@ -20,6 +22,7 @@ use crate::{ component::{Component, ComponentDescriptor, ComponentId, ComponentInfo, Components, Tick}, entity::{AllocAtWithoutReplacement, Entities, Entity, EntityLocation}, event::{Event, EventId, Events, SendBatchIds}, + observer::Observers, query::{DebugCheckedUnwrap, QueryEntityError, QueryState, ReadOnlyWorldQuery, WorldQuery}, removal_detection::RemovedComponentEvents, schedule::{Schedule, ScheduleLabel, Schedules}, @@ -67,24 +70,27 @@ pub struct World { pub(crate) archetypes: Archetypes, pub(crate) storages: Storages, pub(crate) bundles: Bundles, + pub(crate) observers: Observers, pub(crate) removed_components: RemovedComponentEvents, /// Access cache used by [`WorldCell`]. Is only accessed in the `Drop` impl of `WorldCell`. pub(crate) archetype_component_access: ArchetypeComponentAccess, pub(crate) change_tick: AtomicU32, pub(crate) last_change_tick: Tick, pub(crate) last_check_tick: Tick, + pub(crate) last_event_id: u32, pub(crate) command_queue: CommandQueue, } impl Default for World { fn default() -> Self { - Self { + let mut world = Self { id: WorldId::new().expect("More `bevy` `World`s have been created than is supported"), entities: Entities::new(), components: Default::default(), archetypes: Archetypes::new(), storages: Default::default(), bundles: Default::default(), + observers: Observers::default(), removed_components: Default::default(), archetype_component_access: Default::default(), // Default value is `1`, and `last_change_tick`s default to `0`, such that changes @@ -92,12 +98,20 @@ impl Default for World { change_tick: AtomicU32::new(1), last_change_tick: Tick::new(0), last_check_tick: Tick::new(0), + last_event_id: 0, command_queue: CommandQueue::default(), - } + }; + world.bootstrap(); + world } } impl World { + #[inline] + fn bootstrap(&mut self) { + self.bootstrap_observers(); + self.entities.set_constant(); + } /// Creates a new empty [`World`]. /// /// # Panics diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index f5dd64c72530b..730979c69add2 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -11,6 +11,7 @@ use crate::{ ComponentId, ComponentStorage, ComponentTicks, Components, StorageType, Tick, TickCells, }, entity::{Entities, Entity, EntityLocation}, + observer::Observers, prelude::Component, removal_detection::RemovedComponentEvents, storage::{Column, ComponentSparseSet, Storages}, @@ -566,6 +567,14 @@ impl<'w> UnsafeWorldCell<'w> { .get(component_id)? .get_with_ticks() } + + pub(crate) unsafe fn archetypes_mut(self) -> &'w mut Archetypes { + unsafe { &mut self.world_mut().archetypes } + } + + pub(crate) unsafe fn observers_mut(self) -> &'w mut Observers { + unsafe { &mut self.world_mut().observers } + } } impl Debug for UnsafeWorldCell<'_> { diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 9e560f22ad855..f2cb4b5848452 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -451,7 +451,7 @@ unsafe fn initialize_render_app(app: &mut App) { assert_eq!( render_app.world.entities().len(), - 0, + render_app.world.entities().constant_count(), "An entity was spawned after the entity list was cleared last frame and before the extract schedule began. This is not supported", ); diff --git a/examples/ecs/observers.rs b/examples/ecs/observers.rs new file mode 100644 index 0000000000000..57a8d31a0393b --- /dev/null +++ b/examples/ecs/observers.rs @@ -0,0 +1,52 @@ +//! This examples illustrates the different ways you can employ observers + +use bevy::prelude::*; + +#[derive(Component, Debug)] +struct MyComponent(usize); + +#[derive(Component)] +struct MyEvent(usize); + +#[derive(Resource, Default)] +struct MyResource(usize); + +fn main() { + App::new().add_systems(Startup, setup).run(); +} + +fn setup(world: &mut World) { + world.init_resource::(); + + // Responds to all added instances of MyComponent (or any WorldQuery/Filter) + world.observer(|mut observer: Observer| { + let mut resource = observer.world_mut().resource_mut::(); + resource.0 += 1; + + let count = resource.0; + let my_component = observer.fetch().0; + println!( + "Added: {:?} to entity: {:?}, count: {:?}", + my_component, + observer.source(), + count + ); + }); + + let entity_a = world.spawn(MyComponent(0)).flush(); + + // Responds to MyEvent events targeting this entity + let entity_b = world + .spawn(MyComponent(1)) + .observe(|mut observer: Observer| { + let data = observer.data().0; + let mut my_component = observer.fetch(); + my_component.0 += 1; + println!("Component: {:?}, Event: {:?}", my_component.0, data); + }) + .flush(); + + world.ecs_event(MyEvent(5)).target(entity_b).emit(); + world.ecs_event(MyEvent(10)).target(entity_a).emit(); + world.ecs_event(MyEvent(15)).target(entity_b).emit(); +} From 779eabef07c735fb0595434b30070817d3190546 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Thu, 23 Nov 2023 22:10:46 -0800 Subject: [PATCH 018/109] Initial hooks implementation --- Cargo.toml | 11 + crates/bevy_ecs/src/bundle.rs | 401 ++++++++++------- crates/bevy_ecs/src/component.rs | 48 +- crates/bevy_ecs/src/lib.rs | 14 +- crates/bevy_ecs/src/query/builder.rs | 422 ++++++++++++++++++ crates/bevy_ecs/src/query/fetch.rs | 8 +- crates/bevy_ecs/src/query/filter.rs | 4 +- crates/bevy_ecs/src/schedule/schedule.rs | 2 +- crates/bevy_ecs/src/storage/table.rs | 2 +- .../src/system/commands/command_queue.rs | 6 + crates/bevy_ecs/src/system/mod.rs | 2 +- crates/bevy_ecs/src/world/deferred_world.rs | 241 ++++++++++ crates/bevy_ecs/src/world/entity_ref.rs | 176 ++++---- crates/bevy_ecs/src/world/mod.rs | 92 ++-- crates/bevy_ecs/src/world/spawn_batch.rs | 15 +- examples/ecs/component_hooks.rs | 42 ++ 16 files changed, 1153 insertions(+), 333 deletions(-) create mode 100644 crates/bevy_ecs/src/query/builder.rs create mode 100644 crates/bevy_ecs/src/world/deferred_world.rs create mode 100644 examples/ecs/component_hooks.rs diff --git a/Cargo.toml b/Cargo.toml index cc9c26056daa9..addf71590a71c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1294,6 +1294,17 @@ description = "Change detection on components" category = "ECS (Entity Component System)" wasm = false +[[example]] +name = "component_hooks" +path = "examples/ecs/component_hooks.rs" +doc-scrape-examples = true + +[package.metadata.example.component_hooks] +name = "Component Hooks" +description = "Define component hooks to react to ECS events" +category = "ECS (Entity Component System)" +wasm = false + [[example]] name = "custom_query_param" path = "examples/ecs/custom_query_param.rs" diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 361689e5793ad..9979c0c267c30 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -7,11 +7,12 @@ use bevy_utils::{HashMap, HashSet}; use crate::{ archetype::{ - Archetype, ArchetypeId, Archetypes, BundleComponentStatus, ComponentStatus, + AddBundle, Archetype, ArchetypeId, Archetypes, BundleComponentStatus, ComponentStatus, SpawnBundleStatus, }, component::{Component, ComponentId, ComponentStorage, Components, StorageType, Tick}, entity::{Entities, Entity, EntityLocation}, + prelude::World, query::DebugCheckedUnwrap, storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow}, TypeIdMap, @@ -184,7 +185,7 @@ unsafe impl Bundle for C { storages: &mut Storages, ids: &mut impl FnMut(ComponentId), ) { - ids(components.init_component::(storages)); + ids(components.init_component::(storages).id()); } unsafe fn from_components(ctx: &mut T, func: &mut F) -> Self @@ -345,88 +346,6 @@ impl BundleInfo { &self.component_ids } - pub(crate) fn get_bundle_inserter<'a, 'b>( - &'b self, - entities: &'a mut Entities, - archetypes: &'a mut Archetypes, - components: &Components, - storages: &'a mut Storages, - archetype_id: ArchetypeId, - change_tick: Tick, - ) -> BundleInserter<'a, 'b> { - let new_archetype_id = - self.add_bundle_to_archetype(archetypes, storages, components, archetype_id); - let archetypes_ptr = archetypes.archetypes.as_mut_ptr(); - if new_archetype_id == archetype_id { - let archetype = &mut archetypes[archetype_id]; - let table_id = archetype.table_id(); - BundleInserter { - bundle_info: self, - archetype, - entities, - sparse_sets: &mut storages.sparse_sets, - table: &mut storages.tables[table_id], - archetypes_ptr, - change_tick, - result: InsertBundleResult::SameArchetype, - } - } else { - let (archetype, new_archetype) = archetypes.get_2_mut(archetype_id, new_archetype_id); - let table_id = archetype.table_id(); - if table_id == new_archetype.table_id() { - BundleInserter { - bundle_info: self, - archetype, - archetypes_ptr, - entities, - sparse_sets: &mut storages.sparse_sets, - table: &mut storages.tables[table_id], - change_tick, - result: InsertBundleResult::NewArchetypeSameTable { new_archetype }, - } - } else { - let (table, new_table) = storages - .tables - .get_2_mut(table_id, new_archetype.table_id()); - BundleInserter { - bundle_info: self, - archetype, - sparse_sets: &mut storages.sparse_sets, - entities, - archetypes_ptr, - table, - change_tick, - result: InsertBundleResult::NewArchetypeNewTable { - new_archetype, - new_table, - }, - } - } - } - } - - pub(crate) fn get_bundle_spawner<'a, 'b>( - &'b self, - entities: &'a mut Entities, - archetypes: &'a mut Archetypes, - components: &Components, - storages: &'a mut Storages, - change_tick: Tick, - ) -> BundleSpawner<'a, 'b> { - let new_archetype_id = - self.add_bundle_to_archetype(archetypes, storages, components, ArchetypeId::EMPTY); - let archetype = &mut archetypes[new_archetype_id]; - let table = &mut storages.tables[archetype.table_id()]; - BundleSpawner { - archetype, - bundle_info: self, - table, - entities, - sparse_sets: &mut storages.sparse_sets, - change_tick, - } - } - /// This writes components from a given [`Bundle`] to the given entity. /// /// # Safety @@ -572,52 +491,146 @@ impl BundleInfo { } } -pub(crate) struct BundleInserter<'a, 'b> { - pub(crate) archetype: &'a mut Archetype, - pub(crate) entities: &'a mut Entities, - bundle_info: &'b BundleInfo, - table: &'a mut Table, - sparse_sets: &'a mut SparseSets, - result: InsertBundleResult<'a>, - archetypes_ptr: *mut Archetype, +pub(crate) struct BundleInserter<'w> { + world: &'w mut World, + archetype: *mut Archetype, + table: *mut Table, + bundle_info: *const BundleInfo, + add_bundle: *const AddBundle, + result: InsertBundleResult, change_tick: Tick, } -pub(crate) enum InsertBundleResult<'a> { +pub(crate) enum InsertBundleResult { SameArchetype, NewArchetypeSameTable { - new_archetype: &'a mut Archetype, + new_archetype: *mut Archetype, }, NewArchetypeNewTable { - new_archetype: &'a mut Archetype, - new_table: &'a mut Table, + new_archetype: *mut Archetype, + new_table: *mut Table, }, } -impl<'a, 'b> BundleInserter<'a, 'b> { +impl<'w> BundleInserter<'w> { + #[inline] + pub fn new( + world: &'w mut World, + archetype_id: ArchetypeId, + change_tick: Tick, + ) -> Self { + let bundle_info: *const BundleInfo = world + .bundles + .init_info::(&mut world.components, &mut world.storages); + unsafe { Self::new_with_info(world, archetype_id, bundle_info, change_tick) } + } + + #[inline] + pub(crate) unsafe fn new_with_info( + world: &'w mut World, + archetype_id: ArchetypeId, + bundle_info: *const BundleInfo, + change_tick: Tick, + ) -> Self { + let bundle_info = &*bundle_info; + let bundle_id = bundle_info.id(); + let new_archetype_id = bundle_info.add_bundle_to_archetype( + &mut world.archetypes, + &mut world.storages, + &world.components, + archetype_id, + ); + if new_archetype_id == archetype_id { + let archetype = &mut world.archetypes[archetype_id]; + let table_id = archetype.table_id(); + let add_bundle: *const AddBundle = unsafe { + archetype + .edges() + .get_add_bundle_internal(bundle_id) + .debug_checked_unwrap() + }; + let table: *mut Table = &mut world.storages.tables[table_id]; + let archetype: *mut Archetype = archetype; + Self { + world, + archetype, + table, + bundle_info, + add_bundle, + result: InsertBundleResult::SameArchetype, + change_tick, + } + } else { + let (archetype, new_archetype) = + world.archetypes.get_2_mut(archetype_id, new_archetype_id); + let table_id = archetype.table_id(); + let new_table_id = new_archetype.table_id(); + let add_bundle: *const AddBundle = unsafe { + archetype + .edges() + .get_add_bundle_internal(bundle_id) + .debug_checked_unwrap() + }; + let table: *mut Table = &mut world.storages.tables[table_id]; + let archetype: *mut Archetype = archetype; + let new_archetype: *mut Archetype = new_archetype; + if table_id == new_table_id { + Self { + world, + archetype, + table, + bundle_info, + add_bundle, + result: InsertBundleResult::NewArchetypeSameTable { new_archetype }, + change_tick, + } + } else { + let new_table: *mut Table = &mut world.storages.tables[new_table_id]; + Self { + world, + archetype, + table, + bundle_info, + add_bundle, + result: InsertBundleResult::NewArchetypeNewTable { + new_archetype, + new_table, + }, + change_tick, + } + } + } + } /// # Safety /// `entity` must currently exist in the source archetype for this inserter. `archetype_row` /// must be `entity`'s location in the archetype. `T` must match this [`BundleInfo`]'s type #[inline] - pub unsafe fn insert( + pub(crate) unsafe fn insert( &mut self, entity: Entity, location: EntityLocation, bundle: T, ) -> EntityLocation { - match &mut self.result { + let bundle_info = &*self.bundle_info; + let add_bundle: &AddBundle = &*self.add_bundle; + let world = &mut *self.world; + for (i, component_id) in bundle_info.components().iter().cloned().enumerate() { + let hooks = unsafe { world.components.get_info_unchecked(component_id) }.hooks(); + if let ComponentStatus::Added = add_bundle.bundle_status[i] { + if let Some(hook) = hooks.on_add { + hook(unsafe { world.into_deferred() }, entity) + } + } + if let Some(hook) = hooks.on_insert { + hook(unsafe { world.into_deferred() }, entity) + } + } + + match self.result { InsertBundleResult::SameArchetype => { - // PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty) - // SAFETY: The edge is assured to be initialized when creating the BundleInserter - let add_bundle = unsafe { - self.archetype - .edges() - .get_add_bundle_internal(self.bundle_info.id) - .debug_checked_unwrap() - }; - self.bundle_info.write_components( - self.table, - self.sparse_sets, + bundle_info.write_components( + &mut *self.table, + &mut world.storages.sparse_sets, add_bundle, entity, location.table_row, @@ -627,12 +640,15 @@ impl<'a, 'b> BundleInserter<'a, 'b> { location } InsertBundleResult::NewArchetypeSameTable { new_archetype } => { - let result = self.archetype.swap_remove(location.archetype_row); + let table = &mut *self.table; + let archetype = &mut *self.archetype; + let new_archetype = &mut *new_archetype; + let result = archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { let swapped_location = // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; - self.entities.set( + unsafe { world.entities.get(swapped_entity).debug_checked_unwrap() }; + world.entities.set( swapped_entity.index(), EntityLocation { archetype_id: swapped_location.archetype_id, @@ -643,19 +659,10 @@ impl<'a, 'b> BundleInserter<'a, 'b> { ); } let new_location = new_archetype.allocate(entity, result.table_row); - self.entities.set(entity.index(), new_location); - - // PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty) - // SAFETY: The edge is assured to be initialized when creating the BundleInserter - let add_bundle = unsafe { - self.archetype - .edges() - .get_add_bundle_internal(self.bundle_info.id) - .debug_checked_unwrap() - }; - self.bundle_info.write_components( - self.table, - self.sparse_sets, + world.entities.set(entity.index(), new_location); + bundle_info.write_components( + table, + &mut world.storages.sparse_sets, add_bundle, entity, result.table_row, @@ -668,12 +675,16 @@ impl<'a, 'b> BundleInserter<'a, 'b> { new_archetype, new_table, } => { - let result = self.archetype.swap_remove(location.archetype_row); + let table = &mut *self.table; + let new_table = &mut *new_table; + let archetype = &mut *self.archetype; + let new_archetype = &mut *new_archetype; + let result = archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { let swapped_location = // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; - self.entities.set( + unsafe { world.entities.get(swapped_entity).debug_checked_unwrap() }; + world.entities.set( swapped_entity.index(), EntityLocation { archetype_id: swapped_location.archetype_id, @@ -685,30 +696,25 @@ impl<'a, 'b> BundleInserter<'a, 'b> { } // PERF: store "non bundle" components in edge, then just move those to avoid // redundant copies - let move_result = self - .table - .move_to_superset_unchecked(result.table_row, new_table); + let move_result = table.move_to_superset_unchecked(result.table_row, new_table); let new_location = new_archetype.allocate(entity, move_result.new_row); - self.entities.set(entity.index(), new_location); + world.entities.set(entity.index(), new_location); // if an entity was moved into this entity's table spot, update its table row if let Some(swapped_entity) = move_result.swapped_entity { let swapped_location = // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; - let swapped_archetype = if self.archetype.id() == swapped_location.archetype_id - { - &mut *self.archetype + unsafe { world.entities.get(swapped_entity).debug_checked_unwrap() }; + let swapped_archetype = if archetype.id() == swapped_location.archetype_id { + &mut *archetype } else if new_archetype.id() == swapped_location.archetype_id { new_archetype } else { // SAFETY: the only two borrowed archetypes are above and we just did collision checks - &mut *self - .archetypes_ptr - .add(swapped_location.archetype_id.index()) + &mut world.archetypes[swapped_location.archetype_id] }; - self.entities.set( + world.entities.set( swapped_entity.index(), EntityLocation { archetype_id: swapped_location.archetype_id, @@ -721,17 +727,9 @@ impl<'a, 'b> BundleInserter<'a, 'b> { .set_entity_table_row(swapped_location.archetype_row, result.table_row); } - // PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty) - // SAFETY: The edge is assured to be initialized when creating the BundleInserter - let add_bundle = unsafe { - self.archetype - .edges() - .get_add_bundle_internal(self.bundle_info.id) - .debug_checked_unwrap() - }; - self.bundle_info.write_components( + bundle_info.write_components( new_table, - self.sparse_sets, + &mut world.storages.sparse_sets, add_bundle, entity, move_result.new_row, @@ -742,21 +740,59 @@ impl<'a, 'b> BundleInserter<'a, 'b> { } } } + + #[inline] + pub(crate) fn entities(&mut self) -> &mut Entities { + &mut self.world.entities + } } -pub(crate) struct BundleSpawner<'a, 'b> { - pub(crate) archetype: &'a mut Archetype, - pub(crate) entities: &'a mut Entities, - bundle_info: &'b BundleInfo, - table: &'a mut Table, - sparse_sets: &'a mut SparseSets, +pub(crate) struct BundleSpawner<'w> { + world: &'w mut World, + bundle_info: *const BundleInfo, + archetype: *mut Archetype, + table: *mut Table, change_tick: Tick, } -impl<'a, 'b> BundleSpawner<'a, 'b> { +impl<'w> BundleSpawner<'w> { + #[inline] + pub fn new(world: &'w mut World, change_tick: Tick) -> Self { + let bundle_info: *const BundleInfo = world + .bundles + .init_info::(&mut world.components, &mut world.storages); + unsafe { Self::new_with_info(world, bundle_info, change_tick) } + } + + pub(crate) unsafe fn new_with_info( + world: &'w mut World, + bundle_info: *const BundleInfo, + change_tick: Tick, + ) -> Self { + let bundle_info = &*bundle_info; + let new_archetype_id = bundle_info.add_bundle_to_archetype( + &mut world.archetypes, + &mut world.storages, + &world.components, + ArchetypeId::EMPTY, + ); + let archetype = &mut world.archetypes[new_archetype_id]; + let table: *mut Table = &mut world.storages.tables[archetype.table_id()]; + let archetype: *mut Archetype = archetype; + BundleSpawner { + world, + bundle_info, + archetype, + table, + change_tick, + } + } + pub fn reserve_storage(&mut self, additional: usize) { - self.archetype.reserve(additional); - self.table.reserve(additional); + unsafe { + (&mut *self.archetype).reserve(additional); + (&mut *self.table).reserve(additional); + } } /// # Safety /// `entity` must be allocated (but non-existent), `T` must match this [`BundleInfo`]'s type @@ -766,18 +802,36 @@ impl<'a, 'b> BundleSpawner<'a, 'b> { entity: Entity, bundle: T, ) -> EntityLocation { - let table_row = self.table.allocate(entity); - let location = self.archetype.allocate(entity, table_row); - self.bundle_info.write_components( - self.table, - self.sparse_sets, + let bundle_info = &*self.bundle_info; + for component_id in bundle_info.components().iter().cloned() { + let hooks = self + .world + .components + .get_info_unchecked(component_id) + .hooks(); + if let Some(hook) = hooks.on_add { + hook(self.world.into_deferred(), entity); + } + if let Some(hook) = hooks.on_insert { + hook(self.world.into_deferred(), entity); + } + } + + let archetype = &mut *self.archetype; + let table = &mut *self.table; + + let table_row = table.allocate(entity); + let location = archetype.allocate(entity, table_row); + bundle_info.write_components( + table, + &mut self.world.storages.sparse_sets, &SpawnBundleStatus, entity, table_row, self.change_tick, bundle, ); - self.entities.set(entity.index(), location); + self.world.entities.set(entity.index(), location); location } @@ -786,11 +840,16 @@ impl<'a, 'b> BundleSpawner<'a, 'b> { /// `T` must match this [`BundleInfo`]'s type #[inline] pub unsafe fn spawn(&mut self, bundle: T) -> Entity { - let entity = self.entities.alloc(); + let entity = self.world.entities.alloc(); // SAFETY: entity is allocated (but non-existent), `T` matches this BundleInfo's type self.spawn_non_existent(entity, bundle); entity } + + #[inline] + pub(crate) fn entities(&mut self) -> &mut Entities { + &mut self.world.entities + } } /// Metadata for bundles. Stores a [`BundleInfo`] for each type of [`Bundle`] in a given world. @@ -828,7 +887,7 @@ impl Bundles { storages: &mut Storages, ) -> &'a BundleInfo { let bundle_infos = &mut self.bundle_infos; - let id = self.bundle_ids.entry(TypeId::of::()).or_insert_with(|| { + let id = *self.bundle_ids.entry(TypeId::of::()).or_insert_with(|| { let mut component_ids = Vec::new(); T::component_ids(components, storages, &mut |id| component_ids.push(id)); let id = BundleId(bundle_infos.len()); @@ -842,7 +901,11 @@ impl Bundles { id }); // SAFETY: index either exists, or was initialized - unsafe { self.bundle_infos.get_unchecked(id.0) } + unsafe { self.get_unchecked(id) } + } + + pub(crate) unsafe fn get_unchecked<'a>(&'a self, id: BundleId) -> &'a BundleInfo { + self.bundle_infos.get_unchecked(id.0) } /// Initializes a new [`BundleInfo`] for a dynamic [`Bundle`]. diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index ae1582b547e25..3155c9b54ae4a 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -3,9 +3,10 @@ use crate::{ self as bevy_ecs, change_detection::MAX_CHANGE_AGE, + entity::Entity, storage::{SparseSetIndex, Storages}, system::{Local, Resource, SystemParam}, - world::{FromWorld, World}, + world::{DeferredWorld, FromWorld, World}, TypeIdMap, }; pub use bevy_ecs_macros::Component; @@ -263,6 +264,25 @@ impl ComponentInfo { pub(crate) fn new(id: ComponentId, descriptor: ComponentDescriptor) -> Self { ComponentInfo { id, descriptor } } + + pub fn on_add(&mut self, hook: ComponentHook) -> &mut Self { + self.descriptor.hooks.on_add = Some(hook); + self + } + + pub fn on_insert(&mut self, hook: ComponentHook) -> &mut Self { + self.descriptor.hooks.on_insert = Some(hook); + self + } + + pub fn on_remove(&mut self, hook: ComponentHook) -> &mut Self { + self.descriptor.hooks.on_remove = Some(hook); + self + } + + pub fn hooks(&self) -> &ComponentHooks { + &self.descriptor.hooks + } } /// A value which uniquely identifies the type of a [`Component`] within a @@ -318,6 +338,15 @@ impl SparseSetIndex for ComponentId { } } +pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, Entity); + +#[derive(Clone, Default)] +pub struct ComponentHooks { + pub(crate) on_add: Option, + pub(crate) on_insert: Option, + pub(crate) on_remove: Option, +} + /// A value describing a component or resource, which may or may not correspond to a Rust type. #[derive(Clone)] pub struct ComponentDescriptor { @@ -330,6 +359,7 @@ pub struct ComponentDescriptor { is_send_and_sync: bool, type_id: Option, layout: Layout, + hooks: ComponentHooks, // SAFETY: this function must be safe to call with pointers pointing to items of the type // this descriptor describes. // None if the underlying type doesn't need to be dropped @@ -363,6 +393,7 @@ impl ComponentDescriptor { is_send_and_sync: true, type_id: Some(TypeId::of::()), layout: Layout::new::(), + hooks: ComponentHooks::default(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), } } @@ -384,6 +415,7 @@ impl ComponentDescriptor { is_send_and_sync: true, type_id: None, layout, + hooks: ComponentHooks::default(), drop, } } @@ -400,6 +432,7 @@ impl ComponentDescriptor { is_send_and_sync: true, type_id: Some(TypeId::of::()), layout: Layout::new::(), + hooks: ComponentHooks::default(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), } } @@ -411,6 +444,7 @@ impl ComponentDescriptor { is_send_and_sync: false, type_id: Some(TypeId::of::()), layout: Layout::new::(), + hooks: ComponentHooks::default(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), } } @@ -453,7 +487,7 @@ impl Components { /// * [`Components::component_id()`] /// * [`Components::init_component_with_descriptor()`] #[inline] - pub fn init_component(&mut self, storages: &mut Storages) -> ComponentId { + pub fn init_component(&mut self, storages: &mut Storages) -> &mut ComponentInfo { let type_id = TypeId::of::(); let Components { @@ -464,7 +498,7 @@ impl Components { let index = indices.entry(type_id).or_insert_with(|| { Components::init_component_inner(components, storages, ComponentDescriptor::new::()) }); - ComponentId(*index) + &mut components[*index] } /// Initializes a component described by `descriptor`. @@ -482,9 +516,9 @@ impl Components { &mut self, storages: &mut Storages, descriptor: ComponentDescriptor, - ) -> ComponentId { + ) -> &mut ComponentInfo { let index = Components::init_component_inner(&mut self.components, storages, descriptor); - ComponentId(index) + &mut self.components[index] } #[inline] @@ -563,7 +597,7 @@ impl Components { /// #[derive(Component)] /// struct ComponentA; /// - /// let component_a_id = world.init_component::(); + /// let component_a_id = world.init_component::().id(); /// /// assert_eq!(component_a_id, world.components().component_id::().unwrap()) /// ``` @@ -872,7 +906,7 @@ struct InitComponentId { impl FromWorld for InitComponentId { fn from_world(world: &mut World) -> Self { Self { - component_id: world.init_component::(), + component_id: world.init_component::().id(), marker: PhantomData, } } diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 69ce32e8a8806..9cdcde75c062d 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -157,8 +157,8 @@ mod tests { assert_eq!( ids, &[ - world.init_component::(), - world.init_component::(), + world.init_component::().id(), + world.init_component::().id(), ] ); @@ -211,10 +211,10 @@ mod tests { assert_eq!( ids, &[ - world.init_component::(), - world.init_component::(), - world.init_component::(), - world.init_component::(), + world.init_component::().id(), + world.init_component::().id(), + world.init_component::().id(), + world.init_component::().id(), ] ); @@ -264,7 +264,7 @@ mod tests { }, ); - assert_eq!(ids, &[world.init_component::(),]); + assert_eq!(ids, &[world.init_component::().id(),]); let e4 = world .spawn(BundleWithIgnored { diff --git a/crates/bevy_ecs/src/query/builder.rs b/crates/bevy_ecs/src/query/builder.rs new file mode 100644 index 0000000000000..d1beaa513b55f --- /dev/null +++ b/crates/bevy_ecs/src/query/builder.rs @@ -0,0 +1,422 @@ +use std::marker::PhantomData; + +use crate::{component::ComponentId, prelude::*}; + +use super::{FilteredAccess, ReadOnlyWorldQuery, WorldQuery}; + +/// Builder struct to create [`QueryState`] instances at runtime. +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # +/// # #[derive(Component)] +/// # struct A; +/// # +/// # #[derive(Component)] +/// # struct B; +/// # +/// # #[derive(Component)] +/// # struct C; +/// # +/// let mut world = World::new(); +/// let entity_a = world.spawn((A, B)).id(); +/// let entity_b = world.spawn((A, C)).id(); +/// +/// // Instantiate the builder using the type signature of the iterator you will consume +/// let mut query = QueryBuilder::<(Entity, &B)>::new(&mut world) +/// // Add additional terms through builder methods +/// .with::() +/// .without::() +/// .build(); +/// +/// // Consume the QueryState +/// let (entity, b) = query.single(&world); +///``` +pub struct QueryBuilder<'w, Q: WorldQuery = (), F: ReadOnlyWorldQuery = ()> { + access: FilteredAccess, + world: &'w mut World, + or: bool, + first: bool, + _marker: PhantomData<(Q, F)>, +} + +impl<'w, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryBuilder<'w, Q, F> { + /// Creates a new builder with the accesses required for `Q` and `F` + pub fn new(world: &'w mut World) -> Self { + let fetch_state = Q::init_state(world); + let filter_state = F::init_state(world); + + let mut access = FilteredAccess::default(); + Q::update_component_access(&fetch_state, &mut access); + + // Use a temporary empty FilteredAccess for filters. This prevents them from conflicting with the + // main Query's `fetch_state` access. Filters are allowed to conflict with the main query fetch + // because they are evaluated *before* a specific reference is constructed. + let mut filter_access = FilteredAccess::default(); + F::update_component_access(&filter_state, &mut filter_access); + + // Merge the temporary filter access with the main access. This ensures that filter access is + // properly considered in a global "cross-query" context (both within systems and across systems). + access.extend(&filter_access); + + Self { + access, + world, + or: false, + first: false, + _marker: PhantomData, + } + } + + /// Returns a reference to the world passed to [`Self::new`]. + pub fn world(&self) -> &World { + self.world + } + + /// Returns a reference to the world passed to [`Self::new`]. + pub fn world_mut(&mut self) -> &mut World { + self.world + } + + /// Adds access to self's underlying [`FilteredAccess`] respecting [`Self::or`] and [`Self::and`] + pub fn extend_access(&mut self, mut access: FilteredAccess) { + if self.or { + if self.first { + access.required.clear(); + self.access.extend(&access); + self.first = false; + } else { + self.access.append_or(&access); + } + } else { + self.access.extend(&access); + } + } + + /// Adds accesses required for `T` to self. + pub fn push(&mut self) -> &mut Self { + let state = T::init_state(self.world); + let mut access = FilteredAccess::default(); + T::update_component_access(&state, &mut access); + self.extend_access(access); + self + } + + /// Adds [`With`] to the [`FilteredAccess`] of self. + pub fn with(&mut self) -> &mut Self { + self.push::>(); + self + } + + /// Adds [`With`] to the [`FilteredAccess`] of self from a runtime [`ComponentId`]. + pub fn with_id(&mut self, id: ComponentId) -> &mut Self { + let mut access = FilteredAccess::default(); + access.and_with(id); + self.extend_access(access); + self + } + + /// Adds [`Without`] to the [`FilteredAccess`] of self. + pub fn without(&mut self) -> &mut Self { + self.push::>(); + self + } + + /// Adds [`Without`] to the [`FilteredAccess`] of self from a runtime [`ComponentId`]. + pub fn without_id(&mut self, id: ComponentId) -> &mut Self { + let mut access = FilteredAccess::default(); + access.and_without(id); + self.extend_access(access); + self + } + + /// Adds `&T` to the [`FilteredAccess`] of self. + pub fn ref_id(&mut self, id: ComponentId) -> &mut Self { + self.with_id(id); + self.access.add_read(id); + self + } + + /// Adds `&mut T` to the [`FilteredAccess`] of self. + pub fn mut_id(&mut self, id: ComponentId) -> &mut Self { + self.with_id(id); + self.access.add_write(id); + self + } + + /// Takes a function over mutable access to a [`QueryBuilder`], calls that function + /// on an empty builder and then adds all accesses from that builder to self as optional. + pub fn optional(&mut self, f: impl Fn(&mut QueryBuilder)) -> &mut Self { + let mut builder = QueryBuilder::new(self.world); + f(&mut builder); + self.access.extend_access(builder.access()); + self + } + + /// Takes a function over mutable access to a [`QueryBuilder`], calls that function + /// on an empty builder and then adds all accesses from that builder to self. + /// + /// Primarily used when inside a [`Self::or`] closure to group several terms. + pub fn and(&mut self, f: impl Fn(&mut QueryBuilder)) -> &mut Self { + let mut builder = QueryBuilder::new(self.world); + f(&mut builder); + let access = builder.access().clone(); + self.extend_access(access); + self + } + + /// Takes a function over mutable access to a [`QueryBuilder`], calls that function + /// on an empty builder, all accesses added to that builder will become terms in an or expression. + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # + /// # #[derive(Component)] + /// # struct A; + /// # + /// # #[derive(Component)] + /// # struct B; + /// # + /// # let mut world = World::new(); + /// # + /// QueryBuilder::::new(&mut world).or(|builder| { + /// builder.with::(); + /// builder.with::(); + /// }); + /// // is equivalent to + /// QueryBuilder::::new(&mut world).push::, With)>>(); + /// ``` + pub fn or(&mut self, f: impl Fn(&mut QueryBuilder)) -> &mut Self { + let mut builder = QueryBuilder::new(self.world); + builder.or = true; + builder.first = true; + f(&mut builder); + self.access.extend(builder.access()); + self + } + + /// Returns a reference to the the [`FilteredAccess`] that will be provided to the built [`Query`]. + pub fn access(&self) -> &FilteredAccess { + &self.access + } + + /// Transmute the existing builder adding required accesses. + /// This will maintain all exisiting accesses. + /// + /// If including a filter type see [`Self::transmute_filtered`] + pub fn transmute(&mut self) -> &mut QueryBuilder<'w, NewQ> { + self.transmute_filtered::() + } + + /// Transmute the existing builder adding required accesses. + /// This will maintain all existing accesses. + pub fn transmute_filtered( + &mut self, + ) -> &mut QueryBuilder<'w, NewQ, NewF> { + let mut fetch_state = NewQ::init_state(self.world); + let filter_state = NewF::init_state(self.world); + + NewQ::set_access(&mut fetch_state, &self.access); + + let mut access = FilteredAccess::default(); + NewQ::update_component_access(&fetch_state, &mut access); + NewF::update_component_access(&filter_state, &mut access); + + self.extend_access(access); + // SAFETY: + // - We have included all required acceses for NewQ and NewF + // - The layout of all QueryBuilder instances is the same + unsafe { std::mem::transmute(self) } + } + + /// Create a [`QueryState`] with the accesses of the builder. + pub fn build(&mut self) -> QueryState { + QueryState::::from_builder(self) + } +} + +#[cfg(test)] +mod tests { + use crate as bevy_ecs; + use crate::prelude::*; + use crate::world::FilteredEntityRef; + + use super::QueryBuilder; + + #[derive(Component, PartialEq, Debug)] + struct A(usize); + + #[derive(Component, PartialEq, Debug)] + struct B(usize); + + #[derive(Component, PartialEq, Debug)] + struct C(usize); + + #[test] + fn builder_with_without_static() { + let mut world = World::new(); + let entity_a = world.spawn((A(0), B(0))).id(); + let entity_b = world.spawn((A(0), C(0))).id(); + + let mut query_a = QueryBuilder::::new(&mut world) + .with::() + .without::() + .build(); + assert_eq!(entity_a, query_a.single(&world)); + + let mut query_b = QueryBuilder::::new(&mut world) + .with::() + .without::() + .build(); + assert_eq!(entity_b, query_b.single(&world)); + } + + #[test] + fn builder_with_without_dynamic() { + let mut world = World::new(); + let entity_a = world.spawn((A(0), B(0))).id(); + let entity_b = world.spawn((A(0), C(0))).id(); + let component_id_a = world.init_component::().id(); + let component_id_b = world.init_component::().id(); + let component_id_c = world.init_component::().id(); + + let mut query_a = QueryBuilder::::new(&mut world) + .with_id(component_id_a) + .without_id(component_id_c) + .build(); + assert_eq!(entity_a, query_a.single(&world)); + + let mut query_b = QueryBuilder::::new(&mut world) + .with_id(component_id_a) + .without_id(component_id_b) + .build(); + assert_eq!(entity_b, query_b.single(&world)); + } + + #[test] + fn builder_or() { + let mut world = World::new(); + world.spawn((A(0), B(0))); + world.spawn(B(0)); + world.spawn(C(0)); + + let mut query_a = QueryBuilder::::new(&mut world) + .or(|builder| { + builder.with::(); + builder.with::(); + }) + .build(); + assert_eq!(2, query_a.iter(&world).count()); + + let mut query_b = QueryBuilder::::new(&mut world) + .or(|builder| { + builder.with::(); + builder.without::(); + }) + .build(); + dbg!(&query_b.component_access); + assert_eq!(2, query_b.iter(&world).count()); + + let mut query_c = QueryBuilder::::new(&mut world) + .or(|builder| { + builder.with::(); + builder.with::(); + builder.with::(); + }) + .build(); + assert_eq!(3, query_c.iter(&world).count()); + } + + #[test] + fn builder_transmute() { + let mut world = World::new(); + world.spawn(A(0)); + world.spawn((A(1), B(0))); + let mut query = QueryBuilder::<()>::new(&mut world) + .with::() + .transmute::<&A>() + .build(); + + query.iter(&world).for_each(|a| assert_eq!(a.0, 1)); + } + + #[test] + fn builder_static_components() { + let mut world = World::new(); + let entity = world.spawn((A(0), B(1))).id(); + + let mut query = QueryBuilder::::new(&mut world) + .push::<&A>() + .push::<&B>() + .build(); + + let entity_ref = query.single(&world); + + assert_eq!(entity, entity_ref.id()); + + let a = entity_ref.get::().unwrap(); + let b = entity_ref.get::().unwrap(); + + assert_eq!(0, a.0); + assert_eq!(1, b.0); + } + + #[test] + fn builder_dynamic_components() { + let mut world = World::new(); + let entity = world.spawn((A(0), B(1))).id(); + let component_id_a = world.init_component::().id(); + let component_id_b = world.init_component::().id(); + + let mut query = QueryBuilder::::new(&mut world) + .ref_id(component_id_a) + .ref_id(component_id_b) + .build(); + + let entity_ref = query.single(&world); + + assert_eq!(entity, entity_ref.id()); + + let a = entity_ref.get_by_id(component_id_a).unwrap(); + let b = entity_ref.get_by_id(component_id_b).unwrap(); + + // SAFETY: We set these pointers to point to these components + unsafe { + assert_eq!(0, a.deref::().0); + assert_eq!(1, b.deref::().0); + } + } + + #[test] + fn builder_query_system() { + let mut world = World::new(); + world.spawn(A(0)); + let entity = world.spawn((A(1), B(0))).id(); + + let sys = move |query: Query<(Entity, &A)>| { + let (e, a) = query.single(); + assert_eq!(e, entity); + assert_eq!(1, a.0); + }; + + // Add additional terms that don't appear in the original query + let query = QueryBuilder::<(Entity, &A)>::new(&mut world) + .with::() + .build(); + let mut system = IntoSystem::into_system(sys); + system.initialize(&mut world); + + // SAFETY: We know the system param we are modifying has a compatible type signature + unsafe { system.state_mut().0 = query }; + system.run((), &mut world); + + // Alternatively truncate terms from a query to match the system + let query = QueryBuilder::<(Entity, &A, &B)>::new(&mut world).build(); + let mut system = IntoSystem::into_system(sys); + system.initialize(&mut world); + + // SAFETY: We know the system param we are modifying has a compatible type signature + unsafe { system.state_mut().0 = query.transmute(&world) }; + system.run((), &mut world); + } +} diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 00df1fe63e17c..3656931cb0398 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -639,7 +639,7 @@ unsafe impl WorldQuery for &T { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::() + world.init_component::().id() } fn matches_component_set( @@ -806,7 +806,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::() + world.init_component::().id() } fn matches_component_set( @@ -973,7 +973,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::() + world.init_component::().id() } fn matches_component_set( @@ -1229,7 +1229,7 @@ unsafe impl WorldQuery for Has { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::() + world.init_component::().id() } fn matches_component_set( diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index dcc2cedeea20b..8e49391dcee60 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -184,7 +184,7 @@ unsafe impl WorldQuery for With { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::() + world.init_component::().id() } fn matches_component_set( @@ -296,7 +296,7 @@ unsafe impl WorldQuery for Without { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::() + world.init_component::().id() } fn matches_component_set( diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 5b042590d5cf2..c1cce703f4361 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -118,7 +118,7 @@ impl Schedules { /// Ignore system order ambiguities caused by conflicts on [`Component`]s of type `T`. pub fn allow_ambiguous_component(&mut self, world: &mut World) { self.ignored_scheduling_ambiguities - .insert(world.init_component::()); + .insert(world.init_component::().id()); } /// Ignore system order ambiguities caused by conflicts on [`Resource`]s of type `T`. diff --git a/crates/bevy_ecs/src/storage/table.rs b/crates/bevy_ecs/src/storage/table.rs index e1955bc598f89..ddec964226d61 100644 --- a/crates/bevy_ecs/src/storage/table.rs +++ b/crates/bevy_ecs/src/storage/table.rs @@ -918,7 +918,7 @@ mod tests { fn table() { let mut components = Components::default(); let mut storages = Storages::default(); - let component_id = components.init_component::>(&mut storages); + let component_id = components.init_component::>(&mut storages).id(); let columns = &[component_id]; let mut table = TableBuilder::with_capacity(0, columns.len()) .add_column(components.get_info(component_id).unwrap()) diff --git a/crates/bevy_ecs/src/system/commands/command_queue.rs b/crates/bevy_ecs/src/system/commands/command_queue.rs index f68031c3d39b0..f1319e716f4f7 100644 --- a/crates/bevy_ecs/src/system/commands/command_queue.rs +++ b/crates/bevy_ecs/src/system/commands/command_queue.rs @@ -151,12 +151,18 @@ impl CommandQueue { // or 1 byte past the end, so this addition will not overflow the pointer's allocation. cursor = unsafe { cursor.add(size) }; } + + world.flush_commands(); } /// Take all commands from `other` and append them to `self`, leaving `other` empty pub fn append(&mut self, other: &mut CommandQueue) { self.bytes.append(&mut other.bytes); } + + pub fn is_empty(&self) -> bool { + self.bytes.is_empty() + } } impl Drop for CommandQueue { diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 4bd91ea692a76..547e963ff175b 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -1442,7 +1442,7 @@ mod tests { let mut world = World::default(); let mut system = IntoSystem::into_system(a_not_b_system); let mut expected_ids = HashSet::::new(); - let a_id = world.init_component::(); + let a_id = world.init_component::().id(); // set up system and verify its access is empty system.initialize(&mut world); diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs new file mode 100644 index 0000000000000..99f6a917e5010 --- /dev/null +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -0,0 +1,241 @@ +use std::ops::Deref; + +use crate::{ + change_detection::MutUntyped, + component::ComponentId, + entity::Entity, + event::{Event, EventId, Events, SendBatchIds}, + prelude::{Component, QueryState}, + query::{ReadOnlyWorldQuery, WorldQuery}, + system::{CommandQueue, Commands, Query, Resource}, +}; + +use super::{unsafe_world_cell::UnsafeWorldCell, Mut, World}; + +pub struct DeferredWorld<'w> { + world: UnsafeWorldCell<'w>, + command_queue: CommandQueue, +} + +impl<'w> Deref for DeferredWorld<'w> { + type Target = World; + + fn deref(&self) -> &'w Self::Target { + unsafe { self.world.world() } + } +} + +impl<'w> DeferredWorld<'w> { + pub fn commands(&mut self) -> Commands { + Commands::new(&mut self.command_queue, unsafe { self.world.world() }) + } + + /// Retrieves a mutable reference to the given `entity`'s [`Component`] of the given type. + /// Returns `None` if the `entity` does not have a [`Component`] of the given type. + #[inline] + pub fn get_mut(&mut self, entity: Entity) -> Option> { + // SAFETY: + // - `as_unsafe_world_cell` is the only thing that is borrowing world + // - `as_unsafe_world_cell` provides mutable permission to everything + // - `&mut self` ensures no other borrows on world data + unsafe { self.world.get_entity(entity)?.get_mut() } + } + + /// Returns [`Query`] for the given [`QueryState`], which is used to efficiently + /// run queries on the [`World`] by storing and reusing the [`QueryState`]. + #[inline] + pub fn query<'s, Q: WorldQuery, F: ReadOnlyWorldQuery>( + &mut self, + state: &'s mut QueryState, + ) -> Query<'_, 's, Q, F> { + unsafe { + state.update_archetypes(self.world.world()); + Query::new( + self.world, + state, + self.world.last_change_tick(), + self.world.change_tick(), + false, + ) + } + } + + /// Gets a mutable reference to the resource of the given type + /// + /// # Panics + /// + /// Panics if the resource does not exist. + /// Use [`get_resource_mut`](World::get_resource_mut) instead if you want to handle this case. + /// + /// If you want to instead insert a value if the resource does not exist, + /// use [`get_resource_or_insert_with`](World::get_resource_or_insert_with). + #[inline] + #[track_caller] + pub fn resource_mut(&mut self) -> Mut<'_, R> { + match self.get_resource_mut() { + Some(x) => x, + None => panic!( + "Requested resource {} does not exist in the `World`. + Did you forget to add it using `app.insert_resource` / `app.init_resource`? + Resources are also implicitly added via `app.add_event`, + and can be added by plugins.", + std::any::type_name::() + ), + } + } + + /// Gets a mutable reference to the resource of the given type if it exists + #[inline] + pub fn get_resource_mut(&mut self) -> Option> { + // SAFETY: + // - `as_unsafe_world_cell` gives permission to access everything mutably + // - `&mut self` ensures nothing in world is borrowed + unsafe { self.world.get_resource_mut() } + } + + /// Gets a mutable reference to the non-send resource of the given type, if it exists. + /// + /// # Panics + /// + /// Panics if the resource does not exist. + /// Use [`get_non_send_resource_mut`](World::get_non_send_resource_mut) instead if you want to handle this case. + /// + /// This function will panic if it isn't called from the same thread that the resource was inserted from. + #[inline] + #[track_caller] + pub fn non_send_resource_mut(&mut self) -> Mut<'_, R> { + match self.get_non_send_resource_mut() { + Some(x) => x, + None => panic!( + "Requested non-send resource {} does not exist in the `World`. + Did you forget to add it using `app.insert_non_send_resource` / `app.init_non_send_resource`? + Non-send resources can also be be added by plugins.", + std::any::type_name::() + ), + } + } + + /// Gets a mutable reference to the non-send resource of the given type, if it exists. + /// Otherwise returns `None`. + /// + /// # Panics + /// This function will panic if it isn't called from the same thread that the resource was inserted from. + #[inline] + pub fn get_non_send_resource_mut(&mut self) -> Option> { + // SAFETY: + // - `as_unsafe_world_cell` gives permission to access the entire world mutably + // - `&mut self` ensures that there are no borrows of world data + unsafe { self.world.get_non_send_resource_mut() } + } + + /// Sends an [`Event`]. + /// This method returns the [ID](`EventId`) of the sent `event`, + /// or [`None`] if the `event` could not be sent. + #[inline] + pub fn send_event(&mut self, event: E) -> Option> { + self.send_event_batch(std::iter::once(event))?.next() + } + + /// Sends the default value of the [`Event`] of type `E`. + /// This method returns the [ID](`EventId`) of the sent `event`, + /// or [`None`] if the `event` could not be sent. + #[inline] + pub fn send_event_default(&mut self) -> Option> { + self.send_event(E::default()) + } + + /// Sends a batch of [`Event`]s from an iterator. + /// This method returns the [IDs](`EventId`) of the sent `events`, + /// or [`None`] if the `event` could not be sent. + #[inline] + pub fn send_event_batch( + &mut self, + events: impl IntoIterator, + ) -> Option> { + let Some(mut events_resource) = self.get_resource_mut::>() else { + bevy_utils::tracing::error!( + "Unable to send event `{}`\n\tEvent must be added to the app with `add_event()`\n\thttps://docs.rs/bevy/*/bevy/app/struct.App.html#method.add_event ", + std::any::type_name::() + ); + return None; + }; + Some(events_resource.send_batch(events)) + } + + /// Gets a pointer to the resource with the id [`ComponentId`] if it exists. + /// The returned pointer may be used to modify the resource, as long as the mutable borrow + /// of the [`World`] is still valid. + /// + /// **You should prefer to use the typed API [`World::get_resource_mut`] where possible and only + /// use this in cases where the actual types are not known at compile time.** + #[inline] + pub fn get_resource_mut_by_id(&mut self, component_id: ComponentId) -> Option> { + // SAFETY: + // - `&mut self` ensures that all accessed data is unaliased + // - `as_unsafe_world_cell` provides mutable permission to the whole world + unsafe { self.world.get_resource_mut_by_id(component_id) } + } + + /// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists. + /// The returned pointer may be used to modify the resource, as long as the mutable borrow + /// of the [`World`] is still valid. + /// + /// **You should prefer to use the typed API [`World::get_resource_mut`] where possible and only + /// use this in cases where the actual types are not known at compile time.** + /// + /// # Panics + /// This function will panic if it isn't called from the same thread that the resource was inserted from. + #[inline] + pub fn get_non_send_mut_by_id(&mut self, component_id: ComponentId) -> Option> { + // SAFETY: + // - `&mut self` ensures that all accessed data is unaliased + // - `as_unsafe_world_cell` provides mutable permission to the whole world + unsafe { self.world.get_non_send_resource_mut_by_id(component_id) } + } + + /// Retrieves a mutable untyped reference to the given `entity`'s [`Component`] of the given [`ComponentId`]. + /// Returns `None` if the `entity` does not have a [`Component`] of the given type. + /// + /// **You should prefer to use the typed API [`World::get_mut`] where possible and only + /// use this in cases where the actual types are not known at compile time.** + #[inline] + pub fn get_mut_by_id( + &mut self, + entity: Entity, + component_id: ComponentId, + ) -> Option> { + // SAFETY: + // - `&mut self` ensures that all accessed data is unaliased + // - `as_unsafe_world_cell` provides mutable permission to the whole world + unsafe { self.world.get_entity(entity)?.get_mut_by_id(component_id) } + } +} + +impl World { + pub unsafe fn into_deferred(&self) -> DeferredWorld<'_> { + DeferredWorld { + world: self.as_unsafe_world_cell_readonly(), + command_queue: CommandQueue::default(), + } + } +} + +impl<'w> Into> for &'w mut World { + fn into(self) -> DeferredWorld<'w> { + DeferredWorld { + world: self.as_unsafe_world_cell(), + command_queue: CommandQueue::default(), + } + } +} + +impl<'w> Drop for DeferredWorld<'w> { + fn drop(&mut self) { + unsafe { + self.world + .world_mut() + .command_queue + .append(&mut self.command_queue) + } + } +} diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index c94b45dad10ee..c24a47505acb3 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -4,7 +4,6 @@ use crate::{ change_detection::MutUntyped, component::{Component, ComponentId, ComponentTicks, Components, StorageType}, entity::{Entities, Entity, EntityLocation}, - removal_detection::RemovedComponentEvents, storage::Storages, world::{Mut, World}, }; @@ -569,23 +568,11 @@ impl<'w> EntityWorldMut<'w> { /// This will overwrite any previous value(s) of the same component type. pub fn insert(&mut self, bundle: T) -> &mut Self { let change_tick = self.world.change_tick(); - let bundle_info = self - .world - .bundles - .init_info::(&mut self.world.components, &mut self.world.storages); - let mut bundle_inserter = bundle_info.get_bundle_inserter( - &mut self.world.entities, - &mut self.world.archetypes, - &self.world.components, - &mut self.world.storages, - self.location.archetype_id, - change_tick, - ); + let mut bundle_inserter = + BundleInserter::new::(self.world, self.location.archetype_id, change_tick); // SAFETY: location matches current entity. `T` matches `bundle_info` - unsafe { - self.location = bundle_inserter.insert(self.entity, self.location, bundle); - } - + self.location = unsafe { bundle_inserter.insert(self.entity, self.location, bundle) }; + self.world.flush_commands(); self } @@ -605,28 +592,28 @@ impl<'w> EntityWorldMut<'w> { component: OwningPtr<'_>, ) -> &mut Self { let change_tick = self.world.change_tick(); - - let bundles = &mut self.world.bundles; - let components = &mut self.world.components; - - let (bundle_info, storage_type) = bundles.init_component_info(components, component_id); - let bundle_inserter = bundle_info.get_bundle_inserter( - &mut self.world.entities, - &mut self.world.archetypes, - &self.world.components, - &mut self.world.storages, - self.location.archetype_id, - change_tick, - ); + let (bundle_info, storage_type) = self + .world + .bundles + .init_component_info(&self.world.components, component_id); + let bundle_info: *const BundleInfo = bundle_info; + let bundle_inserter = unsafe { + BundleInserter::new_with_info( + self.world, + self.location.archetype_id, + bundle_info, + change_tick, + ) + }; self.location = insert_dynamic_bundle( bundle_inserter, self.entity, self.location, Some(component).into_iter(), - Some(storage_type).into_iter(), + Some(storage_type).iter().cloned(), ); - + self.world.flush_commands(); self } @@ -648,28 +635,29 @@ impl<'w> EntityWorldMut<'w> { iter_components: I, ) -> &mut Self { let change_tick = self.world.change_tick(); - - let bundles = &mut self.world.bundles; - let components = &mut self.world.components; - - let (bundle_info, storage_types) = bundles.init_dynamic_info(components, component_ids); - let bundle_inserter = bundle_info.get_bundle_inserter( - &mut self.world.entities, - &mut self.world.archetypes, - &self.world.components, - &mut self.world.storages, - self.location.archetype_id, - change_tick, - ); + let (bundle_info, storage_types) = self + .world + .bundles + .init_dynamic_info(&self.world.components, component_ids); + let bundle_info: *const BundleInfo = bundle_info; + let storage_types: *const Vec = storage_types; + let bundle_inserter = unsafe { + BundleInserter::new_with_info( + self.world, + self.location.archetype_id, + bundle_info, + change_tick, + ) + }; self.location = insert_dynamic_bundle( bundle_inserter, self.entity, self.location, iter_components, - storage_types.iter().cloned(), + (&*storage_types).iter().cloned(), ); - + self.world.flush_commands(); self } @@ -680,19 +668,17 @@ impl<'w> EntityWorldMut<'w> { // TODO: BundleRemover? #[must_use] pub fn take(&mut self) -> Option { - let archetypes = &mut self.world.archetypes; let storages = &mut self.world.storages; let components = &mut self.world.components; - let entities = &mut self.world.entities; - let removed_components = &mut self.world.removed_components; - - let bundle_info = self.world.bundles.init_info::(components, storages); + let bundle_id = self.world.bundles.init_info::(components, storages).id(); + // Reborrow to drop &mut World + let bundle_info = unsafe { self.world.bundles.get_unchecked(bundle_id) }; let old_location = self.location; // SAFETY: `archetype_id` exists because it is referenced in the old `EntityLocation` which is valid, // components exist in `bundle_info` because `Bundles::init_info` initializes a `BundleInfo` containing all components of the bundle type `T` let new_archetype_id = unsafe { remove_bundle_from_archetype( - archetypes, + &mut self.world.archetypes, storages, components, old_location.archetype_id, @@ -705,8 +691,24 @@ impl<'w> EntityWorldMut<'w> { return None; } - let mut bundle_components = bundle_info.components().iter().cloned(); + // Reborrow so world is not mutably borrowed let entity = self.entity; + for component_id in bundle_info.components().iter().cloned() { + self.world.removed_components.send(component_id, entity); + unsafe { + let info = self.world.components.get_info_unchecked(component_id); + if let Some(hook) = info.hooks().on_remove { + hook(self.world.into_deferred(), self.entity) + } + } + } + let archetypes = &mut self.world.archetypes; + let storages = &mut self.world.storages; + let components = &mut self.world.components; + let entities = &mut self.world.entities; + + let entity = self.entity; + let mut bundle_components = bundle_info.components().iter().cloned(); // SAFETY: bundle components are iterated in order, which guarantees that the component type // matches let result = unsafe { @@ -716,14 +718,7 @@ impl<'w> EntityWorldMut<'w> { // - entity location is valid // - table row is removed below, without dropping the contents // - `components` comes from the same world as `storages` - take_component( - storages, - components, - removed_components, - component_id, - entity, - old_location, - ) + take_component(storages, components, component_id, entity, old_location) }) }; @@ -740,7 +735,7 @@ impl<'w> EntityWorldMut<'w> { new_archetype_id, ); } - + self.world.flush_commands(); Some(result) } @@ -831,10 +826,10 @@ impl<'w> EntityWorldMut<'w> { let archetypes = &mut self.world.archetypes; let storages = &mut self.world.storages; let components = &mut self.world.components; - let entities = &mut self.world.entities; - let removed_components = &mut self.world.removed_components; - let bundle_info = self.world.bundles.init_info::(components, storages); + let bundle_id = self.world.bundles.init_info::(components, storages).id(); + // Reborrow to drop &mut World + let bundle_info = unsafe { self.world.bundles.get_unchecked(bundle_id) }; let old_location = self.location; // SAFETY: `archetype_id` exists because it is referenced in the old `EntityLocation` which is valid, @@ -855,16 +850,24 @@ impl<'w> EntityWorldMut<'w> { return self; } - let old_archetype = &mut archetypes[old_location.archetype_id]; + let old_archetype = &self.world.archetypes[old_location.archetype_id]; + // Reborrow so world is not mutably borrowed let entity = self.entity; for component_id in bundle_info.components().iter().cloned() { if old_archetype.contains(component_id) { - removed_components.send(component_id, entity); + self.world.removed_components.send(component_id, entity); + unsafe { + let info = self.world.components.get_info_unchecked(component_id); + if let Some(hook) = info.hooks().on_remove { + hook(self.world.into_deferred(), self.entity) + } + } // Make sure to drop components stored in sparse sets. // Dense components are dropped later in `move_to_and_drop_missing_unchecked`. if let Some(StorageType::SparseSet) = old_archetype.get_storage_type(component_id) { - storages + self.world + .storages .sparse_sets .get_mut(component_id) .unwrap() @@ -880,13 +883,13 @@ impl<'w> EntityWorldMut<'w> { &mut self.location, old_location.archetype_id, old_location, - entities, - archetypes, - storages, + &mut self.world.entities, + &mut self.world.archetypes, + &mut self.world.storages, new_archetype_id, ); } - + self.world.flush_commands(); self } @@ -895,6 +898,17 @@ impl<'w> EntityWorldMut<'w> { debug!("Despawning entity {:?}", self.entity); let world = self.world; world.flush(); + let archetype = &world.archetypes[self.location.archetype_id]; + for component_id in archetype.components() { + world.removed_components.send(component_id, self.entity); + unsafe { + let info = world.components().get_info_unchecked(component_id); + if let Some(hook) = info.hooks().on_remove { + hook(world.into_deferred(), self.entity) + } + } + } + let location = world .entities .free(self.entity) @@ -903,10 +917,7 @@ impl<'w> EntityWorldMut<'w> { let moved_entity; { - let archetype = &mut world.archetypes[location.archetype_id]; - for component_id in archetype.components() { - world.removed_components.send(component_id, self.entity); - } + let archetype = &mut world.archetypes[self.location.archetype_id]; let remove_result = archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = remove_result.swapped_entity { let swapped_location = world.entities.get(swapped_entity).unwrap(); @@ -954,6 +965,7 @@ impl<'w> EntityWorldMut<'w> { world.archetypes[moved_location.archetype_id] .set_entity_table_row(moved_location.archetype_row, table_row); } + world.flush_commands(); } /// Gets read-only access to the world that the current entity belongs to. @@ -1431,7 +1443,7 @@ unsafe fn insert_dynamic_bundle< I: Iterator>, S: Iterator, >( - mut bundle_inserter: BundleInserter<'_, '_>, + mut bundle_inserter: BundleInserter<'_>, entity: Entity, location: EntityLocation, components: I, @@ -1587,14 +1599,12 @@ fn sorted_remove(source: &mut Vec, remove: &[T]) { pub(crate) unsafe fn take_component<'a>( storages: &'a mut Storages, components: &Components, - removed_components: &mut RemovedComponentEvents, component_id: ComponentId, entity: Entity, location: EntityLocation, ) -> OwningPtr<'a> { // SAFETY: caller promises component_id to be valid let component_info = components.get_info_unchecked(component_id); - removed_components.send(component_id, entity); match component_info.storage_type() { StorageType::Table => { let table = &mut storages.tables[location.table_id]; @@ -1897,7 +1907,7 @@ mod tests { #[test] fn entity_mut_insert_by_id() { let mut world = World::new(); - let test_component_id = world.init_component::(); + let test_component_id = world.init_component::().id(); let mut entity = world.spawn_empty(); OwningPtr::make(TestComponent(42), |ptr| { @@ -1925,8 +1935,8 @@ mod tests { #[test] fn entity_mut_insert_bundle_by_id() { let mut world = World::new(); - let test_component_id = world.init_component::(); - let test_component_2_id = world.init_component::(); + let test_component_id = world.init_component::().id(); + let test_component_2_id = world.init_component::().id(); let component_ids = [test_component_id, test_component_2_id]; let test_component_value = TestComponent(42); diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 397f38bc795f3..9af73d8c6870d 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1,5 +1,6 @@ //! Defines the [`World`] and APIs for accessing it directly. +mod deferred_world; mod entity_ref; pub mod error; mod spawn_batch; @@ -7,13 +8,14 @@ pub mod unsafe_world_cell; mod world_cell; pub use crate::change_detection::{Mut, Ref, CHECK_TICK_THRESHOLD}; +pub use deferred_world::DeferredWorld; pub use entity_ref::{EntityMut, EntityRef, EntityWorldMut, Entry, OccupiedEntry, VacantEntry}; pub use spawn_batch::*; pub use world_cell::*; use crate::{ archetype::{ArchetypeComponentId, ArchetypeId, ArchetypeRow, Archetypes}, - bundle::{Bundle, BundleInserter, BundleSpawner, Bundles}, + bundle::{Bundle, BundleInfo, BundleInserter, BundleSpawner, Bundles}, change_detection::{MutUntyped, TicksMut}, component::{Component, ComponentDescriptor, ComponentId, ComponentInfo, Components, Tick}, entity::{AllocAtWithoutReplacement, Entities, Entity, EntityLocation}, @@ -22,7 +24,7 @@ use crate::{ removal_detection::RemovedComponentEvents, schedule::{Schedule, ScheduleLabel, Schedules}, storage::{ResourceData, Storages}, - system::Resource, + system::{CommandQueue, Resource}, world::error::TryRunScheduleError, }; use bevy_ptr::{OwningPtr, Ptr}; @@ -71,6 +73,8 @@ pub struct World { pub(crate) change_tick: AtomicU32, pub(crate) last_change_tick: Tick, pub(crate) last_check_tick: Tick, + pub(crate) command_queue: CommandQueue, + flushing_commands: bool, } impl Default for World { @@ -89,6 +93,8 @@ impl Default for World { change_tick: AtomicU32::new(1), last_change_tick: Tick::new(0), last_check_tick: Tick::new(0), + command_queue: CommandQueue::default(), + flushing_commands: false, } } } @@ -178,7 +184,7 @@ impl World { } /// Initializes a new [`Component`] type and returns the [`ComponentId`] created for it. - pub fn init_component(&mut self) -> ComponentId { + pub fn init_component(&mut self) -> &mut ComponentInfo { self.components.init_component::(&mut self.storages) } @@ -194,7 +200,7 @@ impl World { pub fn init_component_with_descriptor( &mut self, descriptor: ComponentDescriptor, - ) -> ComponentId { + ) -> &mut ComponentInfo { self.components .init_component_with_descriptor(&mut self.storages, descriptor) } @@ -215,7 +221,7 @@ impl World { /// #[derive(Component)] /// struct ComponentA; /// - /// let component_a_id = world.init_component::(); + /// let component_a_id = world.init_component::().id(); /// /// assert_eq!(component_a_id, world.component_id::().unwrap()) /// ``` @@ -740,19 +746,9 @@ impl World { let change_tick = self.change_tick(); let entity = self.entities.alloc(); let entity_location = { - let bundle_info = self - .bundles - .init_info::(&mut self.components, &mut self.storages); - let mut spawner = bundle_info.get_bundle_spawner( - &mut self.entities, - &mut self.archetypes, - &self.components, - &mut self.storages, - change_tick, - ); - + let mut bundle_spawner = BundleSpawner::new::(self, change_tick); // SAFETY: bundle's type matches `bundle_info`, entity is allocated but non-existent - unsafe { spawner.spawn_non_existent(entity, bundle) } + unsafe { bundle_spawner.spawn_non_existent(entity, bundle) } }; // SAFETY: entity and location are valid, as they were just created above @@ -1438,26 +1434,23 @@ impl World { let bundle_info = self .bundles .init_info::(&mut self.components, &mut self.storages); - enum SpawnOrInsert<'a, 'b> { - Spawn(BundleSpawner<'a, 'b>), - Insert(BundleInserter<'a, 'b>, ArchetypeId), + enum SpawnOrInsert<'w> { + Spawn(BundleSpawner<'w>), + Insert(BundleInserter<'w>, ArchetypeId), } - impl<'a, 'b> SpawnOrInsert<'a, 'b> { + impl<'w> SpawnOrInsert<'w> { fn entities(&mut self) -> &mut Entities { match self { - SpawnOrInsert::Spawn(spawner) => spawner.entities, - SpawnOrInsert::Insert(inserter, _) => inserter.entities, + SpawnOrInsert::Spawn(spawner) => spawner.entities(), + SpawnOrInsert::Insert(inserter, _) => inserter.entities(), } } } - let mut spawn_or_insert = SpawnOrInsert::Spawn(bundle_info.get_bundle_spawner( - &mut self.entities, - &mut self.archetypes, - &self.components, - &mut self.storages, - change_tick, - )); + let bundle_info: *const BundleInfo = bundle_info; + let mut spawn_or_insert = SpawnOrInsert::Spawn(unsafe { + BundleSpawner::new_with_info(self, bundle_info, change_tick) + }); let mut invalid_entities = Vec::new(); for (entity, bundle) in iter { @@ -1474,14 +1467,14 @@ impl World { unsafe { inserter.insert(entity, location, bundle) }; } _ => { - let mut inserter = bundle_info.get_bundle_inserter( - &mut self.entities, - &mut self.archetypes, - &self.components, - &mut self.storages, - location.archetype_id, - change_tick, - ); + let mut inserter = unsafe { + BundleInserter::new_with_info( + self, + location.archetype_id, + bundle_info, + change_tick, + ) + }; // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter unsafe { inserter.insert(entity, location, bundle) }; spawn_or_insert = @@ -1494,13 +1487,8 @@ impl World { // SAFETY: `entity` is allocated (but non existent), bundle matches inserter unsafe { spawner.spawn_non_existent(entity, bundle) }; } else { - let mut spawner = bundle_info.get_bundle_spawner( - &mut self.entities, - &mut self.archetypes, - &self.components, - &mut self.storages, - change_tick, - ); + let mut spawner = + unsafe { BundleSpawner::new_with_info(self, bundle_info, change_tick) }; // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter unsafe { spawner.spawn_non_existent(entity, bundle) }; spawn_or_insert = SpawnOrInsert::Spawn(spawner); @@ -1735,6 +1723,18 @@ impl World { } } + pub fn flush_commands(&mut self) { + if !self.flushing_commands { + self.flushing_commands = true; + while !self.command_queue.is_empty() { + let mut commands = CommandQueue::default(); + std::mem::swap(&mut commands, &mut self.command_queue); + commands.apply(self) + } + self.flushing_commands = false; + } + } + /// Increments the world's current change tick and returns the old value. #[inline] pub fn increment_change_tick(&self) -> Tick { @@ -2347,7 +2347,7 @@ mod tests { ) }; - let component_id = world.init_component_with_descriptor(descriptor); + let component_id = world.init_component_with_descriptor(descriptor).id(); let value: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7]; OwningPtr::make(value, |ptr| { diff --git a/crates/bevy_ecs/src/world/spawn_batch.rs b/crates/bevy_ecs/src/world/spawn_batch.rs index e21199f56444c..ef90947e54d12 100644 --- a/crates/bevy_ecs/src/world/spawn_batch.rs +++ b/crates/bevy_ecs/src/world/spawn_batch.rs @@ -15,7 +15,7 @@ where I::Item: Bundle, { inner: I, - spawner: BundleSpawner<'w, 'w>, + spawner: BundleSpawner<'w>, } impl<'w, I> SpawnBatchIter<'w, I> @@ -33,18 +33,9 @@ where let (lower, upper) = iter.size_hint(); let length = upper.unwrap_or(lower); - - let bundle_info = world - .bundles - .init_info::(&mut world.components, &mut world.storages); world.entities.reserve(length as u32); - let mut spawner = bundle_info.get_bundle_spawner( - &mut world.entities, - &mut world.archetypes, - &world.components, - &mut world.storages, - change_tick, - ); + + let mut spawner = BundleSpawner::new::(world, change_tick); spawner.reserve_storage(length); Self { diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs new file mode 100644 index 0000000000000..510474e455db1 --- /dev/null +++ b/examples/ecs/component_hooks.rs @@ -0,0 +1,42 @@ +use std::collections::HashSet; + +use bevy::prelude::*; + +#[derive(Component, Debug)] +struct MyComponent(usize); + +#[derive(Resource, Default, Debug, Deref, DerefMut)] +struct MyComponentIndex(HashSet); + +fn main() { + App::new() + .add_systems(Startup, (setup, trigger_hooks).chain()) + .init_resource::() + .run(); +} + +fn setup(world: &mut World) { + world + .init_component::() + .on_add(|mut world, entity| { + println!("Added MyComponent to: {:?}", entity); + world.resource_mut::().insert(entity); + }) + .on_remove(|mut world, entity| { + println!( + "Removed MyComponent from: {:?} {:?}", + entity, + world.get::(entity) + ); + let mut index = world.resource_mut::(); + index.remove(&entity); + println!("Current index: {:?}", *index) + }); +} + +fn trigger_hooks(mut commands: Commands) { + let entity_a = commands.spawn(MyComponent(0)).id(); + let entity_b = commands.spawn(MyComponent(1)).id(); + commands.entity(entity_b).despawn(); + commands.entity(entity_a).despawn(); +} From 6ae319e8c2b63f7af5741a18a7c9a823ee9ad4fe Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Fri, 24 Nov 2023 18:20:32 -0800 Subject: [PATCH 019/109] Perf improvements, archetype flags --- crates/bevy_ecs/Cargo.toml | 1 + crates/bevy_ecs/src/archetype.rs | 97 +++++++++++++++++-- crates/bevy_ecs/src/bundle.rs | 86 ++++++++++------ crates/bevy_ecs/src/component.rs | 31 ++++-- crates/bevy_ecs/src/lib.rs | 14 +-- crates/bevy_ecs/src/query/fetch.rs | 8 +- crates/bevy_ecs/src/query/filter.rs | 4 +- crates/bevy_ecs/src/schedule/schedule.rs | 2 +- crates/bevy_ecs/src/storage/table.rs | 2 +- crates/bevy_ecs/src/system/mod.rs | 2 +- crates/bevy_ecs/src/world/deferred_world.rs | 85 +++++++--------- crates/bevy_ecs/src/world/entity_ref.rs | 71 ++++++++------ crates/bevy_ecs/src/world/mod.rs | 23 ++++- .../bevy_ecs/src/world/unsafe_world_cell.rs | 8 +- examples/ecs/component_hooks.rs | 6 +- 15 files changed, 296 insertions(+), 144 deletions(-) diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index 3231e7b4101ff..0746eb72d2f87 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -22,6 +22,7 @@ bevy_utils = { path = "../bevy_utils", version = "0.12.0" } bevy_ecs_macros = { path = "macros", version = "0.12.0" } async-channel = "1.4" +bitflags = "2.3" event-listener = "2.5" thread_local = "1.1.4" fixedbitset = "0.4.2" diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 3e23da6457739..8de848ba5590e 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -21,9 +21,10 @@ use crate::{ bundle::BundleId, - component::{ComponentId, StorageType}, + component::{ComponentId, Components, StorageType}, entity::{Entity, EntityLocation}, storage::{ImmutableSparseSet, SparseArray, SparseSet, SparseSetIndex, TableId, TableRow}, + world::unsafe_world_cell::UnsafeWorldCell, }; use std::{ hash::Hash, @@ -107,7 +108,7 @@ impl ArchetypeId { } } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Eq, PartialEq)] pub(crate) enum ComponentStatus { Added, Mutated, @@ -298,6 +299,15 @@ struct ArchetypeComponentInfo { archetype_component_id: ArchetypeComponentId, } +bitflags::bitflags! { + #[derive(Clone, Copy)] + pub struct ArchetypeFlags: u32 { + const ON_ADD_HOOK = (1 << 0); + const ON_INSERT_HOOK = (1 << 1); + const ON_REMOVE_HOOK = (1 << 2); + } +} + /// Metadata for a single archetype within a [`World`]. /// /// For more information, see the *[module level documentation]*. @@ -310,10 +320,12 @@ pub struct Archetype { edges: Edges, entities: Vec, components: ImmutableSparseSet, + flags: ArchetypeFlags, } impl Archetype { pub(crate) fn new( + components: &Components, id: ArchetypeId, table_id: TableId, table_components: impl Iterator, @@ -321,9 +333,13 @@ impl Archetype { ) -> Self { let (min_table, _) = table_components.size_hint(); let (min_sparse, _) = sparse_set_components.size_hint(); - let mut components = SparseSet::with_capacity(min_table + min_sparse); + let mut flags = ArchetypeFlags::empty(); + let mut archetype_components = SparseSet::with_capacity(min_table + min_sparse); for (component_id, archetype_component_id) in table_components { - components.insert( + // SAFETY: We are creating an archetype that includes this component so it must exist + let info = unsafe { components.get_info_unchecked(component_id) }; + info.update_archetype_flags(&mut flags); + archetype_components.insert( component_id, ArchetypeComponentInfo { storage_type: StorageType::Table, @@ -333,7 +349,10 @@ impl Archetype { } for (component_id, archetype_component_id) in sparse_set_components { - components.insert( + // SAFETY: We are creating an archetype that includes this component so it must exist + let info = unsafe { components.get_info_unchecked(component_id) }; + info.update_archetype_flags(&mut flags); + archetype_components.insert( component_id, ArchetypeComponentInfo { storage_type: StorageType::SparseSet, @@ -345,8 +364,9 @@ impl Archetype { id, table_id, entities: Vec::new(), - components: components.into_immutable(), + components: archetype_components.into_immutable(), edges: Default::default(), + flags, } } @@ -356,6 +376,11 @@ impl Archetype { self.id } + #[inline] + pub fn flags(&self) -> ArchetypeFlags { + self.flags + } + /// Fetches the archetype's [`Table`] ID. /// /// [`Table`]: crate::storage::Table @@ -536,6 +561,57 @@ impl Archetype { pub(crate) fn clear_entities(&mut self) { self.entities.clear(); } + + #[inline] + pub(crate) unsafe fn trigger_on_add( + &self, + world: UnsafeWorldCell, + entity: Entity, + targets: impl Iterator, + ) { + if self.flags().contains(ArchetypeFlags::ON_ADD_HOOK) { + for component_id in targets { + let hooks = unsafe { world.components().get_info_unchecked(component_id) }.hooks(); + if let Some(hook) = hooks.on_add { + hook(world.into_deferred(), entity, component_id) + } + } + } + } + + #[inline] + pub(crate) unsafe fn trigger_on_insert( + &self, + world: UnsafeWorldCell, + entity: Entity, + targets: impl Iterator, + ) { + if self.flags().contains(ArchetypeFlags::ON_INSERT_HOOK) { + for component_id in targets { + let hooks = unsafe { world.components().get_info_unchecked(component_id) }.hooks(); + if let Some(hook) = hooks.on_insert { + hook(world.into_deferred(), entity, component_id) + } + } + } + } + + #[inline] + pub(crate) unsafe fn trigger_on_remove( + &self, + world: UnsafeWorldCell, + entity: Entity, + targets: impl Iterator, + ) { + if self.flags().contains(ArchetypeFlags::ON_REMOVE_HOOK) { + for component_id in targets { + let hooks = unsafe { world.components().get_info_unchecked(component_id) }.hooks(); + if let Some(hook) = hooks.on_remove { + hook(world.into_deferred(), entity, component_id); + } + } + } + } } /// The next [`ArchetypeId`] in an [`Archetypes`] collection. @@ -625,7 +701,12 @@ impl Archetypes { by_components: Default::default(), archetype_component_count: 0, }; - archetypes.get_id_or_insert(TableId::empty(), Vec::new(), Vec::new()); + archetypes.get_id_or_insert( + &Components::default(), + TableId::empty(), + Vec::new(), + Vec::new(), + ); archetypes } @@ -704,6 +785,7 @@ impl Archetypes { /// [`TableId`] must exist in tables pub(crate) fn get_id_or_insert( &mut self, + components: &Components, table_id: TableId, table_components: Vec, sparse_set_components: Vec, @@ -729,6 +811,7 @@ impl Archetypes { let sparse_set_archetype_components = (sparse_start..*archetype_component_count).map(ArchetypeComponentId); archetypes.push(Archetype::new( + components, id, table_id, table_components.into_iter().zip(table_archetype_components), diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 9979c0c267c30..1ea87f76ed6f9 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -185,7 +185,7 @@ unsafe impl Bundle for C { storages: &mut Storages, ids: &mut impl FnMut(ComponentId), ) { - ids(components.init_component::(storages).id()); + ids(components.init_component::(storages)); } unsafe fn from_components(ctx: &mut T, func: &mut F) -> Self @@ -346,6 +346,11 @@ impl BundleInfo { &self.component_ids } + #[inline] + pub fn iter_components(&self) -> impl Iterator + '_ { + self.component_ids.iter().cloned() + } + /// This writes components from a given [`Bundle`] to the given entity. /// /// # Safety @@ -478,8 +483,12 @@ impl BundleInfo { new_sparse_set_components }; }; - let new_archetype_id = - archetypes.get_id_or_insert(table_id, table_components, sparse_set_components); + let new_archetype_id = archetypes.get_id_or_insert( + components, + table_id, + table_components, + sparse_set_components, + ); // add an edge from the old archetype to the new archetype archetypes[archetype_id].edges_mut().insert_add_bundle( self.id, @@ -612,22 +621,17 @@ impl<'w> BundleInserter<'w> { bundle: T, ) -> EntityLocation { let bundle_info = &*self.bundle_info; - let add_bundle: &AddBundle = &*self.add_bundle; + let add_bundle = &*self.add_bundle; let world = &mut *self.world; - for (i, component_id) in bundle_info.components().iter().cloned().enumerate() { - let hooks = unsafe { world.components.get_info_unchecked(component_id) }.hooks(); - if let ComponentStatus::Added = add_bundle.bundle_status[i] { - if let Some(hook) = hooks.on_add { - hook(unsafe { world.into_deferred() }, entity) - } - } - if let Some(hook) = hooks.on_insert { - hook(unsafe { world.into_deferred() }, entity) - } - } match self.result { InsertBundleResult::SameArchetype => { + let archetype = &mut *self.archetype; + archetype.trigger_on_insert( + world.as_unsafe_world_cell_readonly(), + entity, + bundle_info.components().iter().cloned(), + ); bundle_info.write_components( &mut *self.table, &mut world.storages.sparse_sets, @@ -643,6 +647,20 @@ impl<'w> BundleInserter<'w> { let table = &mut *self.table; let archetype = &mut *self.archetype; let new_archetype = &mut *new_archetype; + new_archetype.trigger_on_add( + world.as_unsafe_world_cell_readonly(), + entity, + bundle_info + .components() + .iter() + .cloned() + .filter(|id| add_bundle.get_status(id.index()) == ComponentStatus::Added), + ); + new_archetype.trigger_on_insert( + world.as_unsafe_world_cell_readonly(), + entity, + bundle_info.components().iter().cloned(), + ); let result = archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { let swapped_location = @@ -679,6 +697,20 @@ impl<'w> BundleInserter<'w> { let new_table = &mut *new_table; let archetype = &mut *self.archetype; let new_archetype = &mut *new_archetype; + new_archetype.trigger_on_add( + world.as_unsafe_world_cell_readonly(), + entity, + bundle_info + .components() + .iter() + .cloned() + .filter(|id| add_bundle.get_status(id.index()) == ComponentStatus::Added), + ); + new_archetype.trigger_on_insert( + world.as_unsafe_world_cell_readonly(), + entity, + bundle_info.components().iter().cloned(), + ); let result = archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { let swapped_location = @@ -803,19 +835,17 @@ impl<'w> BundleSpawner<'w> { bundle: T, ) -> EntityLocation { let bundle_info = &*self.bundle_info; - for component_id in bundle_info.components().iter().cloned() { - let hooks = self - .world - .components - .get_info_unchecked(component_id) - .hooks(); - if let Some(hook) = hooks.on_add { - hook(self.world.into_deferred(), entity); - } - if let Some(hook) = hooks.on_insert { - hook(self.world.into_deferred(), entity); - } - } + let archetype = &*self.archetype; + archetype.trigger_on_add( + self.world.as_unsafe_world_cell_readonly(), + entity, + bundle_info.iter_components(), + ); + archetype.trigger_on_insert( + self.world.as_unsafe_world_cell_readonly(), + entity, + bundle_info.iter_components(), + ); let archetype = &mut *self.archetype; let table = &mut *self.table; diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 3155c9b54ae4a..21c7ea3d26fa1 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -2,6 +2,7 @@ use crate::{ self as bevy_ecs, + archetype::ArchetypeFlags, change_detection::MAX_CHANGE_AGE, entity::Entity, storage::{SparseSetIndex, Storages}, @@ -280,6 +281,19 @@ impl ComponentInfo { self } + #[inline] + pub fn update_archetype_flags(&self, flags: &mut ArchetypeFlags) { + if self.hooks().on_add.is_some() { + flags.insert(ArchetypeFlags::ON_ADD_HOOK); + } + if self.hooks().on_insert.is_some() { + flags.insert(ArchetypeFlags::ON_INSERT_HOOK); + } + if self.hooks().on_remove.is_some() { + flags.insert(ArchetypeFlags::ON_REMOVE_HOOK); + } + } + pub fn hooks(&self) -> &ComponentHooks { &self.descriptor.hooks } @@ -338,7 +352,7 @@ impl SparseSetIndex for ComponentId { } } -pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, Entity); +pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, Entity, ComponentId); #[derive(Clone, Default)] pub struct ComponentHooks { @@ -487,7 +501,7 @@ impl Components { /// * [`Components::component_id()`] /// * [`Components::init_component_with_descriptor()`] #[inline] - pub fn init_component(&mut self, storages: &mut Storages) -> &mut ComponentInfo { + pub fn init_component(&mut self, storages: &mut Storages) -> ComponentId { let type_id = TypeId::of::(); let Components { @@ -498,7 +512,7 @@ impl Components { let index = indices.entry(type_id).or_insert_with(|| { Components::init_component_inner(components, storages, ComponentDescriptor::new::()) }); - &mut components[*index] + ComponentId(*index) } /// Initializes a component described by `descriptor`. @@ -516,9 +530,9 @@ impl Components { &mut self, storages: &mut Storages, descriptor: ComponentDescriptor, - ) -> &mut ComponentInfo { + ) -> ComponentId { let index = Components::init_component_inner(&mut self.components, storages, descriptor); - &mut self.components[index] + ComponentId(index) } #[inline] @@ -574,6 +588,11 @@ impl Components { self.components.get_unchecked(id.0) } + #[inline] + pub(crate) unsafe fn get_info_mut(&mut self, id: ComponentId) -> &mut ComponentInfo { + self.components.get_unchecked_mut(id.0) + } + /// Type-erased equivalent of [`Components::component_id()`]. #[inline] pub fn get_id(&self, type_id: TypeId) -> Option { @@ -906,7 +925,7 @@ struct InitComponentId { impl FromWorld for InitComponentId { fn from_world(world: &mut World) -> Self { Self { - component_id: world.init_component::().id(), + component_id: world.init_component::(), marker: PhantomData, } } diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 9cdcde75c062d..69ce32e8a8806 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -157,8 +157,8 @@ mod tests { assert_eq!( ids, &[ - world.init_component::().id(), - world.init_component::().id(), + world.init_component::(), + world.init_component::(), ] ); @@ -211,10 +211,10 @@ mod tests { assert_eq!( ids, &[ - world.init_component::().id(), - world.init_component::().id(), - world.init_component::().id(), - world.init_component::().id(), + world.init_component::(), + world.init_component::(), + world.init_component::(), + world.init_component::(), ] ); @@ -264,7 +264,7 @@ mod tests { }, ); - assert_eq!(ids, &[world.init_component::().id(),]); + assert_eq!(ids, &[world.init_component::(),]); let e4 = world .spawn(BundleWithIgnored { diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 3656931cb0398..00df1fe63e17c 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -639,7 +639,7 @@ unsafe impl WorldQuery for &T { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::().id() + world.init_component::() } fn matches_component_set( @@ -806,7 +806,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::().id() + world.init_component::() } fn matches_component_set( @@ -973,7 +973,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::().id() + world.init_component::() } fn matches_component_set( @@ -1229,7 +1229,7 @@ unsafe impl WorldQuery for Has { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::().id() + world.init_component::() } fn matches_component_set( diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index 8e49391dcee60..dcc2cedeea20b 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -184,7 +184,7 @@ unsafe impl WorldQuery for With { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::().id() + world.init_component::() } fn matches_component_set( @@ -296,7 +296,7 @@ unsafe impl WorldQuery for Without { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::().id() + world.init_component::() } fn matches_component_set( diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index c1cce703f4361..5b042590d5cf2 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -118,7 +118,7 @@ impl Schedules { /// Ignore system order ambiguities caused by conflicts on [`Component`]s of type `T`. pub fn allow_ambiguous_component(&mut self, world: &mut World) { self.ignored_scheduling_ambiguities - .insert(world.init_component::().id()); + .insert(world.init_component::()); } /// Ignore system order ambiguities caused by conflicts on [`Resource`]s of type `T`. diff --git a/crates/bevy_ecs/src/storage/table.rs b/crates/bevy_ecs/src/storage/table.rs index ddec964226d61..e1955bc598f89 100644 --- a/crates/bevy_ecs/src/storage/table.rs +++ b/crates/bevy_ecs/src/storage/table.rs @@ -918,7 +918,7 @@ mod tests { fn table() { let mut components = Components::default(); let mut storages = Storages::default(); - let component_id = components.init_component::>(&mut storages).id(); + let component_id = components.init_component::>(&mut storages); let columns = &[component_id]; let mut table = TableBuilder::with_capacity(0, columns.len()) .add_column(components.get_info(component_id).unwrap()) diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 547e963ff175b..4bd91ea692a76 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -1442,7 +1442,7 @@ mod tests { let mut world = World::default(); let mut system = IntoSystem::into_system(a_not_b_system); let mut expected_ids = HashSet::::new(); - let a_id = world.init_component::().id(); + let a_id = world.init_component::(); // set up system and verify its access is empty system.initialize(&mut world); diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 99f6a917e5010..bf492993ddfd5 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -7,27 +7,27 @@ use crate::{ event::{Event, EventId, Events, SendBatchIds}, prelude::{Component, QueryState}, query::{ReadOnlyWorldQuery, WorldQuery}, - system::{CommandQueue, Commands, Query, Resource}, + system::{Commands, Query, Resource}, }; use super::{unsafe_world_cell::UnsafeWorldCell, Mut, World}; pub struct DeferredWorld<'w> { - world: UnsafeWorldCell<'w>, - command_queue: CommandQueue, + world: &'w mut World, } impl<'w> Deref for DeferredWorld<'w> { - type Target = World; + type Target = &'w mut World; - fn deref(&self) -> &'w Self::Target { - unsafe { self.world.world() } + fn deref(&self) -> &Self::Target { + &self.world } } impl<'w> DeferredWorld<'w> { pub fn commands(&mut self) -> Commands { - Commands::new(&mut self.command_queue, unsafe { self.world.world() }) + let world = self.world.as_unsafe_world_cell(); + unsafe { Commands::new(world.get_command_queue(), world.world()) } } /// Retrieves a mutable reference to the given `entity`'s [`Component`] of the given type. @@ -38,23 +38,29 @@ impl<'w> DeferredWorld<'w> { // - `as_unsafe_world_cell` is the only thing that is borrowing world // - `as_unsafe_world_cell` provides mutable permission to everything // - `&mut self` ensures no other borrows on world data - unsafe { self.world.get_entity(entity)?.get_mut() } + unsafe { + self.world + .as_unsafe_world_cell() + .get_entity(entity)? + .get_mut() + } } /// Returns [`Query`] for the given [`QueryState`], which is used to efficiently /// run queries on the [`World`] by storing and reusing the [`QueryState`]. #[inline] pub fn query<'s, Q: WorldQuery, F: ReadOnlyWorldQuery>( - &mut self, + &'w mut self, state: &'s mut QueryState, - ) -> Query<'_, 's, Q, F> { + ) -> Query<'w, 's, Q, F> { unsafe { - state.update_archetypes(self.world.world()); + state.update_archetypes(self.world); + let world = self.world.as_unsafe_world_cell(); Query::new( - self.world, + world, state, - self.world.last_change_tick(), - self.world.change_tick(), + world.last_change_tick(), + world.change_tick(), false, ) } @@ -87,10 +93,7 @@ impl<'w> DeferredWorld<'w> { /// Gets a mutable reference to the resource of the given type if it exists #[inline] pub fn get_resource_mut(&mut self) -> Option> { - // SAFETY: - // - `as_unsafe_world_cell` gives permission to access everything mutably - // - `&mut self` ensures nothing in world is borrowed - unsafe { self.world.get_resource_mut() } + self.world.get_resource_mut() } /// Gets a mutable reference to the non-send resource of the given type, if it exists. @@ -122,10 +125,7 @@ impl<'w> DeferredWorld<'w> { /// This function will panic if it isn't called from the same thread that the resource was inserted from. #[inline] pub fn get_non_send_resource_mut(&mut self) -> Option> { - // SAFETY: - // - `as_unsafe_world_cell` gives permission to access the entire world mutably - // - `&mut self` ensures that there are no borrows of world data - unsafe { self.world.get_non_send_resource_mut() } + self.world.get_non_send_resource_mut() } /// Sends an [`Event`]. @@ -170,10 +170,7 @@ impl<'w> DeferredWorld<'w> { /// use this in cases where the actual types are not known at compile time.** #[inline] pub fn get_resource_mut_by_id(&mut self, component_id: ComponentId) -> Option> { - // SAFETY: - // - `&mut self` ensures that all accessed data is unaliased - // - `as_unsafe_world_cell` provides mutable permission to the whole world - unsafe { self.world.get_resource_mut_by_id(component_id) } + self.world.get_resource_mut_by_id(component_id) } /// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists. @@ -187,10 +184,7 @@ impl<'w> DeferredWorld<'w> { /// This function will panic if it isn't called from the same thread that the resource was inserted from. #[inline] pub fn get_non_send_mut_by_id(&mut self, component_id: ComponentId) -> Option> { - // SAFETY: - // - `&mut self` ensures that all accessed data is unaliased - // - `as_unsafe_world_cell` provides mutable permission to the whole world - unsafe { self.world.get_non_send_resource_mut_by_id(component_id) } + self.world.get_non_send_mut_by_id(component_id) } /// Retrieves a mutable untyped reference to the given `entity`'s [`Component`] of the given [`ComponentId`]. @@ -207,35 +201,26 @@ impl<'w> DeferredWorld<'w> { // SAFETY: // - `&mut self` ensures that all accessed data is unaliased // - `as_unsafe_world_cell` provides mutable permission to the whole world - unsafe { self.world.get_entity(entity)?.get_mut_by_id(component_id) } + unsafe { + self.world + .as_unsafe_world_cell() + .get_entity(entity)? + .get_mut_by_id(component_id) + } } } -impl World { - pub unsafe fn into_deferred(&self) -> DeferredWorld<'_> { +impl<'w> UnsafeWorldCell<'w> { + pub unsafe fn into_deferred(&self) -> DeferredWorld<'w> { DeferredWorld { - world: self.as_unsafe_world_cell_readonly(), - command_queue: CommandQueue::default(), + // SAFETY: Not + world: self.world_mut(), } } } impl<'w> Into> for &'w mut World { fn into(self) -> DeferredWorld<'w> { - DeferredWorld { - world: self.as_unsafe_world_cell(), - command_queue: CommandQueue::default(), - } - } -} - -impl<'w> Drop for DeferredWorld<'w> { - fn drop(&mut self) { - unsafe { - self.world - .world_mut() - .command_queue - .append(&mut self.command_queue) - } + DeferredWorld { world: self } } } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index c24a47505acb3..2741919e8ffbd 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -693,14 +693,17 @@ impl<'w> EntityWorldMut<'w> { // Reborrow so world is not mutably borrowed let entity = self.entity; - for component_id in bundle_info.components().iter().cloned() { + let new_archetype = &self.world.archetypes[new_archetype_id]; + unsafe { + new_archetype.trigger_on_remove( + // SAFETY: the only outstanding borrow of the world is to an Archetype which cannot be modified through a deferred world + self.world.as_unsafe_world_cell_readonly(), + entity, + bundle_info.iter_components(), + ); + } + for component_id in bundle_info.iter_components() { self.world.removed_components.send(component_id, entity); - unsafe { - let info = self.world.components.get_info_unchecked(component_id); - if let Some(hook) = info.hooks().on_remove { - hook(self.world.into_deferred(), self.entity) - } - } } let archetypes = &mut self.world.archetypes; let storages = &mut self.world.storages; @@ -708,7 +711,7 @@ impl<'w> EntityWorldMut<'w> { let entities = &mut self.world.entities; let entity = self.entity; - let mut bundle_components = bundle_info.components().iter().cloned(); + let mut bundle_components = bundle_info.iter_components(); // SAFETY: bundle components are iterated in order, which guarantees that the component type // matches let result = unsafe { @@ -735,6 +738,7 @@ impl<'w> EntityWorldMut<'w> { new_archetype_id, ); } + drop(bundle_components); self.world.flush_commands(); Some(result) } @@ -850,18 +854,21 @@ impl<'w> EntityWorldMut<'w> { return self; } - let old_archetype = &self.world.archetypes[old_location.archetype_id]; // Reborrow so world is not mutably borrowed - let entity = self.entity; - for component_id in bundle_info.components().iter().cloned() { + let entity: Entity = self.entity; + let old_archetype = &self.world.archetypes[old_location.archetype_id]; + unsafe { + old_archetype.trigger_on_remove( + self.world.as_unsafe_world_cell_readonly(), + entity, + bundle_info + .iter_components() + .filter(|id| old_archetype.contains(*id)), + ); + } + for component_id in bundle_info.iter_components() { if old_archetype.contains(component_id) { self.world.removed_components.send(component_id, entity); - unsafe { - let info = self.world.components.get_info_unchecked(component_id); - if let Some(hook) = info.hooks().on_remove { - hook(self.world.into_deferred(), self.entity) - } - } // Make sure to drop components stored in sparse sets. // Dense components are dropped later in `move_to_and_drop_missing_unchecked`. @@ -896,19 +903,22 @@ impl<'w> EntityWorldMut<'w> { /// Despawns the current entity. pub fn despawn(self) { debug!("Despawning entity {:?}", self.entity); - let world = self.world; - world.flush(); - let archetype = &world.archetypes[self.location.archetype_id]; + self.world.flush(); + let archetype = &self.world.archetypes[self.location.archetype_id]; + unsafe { + archetype.trigger_on_remove( + self.world.as_unsafe_world_cell_readonly(), + self.entity, + archetype.components(), + ); + } for component_id in archetype.components() { - world.removed_components.send(component_id, self.entity); - unsafe { - let info = world.components().get_info_unchecked(component_id); - if let Some(hook) = info.hooks().on_remove { - hook(world.into_deferred(), self.entity) - } - } + self.world + .removed_components + .send(component_id, self.entity); } + let world = self.world; let location = world .entities .free(self.entity) @@ -1549,6 +1559,7 @@ unsafe fn remove_bundle_from_archetype( } let new_archetype_id = archetypes.get_id_or_insert( + components, next_table_id, next_table_components, next_sparse_set_components, @@ -1907,7 +1918,7 @@ mod tests { #[test] fn entity_mut_insert_by_id() { let mut world = World::new(); - let test_component_id = world.init_component::().id(); + let test_component_id = world.init_component::(); let mut entity = world.spawn_empty(); OwningPtr::make(TestComponent(42), |ptr| { @@ -1935,8 +1946,8 @@ mod tests { #[test] fn entity_mut_insert_bundle_by_id() { let mut world = World::new(); - let test_component_id = world.init_component::().id(); - let test_component_2_id = world.init_component::().id(); + let test_component_id = world.init_component::(); + let test_component_2_id = world.init_component::(); let component_ids = [test_component_id, test_component_2_id]; let test_component_value = TestComponent(42); diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 9af73d8c6870d..03a8926f8c08c 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -183,11 +183,28 @@ impl World { WorldCell::new(self) } + pub fn register_component(&mut self) -> &mut ComponentInfo { + let type_id = TypeId::of::(); + assert!(self.components.get_id(type_id).is_none(), "Components cannot be registered twice, use init_component instead if this component may already exist in the world."); + let index = self.init_component::(); + // SAFETY: We just created this component + unsafe { self.components.get_info_mut(index) } + } + /// Initializes a new [`Component`] type and returns the [`ComponentId`] created for it. - pub fn init_component(&mut self) -> &mut ComponentInfo { + pub fn init_component(&mut self) -> ComponentId { self.components.init_component::(&mut self.storages) } + pub fn register_component_with_descriptor( + &mut self, + descriptor: ComponentDescriptor, + ) -> &mut ComponentInfo { + let index = self.init_component_with_descriptor(descriptor); + // SAFETY: We just created this component + unsafe { self.components.get_info_mut(index) } + } + /// Initializes a new [`Component`] type and returns the [`ComponentId`] created for it. /// /// This method differs from [`World::init_component`] in that it uses a [`ComponentDescriptor`] @@ -200,7 +217,7 @@ impl World { pub fn init_component_with_descriptor( &mut self, descriptor: ComponentDescriptor, - ) -> &mut ComponentInfo { + ) -> ComponentId { self.components .init_component_with_descriptor(&mut self.storages, descriptor) } @@ -2347,7 +2364,7 @@ mod tests { ) }; - let component_id = world.init_component_with_descriptor(descriptor).id(); + let component_id = world.init_component_with_descriptor(descriptor); let value: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7]; OwningPtr::make(value, |ptr| { diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 25d5ac62c0305..667d6bfaa95e3 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -14,7 +14,7 @@ use crate::{ prelude::Component, removal_detection::RemovedComponentEvents, storage::{Column, ComponentSparseSet, Storages}, - system::Resource, + system::{CommandQueue, Resource}, }; use bevy_ptr::Ptr; use std::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData}; @@ -566,6 +566,12 @@ impl<'w> UnsafeWorldCell<'w> { .get(component_id)? .get_with_ticks() } + + #[inline] + pub(crate) unsafe fn get_command_queue(self) -> &'w mut CommandQueue { + let world = unsafe { &mut *self.0 }; + &mut world.command_queue + } } impl Debug for UnsafeWorldCell<'_> { diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs index 510474e455db1..9309de94e9fc9 100644 --- a/examples/ecs/component_hooks.rs +++ b/examples/ecs/component_hooks.rs @@ -17,12 +17,12 @@ fn main() { fn setup(world: &mut World) { world - .init_component::() - .on_add(|mut world, entity| { + .register_component::() + .on_add(|mut world, entity, _| { println!("Added MyComponent to: {:?}", entity); world.resource_mut::().insert(entity); }) - .on_remove(|mut world, entity| { + .on_remove(|mut world, entity, _| { println!( "Removed MyComponent from: {:?} {:?}", entity, From 5c4702863fa51dc506f426911f9990895d933d4c Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Fri, 24 Nov 2023 19:23:02 -0800 Subject: [PATCH 020/109] Refactoring --- crates/bevy_ecs/src/archetype.rs | 49 ++--------- crates/bevy_ecs/src/bundle.rs | 92 +++++++++++---------- crates/bevy_ecs/src/world/deferred_world.rs | 51 +++++++++++- crates/bevy_ecs/src/world/entity_ref.rs | 33 +++----- 4 files changed, 113 insertions(+), 112 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 8de848ba5590e..0608002bbd431 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -24,7 +24,6 @@ use crate::{ component::{ComponentId, Components, StorageType}, entity::{Entity, EntityLocation}, storage::{ImmutableSparseSet, SparseArray, SparseSet, SparseSetIndex, TableId, TableRow}, - world::unsafe_world_cell::UnsafeWorldCell, }; use std::{ hash::Hash, @@ -563,54 +562,18 @@ impl Archetype { } #[inline] - pub(crate) unsafe fn trigger_on_add( - &self, - world: UnsafeWorldCell, - entity: Entity, - targets: impl Iterator, - ) { - if self.flags().contains(ArchetypeFlags::ON_ADD_HOOK) { - for component_id in targets { - let hooks = unsafe { world.components().get_info_unchecked(component_id) }.hooks(); - if let Some(hook) = hooks.on_add { - hook(world.into_deferred(), entity, component_id) - } - } - } + pub fn has_on_add(&self) -> bool { + self.flags().contains(ArchetypeFlags::ON_ADD_HOOK) } #[inline] - pub(crate) unsafe fn trigger_on_insert( - &self, - world: UnsafeWorldCell, - entity: Entity, - targets: impl Iterator, - ) { - if self.flags().contains(ArchetypeFlags::ON_INSERT_HOOK) { - for component_id in targets { - let hooks = unsafe { world.components().get_info_unchecked(component_id) }.hooks(); - if let Some(hook) = hooks.on_insert { - hook(world.into_deferred(), entity, component_id) - } - } - } + pub fn has_on_insert(&self) -> bool { + self.flags().contains(ArchetypeFlags::ON_INSERT_HOOK) } #[inline] - pub(crate) unsafe fn trigger_on_remove( - &self, - world: UnsafeWorldCell, - entity: Entity, - targets: impl Iterator, - ) { - if self.flags().contains(ArchetypeFlags::ON_REMOVE_HOOK) { - for component_id in targets { - let hooks = unsafe { world.components().get_info_unchecked(component_id) }.hooks(); - if let Some(hook) = hooks.on_remove { - hook(world.into_deferred(), entity, component_id); - } - } - } + pub fn has_on_remove(&self) -> bool { + self.flags().contains(ArchetypeFlags::ON_REMOVE_HOOK) } } diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 1ea87f76ed6f9..7ab66d4690859 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -627,11 +627,19 @@ impl<'w> BundleInserter<'w> { match self.result { InsertBundleResult::SameArchetype => { let archetype = &mut *self.archetype; - archetype.trigger_on_insert( - world.as_unsafe_world_cell_readonly(), - entity, - bundle_info.components().iter().cloned(), - ); + if archetype.has_on_add() { + world.into_deferred().trigger_on_add( + entity, + bundle_info.iter_components().filter(|id| { + add_bundle.get_status(id.index()) == ComponentStatus::Added + }), + ); + } + if archetype.has_on_insert() { + world + .into_deferred() + .trigger_on_insert(entity, bundle_info.iter_components()) + } bundle_info.write_components( &mut *self.table, &mut world.storages.sparse_sets, @@ -647,20 +655,19 @@ impl<'w> BundleInserter<'w> { let table = &mut *self.table; let archetype = &mut *self.archetype; let new_archetype = &mut *new_archetype; - new_archetype.trigger_on_add( - world.as_unsafe_world_cell_readonly(), - entity, - bundle_info - .components() - .iter() - .cloned() - .filter(|id| add_bundle.get_status(id.index()) == ComponentStatus::Added), - ); - new_archetype.trigger_on_insert( - world.as_unsafe_world_cell_readonly(), - entity, - bundle_info.components().iter().cloned(), - ); + if new_archetype.has_on_add() { + world.into_deferred().trigger_on_add( + entity, + bundle_info.iter_components().filter(|id| { + add_bundle.get_status(id.index()) == ComponentStatus::Added + }), + ); + } + if new_archetype.has_on_insert() { + world + .into_deferred() + .trigger_on_insert(entity, bundle_info.iter_components()) + } let result = archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { let swapped_location = @@ -697,20 +704,19 @@ impl<'w> BundleInserter<'w> { let new_table = &mut *new_table; let archetype = &mut *self.archetype; let new_archetype = &mut *new_archetype; - new_archetype.trigger_on_add( - world.as_unsafe_world_cell_readonly(), - entity, - bundle_info - .components() - .iter() - .cloned() - .filter(|id| add_bundle.get_status(id.index()) == ComponentStatus::Added), - ); - new_archetype.trigger_on_insert( - world.as_unsafe_world_cell_readonly(), - entity, - bundle_info.components().iter().cloned(), - ); + if new_archetype.has_on_add() { + world.into_deferred().trigger_on_add( + entity, + bundle_info.iter_components().filter(|id| { + add_bundle.get_status(id.index()) == ComponentStatus::Added + }), + ); + } + if new_archetype.has_on_insert() { + world + .into_deferred() + .trigger_on_insert(entity, bundle_info.iter_components()) + } let result = archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { let swapped_location = @@ -836,16 +842,16 @@ impl<'w> BundleSpawner<'w> { ) -> EntityLocation { let bundle_info = &*self.bundle_info; let archetype = &*self.archetype; - archetype.trigger_on_add( - self.world.as_unsafe_world_cell_readonly(), - entity, - bundle_info.iter_components(), - ); - archetype.trigger_on_insert( - self.world.as_unsafe_world_cell_readonly(), - entity, - bundle_info.iter_components(), - ); + if archetype.has_on_add() { + self.world + .into_deferred() + .trigger_on_add(entity, bundle_info.iter_components()); + } + if archetype.has_on_insert() { + self.world + .into_deferred() + .trigger_on_insert(entity, bundle_info.iter_components()) + } let archetype = &mut *self.archetype; let table = &mut *self.table; diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index bf492993ddfd5..932b9a66a3a82 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -10,7 +10,7 @@ use crate::{ system::{Commands, Query, Resource}, }; -use super::{unsafe_world_cell::UnsafeWorldCell, Mut, World}; +use super::{Mut, World}; pub struct DeferredWorld<'w> { world: &'w mut World, @@ -208,13 +208,56 @@ impl<'w> DeferredWorld<'w> { .get_mut_by_id(component_id) } } + + #[inline] + pub(crate) fn trigger_on_add( + &mut self, + entity: Entity, + targets: impl Iterator, + ) { + for component_id in targets { + let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks(); + if let Some(hook) = hooks.on_add { + hook(DeferredWorld { world: self.world }, entity, component_id) + } + } + } + + #[inline] + pub(crate) fn trigger_on_insert( + &mut self, + entity: Entity, + targets: impl Iterator, + ) { + for component_id in targets { + let hooks = unsafe { self.world.components().get_info_unchecked(component_id) }.hooks(); + if let Some(hook) = hooks.on_insert { + hook(DeferredWorld { world: self.world }, entity, component_id) + } + } + } + + #[inline] + pub(crate) fn trigger_on_remove( + &mut self, + entity: Entity, + targets: impl Iterator, + ) { + for component_id in targets { + let hooks = unsafe { self.world.components().get_info_unchecked(component_id) }.hooks(); + if let Some(hook) = hooks.on_remove { + hook(DeferredWorld { world: self.world }, entity, component_id) + } + } + } } -impl<'w> UnsafeWorldCell<'w> { - pub unsafe fn into_deferred(&self) -> DeferredWorld<'w> { +impl World { + #[inline] + pub unsafe fn into_deferred(&self) -> DeferredWorld { DeferredWorld { // SAFETY: Not - world: self.world_mut(), + world: self.as_unsafe_world_cell_readonly().world_mut(), } } } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 2741919e8ffbd..50ddf83797a93 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -693,14 +693,10 @@ impl<'w> EntityWorldMut<'w> { // Reborrow so world is not mutably borrowed let entity = self.entity; - let new_archetype = &self.world.archetypes[new_archetype_id]; - unsafe { - new_archetype.trigger_on_remove( - // SAFETY: the only outstanding borrow of the world is to an Archetype which cannot be modified through a deferred world - self.world.as_unsafe_world_cell_readonly(), - entity, - bundle_info.iter_components(), - ); + let old_archetype = &self.world.archetypes[old_location.archetype_id]; + if old_archetype.has_on_remove() { + unsafe { self.world.into_deferred() } + .trigger_on_remove(entity, bundle_info.iter_components()); } for component_id in bundle_info.iter_components() { self.world.removed_components.send(component_id, entity); @@ -857,14 +853,9 @@ impl<'w> EntityWorldMut<'w> { // Reborrow so world is not mutably borrowed let entity: Entity = self.entity; let old_archetype = &self.world.archetypes[old_location.archetype_id]; - unsafe { - old_archetype.trigger_on_remove( - self.world.as_unsafe_world_cell_readonly(), - entity, - bundle_info - .iter_components() - .filter(|id| old_archetype.contains(*id)), - ); + if old_archetype.has_on_remove() { + unsafe { self.world.into_deferred() } + .trigger_on_remove(entity, bundle_info.iter_components()) } for component_id in bundle_info.iter_components() { if old_archetype.contains(component_id) { @@ -905,13 +896,11 @@ impl<'w> EntityWorldMut<'w> { debug!("Despawning entity {:?}", self.entity); self.world.flush(); let archetype = &self.world.archetypes[self.location.archetype_id]; - unsafe { - archetype.trigger_on_remove( - self.world.as_unsafe_world_cell_readonly(), - self.entity, - archetype.components(), - ); + if archetype.has_on_remove() { + unsafe { self.world.into_deferred() } + .trigger_on_remove(self.entity, archetype.components()); } + for component_id in archetype.components() { self.world .removed_components From cdd1ee06da31d6acd8e48d83c62108c0f7f70397 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sat, 25 Nov 2023 00:43:31 -0800 Subject: [PATCH 021/109] Improve command application logic --- .../src/system/commands/command_queue.rs | 3 ++ crates/bevy_ecs/src/world/deferred_world.rs | 6 +++ crates/bevy_ecs/src/world/mod.rs | 53 +++++++++++-------- 3 files changed, 41 insertions(+), 21 deletions(-) diff --git a/crates/bevy_ecs/src/system/commands/command_queue.rs b/crates/bevy_ecs/src/system/commands/command_queue.rs index f1319e716f4f7..7129b8c63f06e 100644 --- a/crates/bevy_ecs/src/system/commands/command_queue.rs +++ b/crates/bevy_ecs/src/system/commands/command_queue.rs @@ -104,6 +104,7 @@ impl CommandQueue { pub fn apply(&mut self, world: &mut World) { // flush the previously queued entities world.flush(); + world.set_flushing(true); self.apply_or_drop_queued(Some(world)); } @@ -152,6 +153,7 @@ impl CommandQueue { cursor = unsafe { cursor.add(size) }; } + world.set_flushing(false); world.flush_commands(); } @@ -160,6 +162,7 @@ impl CommandQueue { self.bytes.append(&mut other.bytes); } + /// Returns false if there are any commands in the queue pub fn is_empty(&self) -> bool { self.bytes.is_empty() } diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 932b9a66a3a82..ea6293ac25e20 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -12,6 +12,8 @@ use crate::{ use super::{Mut, World}; +/// An [`World`] reference that prevents structural ECS changes. +/// This includes creating tables, registering components or spawning entities. pub struct DeferredWorld<'w> { world: &'w mut World, } @@ -25,6 +27,7 @@ impl<'w> Deref for DeferredWorld<'w> { } impl<'w> DeferredWorld<'w> { + /// Creates a [`Commands`] instance that pushes to the world's command queue pub fn commands(&mut self) -> Commands { let world = self.world.as_unsafe_world_cell(); unsafe { Commands::new(world.get_command_queue(), world.world()) } @@ -253,6 +256,9 @@ impl<'w> DeferredWorld<'w> { } impl World { + /// Turn a [`World`] reference into a [`DeferredWorld`] + /// + /// Caller must ensure there are no outstanding references to the world's command queue, resource or component data #[inline] pub unsafe fn into_deferred(&self) -> DeferredWorld { DeferredWorld { diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 03a8926f8c08c..b47d9467aa49e 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -183,24 +183,19 @@ impl World { WorldCell::new(self) } - pub fn register_component(&mut self) -> &mut ComponentInfo { - let type_id = TypeId::of::(); - assert!(self.components.get_id(type_id).is_none(), "Components cannot be registered twice, use init_component instead if this component may already exist in the world."); - let index = self.init_component::(); - // SAFETY: We just created this component - unsafe { self.components.get_info_mut(index) } - } - /// Initializes a new [`Component`] type and returns the [`ComponentId`] created for it. pub fn init_component(&mut self) -> ComponentId { self.components.init_component::(&mut self.storages) } - pub fn register_component_with_descriptor( - &mut self, - descriptor: ComponentDescriptor, - ) -> &mut ComponentInfo { - let index = self.init_component_with_descriptor(descriptor); + /// Initializes a new [`Component`] type and returns a mutable reference to the [`ComponentInfo`] created for it. + /// Primarily used for registering hooks. + /// + /// Will panic if a component for `T`` already exists. + pub fn register_component(&mut self) -> &mut ComponentInfo { + let type_id = TypeId::of::(); + assert!(self.components.get_id(type_id).is_none(), "Components cannot be registered twice, use init_component instead if this component may already exist in the world."); + let index = self.init_component::(); // SAFETY: We just created this component unsafe { self.components.get_info_mut(index) } } @@ -222,6 +217,19 @@ impl World { .init_component_with_descriptor(&mut self.storages, descriptor) } + /// Initializes a new [`Component`] type and returns a mutable reference to the [`ComponentInfo`] created for it. + /// Primarily used for registering hooks. + /// + /// Will panic if a component for `T`` already exists. + pub fn register_component_with_descriptor( + &mut self, + descriptor: ComponentDescriptor, + ) -> &mut ComponentInfo { + let index = self.init_component_with_descriptor(descriptor); + // SAFETY: We just created this component + unsafe { self.components.get_info_mut(index) } + } + /// Returns the [`ComponentId`] of the given [`Component`] type `T`. /// /// The returned `ComponentId` is specific to the `World` instance @@ -1740,18 +1748,21 @@ impl World { } } + /// Attempts to apply the internal [`CommandQueue`] to self + /// Will do nothing if already flushing commands. + #[inline] pub fn flush_commands(&mut self) { - if !self.flushing_commands { - self.flushing_commands = true; - while !self.command_queue.is_empty() { - let mut commands = CommandQueue::default(); - std::mem::swap(&mut commands, &mut self.command_queue); - commands.apply(self) - } - self.flushing_commands = false; + if !self.flushing_commands && !self.command_queue.is_empty() { + let mut commands = std::mem::take(&mut self.command_queue); + commands.apply(self); } } + #[inline] + pub(crate) fn set_flushing(&mut self, state: bool) { + self.flushing_commands = state; + } + /// Increments the world's current change tick and returns the old value. #[inline] pub fn increment_change_tick(&self) -> Tick { From 21cd6504f6e77cc7e0493e6c5937166b36418ae6 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sat, 25 Nov 2023 15:44:22 -0800 Subject: [PATCH 022/109] Simplify command application to get recursive ordering --- crates/bevy_ecs/src/lib.rs | 4 ++-- .../src/system/commands/command_queue.rs | 9 ++++----- crates/bevy_ecs/src/world/entity_ref.rs | 20 +++++++++---------- crates/bevy_ecs/src/world/mod.rs | 19 ++++++------------ crates/bevy_ecs/src/world/spawn_batch.rs | 2 +- 5 files changed, 23 insertions(+), 31 deletions(-) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 69ce32e8a8806..cbc1c3e05ca90 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -1063,7 +1063,7 @@ mod tests { fn reserve_and_spawn() { let mut world = World::default(); let e = world.entities().reserve_entity(); - world.flush(); + world.flush_entities(); let mut e_mut = world.entity_mut(e); e_mut.insert(A(0)); assert_eq!(e_mut.get::().unwrap(), &A(0)); @@ -1546,7 +1546,7 @@ mod tests { let e1 = world_a.spawn(A(1)).id(); let e2 = world_a.spawn(A(2)).id(); let e3 = world_a.entities().reserve_entity(); - world_a.flush(); + world_a.flush_entities(); let world_a_max_entities = world_a.entities().len(); world_b.entities.reserve_entities(world_a_max_entities); diff --git a/crates/bevy_ecs/src/system/commands/command_queue.rs b/crates/bevy_ecs/src/system/commands/command_queue.rs index 7129b8c63f06e..f5735fc77c156 100644 --- a/crates/bevy_ecs/src/system/commands/command_queue.rs +++ b/crates/bevy_ecs/src/system/commands/command_queue.rs @@ -103,8 +103,7 @@ impl CommandQueue { #[inline] pub fn apply(&mut self, world: &mut World) { // flush the previously queued entities - world.flush(); - world.set_flushing(true); + world.flush_entities(); self.apply_or_drop_queued(Some(world)); } @@ -151,10 +150,9 @@ impl CommandQueue { // SAFETY: The address just past the command is either within the buffer, // or 1 byte past the end, so this addition will not overflow the pointer's allocation. cursor = unsafe { cursor.add(size) }; - } - world.set_flushing(false); - world.flush_commands(); + world.flush_commands(); + } } /// Take all commands from `other` and append them to `self`, leaving `other` empty @@ -163,6 +161,7 @@ impl CommandQueue { } /// Returns false if there are any commands in the queue + #[inline] pub fn is_empty(&self) -> bool { self.bytes.is_empty() } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 50ddf83797a93..e3349fb23282e 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -698,9 +698,9 @@ impl<'w> EntityWorldMut<'w> { unsafe { self.world.into_deferred() } .trigger_on_remove(entity, bundle_info.iter_components()); } - for component_id in bundle_info.iter_components() { - self.world.removed_components.send(component_id, entity); - } + // for component_id in bundle_info.iter_components() { + // self.world.removed_components.send(component_id, entity); + // } let archetypes = &mut self.world.archetypes; let storages = &mut self.world.storages; let components = &mut self.world.components; @@ -859,7 +859,7 @@ impl<'w> EntityWorldMut<'w> { } for component_id in bundle_info.iter_components() { if old_archetype.contains(component_id) { - self.world.removed_components.send(component_id, entity); + // self.world.removed_components.send(component_id, entity); // Make sure to drop components stored in sparse sets. // Dense components are dropped later in `move_to_and_drop_missing_unchecked`. @@ -894,18 +894,18 @@ impl<'w> EntityWorldMut<'w> { /// Despawns the current entity. pub fn despawn(self) { debug!("Despawning entity {:?}", self.entity); - self.world.flush(); + self.world.flush_entities(); let archetype = &self.world.archetypes[self.location.archetype_id]; if archetype.has_on_remove() { unsafe { self.world.into_deferred() } .trigger_on_remove(self.entity, archetype.components()); } - for component_id in archetype.components() { - self.world - .removed_components - .send(component_id, self.entity); - } + // for component_id in archetype.components() { + // self.world + // .removed_components + // .send(component_id, self.entity); + // } let world = self.world; let location = world diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index b47d9467aa49e..8099bb505cb67 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -74,7 +74,6 @@ pub struct World { pub(crate) last_change_tick: Tick, pub(crate) last_check_tick: Tick, pub(crate) command_queue: CommandQueue, - flushing_commands: bool, } impl Default for World { @@ -94,7 +93,6 @@ impl Default for World { last_change_tick: Tick::new(0), last_check_tick: Tick::new(0), command_queue: CommandQueue::default(), - flushing_commands: false, } } } @@ -447,7 +445,7 @@ impl World { /// scheme worked out to share an ID space (which doesn't happen by default). #[inline] pub fn get_or_spawn(&mut self, entity: Entity) -> Option { - self.flush(); + self.flush_entities(); match self.entities.alloc_at_without_replacement(entity) { AllocAtWithoutReplacement::Exists(location) => { // SAFETY: `entity` exists and `location` is that entity's location @@ -701,7 +699,7 @@ impl World { /// assert_eq!(position.x, 0.0); /// ``` pub fn spawn_empty(&mut self) -> EntityWorldMut { - self.flush(); + self.flush_entities(); let entity = self.entities.alloc(); // SAFETY: entity was just allocated unsafe { self.spawn_at_empty_internal(entity) } @@ -767,7 +765,7 @@ impl World { /// assert_eq!(position.x, 2.0); /// ``` pub fn spawn(&mut self, bundle: B) -> EntityWorldMut { - self.flush(); + self.flush_entities(); let change_tick = self.change_tick(); let entity = self.entities.alloc(); let entity_location = { @@ -1452,7 +1450,7 @@ impl World { I::IntoIter: Iterator, B: Bundle, { - self.flush(); + self.flush_entities(); let change_tick = self.change_tick(); @@ -1734,7 +1732,7 @@ impl World { /// Empties queued entities and adds them to the empty [`Archetype`](crate::archetype::Archetype). /// This should be called before doing operations that might operate on queued entities, /// such as inserting a [`Component`]. - pub(crate) fn flush(&mut self) { + pub(crate) fn flush_entities(&mut self) { let empty_archetype = self.archetypes.empty_mut(); let table = &mut self.storages.tables[empty_archetype.table_id()]; // PERF: consider pre-allocating space for flushed entities @@ -1752,17 +1750,12 @@ impl World { /// Will do nothing if already flushing commands. #[inline] pub fn flush_commands(&mut self) { - if !self.flushing_commands && !self.command_queue.is_empty() { + if !self.command_queue.is_empty() { let mut commands = std::mem::take(&mut self.command_queue); commands.apply(self); } } - #[inline] - pub(crate) fn set_flushing(&mut self, state: bool) { - self.flushing_commands = state; - } - /// Increments the world's current change tick and returns the old value. #[inline] pub fn increment_change_tick(&self) -> Tick { diff --git a/crates/bevy_ecs/src/world/spawn_batch.rs b/crates/bevy_ecs/src/world/spawn_batch.rs index ef90947e54d12..b0586e4c7c8f8 100644 --- a/crates/bevy_ecs/src/world/spawn_batch.rs +++ b/crates/bevy_ecs/src/world/spawn_batch.rs @@ -27,7 +27,7 @@ where pub(crate) fn new(world: &'w mut World, iter: I) -> Self { // Ensure all entity allocations are accounted for so `self.entities` can realloc if // necessary - world.flush(); + world.flush_entities(); let change_tick = world.change_tick(); From 4e086553293551a412800576749e0501ffa5e5c5 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sat, 25 Nov 2023 20:27:48 -0800 Subject: [PATCH 023/109] First docs and safety comment pass --- crates/bevy_ecs/src/archetype.rs | 11 +- crates/bevy_ecs/src/bundle.rs | 359 ++++++++++-------- crates/bevy_ecs/src/component.rs | 47 ++- crates/bevy_ecs/src/world/deferred_world.rs | 112 +++--- crates/bevy_ecs/src/world/entity_ref.rs | 99 +++-- crates/bevy_ecs/src/world/mod.rs | 40 +- crates/bevy_ecs/src/world/spawn_batch.rs | 4 +- .../bevy_ecs/src/world/unsafe_world_cell.rs | 4 + examples/ecs/component_hooks.rs | 2 +- 9 files changed, 398 insertions(+), 280 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 0608002bbd431..80b5ac47c8a65 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -299,8 +299,11 @@ struct ArchetypeComponentInfo { } bitflags::bitflags! { + /// Flags used to keep track of metadata about the component in this [`Archetype`] + /// + /// Used primarily to early-out when there are no [`ComponentHook`] registered for any contained components. #[derive(Clone, Copy)] - pub struct ArchetypeFlags: u32 { + pub(crate) struct ArchetypeFlags: u32 { const ON_ADD_HOOK = (1 << 0); const ON_INSERT_HOOK = (1 << 1); const ON_REMOVE_HOOK = (1 << 2); @@ -375,8 +378,9 @@ impl Archetype { self.id } + /// Fetches the flags for the archetype. #[inline] - pub fn flags(&self) -> ArchetypeFlags { + pub(crate) fn flags(&self) -> ArchetypeFlags { self.flags } @@ -561,16 +565,19 @@ impl Archetype { self.entities.clear(); } + /// Returns true if any of the components in this archetype have `on_add` hooks #[inline] pub fn has_on_add(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_ADD_HOOK) } + /// Returns true if any of the components in this archetype have `on_insert` hooks #[inline] pub fn has_on_insert(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_INSERT_HOOK) } + /// Returns true if any of the components in this archetype have `on_remove` hooks #[inline] pub fn has_on_remove(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_REMOVE_HOOK) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 7ab66d4690859..fe57cc96af184 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -15,6 +15,7 @@ use crate::{ prelude::World, query::DebugCheckedUnwrap, storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow}, + world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld}, TypeIdMap, }; use bevy_ptr::OwningPtr; @@ -346,6 +347,7 @@ impl BundleInfo { &self.component_ids } + /// Returns an iterator over the the [ID](ComponentId) of each component stored in this bundle. #[inline] pub fn iter_components(&self) -> impl Iterator + '_ { self.component_ids.iter().cloned() @@ -501,23 +503,26 @@ impl BundleInfo { } pub(crate) struct BundleInserter<'w> { - world: &'w mut World, - archetype: *mut Archetype, - table: *mut Table, - bundle_info: *const BundleInfo, - add_bundle: *const AddBundle, - result: InsertBundleResult, + world: DeferredWorld<'w>, + archetype: &'w mut Archetype, + entities: &'w mut Entities, + bundle_info: &'w BundleInfo, + table: &'w mut Table, + sparse_sets: &'w mut SparseSets, + result: InsertBundleResult<'w>, + add_bundle_ptr: *const AddBundle, + archetypes_ptr: *mut Archetype, change_tick: Tick, } -pub(crate) enum InsertBundleResult { +pub(crate) enum InsertBundleResult<'w> { SameArchetype, NewArchetypeSameTable { - new_archetype: *mut Archetype, + new_archetype: &'w mut Archetype, }, NewArchetypeNewTable { - new_archetype: *mut Archetype, - new_table: *mut Table, + new_archetype: &'w mut Archetype, + new_table: &'w mut Table, }, } @@ -528,20 +533,31 @@ impl<'w> BundleInserter<'w> { archetype_id: ArchetypeId, change_tick: Tick, ) -> Self { - let bundle_info: *const BundleInfo = world + let bundle_id = world .bundles .init_info::(&mut world.components, &mut world.storages); - unsafe { Self::new_with_info(world, archetype_id, bundle_info, change_tick) } + // SAFETY: We just ensured this bundle exists, re-borrow to drop &mut World + let bundle_info = unsafe { world.bundles.get_unchecked(bundle_id) }; + Self::new_with_info( + world.as_unsafe_world_cell_readonly(), + archetype_id, + bundle_info, + change_tick, + ) } + /// Creates a new [`BundleInserter`]. + /// + /// Implementation guarantees it will not alias `bundle_info`. #[inline] - pub(crate) unsafe fn new_with_info( - world: &'w mut World, + pub(crate) fn new_with_info( + world: UnsafeWorldCell<'w>, archetype_id: ArchetypeId, - bundle_info: *const BundleInfo, + bundle_info: &'w BundleInfo, change_tick: Tick, ) -> Self { - let bundle_info = &*bundle_info; + // SAFETY: We will not make any accesses to the command queue, component or resource data of this world + let (world, deferred_world) = unsafe { world.split_deferred() }; let bundle_id = bundle_info.id(); let new_archetype_id = bundle_info.add_bundle_to_archetype( &mut world.archetypes, @@ -549,62 +565,71 @@ impl<'w> BundleInserter<'w> { &world.components, archetype_id, ); + let archetypes_ptr = world.archetypes.archetypes.as_mut_ptr(); if new_archetype_id == archetype_id { let archetype = &mut world.archetypes[archetype_id]; - let table_id = archetype.table_id(); - let add_bundle: *const AddBundle = unsafe { + // SAFETY: The edge is assured to be initialized when we called add_bundle_to_archetype + let add_bundle_ptr: *const AddBundle = unsafe { archetype .edges() .get_add_bundle_internal(bundle_id) .debug_checked_unwrap() }; - let table: *mut Table = &mut world.storages.tables[table_id]; - let archetype: *mut Archetype = archetype; + let table_id = archetype.table_id(); + let table = &mut world.storages.tables[table_id]; Self { - world, + world: deferred_world, archetype, - table, + entities: &mut world.entities, bundle_info, - add_bundle, + table, + sparse_sets: &mut world.storages.sparse_sets, result: InsertBundleResult::SameArchetype, + archetypes_ptr, + add_bundle_ptr, change_tick, } } else { let (archetype, new_archetype) = world.archetypes.get_2_mut(archetype_id, new_archetype_id); - let table_id = archetype.table_id(); - let new_table_id = new_archetype.table_id(); - let add_bundle: *const AddBundle = unsafe { + // SAFETY: The edge is assured to be initialized when we called add_bundle_to_archetype + let add_bundle_ptr: *const AddBundle = unsafe { archetype .edges() .get_add_bundle_internal(bundle_id) .debug_checked_unwrap() }; - let table: *mut Table = &mut world.storages.tables[table_id]; - let archetype: *mut Archetype = archetype; - let new_archetype: *mut Archetype = new_archetype; + let table_id = archetype.table_id(); + let new_table_id = new_archetype.table_id(); if table_id == new_table_id { + let table = &mut world.storages.tables[table_id]; Self { - world, + world: deferred_world, archetype, - table, + entities: &mut world.entities, bundle_info, - add_bundle, + table, + sparse_sets: &mut world.storages.sparse_sets, result: InsertBundleResult::NewArchetypeSameTable { new_archetype }, + archetypes_ptr, + add_bundle_ptr, change_tick, } } else { - let new_table: *mut Table = &mut world.storages.tables[new_table_id]; + let (table, new_table) = world.storages.tables.get_2_mut(table_id, new_table_id); Self { - world, + world: deferred_world, archetype, - table, + entities: &mut world.entities, bundle_info, - add_bundle, + table, + sparse_sets: &mut world.storages.sparse_sets, result: InsertBundleResult::NewArchetypeNewTable { new_archetype, new_table, }, + archetypes_ptr, + add_bundle_ptr, change_tick, } } @@ -620,29 +645,33 @@ impl<'w> BundleInserter<'w> { location: EntityLocation, bundle: T, ) -> EntityLocation { - let bundle_info = &*self.bundle_info; - let add_bundle = &*self.add_bundle; - let world = &mut *self.world; - - match self.result { + // SAFETY: We must ensure we do not use self.archetype to invalidate this reference + let add_bundle = &*self.add_bundle_ptr; + match &mut self.result { InsertBundleResult::SameArchetype => { - let archetype = &mut *self.archetype; - if archetype.has_on_add() { - world.into_deferred().trigger_on_add( + if self.archetype.has_on_add() { + self.world.trigger_on_add( entity, - bundle_info.iter_components().filter(|id| { + self.bundle_info.iter_components().filter(|id| { add_bundle.get_status(id.index()) == ComponentStatus::Added }), ); } - if archetype.has_on_insert() { - world - .into_deferred() - .trigger_on_insert(entity, bundle_info.iter_components()) + if self.archetype.has_on_insert() { + self.world + .trigger_on_insert(entity, self.bundle_info.iter_components()); } - bundle_info.write_components( - &mut *self.table, - &mut world.storages.sparse_sets, + // PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty) + // SAFETY: The edge is assured to be initialized when creating the BundleInserter + let add_bundle = unsafe { + self.archetype + .edges() + .get_add_bundle_internal(self.bundle_info.id) + .debug_checked_unwrap() + }; + self.bundle_info.write_components( + self.table, + self.sparse_sets, add_bundle, entity, location.table_row, @@ -652,28 +681,24 @@ impl<'w> BundleInserter<'w> { location } InsertBundleResult::NewArchetypeSameTable { new_archetype } => { - let table = &mut *self.table; - let archetype = &mut *self.archetype; - let new_archetype = &mut *new_archetype; if new_archetype.has_on_add() { - world.into_deferred().trigger_on_add( + self.world.trigger_on_add( entity, - bundle_info.iter_components().filter(|id| { + self.bundle_info.iter_components().filter(|id| { add_bundle.get_status(id.index()) == ComponentStatus::Added }), ); } if new_archetype.has_on_insert() { - world - .into_deferred() - .trigger_on_insert(entity, bundle_info.iter_components()) + self.world + .trigger_on_insert(entity, self.bundle_info.iter_components()); } - let result = archetype.swap_remove(location.archetype_row); + let result = self.archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { let swapped_location = // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { world.entities.get(swapped_entity).debug_checked_unwrap() }; - world.entities.set( + unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; + self.entities.set( swapped_entity.index(), EntityLocation { archetype_id: swapped_location.archetype_id, @@ -684,10 +709,10 @@ impl<'w> BundleInserter<'w> { ); } let new_location = new_archetype.allocate(entity, result.table_row); - world.entities.set(entity.index(), new_location); - bundle_info.write_components( - table, - &mut world.storages.sparse_sets, + self.entities.set(entity.index(), new_location); + self.bundle_info.write_components( + self.table, + self.sparse_sets, add_bundle, entity, result.table_row, @@ -700,29 +725,24 @@ impl<'w> BundleInserter<'w> { new_archetype, new_table, } => { - let table = &mut *self.table; - let new_table = &mut *new_table; - let archetype = &mut *self.archetype; - let new_archetype = &mut *new_archetype; if new_archetype.has_on_add() { - world.into_deferred().trigger_on_add( + self.world.trigger_on_add( entity, - bundle_info.iter_components().filter(|id| { + self.bundle_info.iter_components().filter(|id| { add_bundle.get_status(id.index()) == ComponentStatus::Added }), ); } if new_archetype.has_on_insert() { - world - .into_deferred() - .trigger_on_insert(entity, bundle_info.iter_components()) + self.world + .trigger_on_insert(entity, self.bundle_info.iter_components()); } - let result = archetype.swap_remove(location.archetype_row); + let result = self.archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { let swapped_location = // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { world.entities.get(swapped_entity).debug_checked_unwrap() }; - world.entities.set( + unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; + self.entities.set( swapped_entity.index(), EntityLocation { archetype_id: swapped_location.archetype_id, @@ -734,25 +754,31 @@ impl<'w> BundleInserter<'w> { } // PERF: store "non bundle" components in edge, then just move those to avoid // redundant copies - let move_result = table.move_to_superset_unchecked(result.table_row, new_table); + let move_result = self + .table + .move_to_superset_unchecked(result.table_row, new_table); let new_location = new_archetype.allocate(entity, move_result.new_row); - world.entities.set(entity.index(), new_location); + self.entities.set(entity.index(), new_location); // if an entity was moved into this entity's table spot, update its table row if let Some(swapped_entity) = move_result.swapped_entity { let swapped_location = // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { world.entities.get(swapped_entity).debug_checked_unwrap() }; - let swapped_archetype = if archetype.id() == swapped_location.archetype_id { + unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; + let swapped_archetype = if self.archetype.id() == swapped_location.archetype_id + { + let archetype: *mut Archetype = self.archetype; &mut *archetype } else if new_archetype.id() == swapped_location.archetype_id { new_archetype } else { // SAFETY: the only two borrowed archetypes are above and we just did collision checks - &mut world.archetypes[swapped_location.archetype_id] + &mut *self + .archetypes_ptr + .add(swapped_location.archetype_id.index()) }; - world.entities.set( + self.entities.set( swapped_entity.index(), EntityLocation { archetype_id: swapped_location.archetype_id, @@ -765,9 +791,9 @@ impl<'w> BundleInserter<'w> { .set_entity_table_row(swapped_location.archetype_row, result.table_row); } - bundle_info.write_components( + self.bundle_info.write_components( new_table, - &mut world.storages.sparse_sets, + self.sparse_sets, add_bundle, entity, move_result.new_row, @@ -781,33 +807,43 @@ impl<'w> BundleInserter<'w> { #[inline] pub(crate) fn entities(&mut self) -> &mut Entities { - &mut self.world.entities + self.entities } } pub(crate) struct BundleSpawner<'w> { - world: &'w mut World, - bundle_info: *const BundleInfo, - archetype: *mut Archetype, - table: *mut Table, + world: DeferredWorld<'w>, + bundle_info: &'w BundleInfo, + archetype: &'w mut Archetype, + table: &'w mut Table, + sparse_sets: &'w mut SparseSets, + entities: &'w mut Entities, change_tick: Tick, } impl<'w> BundleSpawner<'w> { #[inline] pub fn new(world: &'w mut World, change_tick: Tick) -> Self { - let bundle_info: *const BundleInfo = world + let bundle_id = world .bundles .init_info::(&mut world.components, &mut world.storages); - unsafe { Self::new_with_info(world, bundle_info, change_tick) } + // SAFETY: We just ensured this bundle exists, re-borrow to drop &mut World + let bundle_info = unsafe { world.bundles.get_unchecked(bundle_id) }; + Self::new_with_info( + world.as_unsafe_world_cell_readonly(), + bundle_info, + change_tick, + ) } - pub(crate) unsafe fn new_with_info( - world: &'w mut World, - bundle_info: *const BundleInfo, + #[inline] + pub(crate) fn new_with_info( + world: UnsafeWorldCell<'w>, + bundle_info: &'w BundleInfo, change_tick: Tick, ) -> Self { - let bundle_info = &*bundle_info; + // SAFETY: We will not make any accesses to the command queue, component or resource data of this world + let (world, deferred_world) = unsafe { world.split_deferred() }; let new_archetype_id = bundle_info.add_bundle_to_archetype( &mut world.archetypes, &mut world.storages, @@ -815,23 +851,24 @@ impl<'w> BundleSpawner<'w> { ArchetypeId::EMPTY, ); let archetype = &mut world.archetypes[new_archetype_id]; - let table: *mut Table = &mut world.storages.tables[archetype.table_id()]; - let archetype: *mut Archetype = archetype; - BundleSpawner { - world, + let table = &mut world.storages.tables[archetype.table_id()]; + Self { + world: deferred_world, bundle_info, archetype, table, + sparse_sets: &mut world.storages.sparse_sets, + entities: &mut world.entities, change_tick, } } + #[inline] pub fn reserve_storage(&mut self, additional: usize) { - unsafe { - (&mut *self.archetype).reserve(additional); - (&mut *self.table).reserve(additional); - } + self.archetype.reserve(additional); + self.table.reserve(additional); } + /// # Safety /// `entity` must be allocated (but non-existent), `T` must match this [`BundleInfo`]'s type #[inline] @@ -840,34 +877,28 @@ impl<'w> BundleSpawner<'w> { entity: Entity, bundle: T, ) -> EntityLocation { - let bundle_info = &*self.bundle_info; - let archetype = &*self.archetype; - if archetype.has_on_add() { + if self.archetype.has_on_add() { self.world - .into_deferred() - .trigger_on_add(entity, bundle_info.iter_components()); + .trigger_on_add(entity, self.bundle_info.iter_components()); } - if archetype.has_on_insert() { + if self.archetype.has_on_insert() { self.world - .into_deferred() - .trigger_on_insert(entity, bundle_info.iter_components()) + .trigger_on_insert(entity, self.bundle_info.iter_components()); } - let archetype = &mut *self.archetype; let table = &mut *self.table; - let table_row = table.allocate(entity); - let location = archetype.allocate(entity, table_row); - bundle_info.write_components( + let location = self.archetype.allocate(entity, table_row); + self.bundle_info.write_components( table, - &mut self.world.storages.sparse_sets, + self.sparse_sets, &SpawnBundleStatus, entity, table_row, self.change_tick, bundle, ); - self.world.entities.set(entity.index(), location); + self.entities.set(entity.index(), location); location } @@ -876,7 +907,7 @@ impl<'w> BundleSpawner<'w> { /// `T` must match this [`BundleInfo`]'s type #[inline] pub unsafe fn spawn(&mut self, bundle: T) -> Entity { - let entity = self.world.entities.alloc(); + let entity = self.entities.alloc(); // SAFETY: entity is allocated (but non-existent), `T` matches this BundleInfo's type self.spawn_non_existent(entity, bundle); entity @@ -884,7 +915,19 @@ impl<'w> BundleSpawner<'w> { #[inline] pub(crate) fn entities(&mut self) -> &mut Entities { - &mut self.world.entities + self.entities + } + + /// # Safety: + /// - `Self` must be dropped after running this function as it may invalidate internal pointers. + /// - Caller must ensure that no there are no outstanding access to `self.world` + #[inline] + pub(crate) unsafe fn flush_commands(&mut self) { + // SAFETY: pointers on self can be invalidated, + self.world + .as_unsafe_world_cell_readonly() + .world_mut() + .flush_commands(); } } @@ -895,9 +938,11 @@ pub struct Bundles { /// Cache static [`BundleId`] bundle_ids: TypeIdMap, /// Cache dynamic [`BundleId`] with multiple components - dynamic_bundle_ids: HashMap, (BundleId, Vec)>, + dynamic_bundle_ids: HashMap, BundleId>, + dynamic_bundle_storages: HashMap>, /// Cache optimized dynamic [`BundleId`] with single component - dynamic_component_bundle_ids: HashMap, + dynamic_component_bundle_ids: HashMap, + dynamic_component_storages: HashMap, } impl Bundles { @@ -917,11 +962,11 @@ impl Bundles { } /// Initializes a new [`BundleInfo`] for a statically known type. - pub(crate) fn init_info<'a, T: Bundle>( - &'a mut self, + pub(crate) fn init_info( + &mut self, components: &mut Components, storages: &mut Storages, - ) -> &'a BundleInfo { + ) -> BundleId { let bundle_infos = &mut self.bundle_infos; let id = *self.bundle_ids.entry(TypeId::of::()).or_insert_with(|| { let mut component_ids = Vec::new(); @@ -936,14 +981,35 @@ impl Bundles { bundle_infos.push(bundle_info); id }); - // SAFETY: index either exists, or was initialized - unsafe { self.get_unchecked(id) } + id } - pub(crate) unsafe fn get_unchecked<'a>(&'a self, id: BundleId) -> &'a BundleInfo { + pub(crate) unsafe fn get_unchecked(&self, id: BundleId) -> &BundleInfo { self.bundle_infos.get_unchecked(id.0) } + pub(crate) unsafe fn get_with_storage_unchecked( + &self, + id: BundleId, + ) -> (&BundleInfo, &StorageType) { + ( + self.bundle_infos.get_unchecked(id.0), + self.dynamic_component_storages + .get(&id) + .debug_checked_unwrap(), + ) + } + + pub(crate) unsafe fn get_with_storages_unchecked( + &self, + id: BundleId, + ) -> (&BundleInfo, &Vec) { + ( + self.bundle_infos.get_unchecked(id.0), + self.dynamic_bundle_storages.get(&id).debug_checked_unwrap(), + ) + } + /// Initializes a new [`BundleInfo`] for a dynamic [`Bundle`]. /// /// # Panics @@ -954,25 +1020,22 @@ impl Bundles { &mut self, components: &Components, component_ids: &[ComponentId], - ) -> (&BundleInfo, &Vec) { + ) -> BundleId { let bundle_infos = &mut self.bundle_infos; // Use `raw_entry_mut` to avoid cloning `component_ids` to access `Entry` - let (_, (bundle_id, storage_types)) = self + let (_, bundle_id) = self .dynamic_bundle_ids .raw_entry_mut() .from_key(component_ids) .or_insert_with(|| { - ( - Vec::from(component_ids), - initialize_dynamic_bundle(bundle_infos, components, Vec::from(component_ids)), - ) + let (id, storages) = + initialize_dynamic_bundle(bundle_infos, components, Vec::from(component_ids)); + self.dynamic_bundle_storages + .insert_unique_unchecked(id, storages); + (Vec::from(component_ids), id) }); - - // SAFETY: index either exists, or was initialized - let bundle_info = unsafe { bundle_infos.get_unchecked(bundle_id.0) }; - - (bundle_info, storage_types) + *bundle_id } /// Initializes a new [`BundleInfo`] for a dynamic [`Bundle`] with single component. @@ -984,22 +1047,18 @@ impl Bundles { &mut self, components: &Components, component_id: ComponentId, - ) -> (&BundleInfo, StorageType) { + ) -> BundleId { let bundle_infos = &mut self.bundle_infos; - let (bundle_id, storage_types) = self + let bundle_id = self .dynamic_component_bundle_ids .entry(component_id) .or_insert_with(|| { let (id, storage_type) = initialize_dynamic_bundle(bundle_infos, components, vec![component_id]); - // SAFETY: `storage_type` guaranteed to have length 1 - (id, storage_type[0]) + self.dynamic_component_storages.insert(id, storage_type[0]); + id }); - - // SAFETY: index either exists, or was initialized - let bundle_info = unsafe { bundle_infos.get_unchecked(bundle_id.0) }; - - (bundle_info, *storage_types) + *bundle_id } } diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 21c7ea3d26fa1..0ee72faf749be 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -203,11 +203,23 @@ pub enum StorageType { SparseSet, } +/// The type used for [`Component`] lifecycle hooks such as `on_add`, `on_insert` or `on_remove` +pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, Entity, ComponentId); + +/// Lifecycle hooks for a given [`Component`], stored in it's [`ComponentInfo`] +#[derive(Debug, Clone, Default)] +pub struct ComponentHooks { + pub(crate) on_add: Option, + pub(crate) on_insert: Option, + pub(crate) on_remove: Option, +} + /// Stores metadata for a type of component or resource stored in a specific [`World`]. #[derive(Debug, Clone)] pub struct ComponentInfo { id: ComponentId, descriptor: ComponentDescriptor, + hooks: ComponentHooks, } impl ComponentInfo { @@ -263,26 +275,34 @@ impl ComponentInfo { /// Create a new [`ComponentInfo`]. pub(crate) fn new(id: ComponentId, descriptor: ComponentDescriptor) -> Self { - ComponentInfo { id, descriptor } + ComponentInfo { + id, + descriptor, + hooks: ComponentHooks::default(), + } } + /// Register a [`ComponentHook`] that will be run when this component is added to an entity pub fn on_add(&mut self, hook: ComponentHook) -> &mut Self { - self.descriptor.hooks.on_add = Some(hook); + self.hooks.on_add = Some(hook); self } + /// Register a [`ComponentHook`] that will be run when this component is added or set by `.insert` pub fn on_insert(&mut self, hook: ComponentHook) -> &mut Self { - self.descriptor.hooks.on_insert = Some(hook); + self.hooks.on_insert = Some(hook); self } + /// Register a [`ComponentHook`] that will be run when this component is removed from an entity. pub fn on_remove(&mut self, hook: ComponentHook) -> &mut Self { - self.descriptor.hooks.on_remove = Some(hook); + self.hooks.on_remove = Some(hook); self } + /// Update the given flags to include any [`ComponentHook`] registered to self #[inline] - pub fn update_archetype_flags(&self, flags: &mut ArchetypeFlags) { + pub(crate) fn update_archetype_flags(&self, flags: &mut ArchetypeFlags) { if self.hooks().on_add.is_some() { flags.insert(ArchetypeFlags::ON_ADD_HOOK); } @@ -294,8 +314,9 @@ impl ComponentInfo { } } + /// Provides a reference to the collection of hooks associated with this [`Component`] pub fn hooks(&self) -> &ComponentHooks { - &self.descriptor.hooks + &self.hooks } } @@ -352,15 +373,6 @@ impl SparseSetIndex for ComponentId { } } -pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, Entity, ComponentId); - -#[derive(Clone, Default)] -pub struct ComponentHooks { - pub(crate) on_add: Option, - pub(crate) on_insert: Option, - pub(crate) on_remove: Option, -} - /// A value describing a component or resource, which may or may not correspond to a Rust type. #[derive(Clone)] pub struct ComponentDescriptor { @@ -373,7 +385,6 @@ pub struct ComponentDescriptor { is_send_and_sync: bool, type_id: Option, layout: Layout, - hooks: ComponentHooks, // SAFETY: this function must be safe to call with pointers pointing to items of the type // this descriptor describes. // None if the underlying type doesn't need to be dropped @@ -407,7 +418,6 @@ impl ComponentDescriptor { is_send_and_sync: true, type_id: Some(TypeId::of::()), layout: Layout::new::(), - hooks: ComponentHooks::default(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), } } @@ -429,7 +439,6 @@ impl ComponentDescriptor { is_send_and_sync: true, type_id: None, layout, - hooks: ComponentHooks::default(), drop, } } @@ -446,7 +455,6 @@ impl ComponentDescriptor { is_send_and_sync: true, type_id: Some(TypeId::of::()), layout: Layout::new::(), - hooks: ComponentHooks::default(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), } } @@ -458,7 +466,6 @@ impl ComponentDescriptor { is_send_and_sync: false, type_id: Some(TypeId::of::()), layout: Layout::new::(), - hooks: ComponentHooks::default(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), } } diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index ea6293ac25e20..d12ba0b4248b2 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -10,27 +10,28 @@ use crate::{ system::{Commands, Query, Resource}, }; -use super::{Mut, World}; +use super::{unsafe_world_cell::UnsafeWorldCell, Mut, World}; /// An [`World`] reference that prevents structural ECS changes. /// This includes creating tables, registering components or spawning entities. pub struct DeferredWorld<'w> { - world: &'w mut World, + world: UnsafeWorldCell<'w>, } impl<'w> Deref for DeferredWorld<'w> { - type Target = &'w mut World; + type Target = World; fn deref(&self) -> &Self::Target { - &self.world + // SAFETY: &self ensures ther are no active mutable borrows + unsafe { self.world.world() } } } impl<'w> DeferredWorld<'w> { /// Creates a [`Commands`] instance that pushes to the world's command queue pub fn commands(&mut self) -> Commands { - let world = self.world.as_unsafe_world_cell(); - unsafe { Commands::new(world.get_command_queue(), world.world()) } + // SAFETY: Commands::new only take &World which has no way to access the command queue + unsafe { Commands::new(self.world.get_command_queue(), self.world.world()) } } /// Retrieves a mutable reference to the given `entity`'s [`Component`] of the given type. @@ -41,29 +42,28 @@ impl<'w> DeferredWorld<'w> { // - `as_unsafe_world_cell` is the only thing that is borrowing world // - `as_unsafe_world_cell` provides mutable permission to everything // - `&mut self` ensures no other borrows on world data - unsafe { - self.world - .as_unsafe_world_cell() - .get_entity(entity)? - .get_mut() - } + unsafe { self.world.get_entity(entity)?.get_mut() } } /// Returns [`Query`] for the given [`QueryState`], which is used to efficiently /// run queries on the [`World`] by storing and reusing the [`QueryState`]. + /// + /// # Panics + /// If state is from a different world then self #[inline] pub fn query<'s, Q: WorldQuery, F: ReadOnlyWorldQuery>( &'w mut self, state: &'s mut QueryState, ) -> Query<'w, 's, Q, F> { + state.validate_world(self.world.id()); + state.update_archetypes(self); + // SAFETY: We ran validate_world to ensure our state matches unsafe { - state.update_archetypes(self.world); - let world = self.world.as_unsafe_world_cell(); Query::new( - world, + self.world, state, - world.last_change_tick(), - world.change_tick(), + self.world.last_change_tick(), + self.world.change_tick(), false, ) } @@ -96,7 +96,8 @@ impl<'w> DeferredWorld<'w> { /// Gets a mutable reference to the resource of the given type if it exists #[inline] pub fn get_resource_mut(&mut self) -> Option> { - self.world.get_resource_mut() + // SAFETY: `&mut self` ensures that all accessed data is unaliased + unsafe { self.world.get_resource_mut() } } /// Gets a mutable reference to the non-send resource of the given type, if it exists. @@ -128,7 +129,8 @@ impl<'w> DeferredWorld<'w> { /// This function will panic if it isn't called from the same thread that the resource was inserted from. #[inline] pub fn get_non_send_resource_mut(&mut self) -> Option> { - self.world.get_non_send_resource_mut() + // SAFETY: `&mut self` ensures that all accessed data is unaliased + unsafe { self.world.get_non_send_resource_mut() } } /// Sends an [`Event`]. @@ -173,7 +175,8 @@ impl<'w> DeferredWorld<'w> { /// use this in cases where the actual types are not known at compile time.** #[inline] pub fn get_resource_mut_by_id(&mut self, component_id: ComponentId) -> Option> { - self.world.get_resource_mut_by_id(component_id) + // SAFETY: `&mut self` ensures that all accessed data is unaliased + unsafe { self.world.get_resource_mut_by_id(component_id) } } /// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists. @@ -187,7 +190,8 @@ impl<'w> DeferredWorld<'w> { /// This function will panic if it isn't called from the same thread that the resource was inserted from. #[inline] pub fn get_non_send_mut_by_id(&mut self, component_id: ComponentId) -> Option> { - self.world.get_non_send_mut_by_id(component_id) + // SAFETY: `&mut self` ensures that all accessed data is unaliased + unsafe { self.world.get_non_send_resource_mut_by_id(component_id) } } /// Retrieves a mutable untyped reference to the given `entity`'s [`Component`] of the given [`ComponentId`]. @@ -201,75 +205,91 @@ impl<'w> DeferredWorld<'w> { entity: Entity, component_id: ComponentId, ) -> Option> { - // SAFETY: - // - `&mut self` ensures that all accessed data is unaliased - // - `as_unsafe_world_cell` provides mutable permission to the whole world - unsafe { - self.world - .as_unsafe_world_cell() - .get_entity(entity)? - .get_mut_by_id(component_id) - } + // SAFETY: `&mut self` ensures that all accessed data is unaliased + unsafe { self.world.get_entity(entity)?.get_mut_by_id(component_id) } } + /// Triggers all `on_add` hooks for [`ComponentId`] in target. + /// + /// # Safety + /// Caller must ensure [`ComponentId`] in target exist in self. #[inline] - pub(crate) fn trigger_on_add( + pub(crate) unsafe fn trigger_on_add( &mut self, entity: Entity, targets: impl Iterator, ) { for component_id in targets { + // SAFETY: Caller ensures that these components exist let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks(); if let Some(hook) = hooks.on_add { - hook(DeferredWorld { world: self.world }, entity, component_id) + hook(DeferredWorld { world: self.world }, entity, component_id); } } } + /// Triggers all `on_insert` hooks for [`ComponentId`] in target. + /// + /// # Safety + /// Caller must ensure [`ComponentId`] in target exist in self. #[inline] - pub(crate) fn trigger_on_insert( + pub(crate) unsafe fn trigger_on_insert( &mut self, entity: Entity, targets: impl Iterator, ) { for component_id in targets { + // SAFETY: Caller ensures that these components exist let hooks = unsafe { self.world.components().get_info_unchecked(component_id) }.hooks(); if let Some(hook) = hooks.on_insert { - hook(DeferredWorld { world: self.world }, entity, component_id) + hook(DeferredWorld { world: self.world }, entity, component_id); } } } + /// Triggers all `on_remove` hooks for [`ComponentId`] in target. + /// + /// # Safety + /// Caller must ensure [`ComponentId`] in target exist in self. #[inline] - pub(crate) fn trigger_on_remove( + pub(crate) unsafe fn trigger_on_remove( &mut self, entity: Entity, targets: impl Iterator, ) { for component_id in targets { + // SAFETY: Caller ensures that these components exist let hooks = unsafe { self.world.components().get_info_unchecked(component_id) }.hooks(); if let Some(hook) = hooks.on_remove { - hook(DeferredWorld { world: self.world }, entity, component_id) + hook(DeferredWorld { world: self.world }, entity, component_id); } } } } -impl World { - /// Turn a [`World`] reference into a [`DeferredWorld`] +impl<'w> UnsafeWorldCell<'w> { + /// Turn self into a [`DeferredWorld`] /// + /// # Safety /// Caller must ensure there are no outstanding references to the world's command queue, resource or component data #[inline] - pub unsafe fn into_deferred(&self) -> DeferredWorld { - DeferredWorld { - // SAFETY: Not - world: self.as_unsafe_world_cell_readonly().world_mut(), - } + pub unsafe fn into_deferred(self) -> DeferredWorld<'w> { + DeferredWorld { world: self } + } + + /// Turn self into a mutable world reference and a [`DeferredWorld`] + /// + /// # Safety + /// Caller must ensure that the world reference is not used to construct any references to the world's command queue, resource or component data + pub unsafe fn split_deferred(self) -> (&'w mut World, DeferredWorld<'w>) { + (self.world_mut(), self.into_deferred()) } } -impl<'w> Into> for &'w mut World { - fn into(self) -> DeferredWorld<'w> { - DeferredWorld { world: self } +impl<'w> From<&'w mut World> for DeferredWorld<'w> { + fn from(world: &'w mut World) -> DeferredWorld<'w> { + DeferredWorld { + world: world.as_unsafe_world_cell(), + } } } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index e3349fb23282e..c7763d7c87c82 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -592,26 +592,26 @@ impl<'w> EntityWorldMut<'w> { component: OwningPtr<'_>, ) -> &mut Self { let change_tick = self.world.change_tick(); - let (bundle_info, storage_type) = self + let bundle_id = self .world .bundles .init_component_info(&self.world.components, component_id); - let bundle_info: *const BundleInfo = bundle_info; - let bundle_inserter = unsafe { - BundleInserter::new_with_info( - self.world, - self.location.archetype_id, - bundle_info, - change_tick, - ) - }; + let (bundle_info, storage_type) = self.world.bundles.get_with_storage_unchecked(bundle_id); + + let bundle_inserter = BundleInserter::new_with_info( + // SAFETY: The only outstanding reference to world is bundle_info + self.world.as_unsafe_world_cell_readonly(), + self.location.archetype_id, + bundle_info, + change_tick, + ); self.location = insert_dynamic_bundle( bundle_inserter, self.entity, self.location, Some(component).into_iter(), - Some(storage_type).iter().cloned(), + Some(*storage_type).iter().cloned(), ); self.world.flush_commands(); self @@ -635,27 +635,27 @@ impl<'w> EntityWorldMut<'w> { iter_components: I, ) -> &mut Self { let change_tick = self.world.change_tick(); - let (bundle_info, storage_types) = self + let bundle_id = self .world .bundles .init_dynamic_info(&self.world.components, component_ids); - let bundle_info: *const BundleInfo = bundle_info; - let storage_types: *const Vec = storage_types; - let bundle_inserter = unsafe { - BundleInserter::new_with_info( - self.world, - self.location.archetype_id, - bundle_info, - change_tick, - ) - }; + let (bundle_info, storage_types) = + self.world.bundles.get_with_storages_unchecked(bundle_id); + + let bundle_inserter = BundleInserter::new_with_info( + // SAFETY: The only outstanding reference to world is bundle_info + self.world.as_unsafe_world_cell_readonly(), + self.location.archetype_id, + bundle_info, + change_tick, + ); self.location = insert_dynamic_bundle( bundle_inserter, self.entity, self.location, iter_components, - (&*storage_types).iter().cloned(), + (*storage_types).iter().cloned(), ); self.world.flush_commands(); self @@ -670,8 +670,8 @@ impl<'w> EntityWorldMut<'w> { pub fn take(&mut self) -> Option { let storages = &mut self.world.storages; let components = &mut self.world.components; - let bundle_id = self.world.bundles.init_info::(components, storages).id(); - // Reborrow to drop &mut World + let bundle_id = self.world.bundles.init_info::(components, storages); + // SAFETY: We just ensured this bundle exists let bundle_info = unsafe { self.world.bundles.get_unchecked(bundle_id) }; let old_location = self.location; // SAFETY: `archetype_id` exists because it is referenced in the old `EntityLocation` which is valid, @@ -695,12 +695,17 @@ impl<'w> EntityWorldMut<'w> { let entity = self.entity; let old_archetype = &self.world.archetypes[old_location.archetype_id]; if old_archetype.has_on_remove() { - unsafe { self.world.into_deferred() } - .trigger_on_remove(entity, bundle_info.iter_components()); + // SAFETY: None of our oustanding references can be modified through DeferredWorld + unsafe { + self.world + .as_unsafe_world_cell_readonly() + .into_deferred() + .trigger_on_remove(entity, bundle_info.iter_components()); + }; + } + for component_id in bundle_info.iter_components() { + self.world.removed_components.send(component_id, entity); } - // for component_id in bundle_info.iter_components() { - // self.world.removed_components.send(component_id, entity); - // } let archetypes = &mut self.world.archetypes; let storages = &mut self.world.storages; let components = &mut self.world.components; @@ -827,8 +832,8 @@ impl<'w> EntityWorldMut<'w> { let storages = &mut self.world.storages; let components = &mut self.world.components; - let bundle_id = self.world.bundles.init_info::(components, storages).id(); - // Reborrow to drop &mut World + let bundle_id = self.world.bundles.init_info::(components, storages); + // SAFETY: We just ensured this bundle exists let bundle_info = unsafe { self.world.bundles.get_unchecked(bundle_id) }; let old_location = self.location; @@ -854,12 +859,17 @@ impl<'w> EntityWorldMut<'w> { let entity: Entity = self.entity; let old_archetype = &self.world.archetypes[old_location.archetype_id]; if old_archetype.has_on_remove() { - unsafe { self.world.into_deferred() } - .trigger_on_remove(entity, bundle_info.iter_components()) + // SAFETY: None of our oustanding references can be modified through DeferredWorld + unsafe { + self.world + .as_unsafe_world_cell_readonly() + .into_deferred() + .trigger_on_remove(entity, bundle_info.iter_components()); + }; } for component_id in bundle_info.iter_components() { if old_archetype.contains(component_id) { - // self.world.removed_components.send(component_id, entity); + self.world.removed_components.send(component_id, entity); // Make sure to drop components stored in sparse sets. // Dense components are dropped later in `move_to_and_drop_missing_unchecked`. @@ -897,15 +907,20 @@ impl<'w> EntityWorldMut<'w> { self.world.flush_entities(); let archetype = &self.world.archetypes[self.location.archetype_id]; if archetype.has_on_remove() { - unsafe { self.world.into_deferred() } - .trigger_on_remove(self.entity, archetype.components()); + // SAFETY: None of our oustanding references can be modified through DeferredWorld + unsafe { + self.world + .as_unsafe_world_cell_readonly() + .into_deferred() + .trigger_on_remove(self.entity, archetype.components()); + }; } - // for component_id in archetype.components() { - // self.world - // .removed_components - // .send(component_id, self.entity); - // } + for component_id in archetype.components() { + self.world + .removed_components + .send(component_id, self.entity); + } let world = self.world; let location = world diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 8099bb505cb67..12fc45066a817 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -15,7 +15,7 @@ pub use world_cell::*; use crate::{ archetype::{ArchetypeComponentId, ArchetypeId, ArchetypeRow, Archetypes}, - bundle::{Bundle, BundleInfo, BundleInserter, BundleSpawner, Bundles}, + bundle::{Bundle, BundleInserter, BundleSpawner, Bundles}, change_detection::{MutUntyped, TicksMut}, component::{Component, ComponentDescriptor, ComponentId, ComponentInfo, Components, Tick}, entity::{AllocAtWithoutReplacement, Entities, Entity, EntityLocation}, @@ -189,7 +189,7 @@ impl World { /// Initializes a new [`Component`] type and returns a mutable reference to the [`ComponentInfo`] created for it. /// Primarily used for registering hooks. /// - /// Will panic if a component for `T`` already exists. + /// Will panic if a component for `T` already exists. pub fn register_component(&mut self) -> &mut ComponentInfo { let type_id = TypeId::of::(); assert!(self.components.get_id(type_id).is_none(), "Components cannot be registered twice, use init_component instead if this component may already exist in the world."); @@ -218,7 +218,7 @@ impl World { /// Initializes a new [`Component`] type and returns a mutable reference to the [`ComponentInfo`] created for it. /// Primarily used for registering hooks. /// - /// Will panic if a component for `T`` already exists. + /// Will panic if a component for `T` already exists. pub fn register_component_with_descriptor( &mut self, descriptor: ComponentDescriptor, @@ -1454,9 +1454,11 @@ impl World { let change_tick = self.change_tick(); - let bundle_info = self + let bundle_id = self .bundles .init_info::(&mut self.components, &mut self.storages); + // SAFETY: We just ensured this bundle exists + let bundle_info = unsafe { self.bundles.get_unchecked(bundle_id) }; enum SpawnOrInsert<'w> { Spawn(BundleSpawner<'w>), Insert(BundleInserter<'w>, ArchetypeId), @@ -1470,10 +1472,11 @@ impl World { } } } - let bundle_info: *const BundleInfo = bundle_info; - let mut spawn_or_insert = SpawnOrInsert::Spawn(unsafe { - BundleSpawner::new_with_info(self, bundle_info, change_tick) - }); + let mut spawn_or_insert = SpawnOrInsert::Spawn(BundleSpawner::new_with_info( + self.as_unsafe_world_cell_readonly(), + bundle_info, + change_tick, + )); let mut invalid_entities = Vec::new(); for (entity, bundle) in iter { @@ -1490,14 +1493,12 @@ impl World { unsafe { inserter.insert(entity, location, bundle) }; } _ => { - let mut inserter = unsafe { - BundleInserter::new_with_info( - self, - location.archetype_id, - bundle_info, - change_tick, - ) - }; + let mut inserter = BundleInserter::new_with_info( + self.as_unsafe_world_cell_readonly(), + location.archetype_id, + bundle_info, + change_tick, + ); // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter unsafe { inserter.insert(entity, location, bundle) }; spawn_or_insert = @@ -1510,8 +1511,11 @@ impl World { // SAFETY: `entity` is allocated (but non existent), bundle matches inserter unsafe { spawner.spawn_non_existent(entity, bundle) }; } else { - let mut spawner = - unsafe { BundleSpawner::new_with_info(self, bundle_info, change_tick) }; + let mut spawner = BundleSpawner::new_with_info( + self.as_unsafe_world_cell_readonly(), + bundle_info, + change_tick, + ); // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter unsafe { spawner.spawn_non_existent(entity, bundle) }; spawn_or_insert = SpawnOrInsert::Spawn(spawner); diff --git a/crates/bevy_ecs/src/world/spawn_batch.rs b/crates/bevy_ecs/src/world/spawn_batch.rs index b0586e4c7c8f8..71e367fd1a86f 100644 --- a/crates/bevy_ecs/src/world/spawn_batch.rs +++ b/crates/bevy_ecs/src/world/spawn_batch.rs @@ -51,7 +51,9 @@ where I::Item: Bundle, { fn drop(&mut self) { - for _ in self {} + for _ in &mut *self {} + // SAFETY: `self.spawner` will be dropped immediately after this call. + unsafe { self.spawner.flush_commands() }; } } diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 667d6bfaa95e3..1056549a7a059 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -567,8 +567,12 @@ impl<'w> UnsafeWorldCell<'w> { .get_with_ticks() } + // Returns a mutable reference to worlds internal [`CommandQueue`] + /// # Safety + /// It is the callers responsibility to ensure that no mutable references exist to the command queue at the same time #[inline] pub(crate) unsafe fn get_command_queue(self) -> &'w mut CommandQueue { + // SAFETY: caller ensures there is no `&mut World` let world = unsafe { &mut *self.0 }; &mut world.command_queue } diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs index 9309de94e9fc9..8e8e63de0322f 100644 --- a/examples/ecs/component_hooks.rs +++ b/examples/ecs/component_hooks.rs @@ -30,7 +30,7 @@ fn setup(world: &mut World) { ); let mut index = world.resource_mut::(); index.remove(&entity); - println!("Current index: {:?}", *index) + println!("Current index: {:?}", *index); }); } From b620001a13f04906e84e9c0b44c757dd427c0c40 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sun, 26 Nov 2023 16:32:37 -0800 Subject: [PATCH 024/109] Miri pass --- crates/bevy_ecs/src/bundle.rs | 653 +++++++++++------- crates/bevy_ecs/src/system/system_registry.rs | 4 + crates/bevy_ecs/src/world/deferred_world.rs | 17 +- crates/bevy_ecs/src/world/entity_ref.rs | 146 ++-- crates/bevy_ecs/src/world/mod.rs | 34 +- .../bevy_ecs/src/world/unsafe_world_cell.rs | 12 +- examples/ecs/component_hooks.rs | 77 ++- 7 files changed, 577 insertions(+), 366 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index fe57cc96af184..a32f98a9b0ce6 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -15,7 +15,7 @@ use crate::{ prelude::World, query::DebugCheckedUnwrap, storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow}, - world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld}, + world::unsafe_world_cell::UnsafeWorldCell, TypeIdMap, }; use bevy_ptr::OwningPtr; @@ -502,27 +502,25 @@ impl BundleInfo { } } +// SAFETY: We have exclusive world access so our pointers can't be invalidated externally pub(crate) struct BundleInserter<'w> { - world: DeferredWorld<'w>, - archetype: &'w mut Archetype, - entities: &'w mut Entities, - bundle_info: &'w BundleInfo, - table: &'w mut Table, - sparse_sets: &'w mut SparseSets, - result: InsertBundleResult<'w>, - add_bundle_ptr: *const AddBundle, - archetypes_ptr: *mut Archetype, + world: UnsafeWorldCell<'w>, + bundle_info: *const BundleInfo, + add_bundle: *const AddBundle, + table: *mut Table, + archetype: *mut Archetype, + result: InsertBundleResult, change_tick: Tick, } -pub(crate) enum InsertBundleResult<'w> { +pub(crate) enum InsertBundleResult { SameArchetype, NewArchetypeSameTable { - new_archetype: &'w mut Archetype, + new_archetype: *mut Archetype, }, NewArchetypeNewTable { - new_archetype: &'w mut Archetype, - new_table: &'w mut Table, + new_archetype: *mut Archetype, + new_table: *mut Table, }, } @@ -536,28 +534,23 @@ impl<'w> BundleInserter<'w> { let bundle_id = world .bundles .init_info::(&mut world.components, &mut world.storages); - // SAFETY: We just ensured this bundle exists, re-borrow to drop &mut World - let bundle_info = unsafe { world.bundles.get_unchecked(bundle_id) }; - Self::new_with_info( - world.as_unsafe_world_cell_readonly(), - archetype_id, - bundle_info, - change_tick, - ) + // SAFETY: We just ensured this bundle exists + unsafe { Self::new_with_id(world, archetype_id, bundle_id, change_tick) } } /// Creates a new [`BundleInserter`]. /// - /// Implementation guarantees it will not alias `bundle_info`. + /// # Safety + /// Caller must ensure that BundleId exists in `world.bundles` #[inline] - pub(crate) fn new_with_info( - world: UnsafeWorldCell<'w>, + pub(crate) unsafe fn new_with_id( + world: &'w mut World, archetype_id: ArchetypeId, - bundle_info: &'w BundleInfo, + bundle_id: BundleId, change_tick: Tick, ) -> Self { // SAFETY: We will not make any accesses to the command queue, component or resource data of this world - let (world, deferred_world) = unsafe { world.split_deferred() }; + let bundle_info = world.bundles.get_unchecked(bundle_id); let bundle_id = bundle_info.id(); let new_archetype_id = bundle_info.add_bundle_to_archetype( &mut world.archetypes, @@ -565,11 +558,10 @@ impl<'w> BundleInserter<'w> { &world.components, archetype_id, ); - let archetypes_ptr = world.archetypes.archetypes.as_mut_ptr(); if new_archetype_id == archetype_id { let archetype = &mut world.archetypes[archetype_id]; // SAFETY: The edge is assured to be initialized when we called add_bundle_to_archetype - let add_bundle_ptr: *const AddBundle = unsafe { + let add_bundle = unsafe { archetype .edges() .get_add_bundle_internal(bundle_id) @@ -578,22 +570,19 @@ impl<'w> BundleInserter<'w> { let table_id = archetype.table_id(); let table = &mut world.storages.tables[table_id]; Self { - world: deferred_world, + add_bundle, archetype, - entities: &mut world.entities, bundle_info, table, - sparse_sets: &mut world.storages.sparse_sets, result: InsertBundleResult::SameArchetype, - archetypes_ptr, - add_bundle_ptr, change_tick, + world: world.as_unsafe_world_cell(), } } else { let (archetype, new_archetype) = world.archetypes.get_2_mut(archetype_id, new_archetype_id); // SAFETY: The edge is assured to be initialized when we called add_bundle_to_archetype - let add_bundle_ptr: *const AddBundle = unsafe { + let add_bundle = unsafe { archetype .edges() .get_add_bundle_internal(bundle_id) @@ -604,33 +593,27 @@ impl<'w> BundleInserter<'w> { if table_id == new_table_id { let table = &mut world.storages.tables[table_id]; Self { - world: deferred_world, + add_bundle, archetype, - entities: &mut world.entities, bundle_info, table, - sparse_sets: &mut world.storages.sparse_sets, result: InsertBundleResult::NewArchetypeSameTable { new_archetype }, - archetypes_ptr, - add_bundle_ptr, change_tick, + world: world.as_unsafe_world_cell(), } } else { let (table, new_table) = world.storages.tables.get_2_mut(table_id, new_table_id); Self { - world: deferred_world, + add_bundle, archetype, - entities: &mut world.entities, bundle_info, table, - sparse_sets: &mut world.storages.sparse_sets, result: InsertBundleResult::NewArchetypeNewTable { new_archetype, new_table, }, - archetypes_ptr, - add_bundle_ptr, change_tick, + world: world.as_unsafe_world_cell(), } } } @@ -645,161 +628,205 @@ impl<'w> BundleInserter<'w> { location: EntityLocation, bundle: T, ) -> EntityLocation { - // SAFETY: We must ensure we do not use self.archetype to invalidate this reference - let add_bundle = &*self.add_bundle_ptr; + // SAFETY: We do not make any structural changes to the archetype graph through self.world so these pointers always remain valid + let bundle_info = &*self.bundle_info; + let add_bundle = &*self.add_bundle; + let archetype = &*self.archetype; match &mut self.result { InsertBundleResult::SameArchetype => { - if self.archetype.has_on_add() { - self.world.trigger_on_add( + { + // SAFETY: Mutable references do not alias and will be dropped after this block + let sparse_sets = { + let world = self.world.world_mut(); + &mut world.storages.sparse_sets + }; + let table = &mut *self.table; + bundle_info.write_components( + table, + sparse_sets, + add_bundle, + entity, + location.table_row, + self.change_tick, + bundle, + ); + } + // SAFETY: We have no oustanding mutable references to world as they were dropped + let mut world = self.world.into_deferred(); + if archetype.has_on_add() { + world.trigger_on_add( entity, - self.bundle_info.iter_components().filter(|id| { - add_bundle.get_status(id.index()) == ComponentStatus::Added - }), + bundle_info + .iter_components() + .enumerate() + .filter(|(index, _)| { + add_bundle.get_status(*index) == ComponentStatus::Added + }) + .map(|(_, id)| id), ); } - if self.archetype.has_on_insert() { - self.world - .trigger_on_insert(entity, self.bundle_info.iter_components()); + if archetype.has_on_insert() { + world.trigger_on_insert(entity, bundle_info.iter_components()); } - // PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty) - // SAFETY: The edge is assured to be initialized when creating the BundleInserter - let add_bundle = unsafe { - self.archetype - .edges() - .get_add_bundle_internal(self.bundle_info.id) - .debug_checked_unwrap() - }; - self.bundle_info.write_components( - self.table, - self.sparse_sets, - add_bundle, - entity, - location.table_row, - self.change_tick, - bundle, - ); location } InsertBundleResult::NewArchetypeSameTable { new_archetype } => { + let new_location = { + // SAFETY: Mutable references do not alias and will be dropped after this block + let table = &mut *self.table; + let archetype = &mut *self.archetype; + let new_archetype = &mut **new_archetype; + let (sparse_sets, entities) = { + let world = self.world.world_mut(); + (&mut world.storages.sparse_sets, &mut world.entities) + }; + + let result = archetype.swap_remove(location.archetype_row); + if let Some(swapped_entity) = result.swapped_entity { + let swapped_location = + // SAFETY: If the swap was successful, swapped_entity must be valid. + unsafe { entities.get(swapped_entity).debug_checked_unwrap() }; + entities.set( + swapped_entity.index(), + EntityLocation { + archetype_id: swapped_location.archetype_id, + archetype_row: location.archetype_row, + table_id: swapped_location.table_id, + table_row: swapped_location.table_row, + }, + ); + } + let new_location = new_archetype.allocate(entity, result.table_row); + entities.set(entity.index(), new_location); + bundle_info.write_components( + table, + sparse_sets, + add_bundle, + entity, + result.table_row, + self.change_tick, + bundle, + ); + new_location + }; + + // SAFETY: We have no oustanding mutable references to world as they were dropped + let new_archetype = &**new_archetype; + let mut world = self.world.into_deferred(); if new_archetype.has_on_add() { - self.world.trigger_on_add( + world.trigger_on_add( entity, - self.bundle_info.iter_components().filter(|id| { - add_bundle.get_status(id.index()) == ComponentStatus::Added - }), + bundle_info + .iter_components() + .enumerate() + .filter(|(index, _)| { + add_bundle.get_status(*index) == ComponentStatus::Added + }) + .map(|(_, id)| id), ); } if new_archetype.has_on_insert() { - self.world - .trigger_on_insert(entity, self.bundle_info.iter_components()); - } - let result = self.archetype.swap_remove(location.archetype_row); - if let Some(swapped_entity) = result.swapped_entity { - let swapped_location = - // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; - self.entities.set( - swapped_entity.index(), - EntityLocation { - archetype_id: swapped_location.archetype_id, - archetype_row: location.archetype_row, - table_id: swapped_location.table_id, - table_row: swapped_location.table_row, - }, - ); + world.trigger_on_insert(entity, bundle_info.iter_components()); } - let new_location = new_archetype.allocate(entity, result.table_row); - self.entities.set(entity.index(), new_location); - self.bundle_info.write_components( - self.table, - self.sparse_sets, - add_bundle, - entity, - result.table_row, - self.change_tick, - bundle, - ); new_location } InsertBundleResult::NewArchetypeNewTable { new_archetype, new_table, } => { + let new_location = { + // SAFETY: Mutable references do not alias and will be dropped after this block + let table = &mut *self.table; + let new_table = &mut **new_table; + let archetype = &mut *self.archetype; + let new_archetype = &mut **new_archetype; + let (archetypes, sparse_sets, entities) = { + let world = self.world.world_mut(); + ( + &mut world.archetypes, + &mut world.storages.sparse_sets, + &mut world.entities, + ) + }; + let result = archetype.swap_remove(location.archetype_row); + if let Some(swapped_entity) = result.swapped_entity { + let swapped_location = + // SAFETY: If the swap was successful, swapped_entity must be valid. + unsafe { entities.get(swapped_entity).debug_checked_unwrap() }; + entities.set( + swapped_entity.index(), + EntityLocation { + archetype_id: swapped_location.archetype_id, + archetype_row: location.archetype_row, + table_id: swapped_location.table_id, + table_row: swapped_location.table_row, + }, + ); + } + // PERF: store "non bundle" components in edge, then just move those to avoid + // redundant copies + let move_result = table.move_to_superset_unchecked(result.table_row, new_table); + let new_location = new_archetype.allocate(entity, move_result.new_row); + entities.set(entity.index(), new_location); + + // if an entity was moved into this entity's table spot, update its table row + if let Some(swapped_entity) = move_result.swapped_entity { + let swapped_location = + // SAFETY: If the swap was successful, swapped_entity must be valid. + unsafe { entities.get(swapped_entity).debug_checked_unwrap() }; + let swapped_archetype = if archetype.id() == swapped_location.archetype_id { + archetype + } else if new_archetype.id() == swapped_location.archetype_id { + new_archetype + } else { + // SAFETY: the only two borrowed archetypes are above and we just did collision checks + &mut archetypes.archetypes[swapped_location.archetype_id.index()] + }; + + entities.set( + swapped_entity.index(), + EntityLocation { + archetype_id: swapped_location.archetype_id, + archetype_row: swapped_location.archetype_row, + table_id: swapped_location.table_id, + table_row: result.table_row, + }, + ); + swapped_archetype + .set_entity_table_row(swapped_location.archetype_row, result.table_row); + } + + bundle_info.write_components( + new_table, + sparse_sets, + add_bundle, + entity, + move_result.new_row, + self.change_tick, + bundle, + ); + + new_location + }; + + // SAFETY: We have no oustanding mutable references to world as they were dropped + let new_archetype = &**new_archetype; + let mut world = self.world.into_deferred(); if new_archetype.has_on_add() { - self.world.trigger_on_add( + world.trigger_on_add( entity, - self.bundle_info.iter_components().filter(|id| { - add_bundle.get_status(id.index()) == ComponentStatus::Added - }), + bundle_info + .iter_components() + .enumerate() + .filter(|(index, _)| { + add_bundle.get_status(*index) == ComponentStatus::Added + }) + .map(|(_, id)| id), ); } if new_archetype.has_on_insert() { - self.world - .trigger_on_insert(entity, self.bundle_info.iter_components()); - } - let result = self.archetype.swap_remove(location.archetype_row); - if let Some(swapped_entity) = result.swapped_entity { - let swapped_location = - // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; - self.entities.set( - swapped_entity.index(), - EntityLocation { - archetype_id: swapped_location.archetype_id, - archetype_row: location.archetype_row, - table_id: swapped_location.table_id, - table_row: swapped_location.table_row, - }, - ); + world.trigger_on_insert(entity, bundle_info.iter_components()); } - // PERF: store "non bundle" components in edge, then just move those to avoid - // redundant copies - let move_result = self - .table - .move_to_superset_unchecked(result.table_row, new_table); - let new_location = new_archetype.allocate(entity, move_result.new_row); - self.entities.set(entity.index(), new_location); - - // if an entity was moved into this entity's table spot, update its table row - if let Some(swapped_entity) = move_result.swapped_entity { - let swapped_location = - // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; - let swapped_archetype = if self.archetype.id() == swapped_location.archetype_id - { - let archetype: *mut Archetype = self.archetype; - &mut *archetype - } else if new_archetype.id() == swapped_location.archetype_id { - new_archetype - } else { - // SAFETY: the only two borrowed archetypes are above and we just did collision checks - &mut *self - .archetypes_ptr - .add(swapped_location.archetype_id.index()) - }; - - self.entities.set( - swapped_entity.index(), - EntityLocation { - archetype_id: swapped_location.archetype_id, - archetype_row: swapped_location.archetype_row, - table_id: swapped_location.table_id, - table_row: result.table_row, - }, - ); - swapped_archetype - .set_entity_table_row(swapped_location.archetype_row, result.table_row); - } - - self.bundle_info.write_components( - new_table, - self.sparse_sets, - add_bundle, - entity, - move_result.new_row, - self.change_tick, - bundle, - ); new_location } } @@ -807,17 +834,17 @@ impl<'w> BundleInserter<'w> { #[inline] pub(crate) fn entities(&mut self) -> &mut Entities { - self.entities + // SAFETY: No outstanding references to self.world, changes to entities cannot invalidate our internal pointers + unsafe { &mut self.world.world_mut().entities } } } +// SAFETY: We have exclusive world access so our pointers can't be invalidated externally pub(crate) struct BundleSpawner<'w> { - world: DeferredWorld<'w>, - bundle_info: &'w BundleInfo, - archetype: &'w mut Archetype, - table: &'w mut Table, - sparse_sets: &'w mut SparseSets, - entities: &'w mut Entities, + world: UnsafeWorldCell<'w>, + bundle_info: *const BundleInfo, + table: *mut Table, + archetype: *mut Archetype, change_tick: Tick, } @@ -827,23 +854,21 @@ impl<'w> BundleSpawner<'w> { let bundle_id = world .bundles .init_info::(&mut world.components, &mut world.storages); - // SAFETY: We just ensured this bundle exists, re-borrow to drop &mut World - let bundle_info = unsafe { world.bundles.get_unchecked(bundle_id) }; - Self::new_with_info( - world.as_unsafe_world_cell_readonly(), - bundle_info, - change_tick, - ) + // SAFETY: we initialized this bundle_id in `init_info` + unsafe { Self::new_with_id(world, bundle_id, change_tick) } } + /// Creates a new [`BundleSpawner`]. + /// + /// # Safety + /// Caller must ensure that BundleId exists in `world.bundles` #[inline] - pub(crate) fn new_with_info( - world: UnsafeWorldCell<'w>, - bundle_info: &'w BundleInfo, + pub(crate) unsafe fn new_with_id( + world: &'w mut World, + bundle_id: BundleId, change_tick: Tick, ) -> Self { - // SAFETY: We will not make any accesses to the command queue, component or resource data of this world - let (world, deferred_world) = unsafe { world.split_deferred() }; + let bundle_info = world.bundles.get_unchecked(bundle_id); let new_archetype_id = bundle_info.add_bundle_to_archetype( &mut world.archetypes, &mut world.storages, @@ -853,20 +878,20 @@ impl<'w> BundleSpawner<'w> { let archetype = &mut world.archetypes[new_archetype_id]; let table = &mut world.storages.tables[archetype.table_id()]; Self { - world: deferred_world, bundle_info, - archetype, table, - sparse_sets: &mut world.storages.sparse_sets, - entities: &mut world.entities, + archetype, change_tick, + world: world.as_unsafe_world_cell(), } } #[inline] pub fn reserve_storage(&mut self, additional: usize) { - self.archetype.reserve(additional); - self.table.reserve(additional); + // SAFETY: There are no oustanding world references + let (archetype, table) = unsafe { (&mut *self.archetype, &mut *self.table) }; + archetype.reserve(additional); + table.reserve(additional); } /// # Safety @@ -877,29 +902,41 @@ impl<'w> BundleSpawner<'w> { entity: Entity, bundle: T, ) -> EntityLocation { - if self.archetype.has_on_add() { - self.world - .trigger_on_add(entity, self.bundle_info.iter_components()); + // SAFETY: We do not make any structural changes to the archetype graph through self.world so this pointer always remain valid + let bundle_info = &*self.bundle_info; + let location = { + // SAFETY: Mutable references do not alias and will be dropped after this block + let table = &mut *self.table; + let archetype = &mut *self.archetype; + let (sparse_sets, entities) = { + let world = self.world.world_mut(); + (&mut world.storages.sparse_sets, &mut world.entities) + }; + let table_row = table.allocate(entity); + let location = archetype.allocate(entity, table_row); + bundle_info.write_components( + table, + sparse_sets, + &SpawnBundleStatus, + entity, + table_row, + self.change_tick, + bundle, + ); + entities.set(entity.index(), location); + location + }; + + // SAFETY: We have no oustanding mutable references to world as they were dropped + let archetype = &*self.archetype; + let mut world = self.world.into_deferred(); + if archetype.has_on_add() { + world.trigger_on_add(entity, bundle_info.iter_components()); } - if self.archetype.has_on_insert() { - self.world - .trigger_on_insert(entity, self.bundle_info.iter_components()); + if archetype.has_on_insert() { + world.trigger_on_insert(entity, bundle_info.iter_components()); } - let table = &mut *self.table; - let table_row = table.allocate(entity); - let location = self.archetype.allocate(entity, table_row); - self.bundle_info.write_components( - table, - self.sparse_sets, - &SpawnBundleStatus, - entity, - table_row, - self.change_tick, - bundle, - ); - self.entities.set(entity.index(), location); - location } @@ -907,7 +944,7 @@ impl<'w> BundleSpawner<'w> { /// `T` must match this [`BundleInfo`]'s type #[inline] pub unsafe fn spawn(&mut self, bundle: T) -> Entity { - let entity = self.entities.alloc(); + let entity = self.entities().alloc(); // SAFETY: entity is allocated (but non-existent), `T` matches this BundleInfo's type self.spawn_non_existent(entity, bundle); entity @@ -915,19 +952,16 @@ impl<'w> BundleSpawner<'w> { #[inline] pub(crate) fn entities(&mut self) -> &mut Entities { - self.entities + // SAFETY: No outstanding references to self.world, changes to entities cannot invalidate our internal pointers + unsafe { &mut self.world.world_mut().entities } } /// # Safety: /// - `Self` must be dropped after running this function as it may invalidate internal pointers. - /// - Caller must ensure that no there are no outstanding access to `self.world` #[inline] pub(crate) unsafe fn flush_commands(&mut self) { // SAFETY: pointers on self can be invalidated, - self.world - .as_unsafe_world_cell_readonly() - .world_mut() - .flush_commands(); + self.world.world_mut().flush_commands(); } } @@ -988,26 +1022,17 @@ impl Bundles { self.bundle_infos.get_unchecked(id.0) } - pub(crate) unsafe fn get_with_storage_unchecked( - &self, - id: BundleId, - ) -> (&BundleInfo, &StorageType) { - ( - self.bundle_infos.get_unchecked(id.0), - self.dynamic_component_storages - .get(&id) - .debug_checked_unwrap(), - ) + pub(crate) unsafe fn get_storage_unchecked(&self, id: BundleId) -> StorageType { + *self + .dynamic_component_storages + .get(&id) + .debug_checked_unwrap() } - pub(crate) unsafe fn get_with_storages_unchecked( - &self, - id: BundleId, - ) -> (&BundleInfo, &Vec) { - ( - self.bundle_infos.get_unchecked(id.0), - self.dynamic_bundle_storages.get(&id).debug_checked_unwrap(), - ) + pub(crate) unsafe fn get_storages_unchecked(&mut self, id: BundleId) -> &mut Vec { + self.dynamic_bundle_storages + .get_mut(&id) + .debug_checked_unwrap() } /// Initializes a new [`BundleInfo`] for a dynamic [`Bundle`]. @@ -1086,3 +1111,145 @@ fn initialize_dynamic_bundle( (id, storage_types) } + +mod tests { + use crate as bevy_ecs; + use crate::prelude::*; + + #[derive(Component)] + struct A; + + #[derive(Component)] + struct B; + + #[derive(Component)] + struct C; + + #[derive(Component)] + struct D; + + #[derive(Resource, Default)] + struct R(usize); + + impl R { + fn assert_order(&mut self, count: usize) { + assert_eq!(count, self.0); + self.0 += 1; + } + } + + #[test] + fn component_hook_order_spawn_despawn() { + let mut world = World::new(); + world.init_resource::(); + world + .register_component::() + .on_add(|mut world, _, _| { + world.resource_mut::().assert_order(0); + }) + .on_insert(|mut world, _, _| world.resource_mut::().assert_order(1)) + .on_remove(|mut world, _, _| world.resource_mut::().assert_order(2)); + + let entity = world.spawn(A).id(); + world.despawn(entity); + assert_eq!(3, world.resource::().0); + } + + #[test] + fn component_hook_order_insert_remove() { + let mut world = World::new(); + world.init_resource::(); + world + .register_component::() + .on_add(|mut world, _, _| { + world.resource_mut::().assert_order(0); + }) + .on_insert(|mut world, _, _| { + world.resource_mut::().assert_order(1); + }) + .on_remove(|mut world, _, _| { + world.resource_mut::().assert_order(2); + }); + + let mut entity = world.spawn_empty(); + entity.insert(A); + entity.remove::(); + entity.flush(); + assert_eq!(3, world.resource::().0); + } + + #[test] + fn component_hook_order_recursive() { + let mut world = World::new(); + world.init_resource::(); + world + .register_component::() + .on_add(|mut world, entity, _| { + world.resource_mut::().assert_order(0); + + world.with_commands(|mut commands| { + commands.entity(entity).insert(B); + }); + }) + .on_remove(|mut world, entity, _| { + world.resource_mut::().assert_order(2); + + world.with_commands(|mut commands| { + commands.entity(entity).remove::(); + }); + }); + + world + .register_component::() + .on_add(|mut world, entity, _| { + world.resource_mut::().assert_order(1); + + world.with_commands(|mut commands| { + commands.entity(entity).remove::(); + }); + }) + .on_remove(|mut world, _, _| { + world.resource_mut::().assert_order(3); + }); + + let entity = world.spawn(A).flush(); + let entity = world.get_entity(entity).unwrap(); + assert_eq!(false, entity.contains::()); + assert_eq!(false, entity.contains::()); + assert_eq!(4, world.resource::().0); + } + + #[test] + fn component_hook_order_recursive_multiple() { + let mut world = World::new(); + world.init_resource::(); + world + .register_component::() + .on_add(|mut world, entity, _| { + world.resource_mut::().assert_order(0); + world.with_commands(|mut commands| { + commands.entity(entity).insert(B).insert(D); + }); + }); + + world + .register_component::() + .on_add(|mut world, entity, _| { + world.resource_mut::().assert_order(1); + world.with_commands(|mut commands| { + commands.entity(entity).insert(C); + }); + }); + + world.register_component::().on_add(|mut world, _, _| { + world.resource_mut::().assert_order(2); + }); + + world.register_component::().on_add(|mut world, _, _| { + world.resource_mut::().assert_order(3); + }); + + world.spawn(A).flush(); + assert_eq!(4, world.resource::().0); + } +} diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index 5a9d7269d1032..f74c78673bbf7 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -280,6 +280,10 @@ impl World { .take::>() .ok_or(RegisteredSystemError::Recursive(id))?; + // Flush commands from removing component + // TODO: Consider refactoring to an RegisteredSystem to an Option to avoid removal + drop(entity); + // run the system if !initialized { system.initialize(self); diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index d12ba0b4248b2..1f688f15e3911 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -29,9 +29,12 @@ impl<'w> Deref for DeferredWorld<'w> { impl<'w> DeferredWorld<'w> { /// Creates a [`Commands`] instance that pushes to the world's command queue - pub fn commands(&mut self) -> Commands { - // SAFETY: Commands::new only take &World which has no way to access the command queue - unsafe { Commands::new(self.world.get_command_queue(), self.world.world()) } + #[inline] + pub fn with_commands(&mut self, f: impl Fn(Commands)) { + let world = unsafe { self.world.world_mut() }; + let mut queue = std::mem::take(&mut world.command_queue); + f(Commands::new(&mut queue, world)); + world.command_queue = std::mem::take(&mut queue); } /// Retrieves a mutable reference to the given `entity`'s [`Component`] of the given type. @@ -276,14 +279,6 @@ impl<'w> UnsafeWorldCell<'w> { pub unsafe fn into_deferred(self) -> DeferredWorld<'w> { DeferredWorld { world: self } } - - /// Turn self into a mutable world reference and a [`DeferredWorld`] - /// - /// # Safety - /// Caller must ensure that the world reference is not used to construct any references to the world's command queue, resource or component data - pub unsafe fn split_deferred(self) -> (&'w mut World, DeferredWorld<'w>) { - (self.world_mut(), self.into_deferred()) - } } impl<'w> From<&'w mut World> for DeferredWorld<'w> { diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index c7763d7c87c82..49a74a0bccf6a 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -8,7 +8,6 @@ use crate::{ world::{Mut, World}, }; use bevy_ptr::{OwningPtr, Ptr}; -use bevy_utils::tracing::debug; use std::{any::TypeId, marker::PhantomData}; use super::{unsafe_world_cell::UnsafeEntityCell, Ref}; @@ -572,7 +571,6 @@ impl<'w> EntityWorldMut<'w> { BundleInserter::new::(self.world, self.location.archetype_id, change_tick); // SAFETY: location matches current entity. `T` matches `bundle_info` self.location = unsafe { bundle_inserter.insert(self.entity, self.location, bundle) }; - self.world.flush_commands(); self } @@ -596,13 +594,12 @@ impl<'w> EntityWorldMut<'w> { .world .bundles .init_component_info(&self.world.components, component_id); - let (bundle_info, storage_type) = self.world.bundles.get_with_storage_unchecked(bundle_id); + let storage_type = self.world.bundles.get_storage_unchecked(bundle_id); - let bundle_inserter = BundleInserter::new_with_info( - // SAFETY: The only outstanding reference to world is bundle_info - self.world.as_unsafe_world_cell_readonly(), + let bundle_inserter = BundleInserter::new_with_id( + self.world, self.location.archetype_id, - bundle_info, + bundle_id, change_tick, ); @@ -611,9 +608,8 @@ impl<'w> EntityWorldMut<'w> { self.entity, self.location, Some(component).into_iter(), - Some(*storage_type).iter().cloned(), + Some(storage_type).iter().cloned(), ); - self.world.flush_commands(); self } @@ -639,14 +635,12 @@ impl<'w> EntityWorldMut<'w> { .world .bundles .init_dynamic_info(&self.world.components, component_ids); - let (bundle_info, storage_types) = - self.world.bundles.get_with_storages_unchecked(bundle_id); - - let bundle_inserter = BundleInserter::new_with_info( - // SAFETY: The only outstanding reference to world is bundle_info - self.world.as_unsafe_world_cell_readonly(), + let mut storage_types = + std::mem::take(self.world.bundles.get_storages_unchecked(bundle_id)); + let bundle_inserter = BundleInserter::new_with_id( + self.world, self.location.archetype_id, - bundle_info, + bundle_id, change_tick, ); @@ -657,7 +651,7 @@ impl<'w> EntityWorldMut<'w> { iter_components, (*storage_types).iter().cloned(), ); - self.world.flush_commands(); + *self.world.bundles.get_storages_unchecked(bundle_id) = std::mem::take(&mut storage_types); self } @@ -668,17 +662,18 @@ impl<'w> EntityWorldMut<'w> { // TODO: BundleRemover? #[must_use] pub fn take(&mut self) -> Option { - let storages = &mut self.world.storages; - let components = &mut self.world.components; - let bundle_id = self.world.bundles.init_info::(components, storages); + let world = &mut self.world; + let storages = &mut world.storages; + let components = &mut world.components; + let bundle_id = world.bundles.init_info::(components, storages); // SAFETY: We just ensured this bundle exists - let bundle_info = unsafe { self.world.bundles.get_unchecked(bundle_id) }; + let bundle_info = unsafe { world.bundles.get_unchecked(bundle_id) }; let old_location = self.location; // SAFETY: `archetype_id` exists because it is referenced in the old `EntityLocation` which is valid, // components exist in `bundle_info` because `Bundles::init_info` initializes a `BundleInfo` containing all components of the bundle type `T` let new_archetype_id = unsafe { remove_bundle_from_archetype( - &mut self.world.archetypes, + &mut world.archetypes, storages, components, old_location.archetype_id, @@ -691,25 +686,32 @@ impl<'w> EntityWorldMut<'w> { return None; } - // Reborrow so world is not mutably borrowed let entity = self.entity; - let old_archetype = &self.world.archetypes[old_location.archetype_id]; + let old_archetype = &world.archetypes[old_location.archetype_id]; + + // Drop borrow on world so it can be turned into DeferredWorld + // SAFETY: Changes to Bundles cannot happen through DeferredWorld + let bundle_info = { + let bundle_info: *const BundleInfo = bundle_info; + unsafe { &*bundle_info } + }; if old_archetype.has_on_remove() { - // SAFETY: None of our oustanding references can be modified through DeferredWorld + // SAFETY: All components in the archetype exist in world unsafe { - self.world - .as_unsafe_world_cell_readonly() + world + .as_unsafe_world_cell() .into_deferred() .trigger_on_remove(entity, bundle_info.iter_components()); - }; + } } + for component_id in bundle_info.iter_components() { - self.world.removed_components.send(component_id, entity); + world.removed_components.send(component_id, entity); } - let archetypes = &mut self.world.archetypes; - let storages = &mut self.world.storages; - let components = &mut self.world.components; - let entities = &mut self.world.entities; + let archetypes = &mut world.archetypes; + let storages = &mut world.storages; + let components = &mut world.components; + let entities = &mut world.entities; let entity = self.entity; let mut bundle_components = bundle_info.iter_components(); @@ -739,8 +741,6 @@ impl<'w> EntityWorldMut<'w> { new_archetype_id, ); } - drop(bundle_components); - self.world.flush_commands(); Some(result) } @@ -828,13 +828,14 @@ impl<'w> EntityWorldMut<'w> { /// Removes any components in the [`Bundle`] from the entity. // TODO: BundleRemover? pub fn remove(&mut self) -> &mut Self { - let archetypes = &mut self.world.archetypes; - let storages = &mut self.world.storages; - let components = &mut self.world.components; + let world = &mut self.world; + let archetypes = &mut world.archetypes; + let storages = &mut world.storages; + let components = &mut world.components; - let bundle_id = self.world.bundles.init_info::(components, storages); + let bundle_id = world.bundles.init_info::(components, storages); // SAFETY: We just ensured this bundle exists - let bundle_info = unsafe { self.world.bundles.get_unchecked(bundle_id) }; + let bundle_info = unsafe { world.bundles.get_unchecked(bundle_id) }; let old_location = self.location; // SAFETY: `archetype_id` exists because it is referenced in the old `EntityLocation` which is valid, @@ -855,26 +856,34 @@ impl<'w> EntityWorldMut<'w> { return self; } - // Reborrow so world is not mutably borrowed let entity: Entity = self.entity; - let old_archetype = &self.world.archetypes[old_location.archetype_id]; + let old_archetype = &world.archetypes[old_location.archetype_id]; + + // Drop borrow on world so it can be turned into DeferredWorld + // SAFETY: Changes to Bundles cannot happen through DeferredWorld + let bundle_info = { + let bundle_info: *const BundleInfo = bundle_info; + unsafe { &*bundle_info } + }; if old_archetype.has_on_remove() { - // SAFETY: None of our oustanding references can be modified through DeferredWorld + // SAFETY: All components in the archetype exist in world unsafe { - self.world - .as_unsafe_world_cell_readonly() + world + .as_unsafe_world_cell() .into_deferred() .trigger_on_remove(entity, bundle_info.iter_components()); - }; + } } + + let old_archetype = &world.archetypes[old_location.archetype_id]; for component_id in bundle_info.iter_components() { if old_archetype.contains(component_id) { - self.world.removed_components.send(component_id, entity); + world.removed_components.send(component_id, entity); // Make sure to drop components stored in sparse sets. // Dense components are dropped later in `move_to_and_drop_missing_unchecked`. if let Some(StorageType::SparseSet) = old_archetype.get_storage_type(component_id) { - self.world + world .storages .sparse_sets .get_mut(component_id) @@ -891,38 +900,41 @@ impl<'w> EntityWorldMut<'w> { &mut self.location, old_location.archetype_id, old_location, - &mut self.world.entities, - &mut self.world.archetypes, - &mut self.world.storages, + &mut world.entities, + &mut world.archetypes, + &mut world.storages, new_archetype_id, ); } - self.world.flush_commands(); self } /// Despawns the current entity. pub fn despawn(self) { - debug!("Despawning entity {:?}", self.entity); - self.world.flush_entities(); - let archetype = &self.world.archetypes[self.location.archetype_id]; + let world = self.world; + world.flush_entities(); + let archetype = &world.archetypes[self.location.archetype_id]; if archetype.has_on_remove() { - // SAFETY: None of our oustanding references can be modified through DeferredWorld + // Drop borrow on world so it can be turned into DeferredWorld + // SAFETY: Changes to Archetypes cannot happpen through DeferredWorld + let archetype = { + let archetype: *const Archetype = archetype; + unsafe { &*archetype } + }; + // SAFETY: All components in the archetype exist in world unsafe { - self.world - .as_unsafe_world_cell_readonly() + world + .as_unsafe_world_cell() .into_deferred() .trigger_on_remove(self.entity, archetype.components()); - }; + } } + let archetype = &world.archetypes[self.location.archetype_id]; for component_id in archetype.components() { - self.world - .removed_components - .send(component_id, self.entity); + world.removed_components.send(component_id, self.entity); } - let world = self.world; let location = world .entities .free(self.entity) @@ -979,7 +991,13 @@ impl<'w> EntityWorldMut<'w> { world.archetypes[moved_location.archetype_id] .set_entity_table_row(moved_location.archetype_row, table_row); } - world.flush_commands(); + world.flush_commands() + } + + /// Ensures any commands triggered by the actions of Self are applied, equivalent to [`World::flush_commands`] + pub fn flush(self) -> Entity { + self.world.flush_commands(); + self.entity } /// Gets read-only access to the world that the current entity belongs to. diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 12fc45066a817..d1d46429527d6 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1457,8 +1457,6 @@ impl World { let bundle_id = self .bundles .init_info::(&mut self.components, &mut self.storages); - // SAFETY: We just ensured this bundle exists - let bundle_info = unsafe { self.bundles.get_unchecked(bundle_id) }; enum SpawnOrInsert<'w> { Spawn(BundleSpawner<'w>), Insert(BundleInserter<'w>, ArchetypeId), @@ -1472,11 +1470,10 @@ impl World { } } } - let mut spawn_or_insert = SpawnOrInsert::Spawn(BundleSpawner::new_with_info( - self.as_unsafe_world_cell_readonly(), - bundle_info, - change_tick, - )); + // SAFETY: we initialized this bundle_id in `init_info` + let mut spawn_or_insert = SpawnOrInsert::Spawn(unsafe { + BundleSpawner::new_with_id(self, bundle_id, change_tick) + }); let mut invalid_entities = Vec::new(); for (entity, bundle) in iter { @@ -1493,12 +1490,15 @@ impl World { unsafe { inserter.insert(entity, location, bundle) }; } _ => { - let mut inserter = BundleInserter::new_with_info( - self.as_unsafe_world_cell_readonly(), - location.archetype_id, - bundle_info, - change_tick, - ); + // SAFETY: we initialized this bundle_id in `init_info` + let mut inserter = unsafe { + BundleInserter::new_with_id( + self, + location.archetype_id, + bundle_id, + change_tick, + ) + }; // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter unsafe { inserter.insert(entity, location, bundle) }; spawn_or_insert = @@ -1511,11 +1511,9 @@ impl World { // SAFETY: `entity` is allocated (but non existent), bundle matches inserter unsafe { spawner.spawn_non_existent(entity, bundle) }; } else { - let mut spawner = BundleSpawner::new_with_info( - self.as_unsafe_world_cell_readonly(), - bundle_info, - change_tick, - ); + // SAFETY: we initialized this bundle_id in `init_info` + let mut spawner = + unsafe { BundleSpawner::new_with_id(self, bundle_id, change_tick) }; // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter unsafe { spawner.spawn_non_existent(entity, bundle) }; spawn_or_insert = SpawnOrInsert::Spawn(spawner); diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 1056549a7a059..25d5ac62c0305 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -14,7 +14,7 @@ use crate::{ prelude::Component, removal_detection::RemovedComponentEvents, storage::{Column, ComponentSparseSet, Storages}, - system::{CommandQueue, Resource}, + system::Resource, }; use bevy_ptr::Ptr; use std::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData}; @@ -566,16 +566,6 @@ impl<'w> UnsafeWorldCell<'w> { .get(component_id)? .get_with_ticks() } - - // Returns a mutable reference to worlds internal [`CommandQueue`] - /// # Safety - /// It is the callers responsibility to ensure that no mutable references exist to the command queue at the same time - #[inline] - pub(crate) unsafe fn get_command_queue(self) -> &'w mut CommandQueue { - // SAFETY: caller ensures there is no `&mut World` - let world = unsafe { &mut *self.0 }; - &mut world.command_queue - } } impl Debug for UnsafeWorldCell<'_> { diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs index 8e8e63de0322f..78deb6f2f5966 100644 --- a/examples/ecs/component_hooks.rs +++ b/examples/ecs/component_hooks.rs @@ -1,42 +1,81 @@ -use std::collections::HashSet; +//! This examples illustrates the different ways you can employ component lifecycle hooks use bevy::prelude::*; +use std::collections::HashMap; #[derive(Component, Debug)] -struct MyComponent(usize); +struct MyComponent(KeyCode); #[derive(Resource, Default, Debug, Deref, DerefMut)] -struct MyComponentIndex(HashSet); +struct MyComponentIndex(HashMap); + +#[derive(Event)] +struct MyEvent; fn main() { App::new() - .add_systems(Startup, (setup, trigger_hooks).chain()) + .add_plugins(DefaultPlugins) + .add_systems(Startup, setup) + .add_systems(Update, trigger_hooks) .init_resource::() + .add_event::() .run(); } fn setup(world: &mut World) { + // In order to register component hooks it must be the first time you register the component + // This is to prevent users from overwriting hooks that may be used internally in foreign crates world .register_component::() - .on_add(|mut world, entity, _| { - println!("Added MyComponent to: {:?}", entity); - world.resource_mut::().insert(entity); + // There are 3 component lifecyle hooks: `on_add`, `on_insert` and `on_remove` + // A hook has 3 arguments: + // - a `DeferredWorld`, this allows access to resource and component data as well as `Commands` + // - the entity that triggered the hook + // - the component id of the triggering component, this is mostly used for dynamic components + // + // `on_add` will trigger when a component is inserted onto an entity without it + .on_add(|mut world, entity, component_id| { + // You can access component data from within the hook + let value = world.get::(entity).unwrap().0; + println!( + "Component: {:?} added to: {:?} with value {:?}", + component_id, entity, value + ); + // Or access resources + world + .resource_mut::() + .insert(value, entity); + // Or send events + world.send_event(MyEvent); + }) + // `on_insert` will trigger when a component is inserted onto an entity, + // regardless of whether or not it already had it and after `on_add` if it ran + .on_insert(|world, _, _| { + println!("Current Index: {:?}", world.resource::()); }) - .on_remove(|mut world, entity, _| { + // `on_remove` will trigger when a component is removed from an entity, + // since it runs before the component is removed you can still access the component data + .on_remove(|mut world, entity, component_id| { + let value = world.get::(entity).unwrap().0; println!( - "Removed MyComponent from: {:?} {:?}", - entity, - world.get::(entity) + "Component: {:?} removed from: {:?} with value {:?}", + component_id, entity, value ); - let mut index = world.resource_mut::(); - index.remove(&entity); - println!("Current index: {:?}", *index); + world.resource_mut::().remove(&value); + // You can also issue commands through `.with_commands` + world.with_commands(|mut commands| { + commands.entity(entity).despawn(); + }); }); } -fn trigger_hooks(mut commands: Commands) { - let entity_a = commands.spawn(MyComponent(0)).id(); - let entity_b = commands.spawn(MyComponent(1)).id(); - commands.entity(entity_b).despawn(); - commands.entity(entity_a).despawn(); +fn trigger_hooks(mut commands: Commands, keys: Res>, index: Res) { + for (key, entity) in index.iter() { + if !keys.pressed(*key) { + commands.entity(*entity).remove::(); + } + } + for key in keys.get_just_pressed() { + commands.spawn(MyComponent(*key)); + } } From 9b76944ea7cffb4485760e05e5b1830ce9be2805 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sun, 26 Nov 2023 16:38:13 -0800 Subject: [PATCH 025/109] Add missing cfg(test) --- crates/bevy_ecs/src/bundle.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index a32f98a9b0ce6..5a7c793f2663c 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -1112,6 +1112,7 @@ fn initialize_dynamic_bundle( (id, storage_types) } +#[cfg(test)] mod tests { use crate as bevy_ecs; use crate::prelude::*; From 924fb5dad89618d22ea31dd3c7dfbb56a9f10c91 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sun, 26 Nov 2023 16:43:28 -0800 Subject: [PATCH 026/109] Tidy iterator chains --- crates/bevy_ecs/src/bundle.rs | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 5a7c793f2663c..9ee95c210567b 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -658,11 +658,9 @@ impl<'w> BundleInserter<'w> { entity, bundle_info .iter_components() - .enumerate() - .filter(|(index, _)| { - add_bundle.get_status(*index) == ComponentStatus::Added - }) - .map(|(_, id)| id), + .zip(add_bundle.bundle_status.iter()) + .filter(|(_, &status)| status == ComponentStatus::Added) + .map(|(id, _)| id), ); } if archetype.has_on_insert() { @@ -718,11 +716,9 @@ impl<'w> BundleInserter<'w> { entity, bundle_info .iter_components() - .enumerate() - .filter(|(index, _)| { - add_bundle.get_status(*index) == ComponentStatus::Added - }) - .map(|(_, id)| id), + .zip(add_bundle.bundle_status.iter()) + .filter(|(_, &status)| status == ComponentStatus::Added) + .map(|(id, _)| id), ); } if new_archetype.has_on_insert() { @@ -817,11 +813,9 @@ impl<'w> BundleInserter<'w> { entity, bundle_info .iter_components() - .enumerate() - .filter(|(index, _)| { - add_bundle.get_status(*index) == ComponentStatus::Added - }) - .map(|(_, id)| id), + .zip(add_bundle.bundle_status.iter()) + .filter(|(_, &status)| status == ComponentStatus::Added) + .map(|(id, _)| id), ); } if new_archetype.has_on_insert() { From d23d1b23ee17dd5e358f03e0c2ecd00d61dad074 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sun, 26 Nov 2023 17:00:51 -0800 Subject: [PATCH 027/109] Cleanup --- Cargo.toml | 2 +- crates/bevy_ecs/src/bundle.rs | 2 +- crates/bevy_ecs/src/query/builder.rs | 422 ------------------ crates/bevy_ecs/src/system/system_registry.rs | 4 - crates/bevy_ecs/src/world/deferred_world.rs | 6 +- crates/bevy_ecs/src/world/entity_ref.rs | 13 +- crates/bevy_ecs/src/world/mod.rs | 3 +- 7 files changed, 18 insertions(+), 434 deletions(-) delete mode 100644 crates/bevy_ecs/src/query/builder.rs diff --git a/Cargo.toml b/Cargo.toml index addf71590a71c..b233686e88a5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1301,7 +1301,7 @@ doc-scrape-examples = true [package.metadata.example.component_hooks] name = "Component Hooks" -description = "Define component hooks to react to ECS events" +description = "Define component hooks to manage component lifecycle events" category = "ECS (Entity Component System)" wasm = false diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 9ee95c210567b..f8b563bdf9325 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -619,7 +619,7 @@ impl<'w> BundleInserter<'w> { } } /// # Safety - /// `entity` must currently exist in the source archetype for this inserter. `archetype_row` + /// `entity` must currently exist in the source archetype for this inserter. `location` /// must be `entity`'s location in the archetype. `T` must match this [`BundleInfo`]'s type #[inline] pub(crate) unsafe fn insert( diff --git a/crates/bevy_ecs/src/query/builder.rs b/crates/bevy_ecs/src/query/builder.rs deleted file mode 100644 index d1beaa513b55f..0000000000000 --- a/crates/bevy_ecs/src/query/builder.rs +++ /dev/null @@ -1,422 +0,0 @@ -use std::marker::PhantomData; - -use crate::{component::ComponentId, prelude::*}; - -use super::{FilteredAccess, ReadOnlyWorldQuery, WorldQuery}; - -/// Builder struct to create [`QueryState`] instances at runtime. -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # -/// # #[derive(Component)] -/// # struct A; -/// # -/// # #[derive(Component)] -/// # struct B; -/// # -/// # #[derive(Component)] -/// # struct C; -/// # -/// let mut world = World::new(); -/// let entity_a = world.spawn((A, B)).id(); -/// let entity_b = world.spawn((A, C)).id(); -/// -/// // Instantiate the builder using the type signature of the iterator you will consume -/// let mut query = QueryBuilder::<(Entity, &B)>::new(&mut world) -/// // Add additional terms through builder methods -/// .with::() -/// .without::() -/// .build(); -/// -/// // Consume the QueryState -/// let (entity, b) = query.single(&world); -///``` -pub struct QueryBuilder<'w, Q: WorldQuery = (), F: ReadOnlyWorldQuery = ()> { - access: FilteredAccess, - world: &'w mut World, - or: bool, - first: bool, - _marker: PhantomData<(Q, F)>, -} - -impl<'w, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryBuilder<'w, Q, F> { - /// Creates a new builder with the accesses required for `Q` and `F` - pub fn new(world: &'w mut World) -> Self { - let fetch_state = Q::init_state(world); - let filter_state = F::init_state(world); - - let mut access = FilteredAccess::default(); - Q::update_component_access(&fetch_state, &mut access); - - // Use a temporary empty FilteredAccess for filters. This prevents them from conflicting with the - // main Query's `fetch_state` access. Filters are allowed to conflict with the main query fetch - // because they are evaluated *before* a specific reference is constructed. - let mut filter_access = FilteredAccess::default(); - F::update_component_access(&filter_state, &mut filter_access); - - // Merge the temporary filter access with the main access. This ensures that filter access is - // properly considered in a global "cross-query" context (both within systems and across systems). - access.extend(&filter_access); - - Self { - access, - world, - or: false, - first: false, - _marker: PhantomData, - } - } - - /// Returns a reference to the world passed to [`Self::new`]. - pub fn world(&self) -> &World { - self.world - } - - /// Returns a reference to the world passed to [`Self::new`]. - pub fn world_mut(&mut self) -> &mut World { - self.world - } - - /// Adds access to self's underlying [`FilteredAccess`] respecting [`Self::or`] and [`Self::and`] - pub fn extend_access(&mut self, mut access: FilteredAccess) { - if self.or { - if self.first { - access.required.clear(); - self.access.extend(&access); - self.first = false; - } else { - self.access.append_or(&access); - } - } else { - self.access.extend(&access); - } - } - - /// Adds accesses required for `T` to self. - pub fn push(&mut self) -> &mut Self { - let state = T::init_state(self.world); - let mut access = FilteredAccess::default(); - T::update_component_access(&state, &mut access); - self.extend_access(access); - self - } - - /// Adds [`With`] to the [`FilteredAccess`] of self. - pub fn with(&mut self) -> &mut Self { - self.push::>(); - self - } - - /// Adds [`With`] to the [`FilteredAccess`] of self from a runtime [`ComponentId`]. - pub fn with_id(&mut self, id: ComponentId) -> &mut Self { - let mut access = FilteredAccess::default(); - access.and_with(id); - self.extend_access(access); - self - } - - /// Adds [`Without`] to the [`FilteredAccess`] of self. - pub fn without(&mut self) -> &mut Self { - self.push::>(); - self - } - - /// Adds [`Without`] to the [`FilteredAccess`] of self from a runtime [`ComponentId`]. - pub fn without_id(&mut self, id: ComponentId) -> &mut Self { - let mut access = FilteredAccess::default(); - access.and_without(id); - self.extend_access(access); - self - } - - /// Adds `&T` to the [`FilteredAccess`] of self. - pub fn ref_id(&mut self, id: ComponentId) -> &mut Self { - self.with_id(id); - self.access.add_read(id); - self - } - - /// Adds `&mut T` to the [`FilteredAccess`] of self. - pub fn mut_id(&mut self, id: ComponentId) -> &mut Self { - self.with_id(id); - self.access.add_write(id); - self - } - - /// Takes a function over mutable access to a [`QueryBuilder`], calls that function - /// on an empty builder and then adds all accesses from that builder to self as optional. - pub fn optional(&mut self, f: impl Fn(&mut QueryBuilder)) -> &mut Self { - let mut builder = QueryBuilder::new(self.world); - f(&mut builder); - self.access.extend_access(builder.access()); - self - } - - /// Takes a function over mutable access to a [`QueryBuilder`], calls that function - /// on an empty builder and then adds all accesses from that builder to self. - /// - /// Primarily used when inside a [`Self::or`] closure to group several terms. - pub fn and(&mut self, f: impl Fn(&mut QueryBuilder)) -> &mut Self { - let mut builder = QueryBuilder::new(self.world); - f(&mut builder); - let access = builder.access().clone(); - self.extend_access(access); - self - } - - /// Takes a function over mutable access to a [`QueryBuilder`], calls that function - /// on an empty builder, all accesses added to that builder will become terms in an or expression. - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # #[derive(Component)] - /// # struct A; - /// # - /// # #[derive(Component)] - /// # struct B; - /// # - /// # let mut world = World::new(); - /// # - /// QueryBuilder::::new(&mut world).or(|builder| { - /// builder.with::(); - /// builder.with::(); - /// }); - /// // is equivalent to - /// QueryBuilder::::new(&mut world).push::, With)>>(); - /// ``` - pub fn or(&mut self, f: impl Fn(&mut QueryBuilder)) -> &mut Self { - let mut builder = QueryBuilder::new(self.world); - builder.or = true; - builder.first = true; - f(&mut builder); - self.access.extend(builder.access()); - self - } - - /// Returns a reference to the the [`FilteredAccess`] that will be provided to the built [`Query`]. - pub fn access(&self) -> &FilteredAccess { - &self.access - } - - /// Transmute the existing builder adding required accesses. - /// This will maintain all exisiting accesses. - /// - /// If including a filter type see [`Self::transmute_filtered`] - pub fn transmute(&mut self) -> &mut QueryBuilder<'w, NewQ> { - self.transmute_filtered::() - } - - /// Transmute the existing builder adding required accesses. - /// This will maintain all existing accesses. - pub fn transmute_filtered( - &mut self, - ) -> &mut QueryBuilder<'w, NewQ, NewF> { - let mut fetch_state = NewQ::init_state(self.world); - let filter_state = NewF::init_state(self.world); - - NewQ::set_access(&mut fetch_state, &self.access); - - let mut access = FilteredAccess::default(); - NewQ::update_component_access(&fetch_state, &mut access); - NewF::update_component_access(&filter_state, &mut access); - - self.extend_access(access); - // SAFETY: - // - We have included all required acceses for NewQ and NewF - // - The layout of all QueryBuilder instances is the same - unsafe { std::mem::transmute(self) } - } - - /// Create a [`QueryState`] with the accesses of the builder. - pub fn build(&mut self) -> QueryState { - QueryState::::from_builder(self) - } -} - -#[cfg(test)] -mod tests { - use crate as bevy_ecs; - use crate::prelude::*; - use crate::world::FilteredEntityRef; - - use super::QueryBuilder; - - #[derive(Component, PartialEq, Debug)] - struct A(usize); - - #[derive(Component, PartialEq, Debug)] - struct B(usize); - - #[derive(Component, PartialEq, Debug)] - struct C(usize); - - #[test] - fn builder_with_without_static() { - let mut world = World::new(); - let entity_a = world.spawn((A(0), B(0))).id(); - let entity_b = world.spawn((A(0), C(0))).id(); - - let mut query_a = QueryBuilder::::new(&mut world) - .with::() - .without::() - .build(); - assert_eq!(entity_a, query_a.single(&world)); - - let mut query_b = QueryBuilder::::new(&mut world) - .with::() - .without::() - .build(); - assert_eq!(entity_b, query_b.single(&world)); - } - - #[test] - fn builder_with_without_dynamic() { - let mut world = World::new(); - let entity_a = world.spawn((A(0), B(0))).id(); - let entity_b = world.spawn((A(0), C(0))).id(); - let component_id_a = world.init_component::().id(); - let component_id_b = world.init_component::().id(); - let component_id_c = world.init_component::().id(); - - let mut query_a = QueryBuilder::::new(&mut world) - .with_id(component_id_a) - .without_id(component_id_c) - .build(); - assert_eq!(entity_a, query_a.single(&world)); - - let mut query_b = QueryBuilder::::new(&mut world) - .with_id(component_id_a) - .without_id(component_id_b) - .build(); - assert_eq!(entity_b, query_b.single(&world)); - } - - #[test] - fn builder_or() { - let mut world = World::new(); - world.spawn((A(0), B(0))); - world.spawn(B(0)); - world.spawn(C(0)); - - let mut query_a = QueryBuilder::::new(&mut world) - .or(|builder| { - builder.with::(); - builder.with::(); - }) - .build(); - assert_eq!(2, query_a.iter(&world).count()); - - let mut query_b = QueryBuilder::::new(&mut world) - .or(|builder| { - builder.with::(); - builder.without::(); - }) - .build(); - dbg!(&query_b.component_access); - assert_eq!(2, query_b.iter(&world).count()); - - let mut query_c = QueryBuilder::::new(&mut world) - .or(|builder| { - builder.with::(); - builder.with::(); - builder.with::(); - }) - .build(); - assert_eq!(3, query_c.iter(&world).count()); - } - - #[test] - fn builder_transmute() { - let mut world = World::new(); - world.spawn(A(0)); - world.spawn((A(1), B(0))); - let mut query = QueryBuilder::<()>::new(&mut world) - .with::() - .transmute::<&A>() - .build(); - - query.iter(&world).for_each(|a| assert_eq!(a.0, 1)); - } - - #[test] - fn builder_static_components() { - let mut world = World::new(); - let entity = world.spawn((A(0), B(1))).id(); - - let mut query = QueryBuilder::::new(&mut world) - .push::<&A>() - .push::<&B>() - .build(); - - let entity_ref = query.single(&world); - - assert_eq!(entity, entity_ref.id()); - - let a = entity_ref.get::().unwrap(); - let b = entity_ref.get::().unwrap(); - - assert_eq!(0, a.0); - assert_eq!(1, b.0); - } - - #[test] - fn builder_dynamic_components() { - let mut world = World::new(); - let entity = world.spawn((A(0), B(1))).id(); - let component_id_a = world.init_component::().id(); - let component_id_b = world.init_component::().id(); - - let mut query = QueryBuilder::::new(&mut world) - .ref_id(component_id_a) - .ref_id(component_id_b) - .build(); - - let entity_ref = query.single(&world); - - assert_eq!(entity, entity_ref.id()); - - let a = entity_ref.get_by_id(component_id_a).unwrap(); - let b = entity_ref.get_by_id(component_id_b).unwrap(); - - // SAFETY: We set these pointers to point to these components - unsafe { - assert_eq!(0, a.deref::().0); - assert_eq!(1, b.deref::().0); - } - } - - #[test] - fn builder_query_system() { - let mut world = World::new(); - world.spawn(A(0)); - let entity = world.spawn((A(1), B(0))).id(); - - let sys = move |query: Query<(Entity, &A)>| { - let (e, a) = query.single(); - assert_eq!(e, entity); - assert_eq!(1, a.0); - }; - - // Add additional terms that don't appear in the original query - let query = QueryBuilder::<(Entity, &A)>::new(&mut world) - .with::() - .build(); - let mut system = IntoSystem::into_system(sys); - system.initialize(&mut world); - - // SAFETY: We know the system param we are modifying has a compatible type signature - unsafe { system.state_mut().0 = query }; - system.run((), &mut world); - - // Alternatively truncate terms from a query to match the system - let query = QueryBuilder::<(Entity, &A, &B)>::new(&mut world).build(); - let mut system = IntoSystem::into_system(sys); - system.initialize(&mut world); - - // SAFETY: We know the system param we are modifying has a compatible type signature - unsafe { system.state_mut().0 = query.transmute(&world) }; - system.run((), &mut world); - } -} diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index f74c78673bbf7..5a9d7269d1032 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -280,10 +280,6 @@ impl World { .take::>() .ok_or(RegisteredSystemError::Recursive(id))?; - // Flush commands from removing component - // TODO: Consider refactoring to an RegisteredSystem to an Option to avoid removal - drop(entity); - // run the system if !initialized { system.initialize(self); diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 1f688f15e3911..da1d30e6f67d4 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -12,8 +12,8 @@ use crate::{ use super::{unsafe_world_cell::UnsafeWorldCell, Mut, World}; -/// An [`World`] reference that prevents structural ECS changes. -/// This includes creating tables, registering components or spawning entities. +/// A [`World`] reference that disallows structural ECS changes. +/// This includes initializing resources, registering components or spawning entities. pub struct DeferredWorld<'w> { world: UnsafeWorldCell<'w>, } @@ -22,7 +22,7 @@ impl<'w> Deref for DeferredWorld<'w> { type Target = World; fn deref(&self) -> &Self::Target { - // SAFETY: &self ensures ther are no active mutable borrows + // SAFETY: &self ensures there are no active mutable borrows unsafe { self.world.world() } } } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 49a74a0bccf6a..b115c943e09a3 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -4,6 +4,7 @@ use crate::{ change_detection::MutUntyped, component::{Component, ComponentId, ComponentTicks, Components, StorageType}, entity::{Entities, Entity, EntityLocation}, + removal_detection::RemovedComponentEvents, storage::Storages, world::{Mut, World}, }; @@ -712,6 +713,7 @@ impl<'w> EntityWorldMut<'w> { let storages = &mut world.storages; let components = &mut world.components; let entities = &mut world.entities; + let removed_components = &mut world.removed_components; let entity = self.entity; let mut bundle_components = bundle_info.iter_components(); @@ -724,7 +726,14 @@ impl<'w> EntityWorldMut<'w> { // - entity location is valid // - table row is removed below, without dropping the contents // - `components` comes from the same world as `storages` - take_component(storages, components, component_id, entity, old_location) + take_component( + storages, + components, + removed_components, + component_id, + entity, + old_location, + ) }) }; @@ -1632,12 +1641,14 @@ fn sorted_remove(source: &mut Vec, remove: &[T]) { pub(crate) unsafe fn take_component<'a>( storages: &'a mut Storages, components: &Components, + removed_components: &mut RemovedComponentEvents, component_id: ComponentId, entity: Entity, location: EntityLocation, ) -> OwningPtr<'a> { // SAFETY: caller promises component_id to be valid let component_info = components.get_info_unchecked(component_id); + removed_components.send(component_id, entity); match component_info.storage_type() { StorageType::Table => { let table = &mut storages.tables[location.table_id]; diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index d1d46429527d6..ab2fa5cba918b 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1748,8 +1748,7 @@ impl World { } } - /// Attempts to apply the internal [`CommandQueue`] to self - /// Will do nothing if already flushing commands. + /// Applies the internal [`CommandQueue`] to self #[inline] pub fn flush_commands(&mut self) { if !self.command_queue.is_empty() { From 9b490ad74eae3b65fa7d57c82dbaa36da7be2536 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sun, 26 Nov 2023 17:42:10 -0800 Subject: [PATCH 028/109] CI pass --- crates/bevy_ecs/src/bundle.rs | 4 ++-- crates/bevy_ecs/src/component.rs | 2 +- crates/bevy_ecs/src/world/deferred_world.rs | 1 + crates/bevy_ecs/src/world/entity_ref.rs | 4 ++-- crates/bevy_ecs/src/world/mod.rs | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index f8b563bdf9325..67587f18599d7 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -541,7 +541,7 @@ impl<'w> BundleInserter<'w> { /// Creates a new [`BundleInserter`]. /// /// # Safety - /// Caller must ensure that BundleId exists in `world.bundles` + /// Caller must ensure that `bundle_id` exists in `world.bundles` #[inline] pub(crate) unsafe fn new_with_id( world: &'w mut World, @@ -855,7 +855,7 @@ impl<'w> BundleSpawner<'w> { /// Creates a new [`BundleSpawner`]. /// /// # Safety - /// Caller must ensure that BundleId exists in `world.bundles` + /// Caller must ensure that `bundle_id` exists in `world.bundles` #[inline] pub(crate) unsafe fn new_with_id( world: &'w mut World, diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 0ee72faf749be..a798340d5eef9 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -623,7 +623,7 @@ impl Components { /// #[derive(Component)] /// struct ComponentA; /// - /// let component_a_id = world.init_component::().id(); + /// let component_a_id = world.init_component::(); /// /// assert_eq!(component_a_id, world.components().component_id::().unwrap()) /// ``` diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index da1d30e6f67d4..e3e241a6d43a3 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -31,6 +31,7 @@ impl<'w> DeferredWorld<'w> { /// Creates a [`Commands`] instance that pushes to the world's command queue #[inline] pub fn with_commands(&mut self, f: impl Fn(Commands)) { + // SAFETY: We have exclusive access to the world's command queue let world = unsafe { self.world.world_mut() }; let mut queue = std::mem::take(&mut world.command_queue); f(Commands::new(&mut queue, world)); diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index b115c943e09a3..187effc55d88d 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -691,9 +691,9 @@ impl<'w> EntityWorldMut<'w> { let old_archetype = &world.archetypes[old_location.archetype_id]; // Drop borrow on world so it can be turned into DeferredWorld - // SAFETY: Changes to Bundles cannot happen through DeferredWorld let bundle_info = { let bundle_info: *const BundleInfo = bundle_info; + // SAFETY: Changes to Bundles cannot happen through DeferredWorld unsafe { &*bundle_info } }; if old_archetype.has_on_remove() { @@ -869,9 +869,9 @@ impl<'w> EntityWorldMut<'w> { let old_archetype = &world.archetypes[old_location.archetype_id]; // Drop borrow on world so it can be turned into DeferredWorld - // SAFETY: Changes to Bundles cannot happen through DeferredWorld let bundle_info = { let bundle_info: *const BundleInfo = bundle_info; + // SAFETY: Changes to Bundles cannot happen through DeferredWorld unsafe { &*bundle_info } }; if old_archetype.has_on_remove() { diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index ab2fa5cba918b..c5e08a1c69fbe 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -244,7 +244,7 @@ impl World { /// #[derive(Component)] /// struct ComponentA; /// - /// let component_a_id = world.init_component::().id(); + /// let component_a_id = world.init_component::(); /// /// assert_eq!(component_a_id, world.component_id::().unwrap()) /// ``` From b7e5462be3c736ea6dc9ab02d51ab0c80f5c6333 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sun, 26 Nov 2023 17:57:56 -0800 Subject: [PATCH 029/109] Re-generate README.md --- crates/bevy_ecs/src/bundle.rs | 4 ++-- examples/README.md | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 67587f18599d7..b61b55983f8ce 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -361,10 +361,10 @@ impl BundleInfo { /// in the [`Bundle`], with respect to the entity's original archetype (prior to the bundle being added) /// For example, if the original archetype already has `ComponentA` and `T` also has `ComponentA`, the status /// should be `Mutated`. If the original archetype does not have `ComponentA`, the status should be `Added`. - /// When "inserting" a bundle into an existing entity, [`AddBundle`](crate::archetype::AddBundle) + /// When "inserting" a bundle into an existing entity, [`AddBundle`] /// should be used, which will report `Added` vs `Mutated` status based on the current archetype's structure. /// When spawning a bundle, [`SpawnBundleStatus`] can be used instead, which removes the need - /// to look up the [`AddBundle`](crate::archetype::AddBundle) in the archetype graph, which requires + /// to look up the [`AddBundle`] in the archetype graph, which requires /// ownership of the entity's current archetype. /// /// `table` must be the "new" table for `entity`. `table_row` must have space allocated for the diff --git a/examples/README.md b/examples/README.md index 0450ef88159b8..fa7b2cc7f727c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -219,6 +219,7 @@ Example | Description --- | --- [Apply System Buffers](../examples/ecs/apply_deferred.rs) | Show how to use `apply_deferred` system [Component Change Detection](../examples/ecs/component_change_detection.rs) | Change detection on components +[Component Hooks](../examples/ecs/component_hooks.rs) | Define component hooks to manage component lifecycle events [Custom Query Parameters](../examples/ecs/custom_query_param.rs) | Groups commonly used compound queries and query filters into a single type [ECS Guide](../examples/ecs/ecs_guide.rs) | Full guide to Bevy's ECS [Event](../examples/ecs/event.rs) | Illustrates event creation, activation, and reception From a4d164a330c7598a80d72fff6490bedd65640b40 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sun, 26 Nov 2023 18:08:13 -0800 Subject: [PATCH 030/109] Add missing safety comment --- crates/bevy_ecs/src/world/entity_ref.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 187effc55d88d..93d47d3e7ae8d 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -925,9 +925,9 @@ impl<'w> EntityWorldMut<'w> { let archetype = &world.archetypes[self.location.archetype_id]; if archetype.has_on_remove() { // Drop borrow on world so it can be turned into DeferredWorld - // SAFETY: Changes to Archetypes cannot happpen through DeferredWorld let archetype = { let archetype: *const Archetype = archetype; + // SAFETY: Changes to Archetypes cannot happpen through DeferredWorld unsafe { &*archetype } }; // SAFETY: All components in the archetype exist in world @@ -1000,7 +1000,7 @@ impl<'w> EntityWorldMut<'w> { world.archetypes[moved_location.archetype_id] .set_entity_table_row(moved_location.archetype_row, table_row); } - world.flush_commands() + world.flush_commands(); } /// Ensures any commands triggered by the actions of Self are applied, equivalent to [`World::flush_commands`] From 78207c3bc0d1282c82cc0c4ea46a0a5f707d74d2 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sun, 26 Nov 2023 18:13:48 -0800 Subject: [PATCH 031/109] Simplify asserts --- crates/bevy_ecs/src/bundle.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index b61b55983f8ce..9e989b3c45e43 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -1209,8 +1209,8 @@ mod tests { let entity = world.spawn(A).flush(); let entity = world.get_entity(entity).unwrap(); - assert_eq!(false, entity.contains::()); - assert_eq!(false, entity.contains::()); + assert!(!entity.contains::()); + assert!(!entity.contains::()); assert_eq!(4, world.resource::().0); } From a7b5f281db4f8a743e59131156e913cf400faa39 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Mon, 27 Nov 2023 09:17:30 -0800 Subject: [PATCH 032/109] Relax restrictions on register_component --- crates/bevy_ecs/src/component.rs | 21 +++++++++++++++++++++ crates/bevy_ecs/src/world/mod.rs | 7 ++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index a798340d5eef9..7a3b40545ddb1 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -283,19 +283,40 @@ impl ComponentInfo { } /// Register a [`ComponentHook`] that will be run when this component is added to an entity + /// + /// Will panic if the component already has an `on_add` hook pub fn on_add(&mut self, hook: ComponentHook) -> &mut Self { + assert!( + self.hooks.on_add.is_none(), + "Component id: {:?}, already has an on_add hook", + self.id() + ); self.hooks.on_add = Some(hook); self } /// Register a [`ComponentHook`] that will be run when this component is added or set by `.insert` + /// + /// Will panic if the component already has an `on_insert` hook pub fn on_insert(&mut self, hook: ComponentHook) -> &mut Self { + assert!( + self.hooks.on_insert.is_none(), + "Component id: {:?}, already has an on_insert hook", + self.id() + ); self.hooks.on_insert = Some(hook); self } /// Register a [`ComponentHook`] that will be run when this component is removed from an entity. + /// + /// Will panic if the component already has an `on_remove` hook pub fn on_remove(&mut self, hook: ComponentHook) -> &mut Self { + assert!( + self.hooks.on_remove.is_none(), + "Component id: {:?}, already has an on_remove hook", + self.id() + ); self.hooks.on_remove = Some(hook); self } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index c5e08a1c69fbe..a78bf059608df 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -189,11 +189,10 @@ impl World { /// Initializes a new [`Component`] type and returns a mutable reference to the [`ComponentInfo`] created for it. /// Primarily used for registering hooks. /// - /// Will panic if a component for `T` already exists. + /// Will panic if `T` exists in any archetypes. pub fn register_component(&mut self) -> &mut ComponentInfo { - let type_id = TypeId::of::(); - assert!(self.components.get_id(type_id).is_none(), "Components cannot be registered twice, use init_component instead if this component may already exist in the world."); let index = self.init_component::(); + assert!(!self.archetypes.archetypes.iter().any(|a| a.contains(index)), "Components cannot be registered if they already exist in an archetype, use init_component if {} may already be in use", std::any::type_name::()); // SAFETY: We just created this component unsafe { self.components.get_info_mut(index) } } @@ -217,8 +216,6 @@ impl World { /// Initializes a new [`Component`] type and returns a mutable reference to the [`ComponentInfo`] created for it. /// Primarily used for registering hooks. - /// - /// Will panic if a component for `T` already exists. pub fn register_component_with_descriptor( &mut self, descriptor: ComponentDescriptor, From 7ab60b9a16b0e960533ba9cdc4768bb469c65dca Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Mon, 27 Nov 2023 14:29:29 -0800 Subject: [PATCH 033/109] Update example comments --- examples/ecs/component_hooks.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs index 78deb6f2f5966..5158e1497d82b 100644 --- a/examples/ecs/component_hooks.rs +++ b/examples/ecs/component_hooks.rs @@ -23,8 +23,10 @@ fn main() { } fn setup(world: &mut World) { - // In order to register component hooks it must be the first time you register the component - // This is to prevent users from overwriting hooks that may be used internally in foreign crates + // In order to register component hooks the component must: + // - not belong to any created archetypes + // - not already have a hook of that kind registered + // This is to prevent overriding hooks defined in plugins and other crates as well as keeping things fast world .register_component::() // There are 3 component lifecyle hooks: `on_add`, `on_insert` and `on_remove` From ae2688610ed69f7e5cb4c7b34a0ff7869f9bfb96 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Wed, 29 Nov 2023 16:56:39 -0800 Subject: [PATCH 034/109] API improvements, simplify some usages of unsafe --- crates/bevy_ecs/src/bundle.rs | 23 ++------ crates/bevy_ecs/src/world/deferred_world.rs | 9 +-- crates/bevy_ecs/src/world/entity_ref.rs | 61 ++++++++++----------- crates/bevy_ecs/src/world/mod.rs | 9 ++- examples/ecs/component_hooks.rs | 6 +- 5 files changed, 48 insertions(+), 60 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 9e989b3c45e43..7f58f1ef6edab 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -1181,27 +1181,18 @@ mod tests { .register_component::() .on_add(|mut world, entity, _| { world.resource_mut::().assert_order(0); - - world.with_commands(|mut commands| { - commands.entity(entity).insert(B); - }); + world.commands().entity(entity).insert(B); }) .on_remove(|mut world, entity, _| { world.resource_mut::().assert_order(2); - - world.with_commands(|mut commands| { - commands.entity(entity).remove::(); - }); + world.commands().entity(entity).remove::(); }); world .register_component::() .on_add(|mut world, entity, _| { world.resource_mut::().assert_order(1); - - world.with_commands(|mut commands| { - commands.entity(entity).remove::(); - }); + world.commands().entity(entity).remove::(); }) .on_remove(|mut world, _, _| { world.resource_mut::().assert_order(3); @@ -1222,18 +1213,14 @@ mod tests { .register_component::() .on_add(|mut world, entity, _| { world.resource_mut::().assert_order(0); - world.with_commands(|mut commands| { - commands.entity(entity).insert(B).insert(D); - }); + world.commands().entity(entity).insert(B).insert(D); }); world .register_component::() .on_add(|mut world, entity, _| { world.resource_mut::().assert_order(1); - world.with_commands(|mut commands| { - commands.entity(entity).insert(C); - }); + world.commands().entity(entity).insert(C); }); world.register_component::().on_add(|mut world, _, _| { diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index e3e241a6d43a3..85613b6fb5327 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -30,12 +30,9 @@ impl<'w> Deref for DeferredWorld<'w> { impl<'w> DeferredWorld<'w> { /// Creates a [`Commands`] instance that pushes to the world's command queue #[inline] - pub fn with_commands(&mut self, f: impl Fn(Commands)) { - // SAFETY: We have exclusive access to the world's command queue - let world = unsafe { self.world.world_mut() }; - let mut queue = std::mem::take(&mut world.command_queue); - f(Commands::new(&mut queue, world)); - world.command_queue = std::mem::take(&mut queue); + pub fn commands(&mut self) -> Commands { + // SAFETY: Commands cannot make structural changes. + unsafe { self.world.world_mut().commands() } } /// Retrieves a mutable reference to the given `entity`'s [`Component`] of the given type. diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 93d47d3e7ae8d..a8db8697b89f3 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -688,21 +688,21 @@ impl<'w> EntityWorldMut<'w> { } let entity = self.entity; - let old_archetype = &world.archetypes[old_location.archetype_id]; - - // Drop borrow on world so it can be turned into DeferredWorld - let bundle_info = { + // SAFETY: Archetypes and Bundles cannot be mutably aliased through DeferredWorld + let (old_archetype, bundle_info, mut deferred_world) = unsafe { let bundle_info: *const BundleInfo = bundle_info; - // SAFETY: Changes to Bundles cannot happen through DeferredWorld - unsafe { &*bundle_info } + let world = world.as_unsafe_world_cell(); + ( + &world.archetypes()[old_location.archetype_id], + &*bundle_info, + world.into_deferred(), + ) }; + if old_archetype.has_on_remove() { // SAFETY: All components in the archetype exist in world unsafe { - world - .as_unsafe_world_cell() - .into_deferred() - .trigger_on_remove(entity, bundle_info.iter_components()); + deferred_world.trigger_on_remove(entity, bundle_info.iter_components()); } } @@ -865,22 +865,22 @@ impl<'w> EntityWorldMut<'w> { return self; } - let entity: Entity = self.entity; - let old_archetype = &world.archetypes[old_location.archetype_id]; - - // Drop borrow on world so it can be turned into DeferredWorld - let bundle_info = { + let entity = self.entity; + // SAFETY: Archetypes and Bundles cannot be mutably aliased through DeferredWorld + let (old_archetype, bundle_info, mut deferred_world) = unsafe { let bundle_info: *const BundleInfo = bundle_info; - // SAFETY: Changes to Bundles cannot happen through DeferredWorld - unsafe { &*bundle_info } + let world = world.as_unsafe_world_cell(); + ( + &world.archetypes()[old_location.archetype_id], + &*bundle_info, + world.into_deferred(), + ) }; + if old_archetype.has_on_remove() { // SAFETY: All components in the archetype exist in world unsafe { - world - .as_unsafe_world_cell() - .into_deferred() - .trigger_on_remove(entity, bundle_info.iter_components()); + deferred_world.trigger_on_remove(entity, bundle_info.iter_components()); } } @@ -923,19 +923,18 @@ impl<'w> EntityWorldMut<'w> { let world = self.world; world.flush_entities(); let archetype = &world.archetypes[self.location.archetype_id]; + + // SAFETY: Archetype cannot be mutably aliased by DeferredWorld + let (archetype, mut deferred_world) = unsafe { + let archetype: *const Archetype = archetype; + let world = world.as_unsafe_world_cell(); + (&*archetype, world.into_deferred()) + }; + if archetype.has_on_remove() { - // Drop borrow on world so it can be turned into DeferredWorld - let archetype = { - let archetype: *const Archetype = archetype; - // SAFETY: Changes to Archetypes cannot happpen through DeferredWorld - unsafe { &*archetype } - }; // SAFETY: All components in the archetype exist in world unsafe { - world - .as_unsafe_world_cell() - .into_deferred() - .trigger_on_remove(self.entity, archetype.components()); + deferred_world.trigger_on_remove(self.entity, archetype.components()); } } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index a78bf059608df..e19a71aebd0e5 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -24,7 +24,7 @@ use crate::{ removal_detection::RemovedComponentEvents, schedule::{Schedule, ScheduleLabel, Schedules}, storage::{ResourceData, Storages}, - system::{CommandQueue, Resource}, + system::{CommandQueue, Commands, Resource}, world::error::TryRunScheduleError, }; use bevy_ptr::{OwningPtr, Ptr}; @@ -181,6 +181,13 @@ impl World { WorldCell::new(self) } + /// Creates a new [`Commands`] instance that writes to the world's command queue + /// Use [`World::flush_commands`] to apply all queued commands + #[inline] + pub fn commands(&mut self) -> Commands { + Commands::new_from_entities(&mut self.command_queue, &self.entities) + } + /// Initializes a new [`Component`] type and returns the [`ComponentId`] created for it. pub fn init_component(&mut self) -> ComponentId { self.components.init_component::(&mut self.storages) diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs index 5158e1497d82b..119af24d42754 100644 --- a/examples/ecs/component_hooks.rs +++ b/examples/ecs/component_hooks.rs @@ -64,10 +64,8 @@ fn setup(world: &mut World) { component_id, entity, value ); world.resource_mut::().remove(&value); - // You can also issue commands through `.with_commands` - world.with_commands(|mut commands| { - commands.entity(entity).despawn(); - }); + // You can also issue commands through `.commands()` + world.commands().entity(entity).despawn(); }); } From b61f62ae235481210e891d6ece3890916edc3715 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Wed, 29 Nov 2023 17:13:24 -0800 Subject: [PATCH 035/109] Rebase main. --- crates/bevy_ecs/src/system/commands/command_queue.rs | 4 +++- crates/bevy_ecs/src/world/deferred_world.rs | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/system/commands/command_queue.rs b/crates/bevy_ecs/src/system/commands/command_queue.rs index f5735fc77c156..37826d30af3cd 100644 --- a/crates/bevy_ecs/src/system/commands/command_queue.rs +++ b/crates/bevy_ecs/src/system/commands/command_queue.rs @@ -151,7 +151,9 @@ impl CommandQueue { // or 1 byte past the end, so this addition will not overflow the pointer's allocation. cursor = unsafe { cursor.add(size) }; - world.flush_commands(); + if let Some(world) = &mut world { + world.flush_commands(); + } } } diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 85613b6fb5327..f17800dbdf6c0 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -6,7 +6,7 @@ use crate::{ entity::Entity, event::{Event, EventId, Events, SendBatchIds}, prelude::{Component, QueryState}, - query::{ReadOnlyWorldQuery, WorldQuery}, + query::{WorldQueryData, WorldQueryFilter}, system::{Commands, Query, Resource}, }; @@ -52,7 +52,7 @@ impl<'w> DeferredWorld<'w> { /// # Panics /// If state is from a different world then self #[inline] - pub fn query<'s, Q: WorldQuery, F: ReadOnlyWorldQuery>( + pub fn query<'s, Q: WorldQueryData, F: WorldQueryFilter>( &'w mut self, state: &'s mut QueryState, ) -> Query<'w, 's, Q, F> { From 388036f5f00f519a1c9f40903c209b10f2d9f7d5 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Thu, 30 Nov 2023 17:35:24 -0800 Subject: [PATCH 036/109] More complete example --- crates/bevy_ecs/src/bundle.rs | 46 +++++-- crates/bevy_ecs/src/observer.rs | 128 +++++++++++++------- crates/bevy_ecs/src/system/commands/mod.rs | 70 +++++++++++ crates/bevy_ecs/src/world/deferred_world.rs | 10 +- crates/bevy_ecs/src/world/entity_ref.rs | 15 ++- examples/ecs/observers.rs | 78 +++++++----- 6 files changed, 257 insertions(+), 90 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index b15a0ca67c4c3..1e53d1b8ca0fe 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -662,11 +662,21 @@ impl<'w> BundleInserter<'w> { let mut world = self.world.into_deferred(); world.trigger_on_add(archetype, entity, add_bundle.added.iter().cloned()); if archetype.has_add_observer() { - world.trigger_observers(ON_ADD, entity, add_bundle.added.iter().cloned()); + world.trigger_observers( + ON_ADD, + entity, + location, + add_bundle.added.iter().cloned(), + ); } world.trigger_on_insert(archetype, entity, bundle_info.iter_components()); if archetype.has_insert_observer() { - world.trigger_observers(ON_INSERT, entity, bundle_info.iter_components()); + world.trigger_observers( + ON_INSERT, + entity, + location, + bundle_info.iter_components(), + ); } location @@ -716,11 +726,21 @@ impl<'w> BundleInserter<'w> { let mut world = self.world.into_deferred(); world.trigger_on_add(new_archetype, entity, add_bundle.added.iter().cloned()); if new_archetype.has_add_observer() { - world.trigger_observers(ON_ADD, entity, add_bundle.added.iter().cloned()); + world.trigger_observers( + ON_ADD, + entity, + new_location, + add_bundle.added.iter().cloned(), + ); } world.trigger_on_insert(new_archetype, entity, bundle_info.iter_components()); if new_archetype.has_insert_observer() { - world.trigger_observers(ON_INSERT, entity, bundle_info.iter_components()); + world.trigger_observers( + ON_INSERT, + entity, + new_location, + bundle_info.iter_components(), + ); } new_location } @@ -808,11 +828,21 @@ impl<'w> BundleInserter<'w> { let mut world = self.world.into_deferred(); world.trigger_on_add(new_archetype, entity, add_bundle.added.iter().cloned()); if new_archetype.has_add_observer() { - world.trigger_observers(ON_ADD, entity, add_bundle.added.iter().cloned()) + world.trigger_observers( + ON_ADD, + entity, + new_location, + add_bundle.added.iter().cloned(), + ) } world.trigger_on_insert(new_archetype, entity, bundle_info.iter_components()); if new_archetype.has_insert_observer() { - world.trigger_observers(ON_INSERT, entity, bundle_info.iter_components()) + world.trigger_observers( + ON_INSERT, + entity, + new_location, + bundle_info.iter_components(), + ) } new_location } @@ -920,11 +950,11 @@ impl<'w> BundleSpawner<'w> { let mut world = self.world.into_deferred(); world.trigger_on_add(archetype, entity, bundle_info.iter_components()); if archetype.has_add_observer() { - world.trigger_observers(ON_ADD, entity, bundle_info.iter_components()) + world.trigger_observers(ON_ADD, entity, location, bundle_info.iter_components()) } world.trigger_on_insert(archetype, entity, bundle_info.iter_components()); if archetype.has_insert_observer() { - world.trigger_observers(ON_INSERT, entity, bundle_info.iter_components()) + world.trigger_observers(ON_INSERT, entity, location, bundle_info.iter_components()) } location } diff --git a/crates/bevy_ecs/src/observer.rs b/crates/bevy_ecs/src/observer.rs index 32abd1ddf18c7..a01559066fe4c 100644 --- a/crates/bevy_ecs/src/observer.rs +++ b/crates/bevy_ecs/src/observer.rs @@ -5,8 +5,9 @@ use std::{any::TypeId, marker::PhantomData}; use crate::{ self as bevy_ecs, archetype::{ArchetypeFlags, Archetypes}, - query::{DebugCheckedUnwrap, FilteredAccess, WorldQueryData}, - system::Insert, + entity::EntityLocation, + query::{DebugCheckedUnwrap, FilteredAccess, WorldQuery, WorldQueryData}, + system::{EmitEcsEvent, Insert}, world::*, }; @@ -17,7 +18,7 @@ use crate::{component::ComponentId, prelude::*, query::WorldQueryFilter, world:: pub struct Observer<'w, E, Q: WorldQueryData, F: WorldQueryFilter = ()> { world: DeferredWorld<'w>, - state: &'w mut ObserverState, + state: &'w ObserverState, data: &'w mut E, trigger: ObserverTrigger, } @@ -41,7 +42,24 @@ impl<'w, E, Q: WorldQueryData, F: WorldQueryFilter> Observer<'w, E, Q, F> { self.trigger.event } - pub fn fetch(&mut self) -> Q::Item<'_> { + pub fn fetch(&self) -> ::Item<'_> { + let location = self.world.entities.get(self.trigger.source).unwrap(); + let world = self.world.as_unsafe_world_cell_readonly(); + unsafe { + let mut fetch = Q::ReadOnly::init_fetch( + world, + &self.state.fetch_state, + world.last_change_tick(), + world.change_tick(), + ); + let archetype = world.archetypes().get(location.archetype_id).unwrap(); + let table = world.storages().tables.get(location.table_id).unwrap(); + Q::ReadOnly::set_archetype(&mut fetch, &self.state.fetch_state, archetype, table); + Q::ReadOnly::fetch(&mut fetch, self.trigger.source, location.table_row) + } + } + + pub fn fetch_mut(&mut self) -> Q::Item<'_> { let location = self.world.entities.get(self.trigger.source).unwrap(); let world = self.world.as_unsafe_world_cell(); unsafe { @@ -76,7 +94,7 @@ impl<'w, E, Q: WorldQueryData, F: WorldQueryFilter> Observer<'w, E, Q, F> { } #[derive(Component)] -struct ObserverState { +pub(crate) struct ObserverState { fetch_state: Q::State, filter_state: F::State, component_access: FilteredAccess, @@ -143,7 +161,6 @@ impl<'w, E: EcsEvent> ObserverBuilder<'w, E> { // Allows listening for multiple types of events but without passing typed data pub fn on_event(&mut self) -> &mut ObserverBuilder<'w, NoEvent> { - let type_id = TypeId::of::(); let event = self.world.init_component::(); self.descriptor.events.push(event); // SAFETY: () type will not allow bad memory access as it has no size @@ -185,7 +202,7 @@ impl<'w, E: EcsEvent> ObserverBuilder<'w, E> { &mut self, callback: fn(Observer), ) -> Entity { - let entity = self.world.spawn_observer(&self.descriptor, callback); + let entity = self.enqueue(callback); self.world.flush_commands(); entity } @@ -200,6 +217,7 @@ impl<'w, E: EcsEvent> ObserverBuilder<'w, E> { pub struct ObserverTrigger { observer: Entity, + location: EntityLocation, event: ComponentId, source: Entity, } @@ -227,24 +245,50 @@ impl ObserverComponent { run: |mut world, trigger, ptr, callback| { let callback: fn(Observer) = unsafe { std::mem::transmute(callback.debug_checked_unwrap()) }; - // let last_event = world.last_event_id; - let mut state = unsafe { - world + let state = unsafe { + let mut state = world .get_mut::>(trigger.observer) - .debug_checked_unwrap() + .debug_checked_unwrap(); + let state: *mut ObserverState = state.as_mut(); + &mut *state }; - // if state.last_event_id == last_event { - // return; - // } + // This being stored in a component is not ideal, should be able to check this before fetching + let last_event = world.last_event_id; + if state.last_event_id == last_event { + return; + } + state.last_event_id = last_event; + + let archetype_id = trigger.location.archetype_id; + let archetype = &world.archetypes()[archetype_id]; + if !Q::matches_component_set(&state.fetch_state, &mut |id| { + archetype.contains(id) + }) || !F::matches_component_set(&state.filter_state, &mut |id| { + archetype.contains(id) + }) { + return; + } + + // TODO: Change ticks? + unsafe { + let mut filter_fetch = F::init_fetch( + world.as_unsafe_world_cell_readonly(), + &state.filter_state, + world.last_change_tick(), + world.read_change_tick(), + ); + + if !F::filter_fetch( + &mut filter_fetch, + trigger.source, + trigger.location.table_row, + ) { + return; + } + } - let state: *mut ObserverState = state.as_mut(); // SAFETY: Pointer is valid as we just created it, ObserverState is a private type and so will not be aliased - let observer = Observer::new( - world, - unsafe { &mut *state }, - unsafe { ptr.deref_mut() }, - trigger, - ); + let observer = Observer::new(world, state, unsafe { ptr.deref_mut() }, trigger); callback(observer); }, callback: Some(unsafe { std::mem::transmute(value) }), @@ -254,7 +298,7 @@ impl ObserverComponent { } #[derive(Default, Debug)] -struct CachedObservers { +pub(crate) struct CachedObservers { component_observers: HashMap>, entity_observers: EntityHashMap>, } @@ -361,6 +405,7 @@ impl Observers { &self, event: ComponentId, source: Entity, + location: EntityLocation, components: impl Iterator, mut world: DeferredWorld, data: &mut E, @@ -371,10 +416,11 @@ impl Observers { if let Some(observers) = observers.entity_observers.get(&source) { observers.iter().for_each(|(&observer, runner)| { (runner.run)( - world.clone(), + world.reborrow(), ObserverTrigger { observer, event, + location, source, }, data.into(), @@ -386,10 +432,11 @@ impl Observers { if let Some(observers) = observers.component_observers.get(&component) { observers.iter().for_each(|(&observer, runner)| { (runner.run)( - world.clone(), + world.reborrow(), ObserverTrigger { observer, event, + location, source, }, data.into(), @@ -447,28 +494,27 @@ pub struct OnRemove; pub struct NoEvent; #[derive(Component)] +#[component(storage = "SparseSet")] pub(crate) struct AttachObserver(pub(crate) Entity); #[derive(Component, Default)] +#[component(storage = "SparseSet")] pub(crate) struct ObservedBy(Vec); pub struct EventBuilder<'w, E> { - event: ComponentId, - world: &'w mut World, - data: E, + commands: Commands<'w, 'w>, targets: Vec, components: Vec, + data: Option, } impl<'w, E: EcsEvent> EventBuilder<'w, E> { - pub fn new(world: &'w mut World, data: E) -> Self { - let event = world.init_component::(); + pub fn new(data: E, commands: Commands<'w, 'w>) -> Self { Self { - event, - world, - data, + commands, targets: Vec::new(), components: Vec::new(), + data: Some(data), } } @@ -478,17 +524,11 @@ impl<'w, E: EcsEvent> EventBuilder<'w, E> { } pub fn emit(&mut self) { - let mut world = unsafe { self.world.as_unsafe_world_cell().into_deferred() }; - for &target in &self.targets { - unsafe { - world.trigger_observers_with_data( - self.event, - target, - self.components.iter().cloned(), - &mut self.data, - ) - } - } + self.commands.add(EmitEcsEvent:: { + data: std::mem::take(&mut self.data).unwrap(), + targets: std::mem::take(&mut self.targets), + components: std::mem::take(&mut self.components), + }) } } @@ -567,7 +607,7 @@ impl World { } pub fn ecs_event(&mut self, event: E) -> EventBuilder { - EventBuilder::new(self, event) + EventBuilder::new(event, self.commands()) } pub(crate) fn spawn_observer< diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index c37d223e51991..17468d62c377b 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -4,7 +4,11 @@ mod parallel_scope; use crate::{ self as bevy_ecs, bundle::Bundle, + component::ComponentId, entity::{Entities, Entity}, + observer::{EcsEvent, EventBuilder}, + prelude::Observer, + query::{WorldQueryData, WorldQueryFilter}, system::{RunSystemWithInput, SystemId}, world::{EntityWorldMut, FromWorld, World}, }; @@ -586,6 +590,17 @@ impl<'w, 's> Commands<'w, 's> { pub fn add(&mut self, command: C) { self.queue.push(command); } + + pub fn ecs_event(&mut self, data: E) -> EventBuilder { + EventBuilder::new(data, self.reborrow()) + } + + pub fn reborrow(&mut self) -> Commands { + Commands { + queue: Deferred(self.queue.0), + entities: self.entities, + } + } } /// A [`Command`] which gets executed for a given [`Entity`]. @@ -923,6 +938,17 @@ impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> { pub fn commands(&mut self) -> &mut Commands<'w, 's> { self.commands } + + pub fn observe( + &mut self, + callback: fn(Observer), + ) -> &mut Self { + self.commands.add(Observe:: { + entity: self.entity, + callback, + }); + self + } } impl Command for F @@ -1166,6 +1192,50 @@ impl Command for LogComponents { } } +/// A [`Command`] that spawns an observer attached to a specific entity. +#[derive(Debug)] +pub struct Observe { + /// The entity that will be observed. + pub entity: Entity, + pub callback: fn(Observer), +} + +impl Command + for Observe +{ + fn apply(self, world: &mut World) { + world.entity_mut(self.entity).observe(self.callback); + } +} + +/// A [`Command`] that emits an event to be received by observers. +#[derive(Debug)] +pub struct EmitEcsEvent { + pub data: E, + pub components: Vec, + pub targets: Vec, +} + +impl Command for EmitEcsEvent { + fn apply(mut self, world: &mut World) { + let event = world.init_component::(); + let mut world = unsafe { world.as_unsafe_world_cell().into_deferred() }; + for &target in &self.targets { + if let Some(location) = world.entities().get(target) { + unsafe { + world.trigger_observers_with_data( + event, + target, + location, + self.components.iter().cloned(), + &mut self.data, + ) + } + } + } + } +} + #[cfg(test)] #[allow(clippy::float_cmp, clippy::approx_constant)] mod tests { diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 02db821303e4c..4b7c4e4b1644a 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -4,7 +4,7 @@ use crate::{ archetype::Archetype, change_detection::MutUntyped, component::ComponentId, - entity::Entity, + entity::{Entity, EntityLocation}, event::{Event, EventId, Events, SendBatchIds}, prelude::{Component, QueryState}, query::{WorldQueryData, WorldQueryFilter}, @@ -33,7 +33,7 @@ impl<'w> Deref for DeferredWorld<'w> { impl<'w> DeferredWorld<'w> { #[inline] - pub fn clone(&mut self) -> DeferredWorld { + pub fn reborrow(&mut self) -> DeferredWorld { DeferredWorld { world: self.world } } @@ -320,6 +320,7 @@ impl<'w> DeferredWorld<'w> { &mut self, event: ComponentId, target: Entity, + location: EntityLocation, components: impl Iterator, ) { let (world, observers) = unsafe { @@ -327,7 +328,7 @@ impl<'w> DeferredWorld<'w> { world.world_mut().last_event_id += 1; (world.into_deferred(), &world.world().observers) }; - observers.invoke(event, target, components, world, &mut ()); + observers.invoke(event, target, location, components, world, &mut ()); } /// Triggers all event observers for [`ComponentId`] in target. @@ -339,6 +340,7 @@ impl<'w> DeferredWorld<'w> { &mut self, event: ComponentId, target: Entity, + location: EntityLocation, components: impl Iterator, data: &mut E, ) { @@ -347,7 +349,7 @@ impl<'w> DeferredWorld<'w> { world.world_mut().last_event_id += 1; (world.into_deferred(), &world.world().observers) }; - observers.invoke(event, target, components, world, data); + observers.invoke(event, target, location, components, world, data); } #[inline] diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 3d8133c99f317..5172b78235ba4 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -710,6 +710,7 @@ impl<'w> EntityWorldMut<'w> { deferred_world.trigger_observers( ON_REMOVE, self.entity, + self.location, bundle_info.iter_components(), ) } @@ -891,7 +892,12 @@ impl<'w> EntityWorldMut<'w> { unsafe { deferred_world.trigger_on_remove(old_archetype, entity, bundle_info.iter_components()); if old_archetype.has_remove_observer() { - deferred_world.trigger_observers(ON_REMOVE, entity, bundle_info.iter_components()) + deferred_world.trigger_observers( + ON_REMOVE, + entity, + self.location, + bundle_info.iter_components(), + ) } } @@ -946,7 +952,12 @@ impl<'w> EntityWorldMut<'w> { unsafe { deferred_world.trigger_on_remove(archetype, self.entity, archetype.components()); if archetype.has_remove_observer() { - deferred_world.trigger_observers(ON_REMOVE, self.entity, archetype.components()) + deferred_world.trigger_observers( + ON_REMOVE, + self.entity, + self.location, + archetype.components(), + ) } } diff --git a/examples/ecs/observers.rs b/examples/ecs/observers.rs index f2f785078af05..a3a1a4b41a0eb 100644 --- a/examples/ecs/observers.rs +++ b/examples/ecs/observers.rs @@ -3,51 +3,65 @@ use bevy::prelude::*; #[derive(Component, Debug)] -struct MyComponent(usize); +struct CompA(Entity); -#[derive(Component)] -struct MyEvent(usize); +#[derive(Component, Debug)] +struct CompB; + +#[derive(Component, Debug)] +struct Resize(u64, u64); #[derive(Resource, Default)] -struct MyResource(usize); +struct ResizeCount(usize); fn main() { App::new().add_systems(Startup, setup).run(); } fn setup(world: &mut World) { - world.init_resource::(); - - // Responds to all added instances of MyComponent (or any WorldQuery/Filter) - world.observer(|mut observer: Observer| { - let mut resource = observer.world_mut().resource_mut::(); - resource.0 += 1; - - let count = resource.0; - let my_component = observer.fetch().0; - println!( - "Added: {:?} to entity: {:?}, count: {:?}", - my_component, - observer.source(), - count - ); - }); + world.init_resource::(); - let entity_a = world.spawn(MyComponent(0)).flush(); + // Triggered when &ComponentA is added to any component that also has ComponentB + let observer = world.observer(|mut observer: Observer>| { + // Get source entity that triggered the observer + let source = observer.source(); + // Able to read requested component data as if it was a query + let data = observer.fetch().0; + // Access to all resources and components through DeferredWorld + let world = observer.world_mut(); + // Can submit commands for any structural changes + world.commands().entity(source).remove::(); + // Or to raise other events + world.commands().ecs_event(Resize(2, 4)).target(data).emit(); + }); - // Responds to MyEvent events targeting this entity - let entity_b = world - .spawn(MyComponent(1)) - .observe(|mut observer: Observer| { - let data = observer.data().0; - let mut my_component = observer.fetch(); - my_component.0 += 1; - println!("Component: {:?}, Event: {:?}", my_component.0, data); + // This will not trigger the observer as the entity does not have CompB + let entity = world + .spawn(CompA(observer)) + // Respond to events targetting a specific entity + .observe(|mut observer: Observer| { + // Since Resize carries data you can read/write that data from the observer + let size = observer.data(); + // Simulataneously read components + let data = observer.fetch(); + println!("Received resize: {:?} while data was: {:?}", size, data); + // Write to resources + observer.world_mut().resource_mut::().0 += 1; }) .id(); world.flush_commands(); - world.ecs_event(MyEvent(5)).target(entity_b).emit(); - world.ecs_event(MyEvent(10)).target(entity_a).emit(); - world.ecs_event(MyEvent(15)).target(entity_b).emit(); + + assert_eq!(world.resource::().0, 0); + + // This will spawn an entity with CompA + // - Which will trigger the first observer + // - Removing CompB + // - Emitting Resize targetting `entity` + // - Which will trigger it's entity observer + // - Incrementing ResizeCount + let entity_b = world.spawn((CompA(entity), CompB)).flush(); + + assert!(!world.entity(entity_b).contains::()); + assert_eq!(world.resource::().0, 1); } From 8b15ee1df783ac1a004cb77acefa068e1a5c703b Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Thu, 30 Nov 2023 17:38:54 -0800 Subject: [PATCH 037/109] Additional comments --- examples/ecs/observers.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/ecs/observers.rs b/examples/ecs/observers.rs index a3a1a4b41a0eb..9c976534a1ed0 100644 --- a/examples/ecs/observers.rs +++ b/examples/ecs/observers.rs @@ -22,6 +22,7 @@ fn setup(world: &mut World) { world.init_resource::(); // Triggered when &ComponentA is added to any component that also has ComponentB + // This can take any query types that implement WorldQueryData and WorldQueryFilter let observer = world.observer(|mut observer: Observer>| { // Get source entity that triggered the observer let source = observer.source(); @@ -35,14 +36,15 @@ fn setup(world: &mut World) { world.commands().ecs_event(Resize(2, 4)).target(data).emit(); }); - // This will not trigger the observer as the entity does not have CompB let entity = world + // This will not trigger the observer as the entity does not have CompB .spawn(CompA(observer)) - // Respond to events targetting a specific entity + // Respond to events targeting a specific entity + // Still must match the query in order to trigger .observe(|mut observer: Observer| { // Since Resize carries data you can read/write that data from the observer let size = observer.data(); - // Simulataneously read components + // Simultaneously read components let data = observer.fetch(); println!("Received resize: {:?} while data was: {:?}", size, data); // Write to resources From f374ee8bb9e4d3a6e2546ce3ee0bd998a319b36d Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Thu, 30 Nov 2023 17:43:08 -0800 Subject: [PATCH 038/109] Fix typo --- examples/ecs/observers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ecs/observers.rs b/examples/ecs/observers.rs index 9c976534a1ed0..2ac6e23f60fd4 100644 --- a/examples/ecs/observers.rs +++ b/examples/ecs/observers.rs @@ -21,7 +21,7 @@ fn main() { fn setup(world: &mut World) { world.init_resource::(); - // Triggered when &ComponentA is added to any component that also has ComponentB + // Triggered when &CompA is added to any component that also has ComponentB // This can take any query types that implement WorldQueryData and WorldQueryFilter let observer = world.observer(|mut observer: Observer>| { // Get source entity that triggered the observer From 4d1c719297fc962a74cf3e870ee6f9bfb10ed3ec Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Fri, 1 Dec 2023 19:05:12 -0800 Subject: [PATCH 039/109] Move command queue's off the function stack to prevent overflows --- .../src/system/commands/command_queue.rs | 83 +++++++++++-------- crates/bevy_ecs/src/world/mod.rs | 4 +- 2 files changed, 53 insertions(+), 34 deletions(-) diff --git a/crates/bevy_ecs/src/system/commands/command_queue.rs b/crates/bevy_ecs/src/system/commands/command_queue.rs index 37826d30af3cd..0435c88131518 100644 --- a/crates/bevy_ecs/src/system/commands/command_queue.rs +++ b/crates/bevy_ecs/src/system/commands/command_queue.rs @@ -117,44 +117,61 @@ impl CommandQueue { let bytes_range = self.bytes.as_mut_ptr_range(); // Pointer that will iterate over the entries of the buffer. - let mut cursor = bytes_range.start; + let cursor = bytes_range.start; + + let end = bytes_range.end; // Reset the buffer, so it can be reused after this function ends. // In the loop below, ownership of each command will be transferred into user code. - // SAFETY: `set_len(0)` is always valid. - unsafe { self.bytes.set_len(0) }; - - while cursor < bytes_range.end { - // SAFETY: The cursor is either at the start of the buffer, or just after the previous command. - // Since we know that the cursor is in bounds, it must point to the start of a new command. - let meta = unsafe { cursor.cast::().read_unaligned() }; - // Advance to the bytes just after `meta`, which represent a type-erased command. - // SAFETY: For most types of `Command`, the pointer immediately following the metadata - // is guaranteed to be in bounds. If the command is a zero-sized type (ZST), then the cursor - // might be 1 byte past the end of the buffer, which is safe. - cursor = unsafe { cursor.add(std::mem::size_of::()) }; - // Construct an owned pointer to the command. - // SAFETY: It is safe to transfer ownership out of `self.bytes`, since the call to `set_len(0)` above - // guarantees that nothing stored in the buffer will get observed after this function ends. - // `cmd` points to a valid address of a stored command, so it must be non-null. - let cmd = unsafe { - OwningPtr::::new(std::ptr::NonNull::new_unchecked(cursor.cast())) - }; - // SAFETY: The data underneath the cursor must correspond to the type erased in metadata, - // since they were stored next to each other by `.push()`. - // For ZSTs, the type doesn't matter as long as the pointer is non-null. - let size = unsafe { (meta.consume_command_and_get_size)(cmd, &mut world) }; - // Advance the cursor past the command. For ZSTs, the cursor will not move. - // At this point, it will either point to the next `CommandMeta`, - // or the cursor will be out of bounds and the loop will end. - // SAFETY: The address just past the command is either within the buffer, - // or 1 byte past the end, so this addition will not overflow the pointer's allocation. - cursor = unsafe { cursor.add(size) }; - - if let Some(world) = &mut world { - world.flush_commands(); + let bytes = std::mem::take(&mut self.bytes); + + let mut resolving_commands = vec![(cursor, end, bytes)]; + + while let Some((mut cursor, mut end, mut bytes)) = resolving_commands.pop() { + while cursor < end { + // SAFETY: The cursor is either at the start of the buffer, or just after the previous command. + // Since we know that the cursor is in bounds, it must point to the start of a new command. + let meta = unsafe { cursor.cast::().read_unaligned() }; + // Advance to the bytes just after `meta`, which represent a type-erased command. + // SAFETY: For most types of `Command`, the pointer immediately following the metadata + // is guaranteed to be in bounds. If the command is a zero-sized type (ZST), then the cursor + // might be 1 byte past the end of the buffer, which is safe. + cursor = unsafe { cursor.add(std::mem::size_of::()) }; + // Construct an owned pointer to the command. + // SAFETY: It is safe to transfer ownership out of `self.bytes`, since the call to `set_len(0)` above + // guarantees that nothing stored in the buffer will get observed after this function ends. + // `cmd` points to a valid address of a stored command, so it must be non-null. + let cmd = unsafe { + OwningPtr::::new(std::ptr::NonNull::new_unchecked(cursor.cast())) + }; + // SAFETY: The data underneath the cursor must correspond to the type erased in metadata, + // since they were stored next to each other by `.push()`. + // For ZSTs, the type doesn't matter as long as the pointer is non-null. + let size = unsafe { (meta.consume_command_and_get_size)(cmd, &mut world) }; + // Advance the cursor past the command. For ZSTs, the cursor will not move. + // At this point, it will either point to the next `CommandMeta`, + // or the cursor will be out of bounds and the loop will end. + // SAFETY: The address just past the command is either within the buffer, + // or 1 byte past the end, so this addition will not overflow the pointer's allocation. + cursor = unsafe { cursor.add(size) }; + + if let Some(world) = &mut world { + world.flushing_commands = true; + if !world.command_queue.is_empty() { + if cursor < end { + resolving_commands.push((cursor, end, bytes)); + } + bytes = std::mem::take(&mut world.command_queue.bytes); + let bytes_range = bytes.as_mut_ptr_range(); + cursor = bytes_range.start; + end = bytes_range.end; + } + } } } + if let Some(world) = world { + world.flushing_commands = false; + } } /// Take all commands from `other` and append them to `self`, leaving `other` empty diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index e19a71aebd0e5..b4284e2b45e31 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -74,6 +74,7 @@ pub struct World { pub(crate) last_change_tick: Tick, pub(crate) last_check_tick: Tick, pub(crate) command_queue: CommandQueue, + pub(crate) flushing_commands: bool, } impl Default for World { @@ -93,6 +94,7 @@ impl Default for World { last_change_tick: Tick::new(0), last_check_tick: Tick::new(0), command_queue: CommandQueue::default(), + flushing_commands: false, } } } @@ -1755,7 +1757,7 @@ impl World { /// Applies the internal [`CommandQueue`] to self #[inline] pub fn flush_commands(&mut self) { - if !self.command_queue.is_empty() { + if !self.flushing_commands && !self.command_queue.is_empty() { let mut commands = std::mem::take(&mut self.command_queue); commands.apply(self); } From 6682da3052ca9a55fdeac238b9a4763a52e36697 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Fri, 1 Dec 2023 19:10:44 -0800 Subject: [PATCH 040/109] Remove unused code --- crates/bevy_ecs/src/entity/mod.rs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index 17ff6cbef6f8b..0ac11ae04c63e 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -421,8 +421,6 @@ pub struct Entities { free_cursor: AtomicIdCursor, /// Stores the number of free entities for [`len`](Entities::len) len: u32, - /// Count of entities marked as constant, will cause panics on despawn - constant_count: u32, } impl Entities { @@ -432,7 +430,6 @@ impl Entities { pending: Vec::new(), free_cursor: AtomicIdCursor::new(0), len: 0, - constant_count: 0, } } @@ -611,9 +608,6 @@ impl Entities { /// /// Must not be called while reserved entities are awaiting `flush()`. pub fn free(&mut self, entity: Entity) -> Option { - if entity.index < self.constant_count { - panic!("Cannot despawn entities marked as internal"); - } self.verify_flushed(); let meta = &mut self.meta[entity.index as usize]; @@ -655,7 +649,7 @@ impl Entities { /// Clears all [`Entity`] from the World. pub fn clear(&mut self) { - self.meta.truncate(self.constant_count as usize); + self.meta.clear(); self.pending.clear(); *self.free_cursor.get_mut() = 0; self.len = 0; @@ -709,10 +703,6 @@ impl Entities { } } - pub(crate) fn set_constant(&mut self) { - self.constant_count = self.total_count() as u32; - } - /// Get the [`Entity`] with a given id, if it exists in this [`Entities`] collection /// Returns `None` if this [`Entity`] is outside of the range of currently reserved Entities /// @@ -832,11 +822,6 @@ impl Entities { self.len } - #[inline] - pub fn constant_count(&self) -> u32 { - self.constant_count - } - /// Checks if any entity is currently active. #[inline] pub fn is_empty(&self) -> bool { From a52837ca537dadc859286fa14bd2e73992847679 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Fri, 1 Dec 2023 19:11:09 -0800 Subject: [PATCH 041/109] Strip more code --- crates/bevy_ecs/src/observer.rs | 2 +- crates/bevy_ecs/src/world/mod.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/observer.rs b/crates/bevy_ecs/src/observer.rs index a01559066fe4c..cde8f07633695 100644 --- a/crates/bevy_ecs/src/observer.rs +++ b/crates/bevy_ecs/src/observer.rs @@ -1,6 +1,6 @@ //! Types for creating and storing [`Observer`]s -use std::{any::TypeId, marker::PhantomData}; +use std::marker::PhantomData; use crate::{ self as bevy_ecs, diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index e79a6d86061c0..a8d7b726302c4 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -112,7 +112,6 @@ impl World { #[inline] fn bootstrap(&mut self) { self.bootstrap_observers(); - self.entities.set_constant(); } /// Creates a new empty [`World`]. /// From 615e232d7800f5f4167c7ecb9f32310f7d491830 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Fri, 1 Dec 2023 22:40:26 -0800 Subject: [PATCH 042/109] Major cleanup --- crates/bevy_ecs/src/lib.rs | 6 +- crates/bevy_ecs/src/observer/builder.rs | 113 ++++++ .../src/{observer.rs => observer/mod.rs} | 360 ++++++------------ crates/bevy_ecs/src/observer/runner.rs | 93 +++++ crates/bevy_ecs/src/observer/state.rs | 38 ++ crates/bevy_ecs/src/system/commands/mod.rs | 22 +- .../bevy_ecs/src/world/component_constants.rs | 38 +- crates/bevy_ecs/src/world/deferred_world.rs | 13 + crates/bevy_ecs/src/world/entity_ref.rs | 7 +- crates/bevy_ecs/src/world/mod.rs | 6 + crates/bevy_render/src/lib.rs | 2 +- examples/ecs/observers.rs | 2 +- 12 files changed, 437 insertions(+), 263 deletions(-) create mode 100644 crates/bevy_ecs/src/observer/builder.rs rename crates/bevy_ecs/src/{observer.rs => observer/mod.rs} (59%) create mode 100644 crates/bevy_ecs/src/observer/runner.rs create mode 100644 crates/bevy_ecs/src/observer/state.rs diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index e78b42209c792..5d49e6e830c73 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -36,7 +36,7 @@ pub mod prelude { component::Component, entity::Entity, event::{Event, EventReader, EventWriter, Events}, - observer::{Observer, OnAdd, OnInsert, OnRemove}, + observer::Observer, query::{Added, AnyOf, Changed, Has, Or, QueryState, With, Without}, removal_detection::RemovedComponents, schedule::{ @@ -48,7 +48,9 @@ pub mod prelude { Commands, Deferred, In, IntoSystem, Local, NonSend, NonSendMut, ParallelCommands, ParamSet, Query, ReadOnlySystem, Res, ResMut, Resource, System, SystemParamFunction, }, - world::{EntityMut, EntityRef, EntityWorldMut, FromWorld, World}, + world::{ + EntityMut, EntityRef, EntityWorldMut, FromWorld, OnAdd, OnInsert, OnRemove, World, + }, }; } diff --git a/crates/bevy_ecs/src/observer/builder.rs b/crates/bevy_ecs/src/observer/builder.rs new file mode 100644 index 0000000000000..10444cf130d8e --- /dev/null +++ b/crates/bevy_ecs/src/observer/builder.rs @@ -0,0 +1,113 @@ +use std::marker::PhantomData; + +use super::*; + +/// Builder struct for [`Observer`]. +pub struct ObserverBuilder<'w, E: EcsEvent = NoEvent> { + world: &'w mut World, + descriptor: ObserverDescriptor, + _marker: PhantomData, +} + +impl<'w, E: EcsEvent> ObserverBuilder<'w, E> { + /// Constructs a new [`ObserverBuilder`]. + pub fn new(world: &'w mut World) -> Self { + let mut descriptor = ObserverDescriptor::default(); + let event = world.init_component::(); + if event != NO_EVENT { + descriptor.events.push(event); + } + Self { + world, + descriptor, + _marker: PhantomData::default(), + } + } + + /// Adds `NewE` to the list of events listened to by this observer. + /// Observers that listen to multiple types of events can no longer access the typed event data. + pub fn on_event(&mut self) -> &mut ObserverBuilder<'w, NoEvent> { + let event = self.world.init_component::(); + self.descriptor.events.push(event); + // SAFETY: () type will not allow bad memory access as it has no size + unsafe { std::mem::transmute(self) } + } + + /// Add `events` to the list of events listened to by this observer. + /// Observers that listen to multiple types of events can no longer access the typed event data. + pub fn on_event_ids( + &mut self, + events: impl IntoIterator, + ) -> &mut ObserverBuilder<'w, NoEvent> { + self.descriptor.events.extend(events); + // SAFETY: () type will not allow bad memory access as it has no size + unsafe { std::mem::transmute(self) } + } + + /// Add [`ComponentId`] in `T` to the list of components listened to by this observer. + pub fn components(&mut self) -> &mut Self { + T::component_ids( + &mut self.world.components, + &mut self.world.storages, + &mut |id| self.descriptor.components.push(id), + ); + self + } + + /// Add `ids` to the list of component sources listened to by this observer. + pub fn component_ids(&mut self, ids: impl IntoIterator) -> &mut Self { + self.descriptor.components.extend(ids); + self + } + + /// Adds `source` as the list of entity sources listened to by this observer. + pub fn source(&mut self, source: Entity) -> &mut Self { + self.descriptor.sources.push(source); + self + } + + /// Spawns the resulting observer into the world. + pub fn run( + &mut self, + callback: fn(Observer), + ) -> Entity { + let entity = self.enqueue(callback); + self.world.flush_commands(); + entity + } + + /// Spawns the resulting observer into the world using a [`ObserverRunner`] callback. + /// This is not advised unless you want to respond to events that may not be associated with an entity + /// or otherwise want to override the default runner behaviour. + pub fn runner( + &mut self, + runner: ObserverRunner, + ) -> Entity { + let entity = self.enqueue_runner::(runner); + self.world.flush_commands(); + entity + } + + /// Enqueues a command to spawn the resulting observer in the world. + pub fn enqueue( + &mut self, + callback: fn(Observer), + ) -> Entity { + self.world + .spawn_observer::(ObserverComponent::from(self.descriptor.clone(), callback)) + } + + /// Enqueues a command to spawn the resulting observer in the world using a [`ObserverRunner`] callback. + /// This is not advised unless you want to respond to events that may not be associated with an entity + /// or otherwise want to override the default runner behaviour. + pub fn enqueue_runner( + &mut self, + runner: ObserverRunner, + ) -> Entity { + self.world + .spawn_observer::(ObserverComponent::from_runner( + self.descriptor.clone(), + runner, + )) + } +} diff --git a/crates/bevy_ecs/src/observer.rs b/crates/bevy_ecs/src/observer/mod.rs similarity index 59% rename from crates/bevy_ecs/src/observer.rs rename to crates/bevy_ecs/src/observer/mod.rs index cde8f07633695..5a98b7cb7468c 100644 --- a/crates/bevy_ecs/src/observer.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -1,6 +1,12 @@ //! Types for creating and storing [`Observer`]s -use std::marker::PhantomData; +mod builder; +mod runner; +mod state; + +pub use builder::*; +pub use runner::*; +use state::*; use crate::{ self as bevy_ecs, @@ -16,6 +22,13 @@ use bevy_utils::{EntityHashMap, HashMap}; use crate::{component::ComponentId, prelude::*, query::WorldQueryFilter, world::DeferredWorld}; +/// Trait used to mark components used as ECS events. +pub trait EcsEvent: Component {} + +impl EcsEvent for E {} + +/// Type used in callbacks registered for observers. +/// TODO: Proper docs and examples pub struct Observer<'w, E, Q: WorldQueryData, F: WorldQueryFilter = ()> { world: DeferredWorld<'w>, state: &'w ObserverState, @@ -38,10 +51,12 @@ impl<'w, E, Q: WorldQueryData, F: WorldQueryFilter> Observer<'w, E, Q, F> { } } + /// Returns the event id for the triggering event pub fn event(&self) -> ComponentId { self.trigger.event } + /// Gets read access to the data for [`Q`] from the triggering entity pub fn fetch(&self) -> ::Item<'_> { let location = self.world.entities.get(self.trigger.source).unwrap(); let world = self.world.as_unsafe_world_cell_readonly(); @@ -59,6 +74,7 @@ impl<'w, E, Q: WorldQueryData, F: WorldQueryFilter> Observer<'w, E, Q, F> { } } + /// Gets write access to the data for [`Q`] from the triggering entity pub fn fetch_mut(&mut self) -> Q::Item<'_> { let location = self.world.entities.get(self.trigger.source).unwrap(); let world = self.world.as_unsafe_world_cell(); @@ -76,62 +92,32 @@ impl<'w, E, Q: WorldQueryData, F: WorldQueryFilter> Observer<'w, E, Q, F> { } } + /// Returns a reference to the data associated with the event that triggered the observer. pub fn data(&self) -> &E { &self.data } + /// Returns a mutable reference to the data associated with the event that triggered the observer. + pub fn data_mut(&mut self) -> &mut E { + &mut self.data + } + + /// Returns the entity that triggered the observer. pub fn source(&self) -> Entity { self.trigger.source } + /// Returns a reference to the underlying [`DeferredWorld`] pub fn world(&self) -> &DeferredWorld { &self.world } + /// Returns a mutable reference to the underlying [`DeferredWorld`] pub fn world_mut(&mut self) -> &mut DeferredWorld<'w> { &mut self.world } } -#[derive(Component)] -pub(crate) struct ObserverState { - fetch_state: Q::State, - filter_state: F::State, - component_access: FilteredAccess, - last_event_id: u32, -} - -impl ObserverState { - pub fn new(world: &mut World) -> Self { - let fetch_state = Q::init_state(world); - let filter_state = F::init_state(world); - - let mut component_access = FilteredAccess::default(); - Q::update_component_access(&fetch_state, &mut component_access); - - // Use a temporary empty FilteredAccess for filters. This prevents them from conflicting with the - // main Query's `fetch_state` access. Filters are allowed to conflict with the main query fetch - // because they are evaluated *before* a specific reference is constructed. - let mut filter_component_access = FilteredAccess::default(); - F::update_component_access(&filter_state, &mut filter_component_access); - - // Merge the temporary filter access with the main access. This ensures that filter access is - // properly considered in a global "cross-query" context (both within systems and across systems). - component_access.extend(&filter_component_access); - - Self { - fetch_state, - filter_state, - component_access, - last_event_id: 0, - } - } -} - -pub trait EcsEvent: Component {} - -impl EcsEvent for C {} - #[derive(Default, Clone, Component)] pub(crate) struct ObserverDescriptor { events: Vec, @@ -139,82 +125,7 @@ pub(crate) struct ObserverDescriptor { sources: Vec, } -pub struct ObserverBuilder<'w, E: EcsEvent = NoEvent> { - world: &'w mut World, - descriptor: ObserverDescriptor, - _marker: PhantomData, -} - -impl<'w, E: EcsEvent> ObserverBuilder<'w, E> { - pub fn new(world: &'w mut World) -> Self { - let mut descriptor = ObserverDescriptor::default(); - let event = world.init_component::(); - if event != NO_EVENT { - descriptor.events.push(event); - } - Self { - world, - descriptor, - _marker: PhantomData::default(), - } - } - - // Allows listening for multiple types of events but without passing typed data - pub fn on_event(&mut self) -> &mut ObserverBuilder<'w, NoEvent> { - let event = self.world.init_component::(); - self.descriptor.events.push(event); - // SAFETY: () type will not allow bad memory access as it has no size - unsafe { std::mem::transmute(self) } - } - - pub fn on_event_ids( - &mut self, - events: impl IntoIterator, - ) -> &mut ObserverBuilder<'w, NoEvent> { - self.descriptor.events.extend(events); - // SAFETY: () type will not allow bad memory access as it has no size - unsafe { std::mem::transmute(self) } - } - - pub fn components(&mut self) -> &mut Self { - T::component_ids( - &mut self.world.components, - &mut self.world.storages, - &mut |id| self.descriptor.components.push(id), - ); - self - } - - pub fn component_ids( - &mut self, - ids: impl IntoIterator, - ) -> &mut Self { - self.descriptor.components.extend(ids); - self - } - - pub fn source(&mut self, source: Entity) -> &mut Self { - self.descriptor.sources.push(source); - self - } - - pub fn run( - &mut self, - callback: fn(Observer), - ) -> Entity { - let entity = self.enqueue(callback); - self.world.flush_commands(); - entity - } - - pub fn enqueue( - &mut self, - callback: fn(Observer), - ) -> Entity { - self.world.spawn_observer(&self.descriptor, callback) - } -} - +/// Metadata for the source triggering an [`Observer`], pub struct ObserverTrigger { observer: Entity, location: EntityLocation, @@ -222,87 +133,13 @@ pub struct ObserverTrigger { source: Entity, } -#[derive(Copy, Clone, Debug)] -struct ObserverCallback { - run: fn(DeferredWorld, ObserverTrigger, PtrMut, Option)>), - callback: Option)>, -} - -#[derive(Component)] -pub(crate) struct ObserverComponent { - descriptor: ObserverDescriptor, - runner: ObserverCallback, -} - -impl ObserverComponent { - fn from( - descriptor: ObserverDescriptor, - value: fn(Observer), - ) -> Self { - Self { - descriptor, - runner: ObserverCallback { - run: |mut world, trigger, ptr, callback| { - let callback: fn(Observer) = - unsafe { std::mem::transmute(callback.debug_checked_unwrap()) }; - let state = unsafe { - let mut state = world - .get_mut::>(trigger.observer) - .debug_checked_unwrap(); - let state: *mut ObserverState = state.as_mut(); - &mut *state - }; - // This being stored in a component is not ideal, should be able to check this before fetching - let last_event = world.last_event_id; - if state.last_event_id == last_event { - return; - } - state.last_event_id = last_event; - - let archetype_id = trigger.location.archetype_id; - let archetype = &world.archetypes()[archetype_id]; - if !Q::matches_component_set(&state.fetch_state, &mut |id| { - archetype.contains(id) - }) || !F::matches_component_set(&state.filter_state, &mut |id| { - archetype.contains(id) - }) { - return; - } - - // TODO: Change ticks? - unsafe { - let mut filter_fetch = F::init_fetch( - world.as_unsafe_world_cell_readonly(), - &state.filter_state, - world.last_change_tick(), - world.read_change_tick(), - ); - - if !F::filter_fetch( - &mut filter_fetch, - trigger.source, - trigger.location.table_row, - ) { - return; - } - } - - // SAFETY: Pointer is valid as we just created it, ObserverState is a private type and so will not be aliased - let observer = Observer::new(world, state, unsafe { ptr.deref_mut() }, trigger); - callback(observer); - }, - callback: Some(unsafe { std::mem::transmute(value) }), - }, - } - } -} - #[derive(Default, Debug)] pub(crate) struct CachedObservers { component_observers: HashMap>, entity_observers: EntityHashMap>, } +/// Metadata for observers. Stores a cache mapping event ids to the registered observers. #[derive(Default, Debug)] pub struct Observers { on_add: CachedObservers, @@ -353,7 +190,7 @@ impl Observers { let cache = self.get_observers(event); for &component in &observer.descriptor.components { let observers = cache.component_observers.entry(component).or_default(); - observers.insert(entity, observer.runner); + observers.insert(entity, observer.callback); if observers.len() == 1 { if let Some(flag) = Self::is_archetype_cached(event) { archetypes.update_flags(component, flag, true); @@ -362,7 +199,7 @@ impl Observers { } for &source in &observer.descriptor.sources { let observers = cache.entity_observers.entry(source).or_default(); - observers.insert(entity, observer.runner); + observers.insert(entity, observer.callback); } } } @@ -413,6 +250,7 @@ impl Observers { let Some(observers) = self.try_get_observers(event) else { return; }; + // Run entity observers for source if let Some(observers) = observers.entity_observers.get(&source) { observers.iter().for_each(|(&observer, runner)| { (runner.run)( @@ -428,6 +266,23 @@ impl Observers { ); }); } + // Run component observers for ANY + if let Some(observers) = observers.component_observers.get(&ANY) { + observers.iter().for_each(|(&observer, runner)| { + (runner.run)( + world.reborrow(), + ObserverTrigger { + observer, + event, + location, + source, + }, + data.into(), + runner.callback, + ) + }) + } + // Run component observers for each component for component in components { if let Some(observers) = observers.component_observers.get(&component) { observers.iter().for_each(|(&observer, runner)| { @@ -481,64 +336,69 @@ impl Observers { } } +/// EcsEvent to signify an entity observer being attached to an entity +/// Can be modelled by parent-child relationship if/when that is enforced #[derive(Component)] -pub struct OnAdd; - -#[derive(Component)] -pub struct OnInsert; - -#[derive(Component)] -pub struct OnRemove; - -#[derive(Component)] -pub struct NoEvent; - -#[derive(Component)] -#[component(storage = "SparseSet")] pub(crate) struct AttachObserver(pub(crate) Entity); +/// Tracks a list of entity observers for the attached entity #[derive(Component, Default)] #[component(storage = "SparseSet")] pub(crate) struct ObservedBy(Vec); +/// Type used to construct and emit a [`EcsEvent`] pub struct EventBuilder<'w, E> { - commands: Commands<'w, 'w>, + world: DeferredWorld<'w>, targets: Vec, components: Vec, data: Option, } impl<'w, E: EcsEvent> EventBuilder<'w, E> { - pub fn new(data: E, commands: Commands<'w, 'w>) -> Self { + /// Constructs a new builder that will write it's event to `world`'s command queue + pub fn new(data: E, world: DeferredWorld<'w>) -> Self { Self { - commands, + world, targets: Vec::new(), components: Vec::new(), data: Some(data), } } - pub fn target(&mut self, target: Entity) -> &mut Self { + /// Adds `target` to the list of entities targeted by `self` + pub fn entity(&mut self, target: Entity) -> &mut Self { self.targets.push(target); self } + /// Add the [`ComponentId`] of `T` to the list of components targeted by `self` + pub fn component(&mut self) -> &mut Self { + let component_id = self.world.components().component_id::().expect( + "Cannot emit event for component that does not exist, initialize components before emitting events targeting them." + ); + self.components.push(component_id); + self + } + + /// Adds `component_id` to the list of components targeted by `self` + pub fn component_id(&mut self, component_id: ComponentId) -> &mut Self { + self.components.push(component_id); + self + } + + /// Add the event to the command queue of world pub fn emit(&mut self) { - self.commands.add(EmitEcsEvent:: { + self.world.commands().add(EmitEcsEvent:: { data: std::mem::take(&mut self.data).unwrap(), - targets: std::mem::take(&mut self.targets), + entities: std::mem::take(&mut self.targets), components: std::mem::take(&mut self.components), - }) + }); } } impl World { + /// Initialize components and register hooks for types used by [`Observer`]. pub(crate) fn bootstrap_observers(&mut self) { - assert_eq!(NO_EVENT, self.init_component::()); - assert_eq!(ON_ADD, self.init_component::()); - assert_eq!(ON_INSERT, self.init_component::()); - assert_eq!(ON_REMOVE, self.init_component::()); - // Update event cache when observers are spawned and despawned self.register_component::() .on_add(|mut world, entity, _| { @@ -568,21 +428,24 @@ impl World { observers.unregister(archetypes, entity, observer); }); - // Register attached observer to `ObservedBy` for later despawning - self.register_component::() - .on_insert(|mut world, entity, _| { - let observer = world.get::(entity).unwrap().0; - - match world.get_mut::(entity) { - Some(mut o) => o.0.push(observer), - None => { - world - .commands() - .entity(entity) - .insert(ObservedBy(vec![observer])); - } + // When any entity is targeted for an `AttachObserver` event add it to `ObservedBy` + // or insert `ObservedBy` if it doesn't exist + // Can also use a hooks here instead + self.observer_builder().components::().run( + |mut observer: Observer>| { + let attached_observer = observer.data().0; + if let Some(mut observed_by) = observer.fetch_mut() { + observed_by.0.push(attached_observer); + } else { + let source = observer.source(); + observer + .world_mut() + .commands() + .entity(source) + .insert(ObservedBy(vec![attached_observer])); } - }); + }, + ); // When an entity is despawned while being observed by entity observers despawn them self.register_component::() @@ -595,10 +458,14 @@ impl World { }); } + /// Construct an [`ObserverBuilder`] pub fn observer_builder(&mut self) -> ObserverBuilder { ObserverBuilder::new(self) } + /// Create an [`Observer`] for the components accessed in `Q`. + /// For more control over targetting components see [`Self::observer_builder`]. + /// For observing events targetting a specific entity see [`EntityWorldMut::observe`]. pub fn observer( &mut self, callback: fn(Observer), @@ -606,8 +473,13 @@ impl World { ObserverBuilder::new(self).run(callback) } + /// Constructs an [`EventBuilder`] for an [`EcsEvent`]. pub fn ecs_event(&mut self, event: E) -> EventBuilder { - EventBuilder::new(event, self.commands()) + self.init_component::(); + // TODO: Safe into deferred for world + EventBuilder::new(event, unsafe { + self.as_unsafe_world_cell().into_deferred() + }) } pub(crate) fn spawn_observer< @@ -616,23 +488,23 @@ impl World { F: WorldQueryFilter + 'static, >( &mut self, - descriptor: &ObserverDescriptor, - callback: fn(Observer), + mut observer: ObserverComponent, ) -> Entity { - let mut descriptor = descriptor.clone(); let iterator_state = ObserverState::::new(self); - if descriptor.components.is_empty() && descriptor.sources.is_empty() { - descriptor - .components - .extend(iterator_state.component_access.access().reads_and_writes()); + let components = &mut observer.descriptor.components; + let sources = &observer.descriptor.sources; + // If the observer has no explicit targets use the accesses of the query + if components.is_empty() && sources.is_empty() { + components.extend(iterator_state.component_access.access().reads_and_writes()); + // If there are still no targets add the ANY target + if components.is_empty() { + components.push(ANY); + } } let entity = self.entities.reserve_entity(); self.command_queue.push(Insert { entity, - bundle: ( - iterator_state, - ObserverComponent::from(descriptor, callback), - ), + bundle: (iterator_state, observer), }); entity diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs new file mode 100644 index 0000000000000..dd2f0fb3621ef --- /dev/null +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -0,0 +1,93 @@ +use super::*; + +#[derive(Copy, Clone, Debug)] +pub(crate) struct ObserverCallback { + pub(crate) run: ObserverRunner, + pub(crate) callback: Option)>, +} + +/// Type for function that is run when an observer is triggered +/// Typically refers to the default runner defined in [`ObserverComponent::from`] +pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, Option)>); + +#[derive(Component)] +pub(crate) struct ObserverComponent { + pub(crate) descriptor: ObserverDescriptor, + pub(crate) callback: ObserverCallback, +} + +impl ObserverComponent { + pub(crate) fn from( + descriptor: ObserverDescriptor, + value: fn(Observer), + ) -> Self { + Self { + descriptor, + callback: ObserverCallback { + run: |mut world, trigger, ptr, callback| { + if trigger.source == Entity::PLACEHOLDER { + return; + } + let callback: fn(Observer) = + unsafe { std::mem::transmute(callback.debug_checked_unwrap()) }; + let state = unsafe { + let mut state = world + .get_mut::>(trigger.observer) + .debug_checked_unwrap(); + let state: *mut ObserverState = state.as_mut(); + &mut *state + }; + // This being stored in a component is not ideal, should be able to check this before fetching + let last_event = world.last_event_id; + if state.last_event_id == last_event { + return; + } + state.last_event_id = last_event; + + let archetype_id = trigger.location.archetype_id; + let archetype = &world.archetypes()[archetype_id]; + if !Q::matches_component_set(&state.fetch_state, &mut |id| { + archetype.contains(id) + }) || !F::matches_component_set(&state.filter_state, &mut |id| { + archetype.contains(id) + }) { + return; + } + + // TODO: Change ticks? + unsafe { + let mut filter_fetch = F::init_fetch( + world.as_unsafe_world_cell_readonly(), + &state.filter_state, + world.last_change_tick(), + world.read_change_tick(), + ); + + if !F::filter_fetch( + &mut filter_fetch, + trigger.source, + trigger.location.table_row, + ) { + return; + } + } + + // SAFETY: Pointer is valid as we just created it, ObserverState is a private type and so will not be aliased + let observer = Observer::new(world, state, unsafe { ptr.deref_mut() }, trigger); + callback(observer); + }, + callback: Some(unsafe { std::mem::transmute(value) }), + }, + } + } + + pub(crate) fn from_runner(descriptor: ObserverDescriptor, run: ObserverRunner) -> Self { + Self { + descriptor, + callback: ObserverCallback { + run, + callback: None, + }, + } + } +} diff --git a/crates/bevy_ecs/src/observer/state.rs b/crates/bevy_ecs/src/observer/state.rs new file mode 100644 index 0000000000000..fbaf0dec332a8 --- /dev/null +++ b/crates/bevy_ecs/src/observer/state.rs @@ -0,0 +1,38 @@ +use super::*; + +// State for [`Query`] like behaviour for [`Observer`] +// Will be unified with [`QueryState`] with queries as entities +#[derive(Component)] +pub(crate) struct ObserverState { + pub(crate) fetch_state: Q::State, + pub(crate) filter_state: F::State, + pub(crate) component_access: FilteredAccess, + pub(crate) last_event_id: u32, +} + +impl ObserverState { + pub(crate) fn new(world: &mut World) -> Self { + let fetch_state = Q::init_state(world); + let filter_state = F::init_state(world); + + let mut component_access = FilteredAccess::default(); + Q::update_component_access(&fetch_state, &mut component_access); + + // Use a temporary empty FilteredAccess for filters. This prevents them from conflicting with the + // main Query's `fetch_state` access. Filters are allowed to conflict with the main query fetch + // because they are evaluated *before* a specific reference is constructed. + let mut filter_component_access = FilteredAccess::default(); + F::update_component_access(&filter_state, &mut filter_component_access); + + // Merge the temporary filter access with the main access. This ensures that filter access is + // properly considered in a global "cross-query" context (both within systems and across systems). + component_access.extend(&filter_component_access); + + Self { + fetch_state, + filter_state, + component_access, + last_event_id: 0, + } + } +} diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 17468d62c377b..9c5c66b12456e 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -6,7 +6,7 @@ use crate::{ bundle::Bundle, component::ComponentId, entity::{Entities, Entity}, - observer::{EcsEvent, EventBuilder}, + observer::EcsEvent, prelude::Observer, query::{WorldQueryData, WorldQueryFilter}, system::{RunSystemWithInput, SystemId}, @@ -591,10 +591,7 @@ impl<'w, 's> Commands<'w, 's> { self.queue.push(command); } - pub fn ecs_event(&mut self, data: E) -> EventBuilder { - EventBuilder::new(data, self.reborrow()) - } - + /// Reborrow self as a new instance of [`Commands`] pub fn reborrow(&mut self) -> Commands { Commands { queue: Deferred(self.queue.0), @@ -939,6 +936,8 @@ impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> { self.commands } + /// Creates an [`Observer`] listening for `E` events targetting this entity. + /// In order to trigger the callback the entity must also match the query when the event is fired. pub fn observe( &mut self, callback: fn(Observer), @@ -1197,6 +1196,7 @@ impl Command for LogComponents { pub struct Observe { /// The entity that will be observed. pub entity: Entity, + /// The callback to run when the event is observed. pub callback: fn(Observer), } @@ -1211,16 +1211,20 @@ impl Co /// A [`Command`] that emits an event to be received by observers. #[derive(Debug)] pub struct EmitEcsEvent { + /// Data for the event. pub data: E, + /// Components to trigger observers for. pub components: Vec, - pub targets: Vec, + /// Entities to trigger observers for. + pub entities: Vec, } impl Command for EmitEcsEvent { fn apply(mut self, world: &mut World) { let event = world.init_component::(); let mut world = unsafe { world.as_unsafe_world_cell().into_deferred() }; - for &target in &self.targets { + + for &target in &self.entities { if let Some(location) = world.entities().get(target) { unsafe { world.trigger_observers_with_data( @@ -1229,8 +1233,8 @@ impl Command for EmitEcsEvent { location, self.components.iter().cloned(), &mut self.data, - ) - } + ); + }; } } } diff --git a/crates/bevy_ecs/src/world/component_constants.rs b/crates/bevy_ecs/src/world/component_constants.rs index 70cc6e2ebf5d8..92127311723e1 100644 --- a/crates/bevy_ecs/src/world/component_constants.rs +++ b/crates/bevy_ecs/src/world/component_constants.rs @@ -1,6 +1,34 @@ -use crate::component::ComponentId; +use super::*; +use crate as bevy_ecs; -pub const NO_EVENT: ComponentId = ComponentId::new(0); -pub const ON_ADD: ComponentId = ComponentId::new(1); -pub const ON_INSERT: ComponentId = ComponentId::new(2); -pub const ON_REMOVE: ComponentId = ComponentId::new(3); +/// [`ComponentId`] for [`Any`] +pub const ANY: ComponentId = ComponentId::new(0); +/// [`ComponentId`] for [`NoEvent`] +pub const NO_EVENT: ComponentId = ComponentId::new(1); +/// [`ComponentId`] for [`OnAdd`] +pub const ON_ADD: ComponentId = ComponentId::new(2); +/// [`ComponentId`] for [`OnInsert`] +pub const ON_INSERT: ComponentId = ComponentId::new(3); +/// [`ComponentId`] for [`OnRemove`] +pub const ON_REMOVE: ComponentId = ComponentId::new(4); + +/// Event emitted when a component is added to an entity. +#[derive(Component)] +pub struct OnAdd; + +/// Event emitted when a component is inserted on to to an entity. +#[derive(Component)] +pub struct OnInsert; + +/// Event emitted when a component is removed from an entity. +#[derive(Component)] +pub struct OnRemove; + +/// Type used to signify observers that are listening to multiple events +/// so cannot access event data. +#[derive(Component)] +pub struct NoEvent; + +/// Type used to signify observers that listen to events targetting any entities or components. +#[derive(Component)] +pub struct Any; diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 4b7c4e4b1644a..74a89e9793834 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -6,6 +6,7 @@ use crate::{ component::ComponentId, entity::{Entity, EntityLocation}, event::{Event, EventId, Events, SendBatchIds}, + observer::{EcsEvent, EventBuilder}, prelude::{Component, QueryState}, query::{WorldQueryData, WorldQueryFilter}, system::{Commands, Query, Resource}, @@ -32,6 +33,7 @@ impl<'w> Deref for DeferredWorld<'w> { } impl<'w> DeferredWorld<'w> { + /// Reborrow self as a new instance of [`DeferredWorld`] #[inline] pub fn reborrow(&mut self) -> DeferredWorld { DeferredWorld { world: self.world } @@ -55,6 +57,9 @@ impl<'w> DeferredWorld<'w> { unsafe { self.world.get_entity(entity)?.get_mut() } } + /// Retrieves an [`EntityMut`] that exposes read and write operations for the given `entity`. + /// Returns [`None`] if the `entity` does not exist. + /// Instead of unwrapping the value returned from this function, prefer [`Self::entity_mut`]. #[inline] pub fn get_entity_mut(&mut self, entity: Entity) -> Option { let location = self.entities.get(entity)?; @@ -66,6 +71,9 @@ impl<'w> DeferredWorld<'w> { Some(entity_ref) } + /// Retrieves an [`EntityMut`] that exposes read and write operations for the given `entity`. + /// This will panic if the `entity` does not exist. Use [`Self::get_entity_mut`] if you want + /// to check for entity existence instead of implicitly panic-ing. #[inline] pub fn entity_mut(&mut self, entity: Entity) -> EntityMut { #[inline(never)] @@ -352,6 +360,11 @@ impl<'w> DeferredWorld<'w> { observers.invoke(event, target, location, components, world, data); } + /// Constructs an [`EventBuilder`] for an [`EcsEvent`]. + pub fn ecs_event(&mut self, data: E) -> EventBuilder { + EventBuilder::new(data, self.reborrow()) + } + #[inline] pub(crate) fn as_unsafe_world_cell(&mut self) -> UnsafeWorldCell { self.world diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 5172b78235ba4..b9c14981ff6d3 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -1144,6 +1144,8 @@ impl<'w> EntityWorldMut<'w> { } } + /// Creates an [`Observer`] listening for `E` events targetting this entity. + /// In order to trigger the callback the entity must also match the query when the event is fired. pub fn observe( &mut self, callback: fn(Observer), @@ -1151,7 +1153,10 @@ impl<'w> EntityWorldMut<'w> { let observer = ObserverBuilder::new(self.world) .source(self.entity) .enqueue(callback); - self.insert(AttachObserver(observer)); + self.world + .ecs_event(AttachObserver(observer)) + .entity(self.entity) + .emit(); self } } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index a8d7b726302c4..bc06d0417885f 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -111,6 +111,12 @@ impl Default for World { impl World { #[inline] fn bootstrap(&mut self) { + assert_eq!(ANY, self.init_component::()); + assert_eq!(NO_EVENT, self.init_component::()); + assert_eq!(ON_ADD, self.init_component::()); + assert_eq!(ON_INSERT, self.init_component::()); + assert_eq!(ON_REMOVE, self.init_component::()); + self.bootstrap_observers(); } /// Creates a new empty [`World`]. diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index c8a72ded80389..3de2e7467296f 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -447,7 +447,7 @@ unsafe fn initialize_render_app(app: &mut App) { assert_eq!( render_app.world.entities().len(), - render_app.world.entities().constant_count(), + 0, "An entity was spawned after the entity list was cleared last frame and before the extract schedule began. This is not supported", ); diff --git a/examples/ecs/observers.rs b/examples/ecs/observers.rs index 2ac6e23f60fd4..64ed3c87d2321 100644 --- a/examples/ecs/observers.rs +++ b/examples/ecs/observers.rs @@ -33,7 +33,7 @@ fn setup(world: &mut World) { // Can submit commands for any structural changes world.commands().entity(source).remove::(); // Or to raise other events - world.commands().ecs_event(Resize(2, 4)).target(data).emit(); + world.ecs_event(Resize(2, 4)).entity(data).emit(); }); let entity = world From a5e5f5348e5a65171b6d09b658678b4335ee267b Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Fri, 1 Dec 2023 22:52:56 -0800 Subject: [PATCH 043/109] Add data_ptr --- crates/bevy_ecs/src/observer/mod.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 5a98b7cb7468c..8af358708e13d 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -17,7 +17,7 @@ use crate::{ world::*, }; -use bevy_ptr::PtrMut; +use bevy_ptr::{Ptr, PtrMut}; use bevy_utils::{EntityHashMap, HashMap}; use crate::{component::ComponentId, prelude::*, query::WorldQueryFilter, world::DeferredWorld}; @@ -102,6 +102,11 @@ impl<'w, E, Q: WorldQueryData, F: WorldQueryFilter> Observer<'w, E, Q, F> { &mut self.data } + /// Returns a pointer to the data associated with the event that triggered the observer. + pub fn data_ptr(&self) -> Ptr { + Ptr::from(&self.data) + } + /// Returns the entity that triggered the observer. pub fn source(&self) -> Entity { self.trigger.source From 283c44ddbfa65be46a9bec8ed5fe5bfe1c77a69e Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sat, 2 Dec 2023 00:21:26 -0800 Subject: [PATCH 044/109] Avoiding spawning entity during world bootstrap --- crates/bevy_ecs/src/observer/mod.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 8af358708e13d..5f8490fdbbd38 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -433,24 +433,21 @@ impl World { observers.unregister(archetypes, entity, observer); }); - // When any entity is targeted for an `AttachObserver` event add it to `ObservedBy` + // When `AttachObserver` is inserted onto an event add it to `ObservedBy` // or insert `ObservedBy` if it doesn't exist - // Can also use a hooks here instead - self.observer_builder().components::().run( - |mut observer: Observer>| { - let attached_observer = observer.data().0; - if let Some(mut observed_by) = observer.fetch_mut() { + // Can also use an observer here but avoiding spawning entities in the world + self.register_component::() + .on_insert(|mut world, entity, _| { + let attached_observer = world.get::(entity).unwrap().0; + if let Some(mut observed_by) = world.get_mut::(entity) { observed_by.0.push(attached_observer); } else { - let source = observer.source(); - observer - .world_mut() + world .commands() - .entity(source) + .entity(entity) .insert(ObservedBy(vec![attached_observer])); } - }, - ); + }); // When an entity is despawned while being observed by entity observers despawn them self.register_component::() From 91797f64b67d8708fed0ad726516b0455e9d5d7a Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sat, 2 Dec 2023 00:22:07 -0800 Subject: [PATCH 045/109] Move AttachObserver back to SparseSet --- crates/bevy_ecs/src/observer/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 5f8490fdbbd38..da63de43213e8 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -341,9 +341,10 @@ impl Observers { } } -/// EcsEvent to signify an entity observer being attached to an entity +/// Component to signify an entity observer being attached to an entity /// Can be modelled by parent-child relationship if/when that is enforced #[derive(Component)] +#[component(storage = "SparseSet")] pub(crate) struct AttachObserver(pub(crate) Entity); /// Tracks a list of entity observers for the attached entity From 3bef2ce7f54cdfa210a5365f028cc333c358f1ce Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sat, 2 Dec 2023 14:43:40 -0800 Subject: [PATCH 046/109] Add method to register hooks in component trait definition --- crates/bevy_ecs/src/component.rs | 12 +++++++++++- examples/ecs/component_hooks.rs | 13 ++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 7a3b40545ddb1..361087db59060 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -152,6 +152,10 @@ pub trait Component: Send + Sync + 'static { /// A marker type indicating the storage type used for this component. /// This must be either [`TableStorage`] or [`SparseStorage`]. type Storage: ComponentStorage; + + /// Called when registering this component, allowing mutable access to it's [`ComponentInfo`]. + /// This is currently used for registering hooks. + fn init_component_info(_info: &mut ComponentInfo) {} } /// Marker type for components stored in a [`Table`](crate::storage::Table). @@ -538,7 +542,13 @@ impl Components { .. } = self; let index = indices.entry(type_id).or_insert_with(|| { - Components::init_component_inner(components, storages, ComponentDescriptor::new::()) + let index = Components::init_component_inner( + components, + storages, + ComponentDescriptor::new::(), + ); + T::init_component_info(&mut components[index]); + index }); ComponentId(*index) } diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs index 119af24d42754..98df67daab1b5 100644 --- a/examples/ecs/component_hooks.rs +++ b/examples/ecs/component_hooks.rs @@ -1,11 +1,22 @@ //! This examples illustrates the different ways you can employ component lifecycle hooks +use bevy::ecs::component::{ComponentInfo, TableStorage}; use bevy::prelude::*; use std::collections::HashMap; -#[derive(Component, Debug)] +#[derive(Debug)] struct MyComponent(KeyCode); +impl Component for MyComponent { + type Storage = TableStorage; + + /// Hooks can also be registered during component initialisation by + /// implementing `init_component_info` + fn init_component_info(_info: &mut ComponentInfo) { + // Register hooks... + } +} + #[derive(Resource, Default, Debug, Deref, DerefMut)] struct MyComponentIndex(HashMap); From 9cdb86fab46eb7f7fcd30452455e1d6a8c10c763 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sat, 2 Dec 2023 16:18:56 -0800 Subject: [PATCH 047/109] Merge hooks, refactor to take advantage --- crates/bevy_ecs/src/observer/mod.rs | 97 ++++++++++---------------- crates/bevy_ecs/src/observer/runner.rs | 34 ++++++++- crates/bevy_ecs/src/world/mod.rs | 2 - 3 files changed, 68 insertions(+), 65 deletions(-) diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index da63de43213e8..c01bebf1df40b 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -11,6 +11,7 @@ use state::*; use crate::{ self as bevy_ecs, archetype::{ArchetypeFlags, Archetypes}, + component::{ComponentInfo, SparseStorage}, entity::EntityLocation, query::{DebugCheckedUnwrap, FilteredAccess, WorldQuery, WorldQueryData}, system::{EmitEcsEvent, Insert}, @@ -343,15 +344,45 @@ impl Observers { /// Component to signify an entity observer being attached to an entity /// Can be modelled by parent-child relationship if/when that is enforced -#[derive(Component)] -#[component(storage = "SparseSet")] pub(crate) struct AttachObserver(pub(crate) Entity); +impl Component for AttachObserver { + type Storage = SparseStorage; + + // When `AttachObserver` is inserted onto an event add it to `ObservedBy` + // or insert `ObservedBy` if it doesn't exist + fn init_component_info(info: &mut ComponentInfo) { + info.on_insert(|mut world, entity, _| { + let attached_observer = world.get::(entity).unwrap().0; + if let Some(mut observed_by) = world.get_mut::(entity) { + observed_by.0.push(attached_observer); + } else { + world + .commands() + .entity(entity) + .insert(ObservedBy(vec![attached_observer])); + } + }); + } +} + /// Tracks a list of entity observers for the attached entity -#[derive(Component, Default)] -#[component(storage = "SparseSet")] pub(crate) struct ObservedBy(Vec); +impl Component for ObservedBy { + type Storage = SparseStorage; + + fn init_component_info(info: &mut ComponentInfo) { + info.on_remove(|mut world, entity, _| { + let mut component = world.get_mut::(entity).unwrap(); + let observed_by = std::mem::take(&mut component.0); + observed_by.iter().for_each(|&e| { + world.commands().entity(e).despawn(); + }); + }); + } +} + /// Type used to construct and emit a [`EcsEvent`] pub struct EventBuilder<'w, E> { world: DeferredWorld<'w>, @@ -403,64 +434,6 @@ impl<'w, E: EcsEvent> EventBuilder<'w, E> { } impl World { - /// Initialize components and register hooks for types used by [`Observer`]. - pub(crate) fn bootstrap_observers(&mut self) { - // Update event cache when observers are spawned and despawned - self.register_component::() - .on_add(|mut world, entity, _| { - let (world, archetypes, observers) = unsafe { - let world = world.as_unsafe_world_cell(); - ( - world.into_deferred(), - world.archetypes_mut(), - world.observers_mut(), - ) - }; - - let observer = world.get::(entity).unwrap(); - observers.register(archetypes, entity, observer); - }) - .on_remove(|mut world, entity, _| { - let (world, archetypes, observers) = unsafe { - let world = world.as_unsafe_world_cell(); - ( - world.into_deferred(), - world.archetypes_mut(), - world.observers_mut(), - ) - }; - - let observer = world.get::(entity).unwrap(); - observers.unregister(archetypes, entity, observer); - }); - - // When `AttachObserver` is inserted onto an event add it to `ObservedBy` - // or insert `ObservedBy` if it doesn't exist - // Can also use an observer here but avoiding spawning entities in the world - self.register_component::() - .on_insert(|mut world, entity, _| { - let attached_observer = world.get::(entity).unwrap().0; - if let Some(mut observed_by) = world.get_mut::(entity) { - observed_by.0.push(attached_observer); - } else { - world - .commands() - .entity(entity) - .insert(ObservedBy(vec![attached_observer])); - } - }); - - // When an entity is despawned while being observed by entity observers despawn them - self.register_component::() - .on_remove(|mut world, entity, _| { - let observed_by = - std::mem::take(world.get_mut::(entity).unwrap().as_mut()); - observed_by.0.iter().for_each(|&e| { - world.commands().entity(e).despawn(); - }); - }); - } - /// Construct an [`ObserverBuilder`] pub fn observer_builder(&mut self) -> ObserverBuilder { ObserverBuilder::new(self) diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index dd2f0fb3621ef..8937a0eb247b1 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -10,12 +10,44 @@ pub(crate) struct ObserverCallback { /// Typically refers to the default runner defined in [`ObserverComponent::from`] pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, Option)>); -#[derive(Component)] pub(crate) struct ObserverComponent { pub(crate) descriptor: ObserverDescriptor, pub(crate) callback: ObserverCallback, } +impl Component for ObserverComponent { + type Storage = SparseStorage; + + fn init_component_info(info: &mut ComponentInfo) { + info.on_add(|mut world, entity, _| { + let (world, archetypes, observers) = unsafe { + let world = world.as_unsafe_world_cell(); + ( + world.into_deferred(), + world.archetypes_mut(), + world.observers_mut(), + ) + }; + + let observer = world.get::(entity).unwrap(); + observers.register(archetypes, entity, observer); + }) + .on_remove(|mut world, entity, _| { + let (world, archetypes, observers) = unsafe { + let world = world.as_unsafe_world_cell(); + ( + world.into_deferred(), + world.archetypes_mut(), + world.observers_mut(), + ) + }; + + let observer = world.get::(entity).unwrap(); + observers.unregister(archetypes, entity, observer); + }); + } +} + impl ObserverComponent { pub(crate) fn from( descriptor: ObserverDescriptor, diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index bc06d0417885f..c678e431a06ff 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -116,8 +116,6 @@ impl World { assert_eq!(ON_ADD, self.init_component::()); assert_eq!(ON_INSERT, self.init_component::()); assert_eq!(ON_REMOVE, self.init_component::()); - - self.bootstrap_observers(); } /// Creates a new empty [`World`]. /// From 233fd306bd55cb18847868ac83f4d305cb10250f Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sun, 3 Dec 2023 18:36:58 -0800 Subject: [PATCH 048/109] Remove most instances of unsafe in DeferredWorld --- crates/bevy_ecs/src/world/deferred_world.rs | 88 ++++++++++++++------- 1 file changed, 59 insertions(+), 29 deletions(-) diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index f17800dbdf6c0..dfde9cbfa508d 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -10,20 +10,23 @@ use crate::{ system::{Commands, Query, Resource}, }; -use super::{unsafe_world_cell::UnsafeWorldCell, Mut, World}; +use super::{ + unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell}, + EntityMut, Mut, World, +}; /// A [`World`] reference that disallows structural ECS changes. /// This includes initializing resources, registering components or spawning entities. pub struct DeferredWorld<'w> { - world: UnsafeWorldCell<'w>, + // SAFETY: Implementors must not use this reference to make structural changes + world: &'w mut World, } impl<'w> Deref for DeferredWorld<'w> { type Target = World; fn deref(&self) -> &Self::Target { - // SAFETY: &self ensures there are no active mutable borrows - unsafe { self.world.world() } + self.world } } @@ -31,19 +34,49 @@ impl<'w> DeferredWorld<'w> { /// Creates a [`Commands`] instance that pushes to the world's command queue #[inline] pub fn commands(&mut self) -> Commands { - // SAFETY: Commands cannot make structural changes. - unsafe { self.world.world_mut().commands() } + self.world.commands() } /// Retrieves a mutable reference to the given `entity`'s [`Component`] of the given type. /// Returns `None` if the `entity` does not have a [`Component`] of the given type. #[inline] pub fn get_mut(&mut self, entity: Entity) -> Option> { - // SAFETY: - // - `as_unsafe_world_cell` is the only thing that is borrowing world - // - `as_unsafe_world_cell` provides mutable permission to everything - // - `&mut self` ensures no other borrows on world data - unsafe { self.world.get_entity(entity)?.get_mut() } + self.world.get_mut(entity) + } + + /// Retrieves an [`EntityMut`] that exposes read and write operations for the given `entity`. + /// This will panic if the `entity` does not exist. Use [`Self::get_entity_mut`] if you want + /// to check for entity existence instead of implicitly panic-ing. + #[inline] + #[track_caller] + pub fn entity_mut(&mut self, entity: Entity) -> EntityMut { + #[inline(never)] + #[cold] + #[track_caller] + fn panic_no_entity(entity: Entity) -> ! { + panic!("Entity {entity:?} does not exist"); + } + + match self.get_entity_mut(entity) { + Some(entity) => entity, + None => panic_no_entity(entity), + } + } + + /// Retrieves an [`EntityMut`] that exposes read and write operations for the given `entity`. + /// Returns [`None`] if the `entity` does not exist. + /// Instead of unwrapping the value returned from this function, prefer [`Self::entity_mut`]. + #[inline] + pub fn get_entity_mut(&mut self, entity: Entity) -> Option { + let location = self.entities.get(entity)?; + // SAFETY: `entity` exists and `location` is that entity's location + Some(unsafe { + EntityMut::new(UnsafeEntityCell::new( + self.world.as_unsafe_world_cell(), + entity, + location, + )) + }) } /// Returns [`Query`] for the given [`QueryState`], which is used to efficiently @@ -60,11 +93,12 @@ impl<'w> DeferredWorld<'w> { state.update_archetypes(self); // SAFETY: We ran validate_world to ensure our state matches unsafe { + let world_cell = self.world.as_unsafe_world_cell(); Query::new( - self.world, + world_cell, state, - self.world.last_change_tick(), - self.world.change_tick(), + world_cell.last_change_tick(), + world_cell.change_tick(), false, ) } @@ -97,8 +131,7 @@ impl<'w> DeferredWorld<'w> { /// Gets a mutable reference to the resource of the given type if it exists #[inline] pub fn get_resource_mut(&mut self) -> Option> { - // SAFETY: `&mut self` ensures that all accessed data is unaliased - unsafe { self.world.get_resource_mut() } + self.world.get_resource_mut() } /// Gets a mutable reference to the non-send resource of the given type, if it exists. @@ -130,8 +163,7 @@ impl<'w> DeferredWorld<'w> { /// This function will panic if it isn't called from the same thread that the resource was inserted from. #[inline] pub fn get_non_send_resource_mut(&mut self) -> Option> { - // SAFETY: `&mut self` ensures that all accessed data is unaliased - unsafe { self.world.get_non_send_resource_mut() } + self.world.get_non_send_resource_mut() } /// Sends an [`Event`]. @@ -176,8 +208,7 @@ impl<'w> DeferredWorld<'w> { /// use this in cases where the actual types are not known at compile time.** #[inline] pub fn get_resource_mut_by_id(&mut self, component_id: ComponentId) -> Option> { - // SAFETY: `&mut self` ensures that all accessed data is unaliased - unsafe { self.world.get_resource_mut_by_id(component_id) } + self.world.get_resource_mut_by_id(component_id) } /// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists. @@ -191,8 +222,7 @@ impl<'w> DeferredWorld<'w> { /// This function will panic if it isn't called from the same thread that the resource was inserted from. #[inline] pub fn get_non_send_mut_by_id(&mut self, component_id: ComponentId) -> Option> { - // SAFETY: `&mut self` ensures that all accessed data is unaliased - unsafe { self.world.get_non_send_resource_mut_by_id(component_id) } + self.world.get_non_send_mut_by_id(component_id) } /// Retrieves a mutable untyped reference to the given `entity`'s [`Component`] of the given [`ComponentId`]. @@ -206,8 +236,7 @@ impl<'w> DeferredWorld<'w> { entity: Entity, component_id: ComponentId, ) -> Option> { - // SAFETY: `&mut self` ensures that all accessed data is unaliased - unsafe { self.world.get_entity(entity)?.get_mut_by_id(component_id) } + self.world.get_mut_by_id(entity, component_id) } /// Triggers all `on_add` hooks for [`ComponentId`] in target. @@ -272,17 +301,18 @@ impl<'w> UnsafeWorldCell<'w> { /// Turn self into a [`DeferredWorld`] /// /// # Safety - /// Caller must ensure there are no outstanding references to the world's command queue, resource or component data + /// Caller must ensure there are no outstanding mutable references to world and no + /// outstanding references to the world's command queue, resource or component data #[inline] pub unsafe fn into_deferred(self) -> DeferredWorld<'w> { - DeferredWorld { world: self } + DeferredWorld { + world: self.world_mut(), + } } } impl<'w> From<&'w mut World> for DeferredWorld<'w> { fn from(world: &'w mut World) -> DeferredWorld<'w> { - DeferredWorld { - world: world.as_unsafe_world_cell(), - } + DeferredWorld { world } } } From cdde4d2137b7180665384f20f84896f7d4d2ef9d Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Mon, 11 Dec 2023 18:54:08 -0800 Subject: [PATCH 049/109] Simplify code in BundleInserter --- crates/bevy_ecs/src/bundle.rs | 66 +++++++++++------------------------ 1 file changed, 21 insertions(+), 45 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 0a7284176e077..6d572dd7425e6 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -15,7 +15,7 @@ use crate::{ prelude::World, query::DebugCheckedUnwrap, storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow}, - world::unsafe_world_cell::UnsafeWorldCell, + world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld}, TypeIdMap, }; use bevy_ptr::OwningPtr; @@ -632,6 +632,21 @@ impl<'w> BundleInserter<'w> { let bundle_info = &*self.bundle_info; let add_bundle = &*self.add_bundle; let archetype = &*self.archetype; + let trigger_hooks = |archetype: &Archetype, mut world: DeferredWorld| { + if archetype.has_on_add() { + world.trigger_on_add( + entity, + bundle_info + .iter_components() + .zip(add_bundle.bundle_status.iter()) + .filter(|(_, &status)| status == ComponentStatus::Added) + .map(|(id, _)| id), + ); + } + if archetype.has_on_insert() { + world.trigger_on_insert(entity, bundle_info.iter_components()); + } + }; match &mut self.result { InsertBundleResult::SameArchetype => { { @@ -652,20 +667,7 @@ impl<'w> BundleInserter<'w> { ); } // SAFETY: We have no oustanding mutable references to world as they were dropped - let mut world = self.world.into_deferred(); - if archetype.has_on_add() { - world.trigger_on_add( - entity, - bundle_info - .iter_components() - .zip(add_bundle.bundle_status.iter()) - .filter(|(_, &status)| status == ComponentStatus::Added) - .map(|(id, _)| id), - ); - } - if archetype.has_on_insert() { - world.trigger_on_insert(entity, bundle_info.iter_components()); - } + trigger_hooks(archetype, self.world.into_deferred()); location } InsertBundleResult::NewArchetypeSameTable { new_archetype } => { @@ -709,21 +711,8 @@ impl<'w> BundleInserter<'w> { }; // SAFETY: We have no oustanding mutable references to world as they were dropped - let new_archetype = &**new_archetype; - let mut world = self.world.into_deferred(); - if new_archetype.has_on_add() { - world.trigger_on_add( - entity, - bundle_info - .iter_components() - .zip(add_bundle.bundle_status.iter()) - .filter(|(_, &status)| status == ComponentStatus::Added) - .map(|(id, _)| id), - ); - } - if new_archetype.has_on_insert() { - world.trigger_on_insert(entity, bundle_info.iter_components()); - } + trigger_hooks(&**new_archetype, self.world.into_deferred()); + new_location } InsertBundleResult::NewArchetypeNewTable { @@ -806,21 +795,8 @@ impl<'w> BundleInserter<'w> { }; // SAFETY: We have no oustanding mutable references to world as they were dropped - let new_archetype = &**new_archetype; - let mut world = self.world.into_deferred(); - if new_archetype.has_on_add() { - world.trigger_on_add( - entity, - bundle_info - .iter_components() - .zip(add_bundle.bundle_status.iter()) - .filter(|(_, &status)| status == ComponentStatus::Added) - .map(|(id, _)| id), - ); - } - if new_archetype.has_on_insert() { - world.trigger_on_insert(entity, bundle_info.iter_components()); - } + trigger_hooks(&**new_archetype, self.world.into_deferred()); + new_location } } From fa42ed2398ca2bea47d3ed2a985e028ba0851359 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Thu, 14 Dec 2023 12:31:44 -0800 Subject: [PATCH 050/109] Partial FnMut implementation --- crates/bevy_ecs/src/observer/builder.rs | 4 +- crates/bevy_ecs/src/observer/mod.rs | 21 ++- crates/bevy_ecs/src/observer/runner.rs | 144 ++++++++++++--------- crates/bevy_ecs/src/system/commands/mod.rs | 27 ++-- crates/bevy_ecs/src/world/entity_ref.rs | 5 +- examples/ecs/observers.rs | 1 + 6 files changed, 116 insertions(+), 86 deletions(-) diff --git a/crates/bevy_ecs/src/observer/builder.rs b/crates/bevy_ecs/src/observer/builder.rs index 10444cf130d8e..698b6226f5cff 100644 --- a/crates/bevy_ecs/src/observer/builder.rs +++ b/crates/bevy_ecs/src/observer/builder.rs @@ -69,7 +69,7 @@ impl<'w, E: EcsEvent> ObserverBuilder<'w, E> { /// Spawns the resulting observer into the world. pub fn run( &mut self, - callback: fn(Observer), + callback: impl ObserverCallback + 'static, ) -> Entity { let entity = self.enqueue(callback); self.world.flush_commands(); @@ -91,7 +91,7 @@ impl<'w, E: EcsEvent> ObserverBuilder<'w, E> { /// Enqueues a command to spawn the resulting observer in the world. pub fn enqueue( &mut self, - callback: fn(Observer), + callback: impl ObserverCallback + 'static, ) -> Entity { self.world .spawn_observer::(ObserverComponent::from(self.descriptor.clone(), callback)) diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index c01bebf1df40b..a5ddccb94a2c2 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -124,7 +124,7 @@ impl<'w, E, Q: WorldQueryData, F: WorldQueryFilter> Observer<'w, E, Q, F> { } } -#[derive(Default, Clone, Component)] +#[derive(Default, Clone)] pub(crate) struct ObserverDescriptor { events: Vec, components: Vec, @@ -141,8 +141,8 @@ pub struct ObserverTrigger { #[derive(Default, Debug)] pub(crate) struct CachedObservers { - component_observers: HashMap>, - entity_observers: EntityHashMap>, + component_observers: HashMap>, + entity_observers: EntityHashMap>, } /// Metadata for observers. Stores a cache mapping event ids to the registered observers. @@ -196,7 +196,7 @@ impl Observers { let cache = self.get_observers(event); for &component in &observer.descriptor.components { let observers = cache.component_observers.entry(component).or_default(); - observers.insert(entity, observer.callback); + observers.insert(entity, observer.runner); if observers.len() == 1 { if let Some(flag) = Self::is_archetype_cached(event) { archetypes.update_flags(component, flag, true); @@ -205,7 +205,7 @@ impl Observers { } for &source in &observer.descriptor.sources { let observers = cache.entity_observers.entry(source).or_default(); - observers.insert(entity, observer.callback); + observers.insert(entity, observer.runner); } } } @@ -259,7 +259,7 @@ impl Observers { // Run entity observers for source if let Some(observers) = observers.entity_observers.get(&source) { observers.iter().for_each(|(&observer, runner)| { - (runner.run)( + (runner)( world.reborrow(), ObserverTrigger { observer, @@ -268,14 +268,13 @@ impl Observers { source, }, data.into(), - runner.callback, ); }); } // Run component observers for ANY if let Some(observers) = observers.component_observers.get(&ANY) { observers.iter().for_each(|(&observer, runner)| { - (runner.run)( + (runner)( world.reborrow(), ObserverTrigger { observer, @@ -284,7 +283,6 @@ impl Observers { source, }, data.into(), - runner.callback, ) }) } @@ -292,7 +290,7 @@ impl Observers { for component in components { if let Some(observers) = observers.component_observers.get(&component) { observers.iter().for_each(|(&observer, runner)| { - (runner.run)( + (runner)( world.reborrow(), ObserverTrigger { observer, @@ -301,7 +299,6 @@ impl Observers { source, }, data.into(), - runner.callback, ); }); } @@ -444,7 +441,7 @@ impl World { /// For observing events targetting a specific entity see [`EntityWorldMut::observe`]. pub fn observer( &mut self, - callback: fn(Observer), + callback: impl ObserverCallback + 'static, ) -> Entity { ObserverBuilder::new(self).run(callback) } diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index 8937a0eb247b1..e8104a1c9e4f3 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -1,18 +1,27 @@ use super::*; -#[derive(Copy, Clone, Debug)] -pub(crate) struct ObserverCallback { - pub(crate) run: ObserverRunner, - pub(crate) callback: Option)>, -} - /// Type for function that is run when an observer is triggered /// Typically refers to the default runner defined in [`ObserverComponent::from`] -pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, Option)>); +pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut); + +/// Trait that is implemented for all functions that can be used as [`Observer`] callbacks +pub trait ObserverCallback: Send + Sync { + /// Invokes the callback with the passed [`Observer`] + fn call(&mut self, observer: Observer); +} + +impl) + Send + Sync> + ObserverCallback for C +{ + fn call(&mut self, observer: Observer) { + self(observer) + } +} pub(crate) struct ObserverComponent { pub(crate) descriptor: ObserverDescriptor, - pub(crate) callback: ObserverCallback, + pub(crate) runner: ObserverRunner, + pub(crate) callback: Option>>, } impl Component for ObserverComponent { @@ -51,75 +60,88 @@ impl Component for ObserverComponent { impl ObserverComponent { pub(crate) fn from( descriptor: ObserverDescriptor, - value: fn(Observer), + value: impl ObserverCallback + 'static, ) -> Self { Self { descriptor, - callback: ObserverCallback { - run: |mut world, trigger, ptr, callback| { - if trigger.source == Entity::PLACEHOLDER { - return; - } - let callback: fn(Observer) = - unsafe { std::mem::transmute(callback.debug_checked_unwrap()) }; - let state = unsafe { - let mut state = world - .get_mut::>(trigger.observer) - .debug_checked_unwrap(); - let state: *mut ObserverState = state.as_mut(); - &mut *state - }; - // This being stored in a component is not ideal, should be able to check this before fetching - let last_event = world.last_event_id; - if state.last_event_id == last_event { - return; - } - state.last_event_id = last_event; + runner: |mut world, trigger, ptr| { + if trigger.source == Entity::PLACEHOLDER { + return; + } + println!("Trigger: {:?}", std::any::type_name::<(E, Q, F)>()); + let world = world.as_unsafe_world_cell(); + let observer_cell = + unsafe { world.get_entity(trigger.observer).debug_checked_unwrap() }; + let mut state = unsafe { + observer_cell + .get_mut::>() + .debug_checked_unwrap() + }; - let archetype_id = trigger.location.archetype_id; - let archetype = &world.archetypes()[archetype_id]; - if !Q::matches_component_set(&state.fetch_state, &mut |id| { - archetype.contains(id) - }) || !F::matches_component_set(&state.filter_state, &mut |id| { + // This being stored in a component is not ideal, should be able to check this before fetching + let last_event = unsafe { world.world() }.last_event_id; + if state.last_event_id == last_event { + return; + } + state.last_event_id = last_event; + + let archetype_id = trigger.location.archetype_id; + let archetype = &world.archetypes()[archetype_id]; + if !Q::matches_component_set(&state.fetch_state, &mut |id| archetype.contains(id)) + || !F::matches_component_set(&state.filter_state, &mut |id| { archetype.contains(id) - }) { - return; - } + }) + { + return; + } - // TODO: Change ticks? - unsafe { - let mut filter_fetch = F::init_fetch( - world.as_unsafe_world_cell_readonly(), - &state.filter_state, - world.last_change_tick(), - world.read_change_tick(), - ); + // TODO: Change ticks? + unsafe { + let mut filter_fetch = F::init_fetch( + world, + &state.filter_state, + world.last_change_tick(), + world.change_tick(), + ); - if !F::filter_fetch( - &mut filter_fetch, - trigger.source, - trigger.location.table_row, - ) { - return; - } + if !F::filter_fetch( + &mut filter_fetch, + trigger.source, + trigger.location.table_row, + ) { + return; } + } + let mut component = unsafe { + observer_cell + .get_mut::() + .debug_checked_unwrap() + }; + if let Some(callback) = &mut component.callback { // SAFETY: Pointer is valid as we just created it, ObserverState is a private type and so will not be aliased - let observer = Observer::new(world, state, unsafe { ptr.deref_mut() }, trigger); - callback(observer); - }, - callback: Some(unsafe { std::mem::transmute(value) }), + let observer = Observer::new( + unsafe { world.into_deferred() }, + state.as_mut(), + unsafe { ptr.deref_mut() }, + trigger, + ); + let callback: &mut Box) + Send + Sync> = + unsafe { std::mem::transmute(callback) }; + callback.call(observer); + } }, + callback: Some(unsafe { + std::mem::transmute(Box::new(value) as Box>) + }), } } - pub(crate) fn from_runner(descriptor: ObserverDescriptor, run: ObserverRunner) -> Self { + pub(crate) fn from_runner(descriptor: ObserverDescriptor, runner: ObserverRunner) -> Self { Self { descriptor, - callback: ObserverCallback { - run, - callback: None, - }, + runner, + callback: None, } } } diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 9c5c66b12456e..51310e06ca0c4 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -6,8 +6,7 @@ use crate::{ bundle::Bundle, component::ComponentId, entity::{Entities, Entity}, - observer::EcsEvent, - prelude::Observer, + observer::{EcsEvent, ObserverCallback}, query::{WorldQueryData, WorldQueryFilter}, system::{RunSystemWithInput, SystemId}, world::{EntityWorldMut, FromWorld, World}, @@ -940,11 +939,12 @@ impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> { /// In order to trigger the callback the entity must also match the query when the event is fired. pub fn observe( &mut self, - callback: fn(Observer), + callback: impl ObserverCallback + 'static, ) -> &mut Self { - self.commands.add(Observe:: { + self.commands.add(Observe:: { entity: self.entity, callback, + marker: PhantomData::default(), }); self } @@ -1193,15 +1193,26 @@ impl Command for LogComponents { /// A [`Command`] that spawns an observer attached to a specific entity. #[derive(Debug)] -pub struct Observe { +pub struct Observe< + E: EcsEvent, + Q: WorldQueryData, + F: WorldQueryFilter, + C: ObserverCallback, +> { /// The entity that will be observed. pub entity: Entity, /// The callback to run when the event is observed. - pub callback: fn(Observer), + pub callback: C, + /// Marker for type parameters + pub marker: PhantomData>, } -impl Command - for Observe +impl< + E: EcsEvent, + Q: WorldQueryData + 'static, + F: WorldQueryFilter + 'static, + C: ObserverCallback + 'static, + > Command for Observe { fn apply(self, world: &mut World) { world.entity_mut(self.entity).observe(self.callback); diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index b9c14981ff6d3..56da9afd00161 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -4,8 +4,7 @@ use crate::{ change_detection::MutUntyped, component::{Component, ComponentId, ComponentTicks, Components, StorageType}, entity::{Entities, Entity, EntityLocation}, - observer::{AttachObserver, EcsEvent, ObserverBuilder, Observers}, - prelude::Observer, + observer::{AttachObserver, EcsEvent, ObserverBuilder, ObserverCallback, Observers}, query::{WorldQueryData, WorldQueryFilter}, removal_detection::RemovedComponentEvents, storage::Storages, @@ -1148,7 +1147,7 @@ impl<'w> EntityWorldMut<'w> { /// In order to trigger the callback the entity must also match the query when the event is fired. pub fn observe( &mut self, - callback: fn(Observer), + callback: impl ObserverCallback + 'static, ) -> &mut Self { let observer = ObserverBuilder::new(self.world) .source(self.entity) diff --git a/examples/ecs/observers.rs b/examples/ecs/observers.rs index 64ed3c87d2321..378c784561d0c 100644 --- a/examples/ecs/observers.rs +++ b/examples/ecs/observers.rs @@ -24,6 +24,7 @@ fn setup(world: &mut World) { // Triggered when &CompA is added to any component that also has ComponentB // This can take any query types that implement WorldQueryData and WorldQueryFilter let observer = world.observer(|mut observer: Observer>| { + println!("Hello"); // Get source entity that triggered the observer let source = observer.source(); // Able to read requested component data as if it was a query From 298c598d23200a12ad26f6c30d8952adf8bad3e2 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Thu, 14 Dec 2023 12:50:38 -0800 Subject: [PATCH 051/109] Address non-complex feedback --- crates/bevy_ecs/src/world/entity_ref.rs | 6 +----- crates/bevy_ecs/src/world/unsafe_world_cell.rs | 6 ++---- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index bee07ea5f35f1..5fe0a069431fb 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -706,9 +706,6 @@ impl<'w> EntityWorldMut<'w> { } } - for component_id in bundle_info.iter_components() { - world.removed_components.send(component_id, entity); - } let archetypes = &mut world.archetypes; let storages = &mut world.storages; let components = &mut world.components; @@ -844,7 +841,7 @@ impl<'w> EntityWorldMut<'w> { let location = self.location; let bundle_info = world.bundles.get_unchecked(bundle); - // SAFETY: `archetype_id` exists because it is referenced in `old_location` which is valid + // SAFETY: `archetype_id` exists because it is referenced in `location` which is valid // and components in `bundle_info` must exist due to this functions safety invariants. let new_archetype_id = remove_bundle_from_archetype( &mut world.archetypes, @@ -971,7 +968,6 @@ impl<'w> EntityWorldMut<'w> { } } - let archetype = &world.archetypes[self.location.archetype_id]; for component_id in archetype.components() { world.removed_components.send(component_id, self.entity); } diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 6699d1dab3215..eb4ba71681d1b 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -17,7 +17,7 @@ use crate::{ system::{CommandQueue, Resource}, }; use bevy_ptr::Ptr; -use std::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData}; +use std::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData, ptr::addr_of_mut}; /// Variant of the [`World`] where resource and component accesses take `&self`, and the responsibility to avoid /// aliasing violations are given to the caller instead of being checked at compile-time by rust's unique XOR shared rule. @@ -576,9 +576,7 @@ impl<'w> UnsafeWorldCell<'w> { // SAFETY: // - caller ensures there are no existing mutable references // - caller ensures that we have permission to access the queue - let ptr = unsafe { &mut (*self.0).command_queue as *mut _ }; - // SAFETY: this pointer is valid as we just constructed it - unsafe { &mut *ptr } + unsafe { &mut *addr_of_mut!((*self.0).command_queue) } } } From d800e7be6a0ed8d55bebba374c5d271fafc38d1d Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Thu, 14 Dec 2023 13:00:01 -0800 Subject: [PATCH 052/109] Merge main --- crates/bevy_ecs/src/world/deferred_world.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index b8675d46c0eb7..9950a953eea58 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -6,7 +6,7 @@ use crate::{ entity::Entity, event::{Event, EventId, Events, SendBatchIds}, prelude::{Component, QueryState}, - query::{WorldQueryData, WorldQueryFilter}, + query::{QueryData, QueryFilter}, system::{Commands, Query, Resource}, }; @@ -83,10 +83,10 @@ impl<'w> DeferredWorld<'w> { /// # Panics /// If state is from a different world then self #[inline] - pub fn query<'s, Q: WorldQueryData, F: WorldQueryFilter>( + pub fn query<'s, D: QueryData, F: QueryFilter>( &'w mut self, - state: &'s mut QueryState, - ) -> Query<'w, 's, Q, F> { + state: &'s mut QueryState, + ) -> Query<'w, 's, D, F> { state.validate_world(self.world.id()); state.update_archetypes(self); // SAFETY: We ran validate_world to ensure our state matches From 642fb4394909dfa2a5e1b440e8be0b0c8c25863c Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Thu, 14 Dec 2023 13:21:19 -0800 Subject: [PATCH 053/109] Address soundness concerns --- crates/bevy_ecs/src/bundle.rs | 44 ++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 6d572dd7425e6..96895ea956ff8 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -629,10 +629,10 @@ impl<'w> BundleInserter<'w> { bundle: T, ) -> EntityLocation { // SAFETY: We do not make any structural changes to the archetype graph through self.world so these pointers always remain valid - let bundle_info = &*self.bundle_info; - let add_bundle = &*self.add_bundle; - let archetype = &*self.archetype; let trigger_hooks = |archetype: &Archetype, mut world: DeferredWorld| { + let bundle_info = &*self.bundle_info; + let add_bundle = &*self.add_bundle; + if archetype.has_on_add() { world.trigger_on_add( entity, @@ -656,6 +656,9 @@ impl<'w> BundleInserter<'w> { &mut world.storages.sparse_sets }; let table = &mut *self.table; + let bundle_info = &*self.bundle_info; + let add_bundle = &*self.add_bundle; + bundle_info.write_components( table, sparse_sets, @@ -667,19 +670,20 @@ impl<'w> BundleInserter<'w> { ); } // SAFETY: We have no oustanding mutable references to world as they were dropped + let archetype = &*self.archetype; trigger_hooks(archetype, self.world.into_deferred()); location } InsertBundleResult::NewArchetypeSameTable { new_archetype } => { let new_location = { // SAFETY: Mutable references do not alias and will be dropped after this block - let table = &mut *self.table; - let archetype = &mut *self.archetype; - let new_archetype = &mut **new_archetype; let (sparse_sets, entities) = { let world = self.world.world_mut(); (&mut world.storages.sparse_sets, &mut world.entities) }; + let table = &mut *self.table; + let archetype = &mut *self.archetype; + let new_archetype = &mut **new_archetype; let result = archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { @@ -698,6 +702,9 @@ impl<'w> BundleInserter<'w> { } let new_location = new_archetype.allocate(entity, result.table_row); entities.set(entity.index(), new_location); + + let bundle_info = &*self.bundle_info; + let add_bundle = &*self.add_bundle; bundle_info.write_components( table, sparse_sets, @@ -721,18 +728,20 @@ impl<'w> BundleInserter<'w> { } => { let new_location = { // SAFETY: Mutable references do not alias and will be dropped after this block - let table = &mut *self.table; - let new_table = &mut **new_table; - let archetype = &mut *self.archetype; - let new_archetype = &mut **new_archetype; - let (archetypes, sparse_sets, entities) = { + let (archetypes_ptr, sparse_sets, entities) = { let world = self.world.world_mut(); + let archetype_ptr: *mut Archetype = + world.archetypes.archetypes.as_mut_ptr(); ( - &mut world.archetypes, + archetype_ptr, &mut world.storages.sparse_sets, &mut world.entities, ) }; + let table = &mut *self.table; + let new_table = &mut **new_table; + let archetype = &mut *self.archetype; + let new_archetype = &mut **new_archetype; let result = archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { let swapped_location = @@ -765,7 +774,7 @@ impl<'w> BundleInserter<'w> { new_archetype } else { // SAFETY: the only two borrowed archetypes are above and we just did collision checks - &mut archetypes.archetypes[swapped_location.archetype_id.index()] + &mut *archetypes_ptr.add(swapped_location.archetype_id.index()) }; entities.set( @@ -781,6 +790,8 @@ impl<'w> BundleInserter<'w> { .set_entity_table_row(swapped_location.archetype_row, result.table_row); } + let bundle_info = &*self.bundle_info; + let add_bundle = &*self.add_bundle; bundle_info.write_components( new_table, sparse_sets, @@ -873,17 +884,17 @@ impl<'w> BundleSpawner<'w> { bundle: T, ) -> EntityLocation { // SAFETY: We do not make any structural changes to the archetype graph through self.world so this pointer always remain valid - let bundle_info = &*self.bundle_info; let location = { // SAFETY: Mutable references do not alias and will be dropped after this block - let table = &mut *self.table; - let archetype = &mut *self.archetype; let (sparse_sets, entities) = { let world = self.world.world_mut(); (&mut world.storages.sparse_sets, &mut world.entities) }; + let table = &mut *self.table; + let archetype = &mut *self.archetype; let table_row = table.allocate(entity); let location = archetype.allocate(entity, table_row); + let bundle_info = &*self.bundle_info; bundle_info.write_components( table, sparse_sets, @@ -899,6 +910,7 @@ impl<'w> BundleSpawner<'w> { // SAFETY: We have no oustanding mutable references to world as they were dropped let archetype = &*self.archetype; + let bundle_info = &*self.bundle_info; let mut world = self.world.into_deferred(); if archetype.has_on_add() { world.trigger_on_add(entity, bundle_info.iter_components()); From 95cfbe3262e8647ddd39cef9582c8b2b529c38d2 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Tue, 16 Jan 2024 10:25:49 -0800 Subject: [PATCH 054/109] Improve docs --- crates/bevy_ecs/src/component.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 84246f453670c..cb87ee6e6fe22 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -313,6 +313,7 @@ impl ComponentInfo { } /// Register a [`ComponentHook`] that will be run when this component is removed from an entity. + /// Despawning an entity counts as removing all of it's components. /// /// Will panic if the component already has an `on_remove` hook pub fn on_remove(&mut self, hook: ComponentHook) -> &mut Self { From 0dfe890129fedba96dbf02fa8bf8381affe62bcf Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Mon, 29 Jan 2024 23:16:39 -0800 Subject: [PATCH 055/109] System refactor --- crates/bevy_ecs/macros/src/lib.rs | 4 + crates/bevy_ecs/src/archetype.rs | 2 +- crates/bevy_ecs/src/bundle.rs | 6 +- crates/bevy_ecs/src/observer/builder.rs | 17 +- crates/bevy_ecs/src/observer/mod.rs | 160 +++++++++--------- crates/bevy_ecs/src/observer/runner.rs | 47 ++--- crates/bevy_ecs/src/system/combinator.rs | 2 +- crates/bevy_ecs/src/system/commands/mod.rs | 29 ++-- crates/bevy_ecs/src/system/function_system.rs | 6 +- crates/bevy_ecs/src/system/mod.rs | 6 +- crates/bevy_ecs/src/system/observer_system.rs | 74 ++++++++ crates/bevy_ecs/src/system/system.rs | 1 - crates/bevy_ecs/src/system/system_param.rs | 17 +- crates/bevy_ecs/src/world/deferred_world.rs | 23 ++- crates/bevy_ecs/src/world/entity_ref.rs | 11 +- .../bevy_ecs/src/world/unsafe_world_cell.rs | 8 +- examples/ecs/observers.rs | 7 +- 17 files changed, 247 insertions(+), 173 deletions(-) create mode 100644 crates/bevy_ecs/src/system/observer_system.rs diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index f9d3f0314ba02..f568b4c6e024c 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -429,6 +429,10 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { <#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::apply(&mut state.state, system_meta, world); } + fn queue(state: &mut Self::State, system_meta: &#path::system::SystemMeta, world: #path::world::DeferredWorld) { + <#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::queue(&mut state.state, system_meta, world); + } + unsafe fn get_param<'w, 's>( state: &'s mut Self::State, system_meta: &#path::system::SystemMeta, diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 0cb0b2a6eb4b2..630457b3e1f41 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -846,7 +846,7 @@ impl Archetypes { // This is terrible, we need to refactor the component index for archetype in &mut self.archetypes { if archetype.contains(component_id) { - archetype.flags.set(flags, set) + archetype.flags.set(flags, set); } } } diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 59d72c4d800fc..5bb72d3827a53 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -640,7 +640,7 @@ impl<'w> BundleInserter<'w> { let bundle_info = &*self.bundle_info; let add_bundle = &*self.add_bundle; - world.trigger_on_add(archetype, entity, bundle_info.added.iter().cloned()); + world.trigger_on_add(archetype, entity, add_bundle.added.iter().cloned()); if archetype.has_add_observer() { world.trigger_observers(ON_ADD, entity, location, add_bundle.added.iter().cloned()); } @@ -919,11 +919,11 @@ impl<'w> BundleSpawner<'w> { let mut world = self.world.into_deferred(); world.trigger_on_add(archetype, entity, bundle_info.iter_components()); if archetype.has_add_observer() { - world.trigger_observers(ON_ADD, entity, location, bundle_info.iter_components()) + world.trigger_observers(ON_ADD, entity, location, bundle_info.iter_components()); } world.trigger_on_insert(archetype, entity, bundle_info.iter_components()); if archetype.has_insert_observer() { - world.trigger_observers(ON_INSERT, entity, location, bundle_info.iter_components()) + world.trigger_observers(ON_INSERT, entity, location, bundle_info.iter_components()); } location } diff --git a/crates/bevy_ecs/src/observer/builder.rs b/crates/bevy_ecs/src/observer/builder.rs index 234c2923e818c..4d33c87ad1dda 100644 --- a/crates/bevy_ecs/src/observer/builder.rs +++ b/crates/bevy_ecs/src/observer/builder.rs @@ -22,7 +22,7 @@ impl<'w, E: EcsEvent> ObserverBuilder<'w, E> { Self { world, descriptor, - _marker: PhantomData::default(), + _marker: PhantomData, } } @@ -69,7 +69,7 @@ impl<'w, E: EcsEvent> ObserverBuilder<'w, E> { } /// Spawns the resulting observer into the world. - pub fn run(&mut self, callback: impl IntoObserverSystem + 'static) -> Entity { + pub fn run(&mut self, callback: impl IntoObserverSystem) -> Entity { let entity = self.enqueue(callback); self.world.flush_commands(); entity @@ -85,19 +85,18 @@ impl<'w, E: EcsEvent> ObserverBuilder<'w, E> { } /// Enqueues a command to spawn the resulting observer in the world. - pub fn enqueue(&mut self, callback: impl IntoObserverSystem) -> Entity { + pub fn enqueue(&mut self, callback: impl IntoObserverSystem) -> Entity { let component = ObserverComponent::from(self.world, self.descriptor.clone(), callback); - self.world.spawn_observer::(component) + self.world.spawn_observer(component) } /// Enqueues a command to spawn the resulting observer in the world using a [`ObserverRunner`] callback. /// This is not advised unless you want to respond to events that may not be associated with an entity /// or otherwise want to override the default runner behaviour. pub fn enqueue_runner(&mut self, runner: ObserverRunner) -> Entity { - self.world - .spawn_observer::(ObserverComponent::from_runner( - self.descriptor.clone(), - runner, - )) + self.world.spawn_observer(ObserverComponent::from_runner( + self.descriptor.clone(), + runner, + )) } } diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 954a8ba172c5f..fd35252c421bc 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -7,11 +7,11 @@ pub use builder::*; pub use runner::*; use crate::{ - archetype::{ArchetypeFlags, Archetypes}, + archetype::ArchetypeFlags, component::{ComponentInfo, SparseStorage}, entity::EntityLocation, query::DebugCheckedUnwrap, - system::{EmitEcsEvent, Insert, IntoObserverSystem}, + system::{EmitEcsEvent, IntoObserverSystem}, world::*, }; @@ -44,12 +44,12 @@ impl<'w, E> Observer<'w, E> { /// Returns a reference to the data associated with the event that triggered the observer. pub fn data(&self) -> &E { - &self.data + self.data } /// Returns a mutable reference to the data associated with the event that triggered the observer. pub fn data_mut(&mut self) -> &mut E { - &mut self.data + self.data } /// Returns a pointer to the data associated with the event that triggered the observer. @@ -125,76 +125,23 @@ impl Observers { } } - pub(crate) fn register( - &mut self, - archetypes: &mut Archetypes, - entity: Entity, - observer: &ObserverComponent, - ) { - for &event in &observer.descriptor.events { - let cache = self.get_observers(event); - for &component in &observer.descriptor.components { - let observers = cache.component_observers.entry(component).or_default(); - observers.insert(entity, observer.runner); - if observers.len() == 1 { - if let Some(flag) = Self::is_archetype_cached(event) { - archetypes.update_flags(component, flag, true); - } - } - } - for &source in &observer.descriptor.sources { - let observers = cache.entity_observers.entry(source).or_default(); - observers.insert(entity, observer.runner); - } - } - } - - pub(crate) fn unregister( - &mut self, - archetypes: &mut Archetypes, - entity: Entity, - observer: &ObserverComponent, - ) { - for &event in &observer.descriptor.events { - let Some(cache) = self.try_get_observers_mut(event) else { - continue; - }; - for component in &observer.descriptor.components { - let Some(observers) = cache.component_observers.get_mut(component) else { - continue; - }; - observers.remove(&entity); - if observers.is_empty() { - cache.component_observers.remove(component); - if let Some(flag) = Self::is_archetype_cached(event) { - archetypes.update_flags(*component, flag, false); - } - } - } - for source in &observer.descriptor.sources { - let Some(observers) = cache.entity_observers.get_mut(source) else { - continue; - }; - observers.remove(&entity); - if observers.is_empty() { - cache.entity_observers.remove(source); - } - } - } - } - pub(crate) fn invoke( - &self, + mut world: DeferredWorld, event: ComponentId, source: Entity, location: EntityLocation, components: impl Iterator, - mut world: DeferredWorld, data: &mut E, ) { - let Some(observers) = self.try_get_observers(event) else { - return; + let (mut world, observers) = unsafe { + let world = world.as_unsafe_world_cell(); + let observers = world.observers(); + let Some(observers) = observers.try_get_observers(event) else { + return; + }; + (world.into_deferred(), observers) }; + // Run entity observers for source if let Some(observers) = observers.entity_observers.get(&source) { observers.iter().for_each(|(&observer, runner)| { @@ -222,8 +169,8 @@ impl Observers { source, }, data.into(), - ) - }) + ); + }); } // Run component observers for each component for component in components { @@ -377,10 +324,7 @@ impl World { } /// Spawn an [`Observer`] and returns it's [`Entity`] - pub fn observer( - &mut self, - callback: impl IntoObserverSystem + 'static, - ) -> Entity { + pub fn observer(&mut self, callback: impl IntoObserverSystem) -> Entity { ObserverBuilder::new(self).run(callback) } @@ -391,10 +335,7 @@ impl World { EventBuilder::new(event, self.commands()) } - pub(crate) fn spawn_observer( - &mut self, - mut observer: ObserverComponent, - ) -> Entity { + pub(crate) fn spawn_observer(&mut self, mut observer: ObserverComponent) -> Entity { let components = &mut observer.descriptor.components; let sources = &observer.descriptor.sources; // If the observer has no explicit targets use the accesses of the query @@ -402,11 +343,72 @@ impl World { components.push(ANY); } let entity = self.entities.reserve_entity(); - self.command_queue.push(Insert { - entity, - bundle: observer, + + self.command_queue.push(move |world: &mut World| { + if let Some(mut entity) = world.get_entity_mut(entity) { + entity.insert(observer); + } }); entity } + + pub(crate) fn register_observer(&mut self, entity: Entity) { + let observer_component: *const ObserverComponent = + self.get::(entity).unwrap(); + // TODO: Make less nasty + let observer_component = unsafe { &*observer_component }; + + for &event in &observer_component.descriptor.events { + let cache = self.observers.get_observers(event); + for &component in &observer_component.descriptor.components { + let observers = cache.component_observers.entry(component).or_default(); + observers.insert(entity, observer_component.runner); + if observers.len() == 1 { + if let Some(flag) = Observers::is_archetype_cached(event) { + self.archetypes.update_flags(component, flag, true); + } + } + } + for &source in &observer_component.descriptor.sources { + let observers = cache.entity_observers.entry(source).or_default(); + observers.insert(entity, observer_component.runner); + } + } + } + + pub(crate) fn unregister_observer(&mut self, entity: Entity) { + let observer_component: *const ObserverComponent = + self.get::(entity).unwrap(); + + // TODO: Make less nasty + let observer_component = unsafe { &*observer_component }; + + for &event in &observer_component.descriptor.events { + let Some(cache) = self.observers.try_get_observers_mut(event) else { + continue; + }; + for component in &observer_component.descriptor.components { + let Some(observers) = cache.component_observers.get_mut(component) else { + continue; + }; + observers.remove(&entity); + if observers.is_empty() { + cache.component_observers.remove(component); + if let Some(flag) = Observers::is_archetype_cached(event) { + self.archetypes.update_flags(*component, flag, false); + } + } + } + for source in &observer_component.descriptor.sources { + let Some(observers) = cache.entity_observers.get_mut(source) else { + continue; + }; + observers.remove(&entity); + if observers.is_empty() { + cache.entity_observers.remove(source); + } + } + } + } } diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index 4e8981f0781ab..acbf0b2017910 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -6,13 +6,12 @@ use super::*; /// Typically refers to the default runner defined in [`ObserverComponent::from`] pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut); -type BoxedObserverSystem = - Box, Out = ()>>; +pub type BoxedObserverSystem = Box>; pub(crate) struct ObserverComponent { pub(crate) descriptor: ObserverDescriptor, pub(crate) runner: ObserverRunner, - pub(crate) system: Option>, + pub(crate) system: Option, pub(crate) last_event_id: u32, } @@ -22,32 +21,12 @@ impl Component for ObserverComponent { fn init_component_info(info: &mut ComponentInfo) { info.on_add(|mut world, entity, _| { world.commands().add(move |world: &mut World| { - let (archetypes, observers, world) = unsafe { - let world = world.as_unsafe_world_cell(); - ( - world.archetypes_mut(), - world.observers_mut(), - world.into_deferred(), - ) - }; - - let observer = world.get::(entity).unwrap(); - observers.register(archetypes, entity, observer); - }) + world.register_observer(entity); + }); }) .on_remove(|mut world, entity, _| { world.commands().add(move |world: &mut World| { - let (archetypes, observers, world) = unsafe { - let world = world.as_unsafe_world_cell(); - ( - world.archetypes_mut(), - world.observers_mut(), - world.into_deferred(), - ) - }; - - let observer = world.get::(entity).unwrap(); - observers.unregister(archetypes, entity, observer); + world.unregister_observer(entity); }); }); } @@ -61,11 +40,11 @@ impl ObserverComponent { ) -> Self { let mut system = IntoObserverSystem::into_system(system); assert!( - !System::is_exclusive(&system), + !system.is_exclusive(), "Cannot run exclusive systems in Observers" ); system.initialize(world); - let system: Box, Out = ()>> = Box::new(system); + let system: BoxedObserverSystem = Box::new(system); Self { descriptor, runner: |mut world, trigger, ptr| { @@ -88,12 +67,16 @@ impl ObserverComponent { state.last_event_id = last_event; let observer: Observer = Observer::new(unsafe { ptr.deref_mut() }, trigger); + let mut system: Box> = unsafe { + let system = state.system.take().debug_checked_unwrap(); + std::mem::transmute(system) + }; + + system.update_archetype_component_access(world); unsafe { - let mut system = state.system.take().debug_checked_unwrap(); - system.update_archetype_component_access(world); - system.run(std::mem::transmute(observer), world.world_mut()); + system.run_unsafe(std::mem::transmute(observer), world); system.queue_deferred(world.into_deferred()); - state.system = Some(system); + state.system = Some(std::mem::transmute(system)); } }, last_event_id: 0, diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index 61b30a50d2947..5a7a403c47169 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -8,7 +8,7 @@ use crate::{ prelude::World, query::Access, schedule::InternedSystemSet, - world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld}, + world::unsafe_world_cell::UnsafeWorldCell, }; use super::{ReadOnlySystem, System}; diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index cf79562db0ec3..98939271aca59 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -7,8 +7,9 @@ use crate::{ component::ComponentId, entity::{Entities, Entity}, observer::EcsEvent, + prelude::Observer, system::{RunSystemWithInput, SystemId}, - world::{EntityWorldMut, FromWorld, World}, + world::{DeferredWorld, EntityWorldMut, FromWorld, World}, }; use bevy_ecs_macros::SystemParam; use bevy_utils::tracing::{error, info}; @@ -16,7 +17,9 @@ pub use command_queue::CommandQueue; pub use parallel_scope::*; use std::marker::PhantomData; -use super::{Deferred, IntoObserverSystem, ObserverSystem, Resource, SystemBuffer, SystemMeta}; +use super::{ + Deferred, IntoObserverSystem, IntoSystem, ObserverSystem, Resource, SystemBuffer, SystemMeta, +}; /// A [`World`] mutation. /// @@ -113,7 +116,7 @@ pub trait Command: Send + 'static { /// [`Schedule::apply_deferred`]: crate::schedule::Schedule::apply_deferred #[derive(SystemParam)] pub struct Commands<'w, 's> { - pub(crate) queue: Deferred<'s, CommandQueue>, + queue: Deferred<'s, CommandQueue>, entities: &'w Entities, } @@ -124,6 +127,11 @@ impl SystemBuffer for CommandQueue { let _span_guard = _system_meta.commands_span.enter(); self.apply(world); } + + #[inline] + fn queue(&mut self, _system_meta: &SystemMeta, mut world: DeferredWorld) { + world.commands().append(self); + } } impl<'w, 's> Commands<'w, 's> { @@ -972,13 +980,10 @@ impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> { /// Creates an [`Observer`] listening for `E` events targetting this entity. /// In order to trigger the callback the entity must also match the query when the event is fired. - pub fn observe( - &mut self, - callback: impl IntoObserverSystem, - ) -> &mut Self { + pub fn observe(&mut self, system: impl IntoObserverSystem) -> &mut Self { self.commands.add(Observe:: { entity: self.entity, - callback: IntoObserverSystem::into_system(callback), + system: IntoObserverSystem::into_system(system), marker: PhantomData::default(), }); self @@ -1124,18 +1129,18 @@ fn log_components(entity: Entity, world: &mut World) { /// A [`Command`] that spawns an observer attached to a specific entity. #[derive(Debug)] -pub struct Observe> { +pub struct Observe> { /// The entity that will be observed. pub entity: Entity, /// The callback to run when the event is observed. - pub callback: C, + pub system: C, /// Marker for type parameters pub marker: PhantomData, } -impl + Send + 'static> Command for Observe { +impl> Command for Observe { fn apply(self, world: &mut World) { - world.entity_mut(self.entity).observe(self.callback); + world.entity_mut(self.entity).observe(self.system); } } diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index f2c705aede9ad..bf3cd08149dac 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -5,7 +5,7 @@ use crate::{ query::{Access, FilteredAccessSet}, schedule::{InternedSystemSet, SystemSet}, system::{check_system_change_tick, ReadOnlySystemParam, System, SystemParam, SystemParamItem}, - world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World, WorldId}, + world::{unsafe_world_cell::UnsafeWorldCell, World, WorldId}, }; use bevy_utils::all_tuples; @@ -383,8 +383,8 @@ where F: SystemParamFunction, { func: F, - param_state: Option<::State>, - system_meta: SystemMeta, + pub(crate) param_state: Option<::State>, + pub(crate) system_meta: SystemMeta, world_id: Option, archetype_generation: ArchetypeGeneration, // NOTE: PhantomData T> gives this safe Send/Sync impls diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 3b4937a2b8561..4408325d1ef3f 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -107,8 +107,7 @@ mod commands; mod exclusive_function_system; mod exclusive_system_param; mod function_system; -mod observer_function_system; -mod observer_system_param; +mod observer_system; mod query; #[allow(clippy::module_inception)] mod system; @@ -124,8 +123,7 @@ pub use commands::*; pub use exclusive_function_system::*; pub use exclusive_system_param::*; pub use function_system::*; -pub use observer_function_system::*; -pub use observer_system_param::*; +pub use observer_system::*; pub use query::*; pub use system::*; pub use system_name::*; diff --git a/crates/bevy_ecs/src/system/observer_system.rs b/crates/bevy_ecs/src/system/observer_system.rs new file mode 100644 index 0000000000000..55b3faf7d4311 --- /dev/null +++ b/crates/bevy_ecs/src/system/observer_system.rs @@ -0,0 +1,74 @@ +use crate::{ + prelude::Observer, + query::{QueryData, QueryFilter}, + system::{System, SystemParam}, + world::DeferredWorld, +}; + +use bevy_utils::all_tuples; +#[cfg(feature = "trace")] +use bevy_utils::tracing::{info_span, Span}; + +use super::{ + Commands, FunctionSystem, IntoSystem, Query, Res, ResMut, Resource, SystemParamFunction, +}; + +pub trait ObserverSystem: + System, Out = ()> + Send + 'static +{ + fn queue_deferred(&mut self, world: DeferredWorld) {} +} + +pub trait ObserverSystemParam: SystemParam {} + +impl<'w, D: QueryData + 'static, F: QueryFilter + 'static> ObserverSystemParam + for Query<'w, 'w, D, F> +{ +} + +impl<'w, T: Resource> ObserverSystemParam for Res<'w, T> {} + +impl<'w, T: Resource> ObserverSystemParam for ResMut<'w, T> {} + +impl<'w> ObserverSystemParam for Commands<'w, 'w> {} + +/// SAFETY: `F`'s param is [`ReadOnlySystemParam`], so this system will only read from the world. +impl ObserverSystem for FunctionSystem +where + Marker: 'static, + F: SystemParamFunction, Out = ()>, + F::Param: ObserverSystemParam, +{ + fn queue_deferred(&mut self, world: DeferredWorld) { + let param_state = self.param_state.as_mut().unwrap(); + F::Param::queue(param_state, &self.system_meta, world); + } +} + +pub trait IntoObserverSystem { + type System: ObserverSystem; + + fn into_system(this: Self) -> Self::System; +} + +impl, (), M>, M, E: 'static> IntoObserverSystem for S +where + S::System: ObserverSystem, +{ + type System = , (), M>>::System; + + fn into_system(this: Self) -> Self::System { + IntoSystem::into_system(this) + } +} + +macro_rules! impl_observer_system_param_tuple { + ($($param: ident),*) => { + #[allow(clippy::undocumented_unsafe_blocks)] + #[allow(non_snake_case)] + impl<$($param: ObserverSystemParam),*> ObserverSystemParam for ($($param,)*) { + } + }; +} + +all_tuples!(impl_observer_system_param_tuple, 0, 16, P); diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index c099d119795d4..073c93085937c 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -4,7 +4,6 @@ use core::fmt::Debug; use crate::component::Tick; use crate::schedule::InternedSystemSet; use crate::world::unsafe_world_cell::UnsafeWorldCell; -use crate::world::DeferredWorld; use crate::{archetype::ArchetypeComponentId, component::ComponentId, query::Access, world::World}; use std::any::TypeId; diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index a3d5872c8a123..94dcb65ae594e 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -10,7 +10,7 @@ use crate::{ ReadOnlyQueryData, }, system::{Query, SystemMeta}, - world::{unsafe_world_cell::UnsafeWorldCell, FromWorld, World}, + world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, FromWorld, World}, }; use bevy_ecs_macros::impl_param_set; pub use bevy_ecs_macros::Resource; @@ -125,6 +125,10 @@ pub unsafe trait SystemParam: Sized { #[allow(unused_variables)] fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) {} + #[inline] + #[allow(unused_variables)] + fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) {} + /// Creates a parameter to be passed into a [`SystemParamFunction`]. /// /// [`SystemParamFunction`]: super::SystemParamFunction @@ -751,6 +755,8 @@ unsafe impl<'a, T: FromWorld + Send + 'static> SystemParam for Local<'a, T> { pub trait SystemBuffer: FromWorld + Send + 'static { /// Applies any deferred mutations to the [`World`]. fn apply(&mut self, system_meta: &SystemMeta, world: &mut World); + + fn queue(&mut self, system_meta: &SystemMeta, mut world: DeferredWorld) {} } /// A [`SystemParam`] that stores a buffer which gets applied to the [`World`] during @@ -907,6 +913,10 @@ unsafe impl SystemParam for Deferred<'_, T> { state.get().apply(system_meta, world); } + fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) { + state.get().queue(system_meta, world); + } + unsafe fn get_param<'w, 's>( state: &'s mut Self::State, _system_meta: &SystemMeta, @@ -1313,6 +1323,11 @@ macro_rules! impl_system_param_tuple { $($param::apply($param, _system_meta, _world);)* } + #[inline] + fn queue(($($param,)*): &mut Self::State, _system_meta: &SystemMeta, mut _world: DeferredWorld) { + $($param::queue($param, _system_meta, _world.reborrow());)* + } + #[inline] #[allow(clippy::unused_unit)] unsafe fn get_param<'w, 's>( diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 73acaf546a870..bb7940604f47d 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -6,7 +6,7 @@ use crate::{ component::ComponentId, entity::{Entity, EntityLocation}, event::{Event, EventId, Events, SendBatchIds}, - observer::{EcsEvent, EventBuilder}, + observer::{EcsEvent, EventBuilder, Observers}, prelude::{Component, QueryState}, query::{QueryData, QueryFilter}, system::{Commands, Query, Resource}, @@ -331,12 +331,14 @@ impl<'w> DeferredWorld<'w> { location: EntityLocation, components: impl Iterator, ) { - let (world, observers) = unsafe { - let world = self.as_unsafe_world_cell(); - world.world_mut().last_event_id += 1; - (world.into_deferred(), &world.world().observers) - }; - observers.invoke(event, target, location, components, world, &mut ()); + Observers::invoke( + self.reborrow(), + event, + target, + location, + components, + &mut (), + ); } /// Triggers all event observers for [`ComponentId`] in target. @@ -352,12 +354,7 @@ impl<'w> DeferredWorld<'w> { components: impl Iterator, data: &mut E, ) { - let (world, observers) = unsafe { - let world = self.as_unsafe_world_cell(); - world.world_mut().last_event_id += 1; - (world.into_deferred(), &world.world().observers) - }; - observers.invoke(event, target, location, components, world, data); + Observers::invoke(self.reborrow(), event, target, location, components, data); } /// Constructs an [`EventBuilder`] for an [`EcsEvent`]. diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 657a0c6c7c33a..d29beda2d3ca7 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -5,10 +5,11 @@ use crate::{ component::{Component, ComponentId, ComponentTicks, Components, StorageType}, entity::{Entities, Entity, EntityLocation}, observer::{AttachObserver, EcsEvent, ObserverBuilder, Observers}, - query::{Access, QueryData, QueryFilter}, + prelude::Observer, + query::{Access, DebugCheckedUnwrap}, removal_detection::RemovedComponentEvents, storage::Storages, - system::IntoObserverSystem, + system::{IntoObserverSystem, IntoSystem}, world::{Mut, World}, }; use bevy_ptr::{OwningPtr, Ptr}; @@ -712,7 +713,7 @@ impl<'w> EntityWorldMut<'w> { self.entity, self.location, bundle_info.iter_components(), - ) + ); } } @@ -888,7 +889,7 @@ impl<'w> EntityWorldMut<'w> { entity, self.location, bundle_info.iter_components(), - ) + ); } } @@ -993,7 +994,7 @@ impl<'w> EntityWorldMut<'w> { self.entity, self.location, archetype.components(), - ) + ); } } diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 562adea049d61..bdf2c0d6685fa 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -568,12 +568,8 @@ impl<'w> UnsafeWorldCell<'w> { .get_with_ticks() } - pub(crate) unsafe fn archetypes_mut(self) -> &'w mut Archetypes { - unsafe { &mut self.world_mut().archetypes } - } - - pub(crate) unsafe fn observers_mut(self) -> &'w mut Observers { - unsafe { &mut self.world_mut().observers } + pub(crate) unsafe fn observers(self) -> &'w Observers { + &unsafe { self.world_metadata() }.observers } // Returns a mutable reference to the underlying world's [`CommandQueue`]. diff --git a/examples/ecs/observers.rs b/examples/ecs/observers.rs index f7db000ec9343..177ec28c1cdd5 100644 --- a/examples/ecs/observers.rs +++ b/examples/ecs/observers.rs @@ -23,12 +23,13 @@ fn setup(world: &mut World) { // Triggered when &CompA is added to an entity, runs any non-exclusive system let observer = world.observer_builder().components::().run( - |observer: Observer, mut commands: Commands, query: Query<&CompA, With>| { + |observer: In>, + mut commands: Commands, + query: Query<&CompA, With>| { // Get source entity that triggered the observer let source = observer.source(); // Able to read component data via a query if let Ok(data) = query.get(source) { - println!("Hello"); // Can submit commands for any structural changes commands.entity(source).remove::(); // Or to raise other events @@ -42,7 +43,7 @@ fn setup(world: &mut World) { .spawn(CompA(observer)) // Respond to events targeting a specific entity .observe( - |observer: Observer, query: Query<&CompA>, mut res: ResMut| { + |observer: In>, query: Query<&CompA>, mut res: ResMut| { // Since Resize carries data you can read/write that data from the observer let size = observer.data(); // Simultaneously read components From 940efbe4bdeaafbdeb00d60e0e4bc6a99e7b0270 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Mon, 29 Jan 2024 23:42:42 -0800 Subject: [PATCH 056/109] Clean-up --- crates/bevy_ecs/src/bundle.rs | 1 - crates/bevy_ecs/src/observer/mod.rs | 1 + crates/bevy_ecs/src/observer/runner.rs | 1 - crates/bevy_ecs/src/system/commands/command_queue.rs | 2 +- crates/bevy_ecs/src/system/observer_system.rs | 2 +- crates/bevy_ecs/src/system/system_param.rs | 2 +- crates/bevy_ecs/src/world/unsafe_world_cell.rs | 4 ++++ 7 files changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 5bb72d3827a53..118c505a5b787 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -721,7 +721,6 @@ impl<'w> BundleInserter<'w> { // SAFETY: We have no oustanding mutable references to world as they were dropped let new_archetype = &**new_archetype; - let mut world = self.world.into_deferred(); trigger_hooks(new_archetype, self.world.into_deferred()); new_location } diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index fd35252c421bc..5d9786926fcc6 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -135,6 +135,7 @@ impl Observers { ) { let (mut world, observers) = unsafe { let world = world.as_unsafe_world_cell(); + world.increment_event_id(); let observers = world.observers(); let Some(observers) = observers.try_get_observers(event) else { return; diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index acbf0b2017910..ef36ac16e4572 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -51,7 +51,6 @@ impl ObserverComponent { if trigger.source == Entity::PLACEHOLDER { return; } - println!("Trigger: {:?}", std::any::type_name::()); let world = world.as_unsafe_world_cell(); let observer_cell = unsafe { world.get_entity(trigger.observer).debug_checked_unwrap() }; diff --git a/crates/bevy_ecs/src/system/commands/command_queue.rs b/crates/bevy_ecs/src/system/commands/command_queue.rs index 9d5c929142acc..07e55cc2e827d 100644 --- a/crates/bevy_ecs/src/system/commands/command_queue.rs +++ b/crates/bevy_ecs/src/system/commands/command_queue.rs @@ -31,7 +31,7 @@ pub struct CommandQueue { // For each command, one `CommandMeta` is stored, followed by zero or more bytes // to store the command itself. To interpret these bytes, a pointer must // be passed to the corresponding `CommandMeta.apply_command_and_get_size` fn pointer. - bytes: Vec>, + pub(crate) bytes: Vec>, } // SAFETY: All commands [`Command`] implement [`Send`] diff --git a/crates/bevy_ecs/src/system/observer_system.rs b/crates/bevy_ecs/src/system/observer_system.rs index 55b3faf7d4311..78dd90a144d76 100644 --- a/crates/bevy_ecs/src/system/observer_system.rs +++ b/crates/bevy_ecs/src/system/observer_system.rs @@ -16,7 +16,7 @@ use super::{ pub trait ObserverSystem: System, Out = ()> + Send + 'static { - fn queue_deferred(&mut self, world: DeferredWorld) {} + fn queue_deferred(&mut self, _world: DeferredWorld); } pub trait ObserverSystemParam: SystemParam {} diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 94dcb65ae594e..a149f9cdc3dec 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -756,7 +756,7 @@ pub trait SystemBuffer: FromWorld + Send + 'static { /// Applies any deferred mutations to the [`World`]. fn apply(&mut self, system_meta: &SystemMeta, world: &mut World); - fn queue(&mut self, system_meta: &SystemMeta, mut world: DeferredWorld) {} + fn queue(&mut self, _system_meta: &SystemMeta, _world: DeferredWorld) {} } /// A [`SystemParam`] that stores a buffer which gets applied to the [`World`] during diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index bdf2c0d6685fa..7afce1e772f56 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -583,6 +583,10 @@ impl<'w> UnsafeWorldCell<'w> { // - caller ensures that we have permission to access the queue unsafe { &mut *addr_of_mut!((*self.0).command_queue) } } + + pub(crate) unsafe fn increment_event_id(self) { + unsafe { (*self.0).last_event_id += 1 } + } } impl Debug for UnsafeWorldCell<'_> { From ffecc786c7834a1a4b56f9b54ee308f734730509 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Mon, 29 Jan 2024 23:47:29 -0800 Subject: [PATCH 057/109] Fix doc tests --- crates/bevy_ecs/src/query/builder.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/query/builder.rs b/crates/bevy_ecs/src/query/builder.rs index 67db644a4fcb4..7c80c86282ea6 100644 --- a/crates/bevy_ecs/src/query/builder.rs +++ b/crates/bevy_ecs/src/query/builder.rs @@ -7,7 +7,7 @@ use super::{FilteredAccess, QueryData, QueryFilter}; /// Builder struct to create [`QueryState`] instances at runtime. /// /// ``` -/// # use bevy_ecs::prelude::*; +/// # use bevy_ecs::{prelude::*, query::QueryBuilder}; /// # /// # #[derive(Component)] /// # struct A; @@ -178,7 +178,7 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> { /// on an empty builder, all accesses added to that builder will become terms in an or expression. /// /// ``` - /// # use bevy_ecs::prelude::*; + /// # use bevy_ecs::{prelude::*, query::QueryBuilder}; /// # /// # #[derive(Component)] /// # struct A; From 961bff7f3d7a22023167467c0034433b090e4f82 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Tue, 30 Jan 2024 00:56:07 -0800 Subject: [PATCH 058/109] Remove In<> --- crates/bevy_ecs/src/system/observer_system.rs | 37 +++++++++++++++++-- examples/ecs/observers.rs | 6 +-- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/crates/bevy_ecs/src/system/observer_system.rs b/crates/bevy_ecs/src/system/observer_system.rs index 78dd90a144d76..364949afc81c8 100644 --- a/crates/bevy_ecs/src/system/observer_system.rs +++ b/crates/bevy_ecs/src/system/observer_system.rs @@ -1,7 +1,7 @@ use crate::{ prelude::Observer, query::{QueryData, QueryFilter}, - system::{System, SystemParam}, + system::{System, SystemParam, SystemParamFunction, SystemParamItem}, world::DeferredWorld, }; @@ -9,9 +9,7 @@ use bevy_utils::all_tuples; #[cfg(feature = "trace")] use bevy_utils::tracing::{info_span, Span}; -use super::{ - Commands, FunctionSystem, IntoSystem, Query, Res, ResMut, Resource, SystemParamFunction, -}; +use super::{Commands, FunctionSystem, IntoSystem, Query, Res, ResMut, Resource}; pub trait ObserverSystem: System, Out = ()> + Send + 'static @@ -72,3 +70,34 @@ macro_rules! impl_observer_system_param_tuple { } all_tuples!(impl_observer_system_param_tuple, 0, 16, P); + +macro_rules! impl_system_function { + ($($param: ident),*) => { + #[allow(non_snake_case)] + impl SystemParamFunction, $($param,)*)> for Func + where + for <'a> &'a mut Func: + FnMut(Observer, $($param),*) + + FnMut(Observer, $(SystemParamItem<$param>),*) + { + type In = Observer<'static, E>; + type Out = (); + type Param = ($($param,)*); + #[inline] + fn run(&mut self, input: Observer<'static, E>, param_value: SystemParamItem< ($($param,)*)>) { + #[allow(clippy::too_many_arguments)] + fn call_inner( + mut f: impl FnMut(Observer<'static, E>, $($param,)*), + input: Observer<'static, E>, + $($param: $param,)* + ){ + f(input, $($param,)*) + } + let ($($param,)*) = param_value; + call_inner(self, input, $($param),*) + } + } + } +} + +all_tuples!(impl_system_function, 0, 16, F); diff --git a/examples/ecs/observers.rs b/examples/ecs/observers.rs index 177ec28c1cdd5..b35bc2448fc1c 100644 --- a/examples/ecs/observers.rs +++ b/examples/ecs/observers.rs @@ -23,9 +23,7 @@ fn setup(world: &mut World) { // Triggered when &CompA is added to an entity, runs any non-exclusive system let observer = world.observer_builder().components::().run( - |observer: In>, - mut commands: Commands, - query: Query<&CompA, With>| { + |observer: Observer, mut commands: Commands, query: Query<&CompA, With>| { // Get source entity that triggered the observer let source = observer.source(); // Able to read component data via a query @@ -43,7 +41,7 @@ fn setup(world: &mut World) { .spawn(CompA(observer)) // Respond to events targeting a specific entity .observe( - |observer: In>, query: Query<&CompA>, mut res: ResMut| { + |observer: Observer, query: Query<&CompA>, mut res: ResMut| { // Since Resize carries data you can read/write that data from the observer let size = observer.data(); // Simultaneously read components From dcee2083421dc5431c3a0d7b047242a5a80f93c5 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Tue, 30 Jan 2024 01:31:49 -0800 Subject: [PATCH 059/109] Add bundle generic --- crates/bevy_ecs/src/observer/builder.rs | 14 ++++++- crates/bevy_ecs/src/observer/mod.rs | 18 +++++++-- crates/bevy_ecs/src/observer/runner.rs | 12 +++--- crates/bevy_ecs/src/system/commands/mod.rs | 11 ++++-- crates/bevy_ecs/src/system/observer_system.rs | 37 ++++++++++--------- crates/bevy_ecs/src/world/entity_ref.rs | 4 +- examples/ecs/observers.rs | 6 ++- 7 files changed, 64 insertions(+), 38 deletions(-) diff --git a/crates/bevy_ecs/src/observer/builder.rs b/crates/bevy_ecs/src/observer/builder.rs index 4d33c87ad1dda..bfc2fb707a3d1 100644 --- a/crates/bevy_ecs/src/observer/builder.rs +++ b/crates/bevy_ecs/src/observer/builder.rs @@ -69,7 +69,12 @@ impl<'w, E: EcsEvent> ObserverBuilder<'w, E> { } /// Spawns the resulting observer into the world. - pub fn run(&mut self, callback: impl IntoObserverSystem) -> Entity { + pub fn run(&mut self, callback: impl IntoObserverSystem) -> Entity { + B::component_ids( + &mut self.world.components, + &mut self.world.storages, + &mut |id| self.descriptor.components.push(id), + ); let entity = self.enqueue(callback); self.world.flush_commands(); entity @@ -85,7 +90,12 @@ impl<'w, E: EcsEvent> ObserverBuilder<'w, E> { } /// Enqueues a command to spawn the resulting observer in the world. - pub fn enqueue(&mut self, callback: impl IntoObserverSystem) -> Entity { + pub fn enqueue(&mut self, callback: impl IntoObserverSystem) -> Entity { + B::component_ids( + &mut self.world.components, + &mut self.world.storages, + &mut |id| self.descriptor.components.push(id), + ); let component = ObserverComponent::from(self.world, self.descriptor.clone(), callback); self.world.spawn_observer(component) } diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 5f13a1b389107..7adcb5e1667f7 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -3,6 +3,8 @@ mod builder; mod runner; +use std::marker::PhantomData; + pub use builder::*; pub use runner::*; @@ -27,14 +29,19 @@ impl EcsEvent for E {} /// Type used in callbacks registered for observers. /// TODO: Proper docs and examples -pub struct Observer<'w, E> { +pub struct Observer<'w, E, B: Bundle = ()> { data: &'w mut E, trigger: ObserverTrigger, + _marker: PhantomData, } -impl<'w, E> Observer<'w, E> { +impl<'w, E, B: Bundle> Observer<'w, E, B> { pub(crate) fn new(data: &'w mut E, trigger: ObserverTrigger) -> Self { - Self { data, trigger } + Self { + data, + trigger, + _marker: PhantomData, + } } /// Returns the event id for the triggering event @@ -334,7 +341,10 @@ impl World { } /// Spawn an [`Observer`] and returns it's [`Entity`] - pub fn observer(&mut self, callback: impl IntoObserverSystem) -> Entity { + pub fn observer( + &mut self, + callback: impl IntoObserverSystem, + ) -> Entity { ObserverBuilder::new(self).run(callback) } diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index ef36ac16e4572..997a220602383 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -6,7 +6,7 @@ use super::*; /// Typically refers to the default runner defined in [`ObserverComponent::from`] pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut); -pub type BoxedObserverSystem = Box>; +pub type BoxedObserverSystem = Box>; pub(crate) struct ObserverComponent { pub(crate) descriptor: ObserverDescriptor, @@ -33,10 +33,10 @@ impl Component for ObserverComponent { } impl ObserverComponent { - pub(crate) fn from( + pub(crate) fn from( world: &mut World, descriptor: ObserverDescriptor, - system: impl IntoObserverSystem, + system: impl IntoObserverSystem, ) -> Self { let mut system = IntoObserverSystem::into_system(system); assert!( @@ -44,7 +44,7 @@ impl ObserverComponent { "Cannot run exclusive systems in Observers" ); system.initialize(world); - let system: BoxedObserverSystem = Box::new(system); + let system: BoxedObserverSystem = Box::new(system); Self { descriptor, runner: |mut world, trigger, ptr| { @@ -65,8 +65,8 @@ impl ObserverComponent { } state.last_event_id = last_event; - let observer: Observer = Observer::new(unsafe { ptr.deref_mut() }, trigger); - let mut system: Box> = unsafe { + let observer: Observer = Observer::new(unsafe { ptr.deref_mut() }, trigger); + let mut system: Box> = unsafe { let system = state.system.take().debug_checked_unwrap(); std::mem::transmute(system) }; diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 7ef2b1517053e..1ab190a9c137f 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -1003,7 +1003,10 @@ impl EntityCommands<'_> { /// Creates an [`Observer`] listening for `E` events targetting this entity. /// In order to trigger the callback the entity must also match the query when the event is fired. - pub fn observe(&mut self, system: impl IntoObserverSystem) -> &mut Self { + pub fn observe( + &mut self, + system: impl IntoObserverSystem, + ) -> &mut Self { self.commands.add(Observe:: { entity: self.entity, system: IntoObserverSystem::into_system(system), @@ -1152,16 +1155,16 @@ fn log_components(entity: Entity, world: &mut World) { /// A [`Command`] that spawns an observer attached to a specific entity. #[derive(Debug)] -pub struct Observe> { +pub struct Observe> { /// The entity that will be observed. pub entity: Entity, /// The callback to run when the event is observed. pub system: C, /// Marker for type parameters - pub marker: PhantomData, + pub marker: PhantomData<(E, B)>, } -impl> Command for Observe { +impl> Command for Observe { fn apply(self, world: &mut World) { world.entity_mut(self.entity).observe(self.system); } diff --git a/crates/bevy_ecs/src/system/observer_system.rs b/crates/bevy_ecs/src/system/observer_system.rs index 364949afc81c8..443e6b07d7c89 100644 --- a/crates/bevy_ecs/src/system/observer_system.rs +++ b/crates/bevy_ecs/src/system/observer_system.rs @@ -1,5 +1,5 @@ use crate::{ - prelude::Observer, + prelude::{Bundle, Observer}, query::{QueryData, QueryFilter}, system::{System, SystemParam, SystemParamFunction, SystemParamItem}, world::DeferredWorld, @@ -11,8 +11,8 @@ use bevy_utils::tracing::{info_span, Span}; use super::{Commands, FunctionSystem, IntoSystem, Query, Res, ResMut, Resource}; -pub trait ObserverSystem: - System, Out = ()> + Send + 'static +pub trait ObserverSystem: + System, Out = ()> + Send + 'static { fn queue_deferred(&mut self, _world: DeferredWorld); } @@ -31,10 +31,10 @@ impl<'w, T: Resource> ObserverSystemParam for ResMut<'w, T> {} impl<'w> ObserverSystemParam for Commands<'w, 'w> {} /// SAFETY: `F`'s param is [`ReadOnlySystemParam`], so this system will only read from the world. -impl ObserverSystem for FunctionSystem +impl ObserverSystem for FunctionSystem where Marker: 'static, - F: SystemParamFunction, Out = ()>, + F: SystemParamFunction, Out = ()>, F::Param: ObserverSystemParam, { fn queue_deferred(&mut self, world: DeferredWorld) { @@ -43,17 +43,18 @@ where } } -pub trait IntoObserverSystem { - type System: ObserverSystem; +pub trait IntoObserverSystem { + type System: ObserverSystem; fn into_system(this: Self) -> Self::System; } -impl, (), M>, M, E: 'static> IntoObserverSystem for S +impl, (), M>, M, E: 'static, B: Bundle> + IntoObserverSystem for S where - S::System: ObserverSystem, + S::System: ObserverSystem, { - type System = , (), M>>::System; + type System = , (), M>>::System; fn into_system(this: Self) -> Self::System { IntoSystem::into_system(this) @@ -74,21 +75,21 @@ all_tuples!(impl_observer_system_param_tuple, 0, 16, P); macro_rules! impl_system_function { ($($param: ident),*) => { #[allow(non_snake_case)] - impl SystemParamFunction, $($param,)*)> for Func + impl SystemParamFunction, $($param,)*)> for Func where for <'a> &'a mut Func: - FnMut(Observer, $($param),*) + - FnMut(Observer, $(SystemParamItem<$param>),*) + FnMut(Observer, $($param),*) + + FnMut(Observer, $(SystemParamItem<$param>),*) { - type In = Observer<'static, E>; + type In = Observer<'static, E, B>; type Out = (); type Param = ($($param,)*); #[inline] - fn run(&mut self, input: Observer<'static, E>, param_value: SystemParamItem< ($($param,)*)>) { + fn run(&mut self, input: Observer<'static, E, B>, param_value: SystemParamItem< ($($param,)*)>) { #[allow(clippy::too_many_arguments)] - fn call_inner( - mut f: impl FnMut(Observer<'static, E>, $($param,)*), - input: Observer<'static, E>, + fn call_inner( + mut f: impl FnMut(Observer<'static, E, B>, $($param,)*), + input: Observer<'static, E, B>, $($param: $param,)* ){ f(input, $($param,)*) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 2187d43f21bff..d15c1a82c4525 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -1181,9 +1181,9 @@ impl<'w> EntityWorldMut<'w> { /// Creates an [`Observer`] listening for `E` events targetting this entity. /// In order to trigger the callback the entity must also match the query when the event is fired. - pub fn observe( + pub fn observe( &mut self, - callback: impl IntoObserverSystem, + callback: impl IntoObserverSystem, ) -> &mut Self { let observer = ObserverBuilder::new(self.world) .source(self.entity) diff --git a/examples/ecs/observers.rs b/examples/ecs/observers.rs index b35bc2448fc1c..a4d7647621572 100644 --- a/examples/ecs/observers.rs +++ b/examples/ecs/observers.rs @@ -22,8 +22,10 @@ fn setup(world: &mut World) { world.init_resource::(); // Triggered when &CompA is added to an entity, runs any non-exclusive system - let observer = world.observer_builder().components::().run( - |observer: Observer, mut commands: Commands, query: Query<&CompA, With>| { + let observer = world.observer( + |observer: Observer, + mut commands: Commands, + query: Query<&CompA, With>| { // Get source entity that triggered the observer let source = observer.source(); // Able to read component data via a query From 43f6d4364ddc74a5a531ba8d0863fedd634486e2 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Tue, 30 Jan 2024 10:31:10 -0800 Subject: [PATCH 060/109] Minor fixes --- Cargo.toml | 2 ++ crates/bevy_ecs/src/system/commands/mod.rs | 2 +- examples/README.md | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c5d2c54acf674..1164c5ac83d3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1366,6 +1366,8 @@ doc-scrape-examples = true [package.metadata.example.component_hooks] name = "Component Hooks" description = "Define component hooks to manage component lifecycle events" +category = "ECS (Entity Component System)" +wasm = false [[example]] name = "custom_schedule" diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 1ab190a9c137f..109a57e765e80 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -1007,7 +1007,7 @@ impl EntityCommands<'_> { &mut self, system: impl IntoObserverSystem, ) -> &mut Self { - self.commands.add(Observe:: { + self.commands.add(Observe:: { entity: self.entity, system: IntoObserverSystem::into_system(system), marker: PhantomData::default(), diff --git a/examples/README.md b/examples/README.md index 31f16a116648c..c5530629029d6 100644 --- a/examples/README.md +++ b/examples/README.md @@ -236,6 +236,7 @@ Example | Description [Hierarchy](../examples/ecs/hierarchy.rs) | Creates a hierarchy of parents and children entities [Iter Combinations](../examples/ecs/iter_combinations.rs) | Shows how to iterate over combinations of query results [Nondeterministic System Order](../examples/ecs/nondeterministic_system_order.rs) | Systems run in parallel, but their order isn't always deterministic. Here's how to detect and fix this. +[Observers](../examples/ecs/observers.rs) | Define observers to react to ECS events [One Shot Systems](../examples/ecs/one_shot_systems.rs) | Shows how to flexibly run systems without scheduling them [Parallel Query](../examples/ecs/parallel_query.rs) | Illustrates parallel queries with `ParallelIterator` [Removal Detection](../examples/ecs/removal_detection.rs) | Query for entities that had a specific component removed earlier in the current frame From 22ed1766f09cd729e7f035e5d446159bc5901d4c Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Fri, 2 Feb 2024 14:41:39 -0800 Subject: [PATCH 061/109] Incorporate feedback, fix command application --- crates/bevy_ecs/src/component.rs | 60 ++++++++++++------- .../src/system/commands/command_queue.rs | 39 ++++++++---- crates/bevy_ecs/src/world/deferred_world.rs | 4 +- crates/bevy_ecs/src/world/mod.rs | 6 +- 4 files changed, 72 insertions(+), 37 deletions(-) diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 75791f76c0b00..5c2cd82c24295 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -286,30 +286,23 @@ impl ComponentInfo { } } - /// Register a [`ComponentHook`] that will be run when this component is added to an entity + /// Register a [`ComponentHook`] that will be run when this component is added to an entity. + /// An `on_add` hook will always be followed by `on_insert`. /// /// Will panic if the component already has an `on_add` hook pub fn on_add(&mut self, hook: ComponentHook) -> &mut Self { - assert!( - self.hooks.on_add.is_none(), - "Component id: {:?}, already has an on_add hook", - self.id() - ); - self.hooks.on_add = Some(hook); - self + self.try_on_add(hook) + .expect("Component id: {:?}, already has an on_add hook") } /// Register a [`ComponentHook`] that will be run when this component is added or set by `.insert` + /// An `on_insert` hook will run even if the entity already has the component unlike `on_add`, + /// `on_insert` also always runs after any `on_add` hooks. /// /// Will panic if the component already has an `on_insert` hook pub fn on_insert(&mut self, hook: ComponentHook) -> &mut Self { - assert!( - self.hooks.on_insert.is_none(), - "Component id: {:?}, already has an on_insert hook", - self.id() - ); - self.hooks.on_insert = Some(hook); - self + self.try_on_insert(hook) + .expect("Component id: {:?}, already has an on_insert hook") } /// Register a [`ComponentHook`] that will be run when this component is removed from an entity. @@ -317,13 +310,38 @@ impl ComponentInfo { /// /// Will panic if the component already has an `on_remove` hook pub fn on_remove(&mut self, hook: ComponentHook) -> &mut Self { - assert!( - self.hooks.on_remove.is_none(), - "Component id: {:?}, already has an on_remove hook", - self.id() - ); + self.try_on_remove(hook) + .expect("Component id: {:?}, already has an on_remove hook") + } + + /// Fallible version of [`Self::on_add`]. + /// Returns `None` if the component already has an `on_add` hook. + pub fn try_on_add(&mut self, hook: ComponentHook) -> Option<&mut Self> { + if self.hooks.on_add.is_some() { + return None; + } + self.hooks.on_add = Some(hook); + Some(self) + } + + /// Fallible version of [`Self::on_insert`]. + /// Returns `None` if the component already has an `on_insert` hook. + pub fn try_on_insert(&mut self, hook: ComponentHook) -> Option<&mut Self> { + if self.hooks.on_insert.is_some() { + return None; + } + self.hooks.on_insert = Some(hook); + Some(self) + } + + /// Fallible version of [`Self::on_remove`]. + /// Returns `None` if the component already has an `on_remove` hook. + pub fn try_on_remove(&mut self, hook: ComponentHook) -> Option<&mut Self> { + if self.hooks.on_remove.is_some() { + return None; + } self.hooks.on_remove = Some(hook); - self + Some(self) } /// Update the given flags to include any [`ComponentHook`] registered to self diff --git a/crates/bevy_ecs/src/system/commands/command_queue.rs b/crates/bevy_ecs/src/system/commands/command_queue.rs index b7679959b417a..3f43d98df2e95 100644 --- a/crates/bevy_ecs/src/system/commands/command_queue.rs +++ b/crates/bevy_ecs/src/system/commands/command_queue.rs @@ -112,7 +112,7 @@ impl CommandQueue { } } - /// Execute the queued [`Command`]s in the world. + /// Execute the queued [`Command`]s in the world after applying any commands in the world's internal queue. /// This clears the queue. #[inline] pub fn apply(&mut self, world: &mut World) { @@ -139,9 +139,23 @@ impl CommandQueue { // In the loop below, ownership of each command will be transferred into user code. let bytes = std::mem::take(&mut self.bytes); - let mut resolving_commands = vec![(cursor, end, bytes)]; + // Create a stack for the command queue's we will be applying as commands may queue additional commands. + // This is preferred over recursion to avoid stack overflows. + let mut resolving_commands = vec![(cursor, end)]; + // Take ownership of buffers so they are not free'd uintil they are iterated. + let mut buffers = vec![bytes]; + + // Add any commands in the world's internal queue to the top of the stack. + if let Some(world) = &mut world { + if !world.command_queue.is_empty() { + let mut bytes = std::mem::take(&mut world.command_queue.bytes); + let bytes_range = bytes.as_mut_ptr_range(); + resolving_commands.push((bytes_range.start, bytes_range.end)); + buffers.push(bytes); + } + } - while let Some((mut cursor, mut end, mut bytes)) = resolving_commands.pop() { + while let Some((mut cursor, mut end)) = resolving_commands.pop() { while cursor < end { // SAFETY: The cursor is either at the start of the buffer, or just after the previous command. // Since we know that the cursor is in bounds, it must point to the start of a new command. @@ -170,26 +184,31 @@ impl CommandQueue { cursor = unsafe { cursor.add(size) }; if let Some(world) = &mut world { - // Mark world as flushing so calls to `World::flush_commands` don't cause multiple queues to be applying simultaneously - world.flushing_commands = true; // If the command we just applied generated more commands we must apply those first if !world.command_queue.is_empty() { // If our current list of commands isn't complete push it to the `resolving_commands` stack to be applied after if cursor < end { - resolving_commands.push((cursor, end, bytes)); + resolving_commands.push((cursor, end)); } + let mut bytes = std::mem::take(&mut world.command_queue.bytes); + // Set our variables to start applying the new queue - bytes = std::mem::take(&mut world.command_queue.bytes); let bytes_range = bytes.as_mut_ptr_range(); cursor = bytes_range.start; end = bytes_range.end; + + // Store our buffer so it is not dropped; + buffers.push(bytes); } } } } - if let Some(world) = world { - // Unblock future calls to `World::flush_commands` - world.flushing_commands = false; + + // Reset the buffer, so it can be reused after this function ends. + if let Some(bytes) = buffers.first_mut() { + self.bytes = std::mem::take(bytes); + // SAFETY: `set_len(0)` is always valid. + unsafe { self.bytes.set_len(0) } } } diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 9950a953eea58..f2574a93919f8 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -107,10 +107,10 @@ impl<'w> DeferredWorld<'w> { /// # Panics /// /// Panics if the resource does not exist. - /// Use [`get_resource_mut`](World::get_resource_mut) instead if you want to handle this case. + /// Use [`get_resource_mut`](DeferredWorld::get_resource_mut) instead if you want to handle this case. /// /// If you want to instead insert a value if the resource does not exist, - /// use [`get_resource_or_insert_with`](World::get_resource_or_insert_with). + /// use [`get_resource_or_insert_with`](DeferredWorld::get_resource_or_insert_with). #[inline] #[track_caller] pub fn resource_mut(&mut self) -> Mut<'_, R> { diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index fb4f44627afb7..cbe21d2c46427 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -80,7 +80,6 @@ pub struct World { pub(crate) last_change_tick: Tick, pub(crate) last_check_tick: Tick, pub(crate) command_queue: CommandQueue, - pub(crate) flushing_commands: bool, } impl Default for World { @@ -100,7 +99,6 @@ impl Default for World { last_change_tick: Tick::new(0), last_check_tick: Tick::new(0), command_queue: CommandQueue::default(), - flushing_commands: false, } } } @@ -1855,10 +1853,10 @@ impl World { } } - /// Applies the internal [`CommandQueue`] to self + /// Applies any commands in the world's internal [`CommandQueue`]. #[inline] pub fn flush_commands(&mut self) { - if !self.flushing_commands && !self.command_queue.is_empty() { + if !self.command_queue.is_empty() { let mut commands = std::mem::take(&mut self.command_queue); commands.apply(self); } From e92c7459d3fa38ccebdbeacc401c187b3f5f7c8c Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Fri, 2 Feb 2024 14:50:01 -0800 Subject: [PATCH 062/109] Fix example metadata, simplify flush_commands --- Cargo.toml | 2 ++ crates/bevy_ecs/src/world/mod.rs | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c16dc5d834f64..5d45635064d8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1377,6 +1377,8 @@ doc-scrape-examples = true [package.metadata.example.component_hooks] name = "Component Hooks" description = "Define component hooks to manage component lifecycle events" +category = "ECS (Entity Component System)" +wasm = false [[example]] name = "custom_schedule" diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index cbe21d2c46427..c2e97c0192771 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1857,8 +1857,7 @@ impl World { #[inline] pub fn flush_commands(&mut self) { if !self.command_queue.is_empty() { - let mut commands = std::mem::take(&mut self.command_queue); - commands.apply(self); + CommandQueue::default().apply(self); } } From fe4fca3ced47d3dcfeb1ae94d07d35419c5df1d4 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Fri, 2 Feb 2024 15:14:33 -0800 Subject: [PATCH 063/109] Remove missing doc link --- crates/bevy_ecs/src/world/deferred_world.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index f2574a93919f8..24641a77bd00f 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -108,9 +108,6 @@ impl<'w> DeferredWorld<'w> { /// /// Panics if the resource does not exist. /// Use [`get_resource_mut`](DeferredWorld::get_resource_mut) instead if you want to handle this case. - /// - /// If you want to instead insert a value if the resource does not exist, - /// use [`get_resource_or_insert_with`](DeferredWorld::get_resource_or_insert_with). #[inline] #[track_caller] pub fn resource_mut(&mut self) -> Mut<'_, R> { From 1c2b18881cfdbedbcc6a4daf820f4ec1665794a2 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sat, 3 Feb 2024 18:04:23 -0800 Subject: [PATCH 064/109] Improve documentation --- crates/bevy_ecs/src/system/exclusive_function_system.rs | 1 + crates/bevy_ecs/src/world/mod.rs | 1 + crates/bevy_ecs/src/world/spawn_batch.rs | 2 ++ 3 files changed, 4 insertions(+) diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index 1d9e1241d2232..d31e68cd7adaa 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -121,6 +121,7 @@ where self.system_meta.last_run.set(*change_tick); *change_tick = change_tick.wrapping_add(1); world.last_change_tick = saved_last_tick; + world.flush_commands(); out } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index c2e97c0192771..44d62b99f8cab 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1854,6 +1854,7 @@ impl World { } /// Applies any commands in the world's internal [`CommandQueue`]. + /// This does not apply commands from any systems, only those in stored in the world. #[inline] pub fn flush_commands(&mut self) { if !self.command_queue.is_empty() { diff --git a/crates/bevy_ecs/src/world/spawn_batch.rs b/crates/bevy_ecs/src/world/spawn_batch.rs index 71e367fd1a86f..ab9cb8f2db01f 100644 --- a/crates/bevy_ecs/src/world/spawn_batch.rs +++ b/crates/bevy_ecs/src/world/spawn_batch.rs @@ -51,7 +51,9 @@ where I::Item: Bundle, { fn drop(&mut self) { + // Iterate through self in order to spawn remaining bundles. for _ in &mut *self {} + // Apply any commands from those operations. // SAFETY: `self.spawner` will be dropped immediately after this call. unsafe { self.spawner.flush_commands() }; } From 3f9c1196f246050b38c8790f5cbe4585b4049a51 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Mon, 5 Feb 2024 16:53:10 -0800 Subject: [PATCH 065/109] Minor safety doc fix --- crates/bevy_ecs/src/world/deferred_world.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 01846c088a49a..e9e89e2b7140d 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -320,7 +320,7 @@ impl<'w> DeferredWorld<'w> { /// Triggers all event observers for [`ComponentId`] in target. /// /// # Safety - /// Caller must ensure [`ComponentId`] in target exist in self. + /// Caller must ensure observers listening for `event` can accept ZST pointers #[inline] pub(crate) unsafe fn trigger_observers( &mut self, From 84c853170de0b0e84f500d872d19272dad24b8fb Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Mon, 5 Feb 2024 17:36:46 -0800 Subject: [PATCH 066/109] Doc fixes --- crates/bevy_ecs/src/bundle.rs | 2 +- crates/bevy_ecs/src/observer/builder.rs | 2 +- crates/bevy_ecs/src/observer/runner.rs | 9 ++++++- crates/bevy_ecs/src/system/commands/mod.rs | 2 +- crates/bevy_ecs/src/system/observer_system.rs | 24 ++++++++++--------- crates/bevy_ecs/src/world/deferred_world.rs | 2 +- crates/bevy_ecs/src/world/entity_ref.rs | 2 +- 7 files changed, 26 insertions(+), 17 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 6f97933f656b2..e2bcb8f40672f 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -203,7 +203,7 @@ unsafe impl Bundle for C { } fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option)) { - ids(components.get_id(TypeId::of::())) + ids(components.get_id(TypeId::of::())); } } diff --git a/crates/bevy_ecs/src/observer/builder.rs b/crates/bevy_ecs/src/observer/builder.rs index 0710b25584f52..bf7a2155243a5 100644 --- a/crates/bevy_ecs/src/observer/builder.rs +++ b/crates/bevy_ecs/src/observer/builder.rs @@ -152,7 +152,7 @@ impl<'w, E: EcsEvent> EventBuilder<'w, E> { } /// Sets the event id of the resulting event, used for dynamic events - /// # Safety : + /// # Safety /// Caller must ensure that the component associated with `id` has the same layout as E #[must_use] pub unsafe fn event_id(&mut self, id: ComponentId) -> &mut Self { diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index 8b55ed13dea22..c259bcb8e5a7d 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -3,7 +3,8 @@ use crate::system::{IntoObserverSystem, ObserverSystem}; use super::*; /// Type for function that is run when an observer is triggered -/// Typically refers to the default runner defined in [`ObserverComponent::from`] +/// Typically refers to the default runner that runs the contained, +/// but can be overriden for custom behaviour. pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut); /// Equivalent to [`BoxedSystem`](crate::system::BoxedSystem) for [`ObserverSystem`]. @@ -56,6 +57,7 @@ impl ObserverComponent { let observer_cell = // SAFETY: Observer was triggered so must still exist in world unsafe { world.get_entity(trigger.observer).debug_checked_unwrap() }; + // SAFETY: Observer was triggered so must have an `ObserverComponent` let mut state = unsafe { observer_cell .get_mut::() @@ -77,6 +79,11 @@ impl ObserverComponent { }; system.update_archetype_component_access(world); + // SAFETY: + // - `update_archetype_component_access` was just called + // - there are no oustanding references to world except a private component + // - system is an `ObserverSystem` so won't mutate world beyond the access of a `DeferredWorld` + // - system is the same type erased system from above unsafe { system.run_unsafe(std::mem::transmute(observer), world); system.queue_deferred(world.into_deferred()); diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 90316733b0697..19bd4f1bcba8d 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -1013,7 +1013,7 @@ impl EntityCommands<'_> { self.commands.reborrow() } - /// Creates an [`Observer`] listening for `E` events targetting this entity. + /// Creates an [`Observer`](crate::observer::Observer) listening for `E` events targetting this entity. /// In order to trigger the callback the entity must also match the query when the event is fired. pub fn observe( &mut self, diff --git a/crates/bevy_ecs/src/system/observer_system.rs b/crates/bevy_ecs/src/system/observer_system.rs index f79eb0d56d675..a4298fc084a2b 100644 --- a/crates/bevy_ecs/src/system/observer_system.rs +++ b/crates/bevy_ecs/src/system/observer_system.rs @@ -5,11 +5,8 @@ use crate::{ world::DeferredWorld, }; -use bevy_utils::all_tuples; -#[cfg(feature = "trace")] -use bevy_utils::tracing::{info_span, Span}; - use super::{Commands, FunctionSystem, IntoSystem, Query, Res, ResMut, Resource}; +use bevy_utils::all_tuples; /// Implemented for systems that have an [`Observer`] as the first argument and only [`ObserverSystemParam`] implementors as parameters. pub trait ObserverSystem: @@ -20,20 +17,25 @@ pub trait ObserverSystem: } /// Implemented for [`SystemParam`] that can be used in [`Observer`] systems. -pub trait ObserverSystemParam: SystemParam {} +/// # Safety +/// Implementing types must ensure their implementation of [`SystemParam::get_param`] does not use `world` except as a [`DeferredWorld`] +pub unsafe trait ObserverSystemParam: SystemParam {} -impl<'w, D: QueryData + 'static, F: QueryFilter + 'static> ObserverSystemParam +/// # Safety: `Query` can make no structural changes +unsafe impl<'w, D: QueryData + 'static, F: QueryFilter + 'static> ObserverSystemParam for Query<'w, 'w, D, F> { } -impl<'w, T: Resource> ObserverSystemParam for Res<'w, T> {} +/// # Safety: `Res` can make no structural changes +unsafe impl<'w, T: Resource> ObserverSystemParam for Res<'w, T> {} -impl<'w, T: Resource> ObserverSystemParam for ResMut<'w, T> {} +/// # Safety: `ResMut` can make no structural changes +unsafe impl<'w, T: Resource> ObserverSystemParam for ResMut<'w, T> {} -impl<'w> ObserverSystemParam for Commands<'w, 'w> {} +/// # Safety: `Commands` can't make structural changes +unsafe impl<'w> ObserverSystemParam for Commands<'w, 'w> {} -/// SAFETY: `F`'s param is [`ReadOnlySystemParam`], so this system will only read from the world. impl ObserverSystem for FunctionSystem where Marker: 'static, @@ -71,7 +73,7 @@ macro_rules! impl_observer_system_param_tuple { ($($param: ident),*) => { #[allow(clippy::undocumented_unsafe_blocks)] #[allow(non_snake_case)] - impl<$($param: ObserverSystemParam),*> ObserverSystemParam for ($($param,)*) { + unsafe impl<$($param: ObserverSystemParam),*> ObserverSystemParam for ($($param,)*) { } }; } diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index e9e89e2b7140d..9910a208ee4e9 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -307,8 +307,8 @@ impl<'w> DeferredWorld<'w> { ) { if archetype.has_remove_hook() { for component_id in targets { - // SAFETY: Caller ensures that these components exist let hooks = + // SAFETY: Caller ensures that these components exist unsafe { self.world.components().get_info_unchecked(component_id) }.hooks(); if let Some(hook) = hooks.on_remove { hook(DeferredWorld { world: self.world }, entity, component_id); diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 2576dac04c7ee..5b39821d2d7d3 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -1179,7 +1179,7 @@ impl<'w> EntityWorldMut<'w> { } } - /// Creates an [`Observer`] listening for `E` events targetting this entity. + /// Creates an [`Observer`](crate::observer::Observer) listening for `E` events targetting this entity. /// In order to trigger the callback the entity must also match the query when the event is fired. pub fn observe( &mut self, From 277d301bffca51c022bbfed95249aaadbd55f331 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Mon, 5 Feb 2024 18:26:02 -0800 Subject: [PATCH 067/109] Allow DeferredWorld as a system parameter, clean up docs --- crates/bevy_ecs/src/query/access.rs | 10 ++++++++ crates/bevy_ecs/src/system/observer_system.rs | 4 ++- crates/bevy_ecs/src/world/deferred_world.rs | 25 +++++++++++++++++-- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index d7253f073ed7f..d339d9c7bf9c0 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -608,6 +608,16 @@ impl FilteredAccessSet { .extend(filtered_access_set.filtered_accesses); } + /// Marks the set as reading all T. + pub fn read_all(&mut self) { + self.combined_access.read_all(); + } + + /// Marks the set as writing all T. + pub fn write_all(&mut self) { + self.combined_access.write_all(); + } + /// Removes all accesses stored in this set. pub fn clear(&mut self) { self.combined_access.clear(); diff --git a/crates/bevy_ecs/src/system/observer_system.rs b/crates/bevy_ecs/src/system/observer_system.rs index a4298fc084a2b..b18fd0ba64463 100644 --- a/crates/bevy_ecs/src/system/observer_system.rs +++ b/crates/bevy_ecs/src/system/observer_system.rs @@ -33,9 +33,11 @@ unsafe impl<'w, T: Resource> ObserverSystemParam for Res<'w, T> {} /// # Safety: `ResMut` can make no structural changes unsafe impl<'w, T: Resource> ObserverSystemParam for ResMut<'w, T> {} -/// # Safety: `Commands` can't make structural changes +/// # Safety: `Commands` can make no structural changes unsafe impl<'w> ObserverSystemParam for Commands<'w, 'w> {} +/// # Safety: `DeferredWorld` can make no structural changes + impl ObserverSystem for FunctionSystem where Marker: 'static, diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 9910a208ee4e9..d55904f03df90 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -3,13 +3,13 @@ use std::ops::Deref; use crate::{ archetype::Archetype, change_detection::MutUntyped, - component::ComponentId, + component::{ComponentId, Tick}, entity::{Entity, EntityLocation}, event::{Event, EventId, Events, SendBatchIds}, observer::{EcsEvent, EventBuilder, Observers}, prelude::{Component, QueryState}, query::{QueryData, QueryFilter}, - system::{Commands, Query, Resource}, + system::{Commands, Query, Resource, SystemMeta, SystemParam}, }; use super::{ @@ -384,3 +384,24 @@ impl<'w> From<&'w mut World> for DeferredWorld<'w> { } } } + +/// # Safety: `DeferredWorld` can read all components and resources but cannot be used to gain any other mutable references. +unsafe impl<'w> SystemParam for DeferredWorld<'w> { + type State = (); + type Item<'world, 'state> = DeferredWorld<'world>; + + fn init_state(_world: &mut World, system_meta: &mut SystemMeta) -> Self::State { + system_meta.component_access_set.read_all(); + system_meta.component_access_set.write_all(); + system_meta.set_has_deferred(); + } + + unsafe fn get_param<'world, 'state>( + _state: &'state mut Self::State, + _system_meta: &SystemMeta, + world: UnsafeWorldCell<'world>, + _change_tick: Tick, + ) -> Self::Item<'world, 'state> { + world.into_deferred() + } +} From 0022c7cd630d040fa5acf11597137ada39b14769 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sun, 11 Feb 2024 11:53:14 -0800 Subject: [PATCH 068/109] Improve handling of command queue buffers --- .../src/system/commands/command_queue.rs | 22 +++++----- .../src/system/exclusive_function_system.rs | 2 +- crates/bevy_ecs/src/world/deferred_world.rs | 40 +++++++++---------- crates/bevy_ecs/src/world/mod.rs | 1 + 4 files changed, 33 insertions(+), 32 deletions(-) diff --git a/crates/bevy_ecs/src/system/commands/command_queue.rs b/crates/bevy_ecs/src/system/commands/command_queue.rs index 3f43d98df2e95..fa55c3a4b8d5a 100644 --- a/crates/bevy_ecs/src/system/commands/command_queue.rs +++ b/crates/bevy_ecs/src/system/commands/command_queue.rs @@ -137,13 +137,14 @@ impl CommandQueue { // Reset the buffer, so it can be reused after this function ends. // In the loop below, ownership of each command will be transferred into user code. - let bytes = std::mem::take(&mut self.bytes); + // SAFETY: `set_len(0)` is always valid. + unsafe { self.bytes.set_len(0) }; // Create a stack for the command queue's we will be applying as commands may queue additional commands. // This is preferred over recursion to avoid stack overflows. let mut resolving_commands = vec![(cursor, end)]; - // Take ownership of buffers so they are not free'd uintil they are iterated. - let mut buffers = vec![bytes]; + // Take ownership of any additional buffers so they are not free'd uintil they are iterated. + let mut buffers = Vec::new(); // Add any commands in the world's internal queue to the top of the stack. if let Some(world) = &mut world { @@ -192,7 +193,7 @@ impl CommandQueue { } let mut bytes = std::mem::take(&mut world.command_queue.bytes); - // Set our variables to start applying the new queue + // Start applying the new queue let bytes_range = bytes.as_mut_ptr_range(); cursor = bytes_range.start; end = bytes_range.end; @@ -202,13 +203,12 @@ impl CommandQueue { } } } - } - - // Reset the buffer, so it can be reused after this function ends. - if let Some(bytes) = buffers.first_mut() { - self.bytes = std::mem::take(bytes); - // SAFETY: `set_len(0)` is always valid. - unsafe { self.bytes.set_len(0) } + // Re-use last buffer to avoid re-allocation + if let (Some(world), Some(buffer)) = (&mut world, buffers.pop()) { + world.command_queue.bytes = buffer; + // SAFETY: `set_len(0)` is always valid. + unsafe { world.command_queue.bytes.set_len(0) }; + } } } diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index d31e68cd7adaa..f7675fe813d25 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -116,12 +116,12 @@ where &self.system_meta, ); let out = self.func.run(world, input, params); + world.flush_commands(); let change_tick = world.change_tick.get_mut(); self.system_meta.last_run.set(*change_tick); *change_tick = change_tick.wrapping_add(1); world.last_change_tick = saved_last_tick; - world.flush_commands(); out } diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 24641a77bd00f..2df839634a987 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -31,6 +31,26 @@ impl<'w> Deref for DeferredWorld<'w> { } } +impl<'w> UnsafeWorldCell<'w> { + /// Turn self into a [`DeferredWorld`] + /// + /// # Safety + /// Caller must ensure there are no outstanding mutable references to world and no + /// outstanding references to the world's command queue, resource or component data + #[inline] + pub unsafe fn into_deferred(self) -> DeferredWorld<'w> { + DeferredWorld { world: self } + } +} + +impl<'w> From<&'w mut World> for DeferredWorld<'w> { + fn from(world: &'w mut World) -> DeferredWorld<'w> { + DeferredWorld { + world: world.as_unsafe_world_cell(), + } + } +} + impl<'w> DeferredWorld<'w> { /// Creates a [`Commands`] instance that pushes to the world's command queue #[inline] @@ -296,23 +316,3 @@ impl<'w> DeferredWorld<'w> { } } } - -impl<'w> UnsafeWorldCell<'w> { - /// Turn self into a [`DeferredWorld`] - /// - /// # Safety - /// Caller must ensure there are no outstanding mutable references to world and no - /// outstanding references to the world's command queue, resource or component data - #[inline] - pub unsafe fn into_deferred(self) -> DeferredWorld<'w> { - DeferredWorld { world: self } - } -} - -impl<'w> From<&'w mut World> for DeferredWorld<'w> { - fn from(world: &'w mut World) -> DeferredWorld<'w> { - DeferredWorld { - world: world.as_unsafe_world_cell(), - } - } -} diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 4be31225f03da..35ba2c1294647 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1854,6 +1854,7 @@ impl World { #[inline] pub fn flush_commands(&mut self) { if !self.command_queue.is_empty() { + // `CommandQueue` application always applies commands from the world queue first so this will apply all stored commands CommandQueue::default().apply(self); } } From 84ff2c71accf3735587aa7a7a023395b539c9c71 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Tue, 20 Feb 2024 12:39:06 -0800 Subject: [PATCH 069/109] Elaborate on ObserverSystem --- crates/bevy_ecs/src/system/observer_system.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/system/observer_system.rs b/crates/bevy_ecs/src/system/observer_system.rs index b18fd0ba64463..f042979213694 100644 --- a/crates/bevy_ecs/src/system/observer_system.rs +++ b/crates/bevy_ecs/src/system/observer_system.rs @@ -18,7 +18,8 @@ pub trait ObserverSystem: /// Implemented for [`SystemParam`] that can be used in [`Observer`] systems. /// # Safety -/// Implementing types must ensure their implementation of [`SystemParam::get_param`] does not use `world` except as a [`DeferredWorld`] +/// Implementing types must ensure their implementation of [`SystemParam::get_param`] does not use `world` except as a [`DeferredWorld`]] +/// `apply_deferred` may also not be called, this trait can be merged with `SystemParam` if we promote these restrictions to that type pub unsafe trait ObserverSystemParam: SystemParam {} /// # Safety: `Query` can make no structural changes From 42de22bd70dc65d2f7971f46ce5ab2a867d7f948 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Fri, 1 Mar 2024 13:50:40 -0800 Subject: [PATCH 070/109] Spelling and typos --- crates/bevy_ecs/src/bundle.rs | 2 +- crates/bevy_ecs/src/observer/mod.rs | 4 ++-- crates/bevy_ecs/src/observer/runner.rs | 4 ++-- crates/bevy_ecs/src/world/entity_ref.rs | 2 +- crates/bevy_ecs/src/world/unsafe_world_cell.rs | 2 +- examples/ecs/component_hooks.rs | 4 ++-- examples/ecs/observers.rs | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 76933b07063d7..9e5eb3a105a81 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -930,7 +930,7 @@ impl<'w> BundleSpawner<'w> { location }; - // SAFETY: We have no oustanding mutable references to world as they were dropped + // SAFETY: We have no outsanding mutable references to world as they were dropped unsafe { let archetype = &*self.archetype; let bundle_info = &*self.bundle_info; diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 189c9eafe98a7..8e357569e02f1 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -156,13 +156,13 @@ impl Observers { // SAFETY: You cannot get a mutable reference to `observers` from `DeferredWorld` let (mut world, observers) = unsafe { let world = world.as_unsafe_world_cell(); - // SAFETY: There are no oustanding world references + // SAFETY: There are no outsanding world references world.increment_event_id(); let observers = world.observers(); let Some(observers) = observers.try_get_observers(event) else { return; }; - // SAFETY: The only oustanding reference to world is `observers` + // SAFETY: The only outsanding reference to world is `observers` (world.into_deferred(), observers) }; diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index 5f0951a6f2bdc..fafc08f2b60ec 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -7,7 +7,7 @@ use super::*; /// Type for function that is run when an observer is triggered /// Typically refers to the default runner that runs the contained, -/// but can be overriden for custom behaviour. +/// but can be overridden for custom behaviour. pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut); /// Equivalent to [`BoxedSystem`](crate::system::BoxedSystem) for [`ObserverSystem`]. @@ -85,7 +85,7 @@ impl ObserverComponent { system.update_archetype_component_access(world); // SAFETY: // - `update_archetype_component_access` was just called - // - there are no oustanding references to world except a private component + // - there are no outsanding references to world except a private component // - system is an `ObserverSystem` so won't mutate world beyond the access of a `DeferredWorld` // - system is the same type erased system from above unsafe { diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 86589a3ca4fc1..6615875182f01 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -1267,7 +1267,7 @@ impl<'w> EntityWorldMut<'w> { } } - /// Creates an [`Observer`](crate::observer::Observer) listening for `E` events targetting this entity. + /// Creates an [`Observer`](crate::observer::Observer) listening for `E` events targeting this entity. /// In order to trigger the callback the entity must also match the query when the event is fired. pub fn observe( &mut self, diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 4fe5bad3c1619..dcd1966af6f8d 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -612,7 +612,7 @@ impl<'w> UnsafeWorldCell<'w> { } /// # Safety - /// It is the callers responsibility to ensure that there are no oustanding + /// It is the callers responsibility to ensure that there are no outsanding /// references to `last_event_id`. pub(crate) unsafe fn increment_event_id(self) { // SAFETY: Caller ensure there are no outstanding references diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs index 7794ca84a8c3f..dcf42bfe36219 100644 --- a/examples/ecs/component_hooks.rs +++ b/examples/ecs/component_hooks.rs @@ -1,6 +1,6 @@ //! This examples illustrates the different ways you can employ component lifecycle hooks -use bevy::ecs::component::{ComponentInfo, TableStorage}; +use bevy::ecs::component::{ComponentHooks, TableStorage}; use bevy::prelude::*; use std::collections::HashMap; @@ -12,7 +12,7 @@ impl Component for MyComponent { /// Hooks can also be registered during component initialisation by /// implementing `init_component_info` - fn init_component_info(_info: &mut ComponentInfo) { + fn register_component_hooks(_hooks: &mut ComponentHooks) { // Register hooks... } } diff --git a/examples/ecs/observers.rs b/examples/ecs/observers.rs index 2eb57123df55e..6052d05753337 100644 --- a/examples/ecs/observers.rs +++ b/examples/ecs/observers.rs @@ -67,7 +67,7 @@ fn setup(world: &mut World) { // This will spawn an entity with CompA // - Which will trigger the first observer // - Removing CompB - // - Emitting Resize targetting `entity` + // - Emitting Resize targeting `entity` // - Which will trigger it's entity observer // - Incrementing ResizeCount let entity_b = world.spawn((CompA(entity), CompB)).flush(); From 6b64539ce401d32ec9ec705dbd970ee74ea00bb6 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Fri, 1 Mar 2024 13:53:27 -0800 Subject: [PATCH 071/109] Fix lints --- crates/bevy_ecs/src/observer/builder.rs | 4 +--- crates/bevy_ecs/src/observer/runner.rs | 5 +---- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/crates/bevy_ecs/src/observer/builder.rs b/crates/bevy_ecs/src/observer/builder.rs index bf7a2155243a5..4f26f17690d14 100644 --- a/crates/bevy_ecs/src/observer/builder.rs +++ b/crates/bevy_ecs/src/observer/builder.rs @@ -1,6 +1,4 @@ -use std::{any::TypeId, marker::PhantomData}; - -use crate::system::IntoObserverSystem; +use std::any::TypeId; use super::*; diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index fafc08f2b60ec..90065e668f724 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -1,7 +1,4 @@ -use crate::{ - component::ComponentHooks, - system::{IntoObserverSystem, ObserverSystem}, -}; +use crate::{component::ComponentHooks, system::ObserverSystem}; use super::*; From 8c0f855993e9b72d0e4f5d79cb3d58e8ecd48455 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Fri, 1 Mar 2024 13:55:37 -0800 Subject: [PATCH 072/109] Typos --- crates/bevy_ecs/src/observer/mod.rs | 2 +- crates/bevy_ecs/src/system/commands/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 8e357569e02f1..cae481870f8da 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -95,7 +95,7 @@ pub struct ObserverTrigger { // Map between an observer entity and it's runner type ObserverMap = EntityHashMap; -/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular event targetted at a specific component. +/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular event targeted at a specific component. #[derive(Default, Debug)] pub struct CachedComponentObservers { // Observers listening to events targeting this component diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index b0ac1aff1c32a..38d1c8f3d0766 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -1013,7 +1013,7 @@ impl EntityCommands<'_> { self.commands.reborrow() } - /// Creates an [`Observer`](crate::observer::Observer) listening for `E` events targetting this entity. + /// Creates an [`Observer`](crate::observer::Observer) listening for `E` events targeting this entity. /// In order to trigger the callback the entity must also match the query when the event is fired. pub fn observe( &mut self, From 238aa9079b686b908f35f969477776cb24fe0e99 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Thu, 7 Mar 2024 18:27:24 -0800 Subject: [PATCH 073/109] Cleanup unsafe code, simplify types and trait definitions --- crates/bevy_ecs/src/bundle.rs | 27 ++--- crates/bevy_ecs/src/component.rs | 5 - crates/bevy_ecs/src/observer/builder.rs | 101 ++++++++++++------ .../bevy_ecs/src/observer/entity_observer.rs | 6 +- crates/bevy_ecs/src/observer/mod.rs | 32 +++--- crates/bevy_ecs/src/observer/runner.rs | 15 +-- crates/bevy_ecs/src/query/builder.rs | 4 +- crates/bevy_ecs/src/system/commands/mod.rs | 51 +++++---- crates/bevy_ecs/src/system/observer_system.rs | 28 +++-- crates/bevy_ecs/src/system/system_param.rs | 21 ++++ crates/bevy_ecs/src/world/command_queue.rs | 9 +- .../bevy_ecs/src/world/component_constants.rs | 15 +-- crates/bevy_ecs/src/world/deferred_world.rs | 44 +++----- crates/bevy_ecs/src/world/entity_ref.rs | 4 +- crates/bevy_ecs/src/world/mod.rs | 12 --- 15 files changed, 203 insertions(+), 171 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 00c40204321ac..f14bd71fed0c5 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -17,12 +17,11 @@ use crate::{ prelude::World, query::DebugCheckedUnwrap, storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow}, - world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, ON_ADD, ON_INSERT}, + world::{unsafe_world_cell::UnsafeWorldCell, ON_ADD, ON_INSERT}, }; use bevy_ptr::{ConstNonNull, OwningPtr}; use bevy_utils::{all_tuples, HashMap, HashSet, TypeIdMap}; -use std::any::TypeId; use std::ptr::NonNull; /// The `Bundle` trait enables insertion and removal of [`Component`]s from an entity. @@ -153,7 +152,7 @@ pub unsafe trait Bundle: DynamicBundle + Send + Sync + 'static { ids: &mut impl FnMut(ComponentId), ); - /// Gets this [`Bundle`]'s component ids, will be `None` if the components has not been registered. + /// Gets this [`Bundle`]'s component ids, will be `None` if the component has not been registered. fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option)); /// Calls `func`, which should return data for each component in the bundle, in the order of @@ -808,21 +807,21 @@ impl<'w> BundleInserter<'w> { // SAFETY: All components in the bundle are guaranteed to exist in the World // as they must be initialized before creating the BundleInfo. unsafe { - deferred_world.trigger_on_add(archetype, entity, add_bundle.added.iter().cloned()); - if archetype.has_add_observer() { + deferred_world.trigger_on_add(new_archetype, entity, add_bundle.added.iter().cloned()); + if new_archetype.has_add_observer() { deferred_world.trigger_observers( ON_ADD, entity, - location, + new_location, add_bundle.added.iter().cloned(), ); } - deferred_world.trigger_on_insert(archetype, entity, bundle_info.iter_components()); - if archetype.has_insert_observer() { + deferred_world.trigger_on_insert(new_archetype, entity, bundle_info.iter_components()); + if new_archetype.has_insert_observer() { deferred_world.trigger_observers( ON_INSERT, entity, - location, + new_location, bundle_info.iter_components(), ); } @@ -902,12 +901,12 @@ impl<'w> BundleSpawner<'w> { entity: Entity, bundle: T, ) -> EntityLocation { - let table = self.table.as_mut(); - let archetype = self.archetype.as_mut(); + // SAFETY: We do not make any structural changes to the archetype graph through self.world so these pointers always remain valid let bundle_info = self.bundle_info.as_ref(); - - // SAFETY: We do not make any structural changes to the archetype graph through self.world so this pointer always remain valid let location = { + let table = self.table.as_mut(); + let archetype = self.archetype.as_mut(); + // SAFETY: Mutable references do not alias and will be dropped after this block let (sparse_sets, entities) = { let world = self.world.world_mut(); @@ -930,6 +929,8 @@ impl<'w> BundleSpawner<'w> { // SAFETY: We have no outstanding mutable references to world as they were dropped let mut deferred_world = unsafe { self.world.into_deferred() }; + // SAFETY: `DeferredWorld` cannot provide mutable access to `Archetypes`. + let archetype = self.archetype.as_ref(); // SAFETY: All components in the bundle are guaranteed to exist in the World // as they must be initialized before creating the BundleInfo. unsafe { diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 7e22956453613..808fb148b5f73 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -634,11 +634,6 @@ impl Components { self.components.get_mut(id.0).map(|info| &mut info.hooks) } - #[inline] - pub(crate) unsafe fn get_info_mut(&mut self, id: ComponentId) -> &mut ComponentInfo { - self.components.get_unchecked_mut(id.0) - } - /// Type-erased equivalent of [`Components::component_id()`]. #[inline] pub fn get_id(&self, type_id: TypeId) -> Option { diff --git a/crates/bevy_ecs/src/observer/builder.rs b/crates/bevy_ecs/src/observer/builder.rs index 4f26f17690d14..855e49a7f0484 100644 --- a/crates/bevy_ecs/src/observer/builder.rs +++ b/crates/bevy_ecs/src/observer/builder.rs @@ -3,17 +3,16 @@ use std::any::TypeId; use super::*; /// Builder struct for [`Observer`]. -pub struct ObserverBuilder<'w, E: EcsEvent = NoEvent> { +pub struct ObserverBuilder<'w, E = ()> { commands: Commands<'w, 'w>, descriptor: ObserverDescriptor, _marker: PhantomData, } -impl<'w, E: EcsEvent> ObserverBuilder<'w, E> { +impl<'w, E: 'static> ObserverBuilder<'w, E> { /// Constructs a new [`ObserverBuilder`]. pub fn new(commands: Commands<'w, 'w>) -> Self { let mut descriptor = ObserverDescriptor::default(); - // TODO: Better messages let event = commands .components() .get_id(TypeId::of::()) @@ -23,10 +22,23 @@ impl<'w, E: EcsEvent> ObserverBuilder<'w, E> { std::any::type_name::(), ) }); + descriptor.events.push(event); - if event != NO_EVENT { - descriptor.events.push(event); + Self { + commands, + descriptor, + _marker: PhantomData, } + } + + /// Constructs an [`ObserverBuilder`] with a dynamic event id. + /// # Safety + /// Caller must ensure that the component associated with `id` is accessible as E + #[must_use] + pub unsafe fn new_with_id(&mut self, event: ComponentId, commands: Commands<'w, 'w>) -> Self { + let mut descriptor = ObserverDescriptor::default(); + descriptor.events.push(event); + Self { commands, descriptor, @@ -36,7 +48,7 @@ impl<'w, E: EcsEvent> ObserverBuilder<'w, E> { /// Adds `NewE` to the list of events listened to by this observer. /// Observers that listen to multiple types of events can no longer access the typed event data. - pub fn on_event(&mut self) -> &mut ObserverBuilder<'w, NoEvent> { + pub fn on_event(&mut self) -> &mut ObserverBuilder<'w, ()> { let event = self .commands .components() @@ -48,7 +60,7 @@ impl<'w, E: EcsEvent> ObserverBuilder<'w, E> { ) }); self.descriptor.events.push(event); - // SAFETY: NoEvent type will not allow bad memory access as it has no size + // SAFETY: () will not allow bad memory access as it has no size unsafe { std::mem::transmute(self) } } @@ -57,7 +69,7 @@ impl<'w, E: EcsEvent> ObserverBuilder<'w, E> { pub fn on_event_ids( &mut self, events: impl IntoIterator, - ) -> &mut ObserverBuilder<'w, NoEvent> { + ) -> &mut ObserverBuilder<'w, ()> { self.descriptor.events.extend(events); // SAFETY: () type will not allow bad memory access as it has no size unsafe { std::mem::transmute(self) } @@ -107,7 +119,7 @@ impl<'w, E: EcsEvent> ObserverBuilder<'w, E> { entity } - /// Spawns the resulting observer into the world using a [`ObserverRunner`] callback. + /// Spawns the resulting observer into the world using an [`ObserverRunner`] callback. /// This is not advised unless you want to override the default runner behaviour. pub fn runner(&mut self, runner: ObserverRunner) -> Entity { let entity = self.commands.spawn_empty().id(); @@ -121,20 +133,29 @@ impl<'w, E: EcsEvent> ObserverBuilder<'w, E> { } /// Type used to construct and emit a [`EcsEvent`] -pub struct EventBuilder<'w, E> { - event: Option, +pub struct EventBuilder<'w, E = ()> { + event: ComponentId, commands: Commands<'w, 'w>, targets: Vec, components: Vec, data: Option, } -impl<'w, E: EcsEvent> EventBuilder<'w, E> { +impl<'w, E: Send + 'static> EventBuilder<'w, E> { /// Constructs a new builder that will write it's event to `world`'s command queue #[must_use] pub fn new(data: E, commands: Commands<'w, 'w>) -> Self { + let event = commands + .components() + .get_id(TypeId::of::()) + .unwrap_or_else(|| { + panic!( + "Cannot emit event for unregistered component type: {}", + std::any::type_name::() + ) + }); Self { - event: None, + event, commands, targets: Vec::new(), components: Vec::new(), @@ -142,19 +163,29 @@ impl<'w, E: EcsEvent> EventBuilder<'w, E> { } } - /// Adds `target` to the list of entities targeted by `self` + /// Sets the event id of the resulting event, used for dynamic events + /// # Safety + /// Caller must ensure that the component associated with `id` is accessible as E #[must_use] - pub fn entity(&mut self, target: Entity) -> &mut Self { - self.targets.push(target); - self + pub unsafe fn new_with_id( + &mut self, + event: ComponentId, + data: E, + commands: Commands<'w, 'w>, + ) -> Self { + Self { + event, + commands, + targets: Vec::new(), + components: Vec::new(), + data: Some(data), + } } - /// Sets the event id of the resulting event, used for dynamic events - /// # Safety - /// Caller must ensure that the component associated with `id` has the same layout as E + /// Adds `target` to the list of entities targeted by `self` #[must_use] - pub unsafe fn event_id(&mut self, id: ComponentId) -> &mut Self { - self.event = Some(id); + pub fn entity(&mut self, target: Entity) -> &mut Self { + self.targets.push(target); self } @@ -167,28 +198,32 @@ impl<'w, E: EcsEvent> EventBuilder<'w, E> { /// Add the event to the command queue of world pub fn emit(&mut self) { - self.commands.add(EmitEcsEvent:: { - event: self.event, - data: std::mem::take(&mut self.data).unwrap(), - entities: std::mem::take(&mut self.targets), - components: std::mem::take(&mut self.components), + // SAFETY: `self.event` is accessible as E, enforced in `Self::new` and `Self::new_with_id`. + self.commands.add(unsafe { + EmitEcsEvent::::new( + self.event, + std::mem::take(&mut self.targets), + std::mem::take(&mut self.components), + std::mem::take(&mut self.data) + .expect("EventBuilder used to send more than one event."), + ) }); } } impl<'w, 's> Commands<'w, 's> { - /// Constructs an [`EventBuilder`] for an [`EcsEvent`]. - pub fn event(&mut self, event: E) -> EventBuilder { + /// Constructs an [`EventBuilder`]. + pub fn event(&mut self, event: E) -> EventBuilder { EventBuilder::new(event, self.reborrow()) } - /// Construct an [`ObserverBuilder`] - pub fn observer_builder(&mut self) -> ObserverBuilder { + /// Construct an [`ObserverBuilder`]. + pub fn observer_builder(&mut self) -> ObserverBuilder { ObserverBuilder::new(self.reborrow()) } - /// Spawn an [`Observer`] and returns it's [`Entity`] - pub fn observer( + /// Spawn an [`Observer`] and returns it's [`Entity`]. + pub fn observer( &mut self, callback: impl IntoObserverSystem, ) -> Entity { diff --git a/crates/bevy_ecs/src/observer/entity_observer.rs b/crates/bevy_ecs/src/observer/entity_observer.rs index 3e2bc7ab5cfbf..a12b993163f94 100644 --- a/crates/bevy_ecs/src/observer/entity_observer.rs +++ b/crates/bevy_ecs/src/observer/entity_observer.rs @@ -1,12 +1,12 @@ use super::*; -use crate::component::ComponentHooks; +use crate::component::{ComponentHooks, StorageType}; /// Component to signify an entity observer being attached to an entity /// Can be modelled by parent-child relationship if/when that is enforced pub(crate) struct AttachObserver(pub(crate) Entity); impl Component for AttachObserver { - type Storage = SparseStorage; + const STORAGE_TYPE: StorageType = StorageType::SparseSet; // When `AttachObserver` is inserted onto an event add it to `ObservedBy` // or insert `ObservedBy` if it doesn't exist @@ -29,7 +29,7 @@ impl Component for AttachObserver { pub(crate) struct ObservedBy(Vec); impl Component for ObservedBy { - type Storage = SparseStorage; + const STORAGE_TYPE: StorageType = StorageType::SparseSet; fn register_component_hooks(hooks: &mut ComponentHooks) { hooks.on_remove(|mut world, entity, _| { diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index cae481870f8da..c3afc6da0cb76 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -12,7 +12,6 @@ pub use runner::*; use crate::{ archetype::ArchetypeFlags, - component::SparseStorage, entity::EntityLocation, query::DebugCheckedUnwrap, system::{EmitEcsEvent, IntoObserverSystem}, @@ -24,13 +23,7 @@ use bevy_utils::{EntityHashMap, HashMap}; use crate::{component::ComponentId, prelude::*, world::DeferredWorld}; -/// Trait used to mark components used as ECS events. -pub trait EcsEvent: Component {} - -impl EcsEvent for E {} - /// Type used in callbacks registered for observers. -/// TODO: Proper docs and examples pub struct Observer<'w, E, B: Bundle = ()> { data: &'w mut E, trigger: ObserverTrigger, @@ -118,7 +111,7 @@ pub struct CachedObservers { /// Metadata for observers. Stores a cache mapping event ids to the registered observers. #[derive(Default, Debug)] pub struct Observers { - // Cached ECS observers to save a lookup for common actions + // Cached ECS observers to save a lookup most common events. on_add: CachedObservers, on_insert: CachedObservers, on_remove: CachedObservers, @@ -234,13 +227,13 @@ impl Observers { impl World { /// Construct an [`ObserverBuilder`] - pub fn observer_builder(&mut self) -> ObserverBuilder { + pub fn observer_builder(&mut self) -> ObserverBuilder { self.init_component::(); ObserverBuilder::new(self.commands()) } - /// Spawn an [`Observer`] and returns it's [`Entity`] - pub fn observer( + /// Spawn an [`Observer`] and returns it's [`Entity`]. + pub fn observer( &mut self, callback: impl IntoObserverSystem, ) -> Entity { @@ -248,17 +241,17 @@ impl World { ObserverBuilder::new(self.commands()).run(callback) } - /// Constructs an [`EventBuilder`] for an [`EcsEvent`]. - pub fn ecs_event(&mut self, event: E) -> EventBuilder { + /// Constructs an [`EventBuilder`]. + pub fn ecs_event(&mut self, event: E) -> EventBuilder { self.init_component::(); EventBuilder::new(event, self.commands()) } pub(crate) fn register_observer(&mut self, entity: Entity) { - let observer_component: *const ObserverComponent = - self.get::(entity).unwrap(); // SAFETY: References do not alias. let (observer_component, archetypes, observers) = unsafe { + let observer_component: *const ObserverComponent = + self.get::(entity).unwrap(); ( &*observer_component, &mut self.archetypes, @@ -269,12 +262,14 @@ impl World { for &event in &descriptor.events { let cache = observers.get_observers(event); + // Observer is not targetting any components so register it as an entity observer if descriptor.components.is_empty() { for &source in &observer_component.descriptor.sources { let map = cache.entity_observers.entry(source).or_default(); map.insert(entity, observer_component.runner); } } else { + // Register observer for each source component for &component in &descriptor.components { let observers = cache @@ -287,8 +282,10 @@ impl World { CachedComponentObservers::default() }); if descriptor.sources.is_empty() { + // Register for all events targetting the component observers.map.insert(entity, observer_component.runner); } else { + // Register for each targetted entity for &source in &descriptor.sources { let map = observers.entity_map.entry(source).or_default(); map.insert(entity, observer_component.runner); @@ -300,11 +297,10 @@ impl World { } pub(crate) fn unregister_observer(&mut self, entity: Entity) { - let observer_component: *const ObserverComponent = - self.get::(entity).unwrap(); - // SAFETY: References do not alias. let (observer_component, archetypes, observers) = unsafe { + let observer_component: *const ObserverComponent = + self.get::(entity).unwrap(); ( &*observer_component, &mut self.archetypes, diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index 90065e668f724..2e08832a0f86d 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -1,4 +1,7 @@ -use crate::{component::ComponentHooks, system::ObserverSystem}; +use crate::{ + component::{ComponentHooks, StorageType}, + system::ObserverSystem, +}; use super::*; @@ -18,7 +21,7 @@ pub(crate) struct ObserverComponent { } impl Component for ObserverComponent { - type Storage = SparseStorage; + const STORAGE_TYPE: StorageType = StorageType::SparseSet; fn register_component_hooks(hooks: &mut ComponentHooks) { hooks @@ -51,9 +54,6 @@ impl ObserverComponent { Self { descriptor, runner: |mut world, trigger, ptr| { - if trigger.source == Entity::PLACEHOLDER { - return; - } let world = world.as_unsafe_world_cell(); let observer_cell = // SAFETY: Observer was triggered so must still exist in world @@ -64,6 +64,8 @@ impl ObserverComponent { .get_mut::() .debug_checked_unwrap() }; + + // TODO: Move this check into the observer cache to avoid dynamic dispatch // SAFETY: We only access world metadata let last_event = unsafe { world.world_metadata() }.last_event_id; if state.last_event_id == last_event { @@ -71,7 +73,7 @@ impl ObserverComponent { } state.last_event_id = last_event; - // SAFETY: Caller ensures `ptr` is castable to `E` + // SAFETY: Caller ensures `ptr` is castable to `&mut E` let observer: Observer = Observer::new(unsafe { ptr.deref_mut() }, trigger); // SAFETY: System is from component let mut system: Box> = unsafe { @@ -80,6 +82,7 @@ impl ObserverComponent { }; system.update_archetype_component_access(world); + // SAFETY: // - `update_archetype_component_access` was just called // - there are no outsanding references to world except a private component diff --git a/crates/bevy_ecs/src/query/builder.rs b/crates/bevy_ecs/src/query/builder.rs index 1f8e4f4a746ca..b19c5a6b516d8 100644 --- a/crates/bevy_ecs/src/query/builder.rs +++ b/crates/bevy_ecs/src/query/builder.rs @@ -7,7 +7,7 @@ use super::{FilteredAccess, QueryData, QueryFilter}; /// Builder struct to create [`QueryState`] instances at runtime. /// /// ``` -/// # use bevy_ecs::{prelude::*, query::QueryBuilder}; +/// # use bevy_ecs::prelude::*; /// # /// # #[derive(Component)] /// # struct A; @@ -178,7 +178,7 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> { /// on an empty builder, all accesses added to that builder will become terms in an or expression. /// /// ``` - /// # use bevy_ecs::{prelude::*, query::QueryBuilder}; + /// # use bevy_ecs::prelude::*; /// # /// # #[derive(Component)] /// # struct A; diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 277440e2cc04e..6fb74c06f4670 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -1,12 +1,12 @@ mod parallel_scope; -use super::{Deferred, IntoObserverSystem, ObserverSystem, Resource, SystemBuffer, SystemMeta}; +use super::{Deferred, IntoObserverSystem, ObserverSystem, Resource}; use crate::{ self as bevy_ecs, bundle::Bundle, component::{ComponentId, Components}, entity::{Entities, Entity}, - observer::EcsEvent, + prelude::Component, system::{RunSystemWithInput, SystemId}, world::{Command, CommandQueue, DeferredWorld, EntityWorldMut, FromWorld, World}, }; @@ -955,8 +955,7 @@ impl EntityCommands<'_> { } /// Creates an [`Observer`](crate::observer::Observer) listening for `E` events targeting this entity. - /// In order to trigger the callback the entity must also match the query when the event is fired. - pub fn observe( + pub fn observe( &mut self, system: impl IntoObserverSystem, ) -> &mut Self { @@ -1108,7 +1107,7 @@ fn log_components(entity: Entity, world: &mut World) { /// A [`Command`] that spawns an observer attached to a specific entity. #[derive(Debug)] -pub struct Observe> { +pub struct Observe> { /// The entity that will be observed. pub entity: Entity, /// The callback to run when the event is observed. @@ -1117,7 +1116,7 @@ pub struct Observe> { pub marker: PhantomData<(E, B)>, } -impl> Command for Observe { +impl> Command for Observe { fn apply(self, world: &mut World) { world.entity_mut(self.entity).observe(self.system); } @@ -1125,28 +1124,44 @@ impl> Command for Observe { - /// [`ComponentId`] for this event, if not set will use the id of `E`. - pub event: Option, - /// Data for the event. - pub data: E, - /// Components to trigger observers for. - pub components: Vec, +pub(crate) struct EmitEcsEvent { + /// [`ComponentId`] for this event. + event: ComponentId, /// Entities to trigger observers for. - pub entities: Vec, + entities: Vec, + /// Components to trigger observers for. + components: Vec, + /// Data for the event. + data: E, +} + +impl EmitEcsEvent { + // SAFETY: Caller must ensure the type represented by `event` is accessible as `E`. + pub(crate) unsafe fn new( + event: ComponentId, + entities: Vec, + components: Vec, + data: E, + ) -> Self { + Self { + event, + entities, + components, + data, + } + } } -impl Command for EmitEcsEvent { +impl Command for EmitEcsEvent { fn apply(mut self, world: &mut World) { - let event = self.event.unwrap_or_else(|| world.init_component::()); let mut world = DeferredWorld::from(world); for &target in &self.entities { if let Some(location) = world.entities().get(target) { - // SAFETY: We called `init_component` for this event or it was passed + // SAFETY: E is accessible as the type represented by self.event, ensured in `Self::new` unsafe { world.trigger_observers_with_data( - event, + self.event, target, location, self.components.iter().cloned(), diff --git a/crates/bevy_ecs/src/system/observer_system.rs b/crates/bevy_ecs/src/system/observer_system.rs index f042979213694..ec9d225461aba 100644 --- a/crates/bevy_ecs/src/system/observer_system.rs +++ b/crates/bevy_ecs/src/system/observer_system.rs @@ -16,28 +16,24 @@ pub trait ObserverSystem: fn queue_deferred(&mut self, _world: DeferredWorld); } -/// Implemented for [`SystemParam`] that can be used in [`Observer`] systems. -/// # Safety -/// Implementing types must ensure their implementation of [`SystemParam::get_param`] does not use `world` except as a [`DeferredWorld`]] -/// `apply_deferred` may also not be called, this trait can be merged with `SystemParam` if we promote these restrictions to that type -pub unsafe trait ObserverSystemParam: SystemParam {} - -/// # Safety: `Query` can make no structural changes -unsafe impl<'w, D: QueryData + 'static, F: QueryFilter + 'static> ObserverSystemParam +/// Marker trait implemented for [`SystemParam`] that can be used in [`Observer`] systems. +/// `queue` may be called instead of `apply` for types implementing this trait. +/// +/// Note: this could be combined with [`SystemParam`] if we promote these restrictions to that trait. +pub trait ObserverSystemParam: SystemParam {} + +impl<'w, D: QueryData + 'static, F: QueryFilter + 'static> ObserverSystemParam for Query<'w, 'w, D, F> { } -/// # Safety: `Res` can make no structural changes -unsafe impl<'w, T: Resource> ObserverSystemParam for Res<'w, T> {} +impl<'w, T: Resource> ObserverSystemParam for Res<'w, T> {} -/// # Safety: `ResMut` can make no structural changes -unsafe impl<'w, T: Resource> ObserverSystemParam for ResMut<'w, T> {} +impl<'w, T: Resource> ObserverSystemParam for ResMut<'w, T> {} -/// # Safety: `Commands` can make no structural changes -unsafe impl<'w> ObserverSystemParam for Commands<'w, 'w> {} +impl<'w> ObserverSystemParam for Commands<'w, 'w> {} -/// # Safety: `DeferredWorld` can make no structural changes +impl<'w> ObserverSystemParam for DeferredWorld<'w> {} impl ObserverSystem for FunctionSystem where @@ -76,7 +72,7 @@ macro_rules! impl_observer_system_param_tuple { ($($param: ident),*) => { #[allow(clippy::undocumented_unsafe_blocks)] #[allow(non_snake_case)] - unsafe impl<$($param: ObserverSystemParam),*> ObserverSystemParam for ($($param,)*) { + impl<$($param: ObserverSystemParam),*> ObserverSystemParam for ($($param,)*) { } }; } diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index f42b52f79ac3d..055508147bcb3 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -667,6 +667,27 @@ unsafe impl SystemParam for &'_ World { } } +/// # Safety: `DeferredWorld` can read all components and resources but cannot be used to gain any other mutable references. +unsafe impl<'w> SystemParam for DeferredWorld<'w> { + type State = (); + type Item<'world, 'state> = DeferredWorld<'world>; + + fn init_state(_world: &mut World, system_meta: &mut SystemMeta) -> Self::State { + system_meta.component_access_set.read_all(); + system_meta.component_access_set.write_all(); + system_meta.set_has_deferred(); + } + + unsafe fn get_param<'world, 'state>( + _state: &'state mut Self::State, + _system_meta: &SystemMeta, + world: UnsafeWorldCell<'world>, + _change_tick: Tick, + ) -> Self::Item<'world, 'state> { + world.into_deferred() + } +} + /// A system local [`SystemParam`]. /// /// A local may only be accessed by the system itself and is therefore not visible to other systems. diff --git a/crates/bevy_ecs/src/world/command_queue.rs b/crates/bevy_ecs/src/world/command_queue.rs index f87494a05a50e..26261639060a6 100644 --- a/crates/bevy_ecs/src/world/command_queue.rs +++ b/crates/bevy_ecs/src/world/command_queue.rs @@ -7,6 +7,8 @@ use bevy_utils::tracing::warn; use crate::world::{Command, World}; +use super::DeferredWorld; + struct CommandMeta { /// SAFETY: The `value` must point to a value of type `T: Command`, /// where `T` is some specific type that was used to produce this metadata. @@ -32,7 +34,7 @@ pub struct CommandQueue { // For each command, one `CommandMeta` is stored, followed by zero or more bytes // to store the command itself. To interpret these bytes, a pointer must // be passed to the corresponding `CommandMeta.apply_command_and_get_size` fn pointer. - pub(crate) bytes: Vec>, + bytes: Vec>, } // CommandQueue needs to implement Debug manually, rather than deriving it, because the derived impl just prints @@ -242,6 +244,11 @@ impl SystemBuffer for CommandQueue { let _span_guard = _system_meta.commands_span.enter(); self.apply(world); } + + #[inline] + fn queue(&mut self, _system_meta: &SystemMeta, mut world: DeferredWorld) { + world.commands().append(self); + } } #[cfg(test)] diff --git a/crates/bevy_ecs/src/world/component_constants.rs b/crates/bevy_ecs/src/world/component_constants.rs index 058cc3b28a81a..4ef61c3b346d0 100644 --- a/crates/bevy_ecs/src/world/component_constants.rs +++ b/crates/bevy_ecs/src/world/component_constants.rs @@ -1,14 +1,14 @@ use super::*; use crate as bevy_ecs; +/// Internal components used by bevy with a fixed component id. +/// Constants are used to skip [`TypeId`] lookups in hot paths. -/// [`ComponentId`] for [`NoEvent`] -pub const NO_EVENT: ComponentId = ComponentId::new(0); /// [`ComponentId`] for [`OnAdd`] -pub const ON_ADD: ComponentId = ComponentId::new(1); +pub const ON_ADD: ComponentId = ComponentId::new(0); /// [`ComponentId`] for [`OnInsert`] -pub const ON_INSERT: ComponentId = ComponentId::new(2); +pub const ON_INSERT: ComponentId = ComponentId::new(1); /// [`ComponentId`] for [`OnRemove`] -pub const ON_REMOVE: ComponentId = ComponentId::new(3); +pub const ON_REMOVE: ComponentId = ComponentId::new(2); /// Event emitted when a component is added to an entity. #[derive(Component)] @@ -21,8 +21,3 @@ pub struct OnInsert; /// Event emitted when a component is removed from an entity. #[derive(Component)] pub struct OnRemove; - -/// Type used to signify observers that are listening to multiple events -/// so cannot access event data. -#[derive(Component)] -pub struct NoEvent; diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 2074ac2f7491c..110121f7915c4 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -3,13 +3,13 @@ use std::ops::Deref; use crate::{ archetype::Archetype, change_detection::MutUntyped, - component::{ComponentId, Tick}, + component::ComponentId, entity::{Entity, EntityLocation}, event::{Event, EventId, Events, SendBatchIds}, - observer::{EcsEvent, EventBuilder, Observers}, + observer::{EventBuilder, Observers}, prelude::{Component, QueryState}, query::{QueryData, QueryFilter}, - system::{Commands, Query, Resource, SystemMeta, SystemParam}, + system::{Commands, Query, Resource}, }; use super::{ @@ -116,19 +116,20 @@ impl<'w> DeferredWorld<'w> { /// # Panics /// If state is from a different world then self #[inline] - pub fn query<'s, Q: QueryData, F: QueryFilter>( + pub fn query<'s, D: QueryData, F: QueryFilter>( &'w mut self, - state: &'s mut QueryState, - ) -> Query<'w, 's, Q, F> { + state: &'s mut QueryState, + ) -> Query<'w, 's, D, F> { state.validate_world(self.world.id()); state.update_archetypes(self); // SAFETY: We ran validate_world to ensure our state matches unsafe { + let world_cell = self.world; Query::new( - self.world, + world_cell, state, - self.world.last_change_tick(), - self.world.change_tick(), + world_cell.last_change_tick(), + world_cell.change_tick(), ) } } @@ -362,7 +363,7 @@ impl<'w> DeferredWorld<'w> { /// Triggers all event observers for [`ComponentId`] in target. /// /// # Safety - /// Caller must ensure observers listening for `event` can accept types sharing a layout with `E` + /// Caller must ensure `E` is accessible as the type represented by `event` #[inline] pub(crate) unsafe fn trigger_observers_with_data( &mut self, @@ -376,7 +377,7 @@ impl<'w> DeferredWorld<'w> { } /// Constructs an [`EventBuilder`] for an [`EcsEvent`]. - pub fn ecs_event(&mut self, data: E) -> EventBuilder { + pub fn ecs_event(&mut self, data: E) -> EventBuilder { EventBuilder::new(data, self.commands()) } @@ -385,24 +386,3 @@ impl<'w> DeferredWorld<'w> { self.world } } - -/// # Safety: `DeferredWorld` can read all components and resources but cannot be used to gain any other mutable references. -unsafe impl<'w> SystemParam for DeferredWorld<'w> { - type State = (); - type Item<'world, 'state> = DeferredWorld<'world>; - - fn init_state(_world: &mut World, system_meta: &mut SystemMeta) -> Self::State { - system_meta.component_access_set.read_all(); - system_meta.component_access_set.write_all(); - system_meta.set_has_deferred(); - } - - unsafe fn get_param<'world, 'state>( - _state: &'state mut Self::State, - _system_meta: &SystemMeta, - world: UnsafeWorldCell<'world>, - _change_tick: Tick, - ) -> Self::Item<'world, 'state> { - world.into_deferred() - } -} diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 6615875182f01..a817e8e6a3e02 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -4,7 +4,7 @@ use crate::{ change_detection::MutUntyped, component::{Component, ComponentId, ComponentTicks, Components, StorageType}, entity::{Entities, Entity, EntityLocation}, - observer::{AttachObserver, EcsEvent, ObserverBuilder, Observers}, + observer::{AttachObserver, ObserverBuilder, Observers}, query::{Access, DebugCheckedUnwrap}, removal_detection::RemovedComponentEvents, storage::Storages, @@ -1269,7 +1269,7 @@ impl<'w> EntityWorldMut<'w> { /// Creates an [`Observer`](crate::observer::Observer) listening for `E` events targeting this entity. /// In order to trigger the callback the entity must also match the query when the event is fired. - pub fn observe( + pub fn observe( &mut self, callback: impl IntoObserverSystem, ) -> &mut Self { diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index c417c3c7ee686..f9f4e89a7d9db 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -152,7 +152,6 @@ impl Default for World { impl World { #[inline] fn bootstrap(&mut self) { - assert_eq!(NO_EVENT, self.init_component::()); assert_eq!(ON_ADD, self.init_component::()); assert_eq!(ON_INSERT, self.init_component::()); assert_eq!(ON_REMOVE, self.init_component::()); @@ -290,17 +289,6 @@ impl World { .init_component_with_descriptor(&mut self.storages, descriptor) } - /// Initializes a new [`Component`] type and returns a mutable reference to the [`ComponentInfo`] created for it. - /// Primarily used for registering hooks. - pub fn register_component_with_descriptor( - &mut self, - descriptor: ComponentDescriptor, - ) -> &mut ComponentInfo { - let index = self.init_component_with_descriptor(descriptor); - // SAFETY: We just created this component - unsafe { self.components.get_info_mut(index) } - } - /// Returns the [`ComponentId`] of the given [`Component`] type `T`. /// /// The returned `ComponentId` is specific to the `World` instance From 8db24ac7b92a3e64ed6d6d83db8db897d173aa24 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Thu, 7 Mar 2024 18:36:40 -0800 Subject: [PATCH 074/109] CI Fixes --- crates/bevy_ecs/src/archetype.rs | 2 +- crates/bevy_ecs/src/lib.rs | 2 +- crates/bevy_ecs/src/observer/mod.rs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index b00d2068e5a0b..d1407bf75c831 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -862,7 +862,7 @@ impl Archetypes { flags: ArchetypeFlags, set: bool, ) { - // This is terrible, we need to refactor the component index + // TODO: Refactor component index to speed this up. for archetype in &mut self.archetypes { if archetype.contains(component_id) { archetype.flags.set(flags, set); diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index cb0240077e997..bc3385df76c80 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -39,7 +39,7 @@ pub mod prelude { entity::{Entity, EntityMapper}, event::{Event, EventReader, EventWriter, Events}, observer::Observer, - query::{Added, AnyOf, Changed, Has, Or, QueryState, With, Without}, + query::{Added, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without}, removal_detection::RemovedComponents, schedule::{ apply_deferred, apply_state_transition, common_conditions::*, Condition, diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index c3afc6da0cb76..f031650c5515e 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -262,7 +262,7 @@ impl World { for &event in &descriptor.events { let cache = observers.get_observers(event); - // Observer is not targetting any components so register it as an entity observer + // Observer is not targeting any components so register it as an entity observer if descriptor.components.is_empty() { for &source in &observer_component.descriptor.sources { let map = cache.entity_observers.entry(source).or_default(); @@ -282,10 +282,10 @@ impl World { CachedComponentObservers::default() }); if descriptor.sources.is_empty() { - // Register for all events targetting the component + // Register for all events targeting the component observers.map.insert(entity, observer_component.runner); } else { - // Register for each targetted entity + // Register for each targeted entity for &source in &descriptor.sources { let map = observers.entity_map.entry(source).or_default(); map.insert(entity, observer_component.runner); From 5faba350f70ed1e0cc57a09dda0891e780f83f55 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Thu, 7 Mar 2024 21:17:13 -0800 Subject: [PATCH 075/109] Fix bugs, add tests --- crates/bevy_ecs/src/bundle.rs | 14 +- crates/bevy_ecs/src/observer/builder.rs | 29 +- .../bevy_ecs/src/observer/entity_observer.rs | 4 +- crates/bevy_ecs/src/observer/mod.rs | 320 ++++++++++++++++-- crates/bevy_ecs/src/observer/runner.rs | 22 +- crates/bevy_ecs/src/system/commands/mod.rs | 17 +- .../src/system/exclusive_function_system.rs | 2 +- crates/bevy_ecs/src/world/command_queue.rs | 2 +- crates/bevy_ecs/src/world/deferred_world.rs | 21 +- crates/bevy_ecs/src/world/entity_ref.rs | 14 +- crates/bevy_ecs/src/world/mod.rs | 14 +- crates/bevy_ecs/src/world/spawn_batch.rs | 2 +- 12 files changed, 358 insertions(+), 103 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index f14bd71fed0c5..b06ce84bba1e0 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -811,8 +811,7 @@ impl<'w> BundleInserter<'w> { if new_archetype.has_add_observer() { deferred_world.trigger_observers( ON_ADD, - entity, - new_location, + Some(entity), add_bundle.added.iter().cloned(), ); } @@ -820,8 +819,7 @@ impl<'w> BundleInserter<'w> { if new_archetype.has_insert_observer() { deferred_world.trigger_observers( ON_INSERT, - entity, - new_location, + Some(entity), bundle_info.iter_components(), ); } @@ -938,8 +936,7 @@ impl<'w> BundleSpawner<'w> { if archetype.has_add_observer() { deferred_world.trigger_observers( ON_ADD, - entity, - location, + Some(entity), bundle_info.iter_components(), ); } @@ -947,8 +944,7 @@ impl<'w> BundleSpawner<'w> { if archetype.has_insert_observer() { deferred_world.trigger_observers( ON_INSERT, - entity, - location, + Some(entity), bundle_info.iter_components(), ); } @@ -980,7 +976,7 @@ impl<'w> BundleSpawner<'w> { #[inline] pub(crate) unsafe fn flush_commands(&mut self) { // SAFETY: pointers on self can be invalidated, - self.world.world_mut().flush_commands(); + self.world.world_mut().flush(); } } diff --git a/crates/bevy_ecs/src/observer/builder.rs b/crates/bevy_ecs/src/observer/builder.rs index 855e49a7f0484..55dae05257507 100644 --- a/crates/bevy_ecs/src/observer/builder.rs +++ b/crates/bevy_ecs/src/observer/builder.rs @@ -18,7 +18,7 @@ impl<'w, E: 'static> ObserverBuilder<'w, E> { .get_id(TypeId::of::()) .unwrap_or_else(|| { panic!( - "Cannot observe event before it is registered: {}", + "Cannot observe event before it is registered with init_component: {}", std::any::type_name::(), ) }); @@ -35,7 +35,7 @@ impl<'w, E: 'static> ObserverBuilder<'w, E> { /// # Safety /// Caller must ensure that the component associated with `id` is accessible as E #[must_use] - pub unsafe fn new_with_id(&mut self, event: ComponentId, commands: Commands<'w, 'w>) -> Self { + pub unsafe fn new_with_id(event: ComponentId, commands: Commands<'w, 'w>) -> Self { let mut descriptor = ObserverDescriptor::default(); descriptor.events.push(event); @@ -55,7 +55,7 @@ impl<'w, E: 'static> ObserverBuilder<'w, E> { .get_id(TypeId::of::()) .unwrap_or_else(|| { panic!( - "Cannot observe event before it is registered: {}", + "Cannot observe event before it is registered with init_component: {}", std::any::type_name::(), ) }); @@ -80,7 +80,7 @@ impl<'w, E: 'static> ObserverBuilder<'w, E> { B::get_component_ids(self.commands.components(), &mut |id| { self.descriptor.components.push(id.unwrap_or_else(|| { panic!( - "Cannot observe event before it is registered: {}", + "Cannot observe event before it is registered with init_component: {}", std::any::type_name::(), ) })); @@ -89,8 +89,11 @@ impl<'w, E: 'static> ObserverBuilder<'w, E> { } /// Add `ids` to the list of component sources listened to by this observer. - pub fn component_ids(&mut self, ids: impl IntoIterator) -> &mut Self { - self.descriptor.components.extend(ids); + pub fn component_ids<'c>( + &mut self, + ids: impl IntoIterator, + ) -> &mut Self { + self.descriptor.components.extend(ids.into_iter().cloned()); self } @@ -105,7 +108,7 @@ impl<'w, E: 'static> ObserverBuilder<'w, E> { B::get_component_ids(self.commands.components(), &mut |id| { self.descriptor.components.push(id.unwrap_or_else(|| { panic!( - "Cannot observe event before it is registered: {}", + "Cannot observe event before it is registered with init_component: {}", std::any::type_name::(), ) })); @@ -115,6 +118,7 @@ impl<'w, E: 'static> ObserverBuilder<'w, E> { self.commands.add(move |world: &mut World| { let component = ObserverComponent::from(world, descriptor, callback); world.entity_mut(entity).insert(component); + world.register_observer(entity); }); entity } @@ -132,7 +136,7 @@ impl<'w, E: 'static> ObserverBuilder<'w, E> { } } -/// Type used to construct and emit a [`EcsEvent`] +/// Type used to construct and emit an ECS event. pub struct EventBuilder<'w, E = ()> { event: ComponentId, commands: Commands<'w, 'w>, @@ -150,7 +154,7 @@ impl<'w, E: Send + 'static> EventBuilder<'w, E> { .get_id(TypeId::of::()) .unwrap_or_else(|| { panic!( - "Cannot emit event for unregistered component type: {}", + "Cannot emit event before it is registered with init_component: {}", std::any::type_name::() ) }); @@ -167,12 +171,7 @@ impl<'w, E: Send + 'static> EventBuilder<'w, E> { /// # Safety /// Caller must ensure that the component associated with `id` is accessible as E #[must_use] - pub unsafe fn new_with_id( - &mut self, - event: ComponentId, - data: E, - commands: Commands<'w, 'w>, - ) -> Self { + pub unsafe fn new_with_id(event: ComponentId, data: E, commands: Commands<'w, 'w>) -> Self { Self { event, commands, diff --git a/crates/bevy_ecs/src/observer/entity_observer.rs b/crates/bevy_ecs/src/observer/entity_observer.rs index a12b993163f94..7efc5fe654863 100644 --- a/crates/bevy_ecs/src/observer/entity_observer.rs +++ b/crates/bevy_ecs/src/observer/entity_observer.rs @@ -36,7 +36,9 @@ impl Component for ObservedBy { let mut component = world.get_mut::(entity).unwrap(); let observed_by = std::mem::take(&mut component.0); observed_by.iter().for_each(|&e| { - world.commands().entity(e).despawn(); + if let Some(mut entity) = world.commands().get_entity(e) { + entity.despawn(); + }; }); }); } diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index f031650c5515e..25f36531ac837 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -12,7 +12,6 @@ pub use runner::*; use crate::{ archetype::ArchetypeFlags, - entity::EntityLocation, query::DebugCheckedUnwrap, system::{EmitEcsEvent, IntoObserverSystem}, world::*, @@ -59,14 +58,14 @@ impl<'w, E, B: Bundle> Observer<'w, E, B> { Ptr::from(&self.data) } - /// Returns the entity that triggered the observer. + /// Returns the entity that triggered the observer, panics if the event was send without a source. pub fn source(&self) -> Entity { - self.trigger.source + self.trigger.source.expect("No source set for this event") } - /// Returns the location of the entity that triggered the observer. - pub fn location(&self) -> EntityLocation { - self.trigger.location + /// Returns the entity that triggered the observer if it was set. + pub fn get_source(&self) -> Option { + self.trigger.source } } @@ -80,9 +79,8 @@ pub(crate) struct ObserverDescriptor { /// Metadata for the source triggering an [`Observer`], pub struct ObserverTrigger { observer: Entity, - location: EntityLocation, event: ComponentId, - source: Entity, + source: Option, } // Map between an observer entity and it's runner @@ -141,8 +139,7 @@ impl Observers { pub(crate) fn invoke( mut world: DeferredWorld, event: ComponentId, - source: Entity, - location: EntityLocation, + source: Option, components: impl Iterator, data: &mut E, ) { @@ -165,27 +162,42 @@ impl Observers { ObserverTrigger { observer, event, - location, source, }, data.into(), ); }; + // Trigger observers listening for any kind of this event observers.map.iter().for_each(&mut trigger_observer); - if let Some(map) = observers.entity_observers.get(&source) { - map.iter().for_each(&mut trigger_observer); + // Trigger entity observers listening for this kind of event + if let Some(source) = source { + if let Some(map) = observers.entity_observers.get(&source) { + map.iter().for_each(&mut trigger_observer); + } + } else { + observers.entity_observers.iter().for_each(|(_, map)| { + map.iter().for_each(&mut trigger_observer); + }) } + // Trigger observers listening to this event targeting a specific component components.for_each(|id| { if let Some(component_observers) = observers.component_observers.get(&id) { component_observers .map .iter() .for_each(&mut trigger_observer); - if let Some(entity_observers) = component_observers.entity_map.get(&source) { - entity_observers.iter().for_each(&mut trigger_observer); + + if let Some(source) = source { + if let Some(map) = component_observers.entity_map.get(&source) { + map.iter().for_each(&mut trigger_observer); + } + } else { + component_observers.entity_map.iter().for_each(|(_, map)| { + map.iter().for_each(&mut trigger_observer); + }) } } }); @@ -262,8 +274,11 @@ impl World { for &event in &descriptor.events { let cache = observers.get_observers(event); - // Observer is not targeting any components so register it as an entity observer - if descriptor.components.is_empty() { + + if descriptor.components.is_empty() && descriptor.sources.is_empty() { + cache.map.insert(entity, observer_component.runner); + } else if descriptor.components.is_empty() { + // Observer is not targeting any components so register it as an entity observer for &source in &observer_component.descriptor.sources { let map = cache.entity_observers.entry(source).or_default(); map.insert(entity, observer_component.runner); @@ -296,23 +311,16 @@ impl World { } } - pub(crate) fn unregister_observer(&mut self, entity: Entity) { - // SAFETY: References do not alias. - let (observer_component, archetypes, observers) = unsafe { - let observer_component: *const ObserverComponent = - self.get::(entity).unwrap(); - ( - &*observer_component, - &mut self.archetypes, - &mut self.observers, - ) - }; - let descriptor = &observer_component.descriptor; + pub(crate) fn unregister_observer(&mut self, entity: Entity, descriptor: ObserverDescriptor) { + let archetypes = &mut self.archetypes; + let observers = &mut self.observers; - for &event in &observer_component.descriptor.events { + for &event in &descriptor.events { let cache = observers.get_observers(event); - if descriptor.components.is_empty() { - for source in &observer_component.descriptor.sources { + if descriptor.components.is_empty() && descriptor.sources.is_empty() { + cache.map.remove(&entity); + } else if descriptor.components.is_empty() { + for source in &descriptor.sources { // This check should be unnecessary since this observer hasn't been unregistered yet let Some(observers) = cache.entity_observers.get_mut(source) else { continue; @@ -352,3 +360,251 @@ impl World { } } } + +#[cfg(test)] +mod tests { + use bevy_ptr::OwningPtr; + + use crate as bevy_ecs; + use crate::component::ComponentDescriptor; + use crate::observer::EventBuilder; + use crate::prelude::*; + + use super::ObserverBuilder; + + #[derive(Component)] + struct A; + + #[derive(Component)] + struct B; + + #[derive(Component)] + struct C; + + #[derive(Component)] + struct EventA; + + #[derive(Resource, Default)] + struct R(usize); + + impl R { + #[track_caller] + fn assert_order(&mut self, count: usize) { + assert_eq!(count, self.0); + self.0 += 1; + } + } + + #[test] + fn observer_order_spawn_despawn() { + let mut world = World::new(); + world.init_resource::(); + + world.observer(|_: Observer, mut res: ResMut| res.assert_order(0)); + world.observer(|_: Observer, mut res: ResMut| res.assert_order(1)); + world.observer(|_: Observer, mut res: ResMut| res.assert_order(2)); + + let entity = world.spawn(A).id(); + world.despawn(entity); + assert_eq!(3, world.resource::().0); + } + + #[test] + fn observer_order_insert_remove() { + let mut world = World::new(); + world.init_resource::(); + + world.observer(|_: Observer, mut res: ResMut| res.assert_order(0)); + world.observer(|_: Observer, mut res: ResMut| res.assert_order(1)); + world.observer(|_: Observer, mut res: ResMut| res.assert_order(2)); + + let mut entity = world.spawn_empty(); + entity.insert(A); + entity.remove::(); + entity.flush(); + assert_eq!(3, world.resource::().0); + } + + #[test] + fn observer_order_recursive() { + let mut world = World::new(); + world.init_resource::(); + world.observer( + |obs: Observer, mut res: ResMut, mut commands: Commands| { + res.assert_order(0); + commands.entity(obs.source()).insert(B); + }, + ); + world.observer( + |obs: Observer, mut res: ResMut, mut commands: Commands| { + res.assert_order(2); + commands.entity(obs.source()).remove::(); + }, + ); + + world.observer( + |obs: Observer, mut res: ResMut, mut commands: Commands| { + res.assert_order(1); + commands.entity(obs.source()).remove::(); + }, + ); + world.observer(|_: Observer, mut res: ResMut| { + res.assert_order(3); + }); + + let entity = world.spawn(A).flush(); + let entity = world.get_entity(entity).unwrap(); + assert!(!entity.contains::()); + assert!(!entity.contains::()); + assert_eq!(4, world.resource::().0); + } + + #[test] + fn observer_multiple_listeners() { + let mut world = World::new(); + world.init_resource::(); + + world.observer(|_: Observer, mut res: ResMut| res.0 += 1); + world.observer(|_: Observer, mut res: ResMut| res.0 += 1); + + world.spawn(A).flush(); + assert_eq!(2, world.resource::().0); + } + + #[test] + fn observer_multiple_events() { + let mut world = World::new(); + world.init_resource::(); + world.init_component::(); + + world + .observer_builder::() + .on_event::() + .run(|_: Observer<_, A>, mut res: ResMut| res.0 += 1); + + let entity = world.spawn(A).id(); + world.despawn(entity); + assert_eq!(2, world.resource::().0); + } + + #[test] + fn observer_multiple_components() { + let mut world = World::new(); + world.init_resource::(); + world.init_component::(); + world.init_component::(); + + world.observer(|_: Observer, mut res: ResMut| res.0 += 1); + + let entity = world.spawn(A).id(); + world.entity_mut(entity).insert(B); + world.flush(); + assert_eq!(2, world.resource::().0); + } + + #[test] + fn observer_despawn() { + let mut world = World::new(); + world.init_resource::(); + + let observer = world + .observer(|_: Observer| panic!("Observer triggered after being despawned.")); + world.despawn(observer); + world.spawn(A).flush(); + } + + #[test] + fn observer_multiple_matches() { + let mut world = World::new(); + world.init_resource::(); + + world.observer(|_: Observer, mut res: ResMut| res.0 += 1); + + world.spawn((A, B)).flush(); + assert_eq!(1, world.resource::().0); + } + + #[test] + fn observer_no_source() { + let mut world = World::new(); + world.init_resource::(); + world.init_component::(); + + world + .spawn_empty() + .observe(|_: Observer, mut res: ResMut| res.0 += 1); + world + .spawn_empty() + .observe(|_: Observer, mut res: ResMut| res.0 += 1); + world.observer(move |obs: Observer, mut res: ResMut| { + assert!(obs.get_source().is_none()); + res.0 += 1 + }); + + world.ecs_event(EventA).emit(); + world.flush(); + assert_eq!(3, world.resource::().0); + } + + #[test] + fn observer_entity_routing() { + let mut world = World::new(); + world.init_resource::(); + world.init_component::(); + + world + .spawn_empty() + .observe(|_: Observer| panic!("Event routed to non-targeted entity.")); + let entity = world + .spawn_empty() + .observe(|_: Observer, mut res: ResMut| res.0 += 1) + .id(); + world.observer(move |obs: Observer, mut res: ResMut| { + assert_eq!(obs.source(), entity); + res.0 += 1 + }); + + world.ecs_event(EventA).entity(entity).emit(); + world.flush(); + assert_eq!(2, world.resource::().0); + } + + #[test] + fn observer_dynamic_component() { + let mut world = World::new(); + world.init_resource::(); + + let component_id = world.init_component_with_descriptor(ComponentDescriptor::new::()); + world + .observer_builder() + .component_ids(&[component_id]) + .run(|_: Observer, mut res: ResMut| res.0 += 1); + + let mut entity = world.spawn_empty(); + OwningPtr::make(A, |ptr| { + // SAFETY: we registered `component_id` above. + unsafe { entity.insert_by_id(component_id, ptr) }; + }); + let entity = entity.flush(); + + world.ecs_event(EventA).entity(entity).emit(); + world.flush(); + assert_eq!(1, world.resource::().0); + } + + #[test] + fn observer_dynamic_event() { + let mut world = World::new(); + world.init_resource::(); + + let event = world.init_component_with_descriptor(ComponentDescriptor::new::()); + // SAFETY: we registered `event` above + unsafe { ObserverBuilder::new_with_id(event, world.commands()) } + .run(|_: Observer, mut res: ResMut| res.0 += 1); + + // SAFETY: we registered `event` above + unsafe { EventBuilder::new_with_id(event, EventA, world.commands()) }.emit(); + world.flush(); + assert_eq!(1, world.resource::().0); + } +} diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index 2e08832a0f86d..ebcce740c86c1 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -24,17 +24,19 @@ impl Component for ObserverComponent { const STORAGE_TYPE: StorageType = StorageType::SparseSet; fn register_component_hooks(hooks: &mut ComponentHooks) { - hooks - .on_add(|mut world, entity, _| { - world.commands().add(move |world: &mut World| { - world.register_observer(entity); - }); - }) - .on_remove(|mut world, entity, _| { - world.commands().add(move |world: &mut World| { - world.unregister_observer(entity); - }); + hooks.on_remove(|mut world, entity, _| { + let descriptor = std::mem::take( + &mut world + .entity_mut(entity) + .get_mut::() + .unwrap() + .as_mut() + .descriptor, + ); + world.commands().add(move |world: &mut World| { + world.unregister_observer(entity, descriptor); }); + }); } } diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 6fb74c06f4670..38271126c59b1 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -1156,14 +1156,23 @@ impl Command for EmitEcsEvent { fn apply(mut self, world: &mut World) { let mut world = DeferredWorld::from(world); - for &target in &self.entities { - if let Some(location) = world.entities().get(target) { + if self.entities.is_empty() { + // SAFETY: E is accessible as the type represented by self.event, ensured in `Self::new` + unsafe { + world.trigger_observers_with_data( + self.event, + None, + self.components.iter().cloned(), + &mut self.data, + ); + }; + } else { + for &target in &self.entities { // SAFETY: E is accessible as the type represented by self.event, ensured in `Self::new` unsafe { world.trigger_observers_with_data( self.event, - target, - location, + Some(target), self.components.iter().cloned(), &mut self.data, ); diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index d4351923adaf4..5f48eec3173a4 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -110,7 +110,7 @@ where ); let out = self.func.run(world, input, params); - world.flush_commands(); + world.flush(); let change_tick = world.change_tick.get_mut(); self.system_meta.last_run.set(*change_tick); *change_tick = change_tick.wrapping_add(1); diff --git a/crates/bevy_ecs/src/world/command_queue.rs b/crates/bevy_ecs/src/world/command_queue.rs index 26261639060a6..f460bbd39f7a4 100644 --- a/crates/bevy_ecs/src/world/command_queue.rs +++ b/crates/bevy_ecs/src/world/command_queue.rs @@ -129,7 +129,7 @@ impl CommandQueue { /// If `world` is [`None`], this will drop the queued [commands](`Command`) (without applying them). /// This clears the queue. #[inline] - fn apply_or_drop_queued(&mut self, mut world: Option<&mut World>) { + pub(crate) fn apply_or_drop_queued(&mut self, mut world: Option<&mut World>) { // The range of pointers of the filled portion of `self.bytes`. let bytes_range = self.bytes.as_mut_ptr_range(); diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 110121f7915c4..c805d1f898b26 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -4,7 +4,7 @@ use crate::{ archetype::Archetype, change_detection::MutUntyped, component::ComponentId, - entity::{Entity, EntityLocation}, + entity::Entity, event::{Event, EventId, Events, SendBatchIds}, observer::{EventBuilder, Observers}, prelude::{Component, QueryState}, @@ -346,18 +346,10 @@ impl<'w> DeferredWorld<'w> { pub(crate) unsafe fn trigger_observers( &mut self, event: ComponentId, - target: Entity, - location: EntityLocation, + source: Option, components: impl Iterator, ) { - Observers::invoke( - self.reborrow(), - event, - target, - location, - components, - &mut (), - ); + Observers::invoke(self.reborrow(), event, source, components, &mut ()); } /// Triggers all event observers for [`ComponentId`] in target. @@ -368,15 +360,14 @@ impl<'w> DeferredWorld<'w> { pub(crate) unsafe fn trigger_observers_with_data( &mut self, event: ComponentId, - target: Entity, - location: EntityLocation, + source: Option, components: impl Iterator, data: &mut E, ) { - Observers::invoke(self.reborrow(), event, target, location, components, data); + Observers::invoke(self.reborrow(), event, source, components, data); } - /// Constructs an [`EventBuilder`] for an [`EcsEvent`]. + /// Constructs an [`EventBuilder`] for an ECS event. pub fn ecs_event(&mut self, data: E) -> EventBuilder { EventBuilder::new(data, self.commands()) } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index a817e8e6a3e02..32def868ec3a1 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -792,8 +792,7 @@ impl<'w> EntityWorldMut<'w> { if old_archetype.has_remove_observer() { deferred_world.trigger_observers( ON_REMOVE, - self.entity, - self.location, + Some(self.entity), bundle_info.iter_components(), ); } @@ -973,8 +972,7 @@ impl<'w> EntityWorldMut<'w> { if old_archetype.has_remove_observer() { deferred_world.trigger_observers( ON_REMOVE, - entity, - self.location, + Some(self.entity), bundle_info.iter_components(), ); } @@ -1074,12 +1072,12 @@ impl<'w> EntityWorldMut<'w> { // SAFETY: All components in the archetype exist in world unsafe { + println!("Despawning"); deferred_world.trigger_on_remove(archetype, self.entity, archetype.components()); if archetype.has_remove_observer() { deferred_world.trigger_observers( ON_REMOVE, - self.entity, - self.location, + Some(self.entity), archetype.components(), ); } @@ -1145,12 +1143,12 @@ impl<'w> EntityWorldMut<'w> { world.archetypes[moved_location.archetype_id] .set_entity_table_row(moved_location.archetype_row, table_row); } - world.flush_commands(); + world.flush(); } /// Ensures any commands triggered by the actions of Self are applied, equivalent to [`World::flush_commands`] pub fn flush(self) -> Entity { - self.world.flush_commands(); + self.world.flush(); self.entity } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index f9f4e89a7d9db..a70374b5442de 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -506,7 +506,7 @@ impl World { /// scheme worked out to share an ID space (which doesn't happen by default). #[inline] pub fn get_or_spawn(&mut self, entity: Entity) -> Option { - self.flush_entities(); + self.flush(); match self.entities.alloc_at_without_replacement(entity) { AllocAtWithoutReplacement::Exists(location) => { // SAFETY: `entity` exists and `location` is that entity's location @@ -760,7 +760,7 @@ impl World { /// assert_eq!(position.x, 0.0); /// ``` pub fn spawn_empty(&mut self) -> EntityWorldMut { - self.flush_entities(); + self.flush(); let entity = self.entities.alloc(); // SAFETY: entity was just allocated unsafe { self.spawn_at_empty_internal(entity) } @@ -826,7 +826,7 @@ impl World { /// assert_eq!(position.x, 2.0); /// ``` pub fn spawn(&mut self, bundle: B) -> EntityWorldMut { - self.flush_entities(); + self.flush(); let change_tick = self.change_tick(); let entity = self.entities.alloc(); let entity_location = { @@ -957,6 +957,7 @@ impl World { /// ``` #[inline] pub fn despawn(&mut self, entity: Entity) -> bool { + self.flush(); if let Some(entity) = self.get_entity_mut(entity) { entity.despawn(); true @@ -1608,7 +1609,7 @@ impl World { I::IntoIter: Iterator, B: Bundle, { - self.flush_entities(); + self.flush(); let change_tick = self.change_tick(); @@ -1897,10 +1898,11 @@ impl World { /// Applies any commands in the world's internal [`CommandQueue`]. /// This does not apply commands from any systems, only those stored in the world. #[inline] - pub fn flush_commands(&mut self) { + pub fn flush(&mut self) { + self.flush_entities(); if !self.command_queue.is_empty() { // `CommandQueue` application always applies commands from the world queue first so this will apply all stored commands - CommandQueue::default().apply(self); + CommandQueue::default().apply_or_drop_queued(Some(self)); } } diff --git a/crates/bevy_ecs/src/world/spawn_batch.rs b/crates/bevy_ecs/src/world/spawn_batch.rs index ab9cb8f2db01f..f6cc9c9a2e6eb 100644 --- a/crates/bevy_ecs/src/world/spawn_batch.rs +++ b/crates/bevy_ecs/src/world/spawn_batch.rs @@ -27,7 +27,7 @@ where pub(crate) fn new(world: &'w mut World, iter: I) -> Self { // Ensure all entity allocations are accounted for so `self.entities` can realloc if // necessary - world.flush_entities(); + world.flush(); let change_tick = world.change_tick(); From 19e55c0ef7a81c3f1de1b24965875d9f401e5562 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Thu, 7 Mar 2024 23:27:23 -0800 Subject: [PATCH 076/109] Revamp example --- crates/bevy_ecs/src/observer/builder.rs | 2 + crates/bevy_ecs/src/observer/mod.rs | 19 +- crates/bevy_ecs/src/world/entity_ref.rs | 1 - examples/ecs/observers.rs | 225 +++++++++++++++++------- 4 files changed, 171 insertions(+), 76 deletions(-) diff --git a/crates/bevy_ecs/src/observer/builder.rs b/crates/bevy_ecs/src/observer/builder.rs index 55dae05257507..395cc71a8ef1b 100644 --- a/crates/bevy_ecs/src/observer/builder.rs +++ b/crates/bevy_ecs/src/observer/builder.rs @@ -212,11 +212,13 @@ impl<'w, E: Send + 'static> EventBuilder<'w, E> { impl<'w, 's> Commands<'w, 's> { /// Constructs an [`EventBuilder`]. + #[must_use] pub fn event(&mut self, event: E) -> EventBuilder { EventBuilder::new(event, self.reborrow()) } /// Construct an [`ObserverBuilder`]. + #[must_use] pub fn observer_builder(&mut self) -> ObserverBuilder { ObserverBuilder::new(self.reborrow()) } diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 25f36531ac837..a8a9e72f1997b 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -176,10 +176,6 @@ impl Observers { if let Some(map) = observers.entity_observers.get(&source) { map.iter().for_each(&mut trigger_observer); } - } else { - observers.entity_observers.iter().for_each(|(_, map)| { - map.iter().for_each(&mut trigger_observer); - }) } // Trigger observers listening to this event targeting a specific component @@ -194,10 +190,6 @@ impl Observers { if let Some(map) = component_observers.entity_map.get(&source) { map.iter().for_each(&mut trigger_observer); } - } else { - component_observers.entity_map.iter().for_each(|(_, map)| { - map.iter().for_each(&mut trigger_observer); - }) } } }); @@ -532,18 +524,15 @@ mod tests { world .spawn_empty() - .observe(|_: Observer, mut res: ResMut| res.0 += 1); - world - .spawn_empty() - .observe(|_: Observer, mut res: ResMut| res.0 += 1); + .observe(|_: Observer| panic!("Event routed to non-targeted entity.")); world.observer(move |obs: Observer, mut res: ResMut| { assert!(obs.get_source().is_none()); - res.0 += 1 + res.0 += 1; }); world.ecs_event(EventA).emit(); world.flush(); - assert_eq!(3, world.resource::().0); + assert_eq!(1, world.resource::().0); } #[test] @@ -561,7 +550,7 @@ mod tests { .id(); world.observer(move |obs: Observer, mut res: ResMut| { assert_eq!(obs.source(), entity); - res.0 += 1 + res.0 += 1; }); world.ecs_event(EventA).entity(entity).emit(); diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 32def868ec3a1..0aa1c93f92333 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -1072,7 +1072,6 @@ impl<'w> EntityWorldMut<'w> { // SAFETY: All components in the archetype exist in world unsafe { - println!("Despawning"); deferred_world.trigger_on_remove(archetype, self.entity, archetype.components()); if archetype.has_remove_observer() { deferred_world.trigger_observers( diff --git a/examples/ecs/observers.rs b/examples/ecs/observers.rs index 6052d05753337..c57c893891423 100644 --- a/examples/ecs/observers.rs +++ b/examples/ecs/observers.rs @@ -1,77 +1,182 @@ -//! This examples illustrates the different ways you can employ observers +//! This examples illustrates several ways you can employ observers -use bevy::prelude::*; +use bevy::{ + prelude::*, + utils::{HashMap, HashSet}, +}; -#[derive(Component, Debug)] -struct CompA(Entity); - -#[derive(Component, Debug)] -struct CompB; +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_systems(Startup, setup) + .add_systems(Update, (draw_shapes, handle_click)) + .run(); +} #[derive(Component)] -struct Resize(u64, u64); - -#[derive(Resource, Default)] -struct ResizeCount(usize); +struct Mine { + pos: Vec2, + size: f32, +} -fn main() { - App::new().add_systems(Startup, setup).run(); +#[derive(Component)] +struct TriggerMines { + pos: Vec2, + radius: f32, } +#[derive(Component)] +struct Explode; + fn setup(world: &mut World) { - world.init_resource::(); - world.init_component::(); - - // Triggered when &CompA is added to an entity, runs any non-exclusive system - let observer = world.observer( - |observer: Observer, - mut commands: Commands, - query: Query<&CompA, With>| { - // Get source entity that triggered the observer - let source = observer.source(); - // Able to read component data via a query - if let Ok(data) = query.get(source) { - // Can submit commands for any structural changes - commands.entity(source).remove::(); - // Or to raise other events - commands.event(Resize(2, 4)).entity(data.0).emit(); + world.spawn(Camera2dBundle::default()); + + // Pre-register all our components, resource and event types. + world.init_resource::(); + world.init_component::(); + world.init_component::(); + world.init_component::(); + + // Observers are triggered when a certain event it fired, each event is represented by a component type. + // This observer runs whenever `TriggerMines` is fired, observers run systems which can be defined as a callback. + world.observer( + |observer: Observer, + mines: Query<&Mine>, + index: Res, + mut commands: Commands| { + // You can access the event data via the `Observer` + let trigger = observer.data(); + // Access resources + for e in index.get_nearby(trigger.pos) { + // Run queries + let mine = mines.get(e).unwrap(); + if mine.pos.distance(trigger.pos) < mine.size + trigger.radius { + // And queue commands, including firing additional events + // Here we fire the `Explode` event at entity `e` + commands.event(Explode).entity(e).emit(); + } } }, ); - let entity = world - // This will not trigger the observer as the entity does not have CompB - .spawn(CompA(observer)) - // Respond to events targeting a specific entity - .observe( - |observer: Observer, query: Query<&CompA>, mut res: ResMut| { - // Since Resize carries data you can read/write that data from the observer - let size = observer.data(); - // Simultaneously read components - if let Ok(data) = query.get(observer.source()) { - println!( - "Received resize: {}, {} while data was: {:?}", - size.0, size.1, data - ); - // Write to resources - res.0 += 1; - } - }, - ) - .id(); + // Observers can also listen for events triggering for a specific component. + // This observer runs whenever the `Mine` component is added to an entity, and places it in a simple spatial index. + world.observer( + |observer: Observer, query: Query<&Mine>, mut index: ResMut| { + let mine = query.get(observer.source()).unwrap(); + let tile = ( + (mine.pos.x / CELL_SIZE).floor() as i32, + (mine.pos.y / CELL_SIZE).floor() as i32, + ); + index.map.entry(tile).or_default().insert(observer.source()); + }, + ); + + // Since observers run systems you can also define them as standalone functions rather than closures. + // This observer runs whenever the `Mine` component is removed from an entity (including despawning it) + // and removes it from the spatial index. + world.observer(remove_mine); + + // Now we spawn a set of random mines. + for _ in 0..1000 { + world + .spawn(Mine { + pos: (Vec2::new(rand::random::(), rand::random::()) - 0.5) * 800.0, + size: 4.0 + rand::random::() * 16.0, + }) + // Observers can also listen to events targeting a specific entity. + // This observer listens to `Explode` events targeted at our mine. + .observe( + |observer: Observer, query: Query<&Mine>, mut commands: Commands| { + // If an event is targeting a specific entity you can access it with `.source()` + let source = observer.source(); + println!("Boom! {:?} exploded.", source); + let Some(mut entity) = commands.get_entity(source) else { + return; + }; + entity.despawn(); + let mine = query.get(source).unwrap(); + // Fire another event to cascade into other mines. + commands + .event(TriggerMines { + pos: mine.pos, + radius: mine.size, + }) + .emit(); + }, + ); + } +} + +#[derive(Resource, Default)] +struct SpatialIndex { + map: HashMap<(i32, i32), HashSet>, +} - world.flush_commands(); +const CELL_SIZE: f32 = 64.0; - assert_eq!(world.resource::().0, 0); +impl SpatialIndex { + // Lookup all entities within adjacent cells of our spatial index + fn get_nearby(&self, pos: Vec2) -> Vec { + let tile = ( + (pos.x / CELL_SIZE).floor() as i32, + (pos.y / CELL_SIZE).floor() as i32, + ); + let mut nearby = Vec::new(); + for x in -1..2 { + for y in -1..2 { + if let Some(mines) = self.map.get(&(tile.0 + x, tile.1 + y)) { + nearby.extend(mines.iter()); + } + } + } + nearby + } +} - // This will spawn an entity with CompA - // - Which will trigger the first observer - // - Removing CompB - // - Emitting Resize targeting `entity` - // - Which will trigger it's entity observer - // - Incrementing ResizeCount - let entity_b = world.spawn((CompA(entity), CompB)).flush(); +// Remove despawned mines from our index +fn remove_mine( + observer: Observer, + query: Query<&Mine>, + mut index: ResMut, +) { + let mine = query.get(observer.source()).unwrap(); + let tile = ( + (mine.pos.x / CELL_SIZE).floor() as i32, + (mine.pos.y / CELL_SIZE).floor() as i32, + ); + index.map.entry(tile).and_modify(|set| { + set.remove(&observer.source()); + }); +} + +// Draw a circle for each mine using `Gizmos` +fn draw_shapes(mut gizmos: Gizmos, mines: Query<&Mine>) { + for mine in mines.iter() { + gizmos.circle_2d( + mine.pos, + mine.size, + Color::hsl((mine.size - 4.0) / 16.0 * 360.0, 1.0, 0.8), + ); + } +} - assert!(!world.entity(entity_b).contains::()); - assert_eq!(world.resource::().0, 1); +// Fire an initial `TriggerMines` event on click +fn handle_click( + mouse_button_input: Res>, + camera: Query<(&Camera, &GlobalTransform)>, + windows: Query<&Window>, + mut commands: Commands, +) { + let (camera, camera_transform) = camera.single(); + if let Some(pos) = windows + .single() + .cursor_position() + .and_then(|cursor| camera.viewport_to_world(camera_transform, cursor)) + .map(|ray| ray.origin.truncate()) + { + if mouse_button_input.just_pressed(MouseButton::Left) { + commands.event(TriggerMines { pos, radius: 1.0 }).emit(); + } + } } From 09fc6b75c9c03095e7aed76e679e0fd90b21ec82 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Thu, 7 Mar 2024 23:42:17 -0800 Subject: [PATCH 077/109] CI Fixes --- crates/bevy_ecs/src/bundle.rs | 1 + crates/bevy_ecs/src/world/entity_ref.rs | 2 +- crates/bevy_ecs/src/world/mod.rs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index b06ce84bba1e0..35ef87b4ae267 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -801,6 +801,7 @@ impl<'w> BundleInserter<'w> { } }; + let new_archetype = &*new_archetype; // SAFETY: We have no outstanding mutable references to world as they were dropped let mut deferred_world = unsafe { self.world.into_deferred() }; diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 0aa1c93f92333..64c95b36756b4 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -1145,7 +1145,7 @@ impl<'w> EntityWorldMut<'w> { world.flush(); } - /// Ensures any commands triggered by the actions of Self are applied, equivalent to [`World::flush_commands`] + /// Ensures any commands triggered by the actions of Self are applied, equivalent to [`World::flush`] pub fn flush(self) -> Entity { self.world.flush(); self.entity diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index a70374b5442de..17e9739b296ba 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -240,7 +240,7 @@ impl World { } /// Creates a new [`Commands`] instance that writes to the world's command queue - /// Use [`World::flush_commands`] to apply all queued commands + /// Use [`World::flush`] to apply all queued commands #[inline] pub fn commands(&mut self) -> Commands { Commands::new_from_entities(&mut self.command_queue, &self.entities, &self.components) From b025837470e1150b0bb886f2ff3c816f99d71312 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Fri, 8 Mar 2024 01:20:50 -0800 Subject: [PATCH 078/109] Slightly tidy up example --- examples/ecs/observers.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/examples/ecs/observers.rs b/examples/ecs/observers.rs index c57c893891423..88a43ebd01ae7 100644 --- a/examples/ecs/observers.rs +++ b/examples/ecs/observers.rs @@ -31,6 +31,19 @@ struct Explode; fn setup(world: &mut World) { world.spawn(Camera2dBundle::default()); + let font = world + .resource::() + .load("fonts/FiraMono-Medium.ttf"); + world.spawn(TextBundle::from_section( + "Click on a \"Mine\" to trigger it.\n\ + When it explodes it will trigger all overlapping mines.", + TextStyle { + font, + font_size: 24., + color: Color::WHITE, + }, + )); + // Pre-register all our components, resource and event types. world.init_resource::(); world.init_component::(); @@ -81,7 +94,10 @@ fn setup(world: &mut World) { for _ in 0..1000 { world .spawn(Mine { - pos: (Vec2::new(rand::random::(), rand::random::()) - 0.5) * 800.0, + pos: Vec2::new( + (rand::random::() - 0.5) * 1200.0, + (rand::random::() - 0.5) * 600.0, + ), size: 4.0 + rand::random::() * 16.0, }) // Observers can also listen to events targeting a specific entity. From 4ddbb940b51847f15fc4bc414fcebc0d8f1dfb75 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Fri, 8 Mar 2024 12:47:45 -0800 Subject: [PATCH 079/109] Minor example tidying --- crates/bevy_ecs/src/observer/builder.rs | 4 ++-- crates/bevy_ecs/src/observer/mod.rs | 4 ++-- examples/ecs/observers.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/bevy_ecs/src/observer/builder.rs b/crates/bevy_ecs/src/observer/builder.rs index 395cc71a8ef1b..386977179c0c1 100644 --- a/crates/bevy_ecs/src/observer/builder.rs +++ b/crates/bevy_ecs/src/observer/builder.rs @@ -104,7 +104,7 @@ impl<'w, E: 'static> ObserverBuilder<'w, E> { } /// Spawns the resulting observer into the world. - pub fn run(&mut self, callback: impl IntoObserverSystem) -> Entity { + pub fn run(&mut self, system: impl IntoObserverSystem) -> Entity { B::get_component_ids(self.commands.components(), &mut |id| { self.descriptor.components.push(id.unwrap_or_else(|| { panic!( @@ -116,7 +116,7 @@ impl<'w, E: 'static> ObserverBuilder<'w, E> { let entity = self.commands.spawn_empty().id(); let descriptor = self.descriptor.clone(); self.commands.add(move |world: &mut World| { - let component = ObserverComponent::from(world, descriptor, callback); + let component = ObserverComponent::from(world, descriptor, system); world.entity_mut(entity).insert(component); world.register_observer(entity); }); diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index a8a9e72f1997b..19f0df756ae66 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -239,10 +239,10 @@ impl World { /// Spawn an [`Observer`] and returns it's [`Entity`]. pub fn observer( &mut self, - callback: impl IntoObserverSystem, + system: impl IntoObserverSystem, ) -> Entity { B::component_ids(&mut self.components, &mut self.storages, &mut |_| {}); - ObserverBuilder::new(self.commands()).run(callback) + ObserverBuilder::new(self.commands()).run(system) } /// Constructs an [`EventBuilder`]. diff --git a/examples/ecs/observers.rs b/examples/ecs/observers.rs index 88a43ebd01ae7..56958c56f5229 100644 --- a/examples/ecs/observers.rs +++ b/examples/ecs/observers.rs @@ -51,7 +51,7 @@ fn setup(world: &mut World) { world.init_component::(); // Observers are triggered when a certain event it fired, each event is represented by a component type. - // This observer runs whenever `TriggerMines` is fired, observers run systems which can be defined as a callback. + // This observer runs whenever `TriggerMines` is fired, observers run systems which can be defined as closures. world.observer( |observer: Observer, mines: Query<&Mine>, @@ -106,10 +106,10 @@ fn setup(world: &mut World) { |observer: Observer, query: Query<&Mine>, mut commands: Commands| { // If an event is targeting a specific entity you can access it with `.source()` let source = observer.source(); - println!("Boom! {:?} exploded.", source); let Some(mut entity) = commands.get_entity(source) else { return; }; + println!("Boom! {:?} exploded.", source); entity.despawn(); let mine = query.get(source).unwrap(); // Fire another event to cascade into other mines. From 50c8c71bedd8a868dbde332fd479f8be136977f9 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Fri, 26 Apr 2024 18:27:36 -0700 Subject: [PATCH 080/109] Minor cleanup --- .../bevy_ecs/src/observer/entity_observer.rs | 35 ++++++++----------- crates/bevy_ecs/src/world/entity_ref.rs | 8 ++--- examples/ecs/observers.rs | 3 +- 3 files changed, 21 insertions(+), 25 deletions(-) diff --git a/crates/bevy_ecs/src/observer/entity_observer.rs b/crates/bevy_ecs/src/observer/entity_observer.rs index 7efc5fe654863..bc3f5eb781c31 100644 --- a/crates/bevy_ecs/src/observer/entity_observer.rs +++ b/crates/bevy_ecs/src/observer/entity_observer.rs @@ -1,27 +1,22 @@ use super::*; use crate::component::{ComponentHooks, StorageType}; -/// Component to signify an entity observer being attached to an entity -/// Can be modelled by parent-child relationship if/when that is enforced -pub(crate) struct AttachObserver(pub(crate) Entity); - -impl Component for AttachObserver { - const STORAGE_TYPE: StorageType = StorageType::SparseSet; +/// Command to attach an entity observer to an entity +pub(crate) struct AttachObserver { + pub(crate) target: Entity, + pub(crate) observer: Entity, +} - // When `AttachObserver` is inserted onto an event add it to `ObservedBy` - // or insert `ObservedBy` if it doesn't exist - fn register_component_hooks(hooks: &mut ComponentHooks) { - hooks.on_insert(|mut world, entity, _| { - let attached_observer = world.get::(entity).unwrap().0; - if let Some(mut observed_by) = world.get_mut::(entity) { - observed_by.0.push(attached_observer); - } else { - world - .commands() - .entity(entity) - .insert(ObservedBy(vec![attached_observer])); - } - }); +impl Command for AttachObserver { + fn apply(self, world: &mut World) { + if let Some(mut target) = world.get_entity_mut(self.target) { + let mut observed_by = target + .entry::() + .or_insert_with(|| ObservedBy(vec![])); + observed_by.0.push(self.observer); + } else { + world.despawn(self.observer); + } } } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 64c95b36756b4..7cee62f3d669d 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -1273,10 +1273,10 @@ impl<'w> EntityWorldMut<'w> { let observer = ObserverBuilder::new(self.world.commands()) .source(self.entity) .run(IntoObserverSystem::into_system(callback)); - self.world - .ecs_event(AttachObserver(observer)) - .entity(self.entity) - .emit(); + self.world.commands().add(AttachObserver { + target: self.entity, + observer, + }); self } } diff --git a/examples/ecs/observers.rs b/examples/ecs/observers.rs index 56958c56f5229..0e09fee303fdd 100644 --- a/examples/ecs/observers.rs +++ b/examples/ecs/observers.rs @@ -50,7 +50,7 @@ fn setup(world: &mut World) { world.init_component::(); world.init_component::(); - // Observers are triggered when a certain event it fired, each event is represented by a component type. + // Observers are triggered when a certain event is fired, each event is represented by a component type. // This observer runs whenever `TriggerMines` is fired, observers run systems which can be defined as closures. world.observer( |observer: Observer, @@ -129,6 +129,7 @@ struct SpatialIndex { map: HashMap<(i32, i32), HashSet>, } +/// Cell size has to be bigger than any `TriggerMine::radius` const CELL_SIZE: f32 = 64.0; impl SpatialIndex { From a6732fed9f8de12044f0265547f4d220f141e993 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Fri, 26 Apr 2024 19:33:57 -0700 Subject: [PATCH 081/109] Satisfy MIRI at the cost of performance --- crates/bevy_ecs/src/observer/runner.rs | 93 ++++++++++++++------------ 1 file changed, 50 insertions(+), 43 deletions(-) diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index ebcce740c86c1..aee6ca8257105 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -1,3 +1,4 @@ +use crate as bevy_ecs; use crate::{ component::{ComponentHooks, StorageType}, system::ObserverSystem, @@ -16,10 +17,14 @@ pub type BoxedObserverSystem = Box>; pub(crate) struct ObserverComponent { pub(crate) descriptor: ObserverDescriptor, pub(crate) runner: ObserverRunner, - pub(crate) system: Option, pub(crate) last_event_id: u32, } +#[derive(Component)] +#[component(storage = "SparseSet")] +// This used to be in `ObserverComponent` but MIRI recently got a new lint that complained about the type erasure +pub(crate) struct ObserverSystemComponent(BoxedObserverSystem); + impl Component for ObserverComponent { const STORAGE_TYPE: StorageType = StorageType::SparseSet; @@ -45,7 +50,7 @@ impl ObserverComponent { world: &mut World, descriptor: ObserverDescriptor, system: impl IntoObserverSystem, - ) -> Self { + ) -> (Self, ObserverSystemComponent) { let mut system = IntoObserverSystem::into_system(system); assert!( !system.is_exclusive(), @@ -53,53 +58,56 @@ impl ObserverComponent { ); system.initialize(world); let system: BoxedObserverSystem = Box::new(system); - Self { - descriptor, - runner: |mut world, trigger, ptr| { - let world = world.as_unsafe_world_cell(); - let observer_cell = + ( + Self { + descriptor, + runner: |mut world, trigger, ptr| { + let world = world.as_unsafe_world_cell(); + let observer_cell = // SAFETY: Observer was triggered so must still exist in world unsafe { world.get_entity(trigger.observer).debug_checked_unwrap() }; - // SAFETY: Observer was triggered so must have an `ObserverComponent` - let mut state = unsafe { - observer_cell - .get_mut::() - .debug_checked_unwrap() - }; + // SAFETY: Observer was triggered so must have an `ObserverComponent` + let mut state = unsafe { + observer_cell + .get_mut::() + .debug_checked_unwrap() + }; - // TODO: Move this check into the observer cache to avoid dynamic dispatch - // SAFETY: We only access world metadata - let last_event = unsafe { world.world_metadata() }.last_event_id; - if state.last_event_id == last_event { - return; - } - state.last_event_id = last_event; + // TODO: Move this check into the observer cache to avoid dynamic dispatch + // SAFETY: We only access world metadata + let last_event = unsafe { world.world_metadata() }.last_event_id; + if state.last_event_id == last_event { + return; + } + state.last_event_id = last_event; - // SAFETY: Caller ensures `ptr` is castable to `&mut E` - let observer: Observer = Observer::new(unsafe { ptr.deref_mut() }, trigger); - // SAFETY: System is from component - let mut system: Box> = unsafe { - let system = state.system.take().debug_checked_unwrap(); - std::mem::transmute(system) - }; + // SAFETY: Caller ensures `ptr` is castable to `&mut E` + let observer: Observer = + Observer::new(unsafe { ptr.deref_mut() }, trigger); + // SAFETY: Observer was triggered so must have an `ObserverSystemComponent` + let system = unsafe { + &mut observer_cell + .get_mut::>() + .debug_checked_unwrap() + .0 + }; - system.update_archetype_component_access(world); + system.update_archetype_component_access(world); - // SAFETY: - // - `update_archetype_component_access` was just called - // - there are no outsanding references to world except a private component - // - system is an `ObserverSystem` so won't mutate world beyond the access of a `DeferredWorld` - // - system is the same type erased system from above - unsafe { - system.run_unsafe(std::mem::transmute(observer), world); - system.queue_deferred(world.into_deferred()); - state.system = Some(std::mem::transmute(system)); - } + // SAFETY: + // - `update_archetype_component_access` was just called + // - there are no outsanding references to world except a private component + // - system is an `ObserverSystem` so won't mutate world beyond the access of a `DeferredWorld` + // - system is the same type erased system from above + unsafe { + system.run_unsafe(std::mem::transmute(observer), world); + system.queue_deferred(world.into_deferred()); + } + }, + last_event_id: 0, }, - last_event_id: 0, - // SAFETY: Same layout - system: Some(unsafe { std::mem::transmute(system) }), - } + ObserverSystemComponent(system), + ) } pub(crate) fn from_runner(descriptor: ObserverDescriptor, runner: ObserverRunner) -> Self { @@ -107,7 +115,6 @@ impl ObserverComponent { descriptor, runner, last_event_id: 0, - system: None, } } } From ae56005f9f8cf4b9ec948f13e281849b86b3961b Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Mon, 6 May 2024 18:49:46 -0700 Subject: [PATCH 082/109] Merge main, incorporate some feedback --- crates/bevy_ecs/src/bundle.rs | 24 +++-------------- crates/bevy_ecs/src/observer/builder.rs | 1 + crates/bevy_ecs/src/observer/mod.rs | 26 +++++++++---------- crates/bevy_ecs/src/observer/runner.rs | 8 +++--- crates/bevy_ecs/src/system/commands/mod.rs | 4 +-- crates/bevy_ecs/src/system/observer_system.rs | 2 +- crates/bevy_ecs/src/system/system_param.rs | 4 +-- crates/bevy_ecs/src/world/deferred_world.rs | 8 ++++-- crates/bevy_ecs/src/world/entity_ref.rs | 10 +++---- crates/bevy_ecs/src/world/mod.rs | 7 +++++ .../bevy_ecs/src/world/unsafe_world_cell.rs | 2 +- 11 files changed, 44 insertions(+), 52 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index c471a7adfe07b..67fa2f232df9b 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -810,19 +810,11 @@ impl<'w> BundleInserter<'w> { unsafe { deferred_world.trigger_on_add(new_archetype, entity, add_bundle.added.iter().cloned()); if new_archetype.has_add_observer() { - deferred_world.trigger_observers( - ON_ADD, - Some(entity), - add_bundle.added.iter().cloned(), - ); + deferred_world.trigger_observers(ON_ADD, entity, add_bundle.added.iter().cloned()); } deferred_world.trigger_on_insert(new_archetype, entity, bundle_info.iter_components()); if new_archetype.has_insert_observer() { - deferred_world.trigger_observers( - ON_INSERT, - Some(entity), - bundle_info.iter_components(), - ); + deferred_world.trigger_observers(ON_INSERT, entity, bundle_info.iter_components()); } } @@ -935,19 +927,11 @@ impl<'w> BundleSpawner<'w> { unsafe { deferred_world.trigger_on_add(archetype, entity, bundle_info.iter_components()); if archetype.has_add_observer() { - deferred_world.trigger_observers( - ON_ADD, - Some(entity), - bundle_info.iter_components(), - ); + deferred_world.trigger_observers(ON_ADD, entity, bundle_info.iter_components()); } deferred_world.trigger_on_insert(archetype, entity, bundle_info.iter_components()); if archetype.has_insert_observer() { - deferred_world.trigger_observers( - ON_INSERT, - Some(entity), - bundle_info.iter_components(), - ); + deferred_world.trigger_observers(ON_INSERT, entity, bundle_info.iter_components()); } }; diff --git a/crates/bevy_ecs/src/observer/builder.rs b/crates/bevy_ecs/src/observer/builder.rs index 205ba7b56e68a..bf7526449573d 100644 --- a/crates/bevy_ecs/src/observer/builder.rs +++ b/crates/bevy_ecs/src/observer/builder.rs @@ -136,6 +136,7 @@ impl<'w, E: 'static> ObserverBuilder<'w, E> { self.commands.add(move |world: &mut World| { let component = ObserverComponent::from_runner(descriptor, runner); world.entity_mut(entity).insert(component); + world.register_observer(entity); }); entity } diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 19f0df756ae66..9d619974bf7f3 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -58,13 +58,8 @@ impl<'w, E, B: Bundle> Observer<'w, E, B> { Ptr::from(&self.data) } - /// Returns the entity that triggered the observer, panics if the event was send without a source. + /// Returns the entity that triggered the observer, could be [`Entity::PLACEHOLDER`]. pub fn source(&self) -> Entity { - self.trigger.source.expect("No source set for this event") - } - - /// Returns the entity that triggered the observer if it was set. - pub fn get_source(&self) -> Option { self.trigger.source } } @@ -80,7 +75,7 @@ pub(crate) struct ObserverDescriptor { pub struct ObserverTrigger { observer: Entity, event: ComponentId, - source: Option, + source: Entity, } // Map between an observer entity and it's runner @@ -139,20 +134,20 @@ impl Observers { pub(crate) fn invoke( mut world: DeferredWorld, event: ComponentId, - source: Option, + source: Entity, components: impl Iterator, data: &mut E, ) { // SAFETY: You cannot get a mutable reference to `observers` from `DeferredWorld` let (mut world, observers) = unsafe { let world = world.as_unsafe_world_cell(); - // SAFETY: There are no outsanding world references + // SAFETY: There are no outstanding world references world.increment_event_id(); let observers = world.observers(); let Some(observers) = observers.try_get_observers(event) else { return; }; - // SAFETY: The only outsanding reference to world is `observers` + // SAFETY: The only outstanding reference to world is `observers` (world.into_deferred(), observers) }; @@ -172,7 +167,7 @@ impl Observers { observers.map.iter().for_each(&mut trigger_observer); // Trigger entity observers listening for this kind of event - if let Some(source) = source { + if source != Entity::PLACEHOLDER { if let Some(map) = observers.entity_observers.get(&source) { map.iter().for_each(&mut trigger_observer); } @@ -186,7 +181,7 @@ impl Observers { .iter() .for_each(&mut trigger_observer); - if let Some(source) = source { + if source != Entity::PLACEHOLDER { if let Some(map) = component_observers.entity_map.get(&source) { map.iter().for_each(&mut trigger_observer); } @@ -241,6 +236,7 @@ impl World { &mut self, system: impl IntoObserverSystem, ) -> Entity { + // Ensure components are registered with the world B::component_ids(&mut self.components, &mut self.storages, &mut |_| {}); ObserverBuilder::new(self.commands()).run(system) } @@ -251,6 +247,7 @@ impl World { EventBuilder::new(event, self.commands()) } + /// Register an observer to the cache, called when an observer is created pub(crate) fn register_observer(&mut self, entity: Entity) { // SAFETY: References do not alias. let (observer_component, archetypes, observers) = unsafe { @@ -303,6 +300,7 @@ impl World { } } + /// Remove the observer from the cache, called when an observer gets despawned pub(crate) fn unregister_observer(&mut self, entity: Entity, descriptor: ObserverDescriptor) { let archetypes = &mut self.archetypes; let observers = &mut self.observers; @@ -461,6 +459,8 @@ mod tests { world.spawn(A).flush(); assert_eq!(2, world.resource::().0); + // Our A entity plus our two observers + assert_eq!(world.entities().len(), 3); } #[test] @@ -526,7 +526,7 @@ mod tests { .spawn_empty() .observe(|_: Observer| panic!("Event routed to non-targeted entity.")); world.observer(move |obs: Observer, mut res: ResMut| { - assert!(obs.get_source().is_none()); + assert_eq!(obs.source(), Entity::PLACEHOLDER); res.0 += 1; }); diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index aee6ca8257105..27d9a4bd4aa80 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -6,8 +6,8 @@ use crate::{ use super::*; -/// Type for function that is run when an observer is triggered -/// Typically refers to the default runner that runs the contained, +/// Type for function that is run when an observer is triggered. +/// Typically refers to the default runner that runs the system stored in the associated [`ObserverSystemComponent`], /// but can be overridden for custom behaviour. pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut); @@ -75,7 +75,7 @@ impl ObserverComponent { // TODO: Move this check into the observer cache to avoid dynamic dispatch // SAFETY: We only access world metadata - let last_event = unsafe { world.world_metadata() }.last_event_id; + let last_event = unsafe { world.world_metadata() }.last_event_id(); if state.last_event_id == last_event { return; } @@ -96,7 +96,7 @@ impl ObserverComponent { // SAFETY: // - `update_archetype_component_access` was just called - // - there are no outsanding references to world except a private component + // - there are no outstanding references to world except a private component // - system is an `ObserverSystem` so won't mutate world beyond the access of a `DeferredWorld` // - system is the same type erased system from above unsafe { diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index b9e0569bcab65..42579a54ec790 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -1241,7 +1241,7 @@ impl Command for EmitEcsEvent { unsafe { world.trigger_observers_with_data( self.event, - None, + Entity::PLACEHOLDER, self.components.iter().cloned(), &mut self.data, ); @@ -1252,7 +1252,7 @@ impl Command for EmitEcsEvent { unsafe { world.trigger_observers_with_data( self.event, - Some(target), + target, self.components.iter().cloned(), &mut self.data, ); diff --git a/crates/bevy_ecs/src/system/observer_system.rs b/crates/bevy_ecs/src/system/observer_system.rs index ec9d225461aba..d6f1b935bdbf5 100644 --- a/crates/bevy_ecs/src/system/observer_system.rs +++ b/crates/bevy_ecs/src/system/observer_system.rs @@ -12,7 +12,7 @@ use bevy_utils::all_tuples; pub trait ObserverSystem: System, Out = ()> + Send + 'static { - /// Queues any deferred mutations to to be applied at the next [`apply_deferred`](crate::prelude::apply_deferred). + /// Queues any deferred mutations to be applied at the next [`apply_deferred`](crate::prelude::apply_deferred). fn queue_deferred(&mut self, _world: DeferredWorld); } diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 0ac9dc312cd9e..d829f6b9e708d 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -158,7 +158,7 @@ pub unsafe trait SystemParam: Sized { #[allow(unused_variables)] fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) {} - /// Queues any deferred mutations to to be applied at the next [`apply_deferred`](crate::prelude::apply_deferred). + /// Queues any deferred mutations to be applied at the next [`apply_deferred`](crate::prelude::apply_deferred). #[inline] #[allow(unused_variables)] fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) {} @@ -812,7 +812,7 @@ unsafe impl<'a, T: FromWorld + Send + 'static> SystemParam for Local<'a, T> { pub trait SystemBuffer: FromWorld + Send + 'static { /// Applies any deferred mutations to the [`World`]. fn apply(&mut self, system_meta: &SystemMeta, world: &mut World); - /// Queues any deferred mutations to to be applied at the next [`apply_deferred`](crate::prelude::apply_deferred). + /// Queues any deferred mutations to be applied at the next [`apply_deferred`](crate::prelude::apply_deferred). fn queue(&mut self, _system_meta: &SystemMeta, _world: DeferredWorld) {} } diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index c805d1f898b26..9c1313a56efe4 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -346,7 +346,7 @@ impl<'w> DeferredWorld<'w> { pub(crate) unsafe fn trigger_observers( &mut self, event: ComponentId, - source: Option, + source: Entity, components: impl Iterator, ) { Observers::invoke(self.reborrow(), event, source, components, &mut ()); @@ -360,7 +360,7 @@ impl<'w> DeferredWorld<'w> { pub(crate) unsafe fn trigger_observers_with_data( &mut self, event: ComponentId, - source: Option, + source: Entity, components: impl Iterator, data: &mut E, ) { @@ -372,6 +372,10 @@ impl<'w> DeferredWorld<'w> { EventBuilder::new(data, self.commands()) } + /// Gets an [`UnsafeWorldCell`] containing the underlying world. + /// + /// # Safety + /// - must only be used to to make non-structural ECS changes #[inline] pub(crate) fn as_unsafe_world_cell(&mut self) -> UnsafeWorldCell { self.world diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 592192be0924e..cdc51353b6488 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -907,7 +907,7 @@ impl<'w> EntityWorldMut<'w> { if old_archetype.has_remove_observer() { deferred_world.trigger_observers( ON_REMOVE, - Some(self.entity), + self.entity, bundle_info.iter_components(), ); } @@ -1090,7 +1090,7 @@ impl<'w> EntityWorldMut<'w> { if old_archetype.has_remove_observer() { deferred_world.trigger_observers( ON_REMOVE, - Some(self.entity), + self.entity, bundle_info.iter_components(), ); } @@ -1211,11 +1211,7 @@ impl<'w> EntityWorldMut<'w> { unsafe { deferred_world.trigger_on_remove(archetype, self.entity, archetype.components()); if archetype.has_remove_observer() { - deferred_world.trigger_observers( - ON_REMOVE, - Some(self.entity), - archetype.components(), - ); + deferred_world.trigger_observers(ON_REMOVE, self.entity, archetype.components()); } } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index befb0f83687ff..c5badd419bb17 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1932,6 +1932,13 @@ impl World { self.last_change_tick } + /// Returns the id of the last ECS event that was fired. + /// Used internally to ensure observers don't trigger multiple times for the same event. + #[inline] + pub(crate) fn last_event_id(&self) -> u32 { + self.last_event_id + } + /// Sets [`World::last_change_tick()`] to the specified value during a scope. /// When the scope terminates, it will return to its old value. /// diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 94686c722b702..81ebc1ce41b9b 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -580,7 +580,7 @@ impl<'w> UnsafeWorldCell<'w> { } /// # Safety - /// It is the callers responsibility to ensure that there are no outsanding + /// It is the callers responsibility to ensure that there are no outstanding /// references to `last_event_id`. pub(crate) unsafe fn increment_event_id(self) { // SAFETY: Caller ensure there are no outstanding references From 908cdbb9ea6cfe021461140cff64fef0aeb92f35 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Wed, 22 May 2024 20:56:11 -0700 Subject: [PATCH 083/109] Rebrand ECS events as Triggers --- crates/bevy_ecs/macros/src/component.rs | 23 +++ crates/bevy_ecs/macros/src/lib.rs | 5 + crates/bevy_ecs/src/lib.rs | 2 +- crates/bevy_ecs/src/observer/builder.rs | 116 ++++++------ crates/bevy_ecs/src/observer/mod.rs | 177 +++++++++--------- crates/bevy_ecs/src/observer/runner.rs | 30 +-- crates/bevy_ecs/src/system/adapter_system.rs | 5 + crates/bevy_ecs/src/system/combinator.rs | 6 + crates/bevy_ecs/src/system/commands/mod.rs | 32 ++-- .../src/system/exclusive_function_system.rs | 7 + crates/bevy_ecs/src/system/function_system.rs | 8 +- crates/bevy_ecs/src/system/observer_system.rs | 52 +---- crates/bevy_ecs/src/system/system.rs | 5 + .../bevy_ecs/src/world/component_constants.rs | 14 +- crates/bevy_ecs/src/world/deferred_world.rs | 8 +- crates/bevy_ecs/src/world/mod.rs | 8 +- .../bevy_ecs/src/world/unsafe_world_cell.rs | 6 +- examples/ecs/observers.rs | 36 ++-- 18 files changed, 283 insertions(+), 257 deletions(-) diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index b7b7e1582c8b3..120d73f67effc 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -65,6 +65,29 @@ pub fn derive_component(input: TokenStream) -> TokenStream { }) } +pub fn derive_trigger(input: TokenStream) -> TokenStream { + let mut ast = parse_macro_input!(input as DeriveInput); + let bevy_ecs_path: Path = crate::bevy_ecs_path(); + + let storage = storage_path(&bevy_ecs_path, StorageTy::SparseSet); + + 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 { + const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage; + } + + impl #bevy_ecs_path::observer::Trigger for #struct_name #type_generics {} + }) +} + pub const COMPONENT: &str = "component"; pub const STORAGE: &str = "storage"; diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 60e8027756731..6a234dff32a9d 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -532,6 +532,11 @@ pub fn derive_component(input: TokenStream) -> TokenStream { component::derive_component(input) } +#[proc_macro_derive(Trigger)] +pub fn derive_trigger(input: TokenStream) -> TokenStream { + component::derive_trigger(input) +} + #[proc_macro_derive(States)] pub fn derive_states(input: TokenStream) -> TokenStream { states::derive_states(input) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 7dc68441554c5..4b48b43461207 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -47,7 +47,7 @@ pub mod prelude { component::Component, entity::{Entity, EntityMapper}, event::{Event, EventReader, EventWriter, Events}, - observer::Observer, + observer::{Observer, Trigger}, query::{Added, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without}, removal_detection::RemovedComponents, schedule::{ diff --git a/crates/bevy_ecs/src/observer/builder.rs b/crates/bevy_ecs/src/observer/builder.rs index bf7526449573d..6c53117bfef3d 100644 --- a/crates/bevy_ecs/src/observer/builder.rs +++ b/crates/bevy_ecs/src/observer/builder.rs @@ -3,26 +3,26 @@ use std::any::TypeId; use super::*; /// Builder struct for [`Observer`]. -pub struct ObserverBuilder<'w, E = ()> { +pub struct ObserverBuilder<'w, T = ()> { commands: Commands<'w, 'w>, descriptor: ObserverDescriptor, - _marker: PhantomData, + _marker: PhantomData, } -impl<'w, E: 'static> ObserverBuilder<'w, E> { +impl<'w, T: 'static> ObserverBuilder<'w, T> { /// Constructs a new [`ObserverBuilder`]. pub fn new(commands: Commands<'w, 'w>) -> Self { let mut descriptor = ObserverDescriptor::default(); - let event = commands + let trigger = commands .components() - .get_id(TypeId::of::()) + .get_id(TypeId::of::()) .unwrap_or_else(|| { panic!( - "Cannot observe event before it is registered with init_component: {}", - std::any::type_name::(), + "Cannot observe trigger before it is registered with init_trigger: {}", + std::any::type_name::(), ) }); - descriptor.events.push(event); + descriptor.triggers.push(trigger); Self { commands, @@ -31,13 +31,13 @@ impl<'w, E: 'static> ObserverBuilder<'w, E> { } } - /// Constructs an [`ObserverBuilder`] with a dynamic event id. + /// Constructs an [`ObserverBuilder`] with a dynamic trigger id. /// # Safety - /// Caller must ensure that the component associated with `id` is accessible as E + /// Caller must ensure that the component associated with `id` is accessible as T #[must_use] - pub unsafe fn new_with_id(event: ComponentId, commands: Commands<'w, 'w>) -> Self { + pub unsafe fn new_with_id(trigger: ComponentId, commands: Commands<'w, 'w>) -> Self { let mut descriptor = ObserverDescriptor::default(); - descriptor.events.push(event); + descriptor.triggers.push(trigger); Self { commands, @@ -46,35 +46,35 @@ impl<'w, E: 'static> ObserverBuilder<'w, E> { } } - /// Adds `NewE` to the list of events listened to by this observer. - /// After calling this function the observer will no longer have access to typed event data - /// to prevent accessing the data as the incorrect type. - /// Observing the same event multiple times has no effect. - pub fn on_event(&mut self) -> &mut ObserverBuilder<'w, ()> { - let event = self + /// Adds `NewT` to the list of triggers listened to by this observer. + /// After calling this function the observer will no longer have access to typed trigger data + /// to prtrigger accessing the data as the incorrect type. + /// Observing the same trigger multiple times has no effect. + pub fn on_trigger(&mut self) -> &mut ObserverBuilder<'w, ()> { + let trigger = self .commands .components() - .get_id(TypeId::of::()) + .get_id(TypeId::of::()) .unwrap_or_else(|| { panic!( - "Cannot observe event before it is registered with init_component: {}", - std::any::type_name::(), + "Cannot observe trigger before it is registered with init_component: {}", + std::any::type_name::(), ) }); - self.descriptor.events.push(event); + self.descriptor.triggers.push(trigger); // SAFETY: () will not allow bad memory access as it has no size unsafe { std::mem::transmute(self) } } - /// Add `events` to the list of events listened to by this observer. - /// After calling this function the observer will no longer have access to typed event data + /// Add `triggers` to the list of triggers listened to by this observer. + /// After calling this function the observer will no longer have access to typed trigger data /// to prevent accessing the data as the incorrect type. - /// Observing the same event multiple times has no effect. - pub fn on_event_ids( + /// Observing the same trigger multiple times has no effect. + pub fn on_trigger_ids( &mut self, - events: impl IntoIterator, + triggers: impl IntoIterator, ) -> &mut ObserverBuilder<'w, ()> { - self.descriptor.events.extend(events); + self.descriptor.triggers.extend(triggers); // SAFETY: () type will not allow bad memory access as it has no size unsafe { std::mem::transmute(self) } } @@ -85,7 +85,7 @@ impl<'w, E: 'static> ObserverBuilder<'w, E> { B::get_component_ids(self.commands.components(), &mut |id| { self.descriptor.components.push(id.unwrap_or_else(|| { panic!( - "Cannot observe event before it is registered with init_component: {}", + "Cannot observe trigger before it is registered with init_component: {}", std::any::type_name::(), ) })); @@ -109,11 +109,11 @@ impl<'w, E: 'static> ObserverBuilder<'w, E> { } /// Spawns the resulting observer into the world. - pub fn run(&mut self, system: impl IntoObserverSystem) -> Entity { + pub fn run(&mut self, system: impl IntoObserverSystem) -> Entity { B::get_component_ids(self.commands.components(), &mut |id| { self.descriptor.components.push(id.unwrap_or_else(|| { panic!( - "Cannot observe event before it is registered with init_component: {}", + "Cannot observe trigger before it is registered with init_component: {}", std::any::type_name::(), ) })); @@ -132,7 +132,7 @@ impl<'w, E: 'static> ObserverBuilder<'w, E> { /// This is not advised unless you want to override the default runner behaviour. pub fn runner(&mut self, runner: ObserverRunner) -> Entity { let entity = self.commands.spawn_empty().id(); - let descriptor = self.descriptor.clone(); + let descriptor: ObserverDescriptor = self.descriptor.clone(); self.commands.add(move |world: &mut World| { let component = ObserverComponent::from_runner(descriptor, runner); world.entity_mut(entity).insert(component); @@ -142,30 +142,30 @@ impl<'w, E: 'static> ObserverBuilder<'w, E> { } } -/// Type used to construct and emit an ECS event. -pub struct EventBuilder<'w, E = ()> { - event: ComponentId, +/// Type used to construct and emit an ECS trigger. +pub struct TriggerBuilder<'w, T = ()> { + trigger: ComponentId, commands: Commands<'w, 'w>, targets: Vec, components: Vec, - data: Option, + data: Option, } -impl<'w, E: Send + 'static> EventBuilder<'w, E> { - /// Constructs a new builder that will write it's event to `world`'s command queue +impl<'w, T: Send + 'static> TriggerBuilder<'w, T> { + /// Constructs a new builder that will write it's trigger to `world`'s command queue #[must_use] - pub fn new(data: E, commands: Commands<'w, 'w>) -> Self { - let event = commands + pub fn new(data: T, commands: Commands<'w, 'w>) -> Self { + let trigger = commands .components() - .get_id(TypeId::of::()) + .get_id(TypeId::of::()) .unwrap_or_else(|| { panic!( - "Cannot emit event before it is registered with init_component: {}", - std::any::type_name::() + "Cannot emit trigger before it is registered with init_component: {}", + std::any::type_name::() ) }); Self { - event, + trigger, commands, targets: Vec::new(), components: Vec::new(), @@ -173,13 +173,13 @@ impl<'w, E: Send + 'static> EventBuilder<'w, E> { } } - /// Sets the event id of the resulting event, used for dynamic events + /// Sets the trigger id of the resulting trigger, used for dynamic triggers /// # Safety /// Caller must ensure that the component associated with `id` is accessible as E #[must_use] - pub unsafe fn new_with_id(event: ComponentId, data: E, commands: Commands<'w, 'w>) -> Self { + pub unsafe fn new_with_id(trigger: ComponentId, data: T, commands: Commands<'w, 'w>) -> Self { Self { - event, + trigger, commands, targets: Vec::new(), components: Vec::new(), @@ -201,38 +201,38 @@ impl<'w, E: Send + 'static> EventBuilder<'w, E> { self } - /// Add the event to the command queue of world + /// Add the trigger to the command queue of world pub fn emit(&mut self) { - // SAFETY: `self.event` is accessible as E, enforced in `Self::new` and `Self::new_with_id`. + // SAFETY: `self.trigger` is accessible as T, enforced in `Self::new` and `Self::new_with_id`. self.commands.add(unsafe { - EmitEcsEvent::::new( - self.event, + EmitTrigger::::new( + self.trigger, std::mem::take(&mut self.targets), std::mem::take(&mut self.components), std::mem::take(&mut self.data) - .expect("EventBuilder used to send more than one event."), + .expect("triggerBuilder used to send more than one trigger."), ) }); } } impl<'w, 's> Commands<'w, 's> { - /// Constructs an [`EventBuilder`]. + /// Constructs a [`TriggerBuilder`]. #[must_use] - pub fn event(&mut self, event: E) -> EventBuilder { - EventBuilder::new(event, self.reborrow()) + pub fn trigger(&mut self, trigger: T) -> TriggerBuilder { + TriggerBuilder::new(trigger, self.reborrow()) } /// Construct an [`ObserverBuilder`]. #[must_use] - pub fn observer_builder(&mut self) -> ObserverBuilder { + pub fn observer_builder(&mut self) -> ObserverBuilder { ObserverBuilder::new(self.reborrow()) } /// Spawn an [`Observer`] and returns it's [`Entity`]. - pub fn observer( + pub fn observer( &mut self, - callback: impl IntoObserverSystem, + callback: impl IntoObserverSystem, ) -> Entity { ObserverBuilder::new(self.reborrow()).run(callback) } diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 9d619974bf7f3..9aef26d0c9483 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -6,31 +6,34 @@ mod runner; use std::marker::PhantomData; -pub use builder::*; -pub(crate) use entity_observer::*; -pub use runner::*; - use crate::{ archetype::ArchetypeFlags, query::DebugCheckedUnwrap, - system::{EmitEcsEvent, IntoObserverSystem}, + system::{EmitTrigger, IntoObserverSystem}, world::*, }; +pub use bevy_ecs_macros::Trigger; +pub use builder::*; +pub(crate) use entity_observer::*; +pub use runner::*; use bevy_ptr::{Ptr, PtrMut}; use bevy_utils::{EntityHashMap, HashMap}; use crate::{component::ComponentId, prelude::*, world::DeferredWorld}; +/// Trait implemented for types that are used as ECS triggers observed by [`Observer`] +pub trait Trigger: Component {} + /// Type used in callbacks registered for observers. -pub struct Observer<'w, E, B: Bundle = ()> { - data: &'w mut E, +pub struct Observer<'w, T, B: Bundle = ()> { + data: &'w mut T, trigger: ObserverTrigger, _marker: PhantomData, } -impl<'w, E, B: Bundle> Observer<'w, E, B> { - pub(crate) fn new(data: &'w mut E, trigger: ObserverTrigger) -> Self { +impl<'w, T, B: Bundle> Observer<'w, T, B> { + pub(crate) fn new(data: &'w mut T, trigger: ObserverTrigger) -> Self { Self { data, trigger, @@ -38,22 +41,22 @@ impl<'w, E, B: Bundle> Observer<'w, E, B> { } } - /// Returns the event id for the triggering event - pub fn event(&self) -> ComponentId { - self.trigger.event + /// Returns the trigger id for the triggering trigger + pub fn trigger(&self) -> ComponentId { + self.trigger.trigger } - /// Returns a reference to the data associated with the event that triggered the observer. - pub fn data(&self) -> &E { + /// Returns a reference to the data associated with the trigger that triggered the observer. + pub fn data(&self) -> &T { self.data } - /// Returns a mutable reference to the data associated with the event that triggered the observer. - pub fn data_mut(&mut self) -> &mut E { + /// Returns a mutable reference to the data associated with the trigger that triggered the observer. + pub fn data_mut(&mut self) -> &mut T { self.data } - /// Returns a pointer to the data associated with the event that triggered the observer. + /// Returns a pointer to the data associated with the trigger that triggered the observer. pub fn data_ptr(&self) -> Ptr { Ptr::from(&self.data) } @@ -66,7 +69,7 @@ impl<'w, E, B: Bundle> Observer<'w, E, B> { #[derive(Default, Clone)] pub(crate) struct ObserverDescriptor { - events: Vec, + triggers: Vec, components: Vec, sources: Vec, } @@ -74,77 +77,77 @@ pub(crate) struct ObserverDescriptor { /// Metadata for the source triggering an [`Observer`], pub struct ObserverTrigger { observer: Entity, - event: ComponentId, + trigger: ComponentId, source: Entity, } // Map between an observer entity and it's runner type ObserverMap = EntityHashMap; -/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular event targeted at a specific component. +/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular trigger targeted at a specific component. #[derive(Default, Debug)] pub struct CachedComponentObservers { - // Observers listening to events targeting this component + // Observers listening to triggers targeting this component map: ObserverMap, - // Observers listening to events targeting this component on a specific entity + // Observers listening to triggers targeting this component on a specific entity entity_map: EntityHashMap, } -/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular event. +/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular trigger. #[derive(Default, Debug)] pub struct CachedObservers { - // Observers listening for any time this event is fired + // Observers listening for any time this trigger is fired map: ObserverMap, - // Observers listening for this event fired at a specific component + // Observers listening for this trigger fired at a specific component component_observers: HashMap, - // Observers listening for this event fired at a specific entity + // Observers listening for this trigger fired at a specific entity entity_observers: EntityHashMap, } -/// Metadata for observers. Stores a cache mapping event ids to the registered observers. +/// Metadata for observers. Stores a cache mapping trigger ids to the registered observers. #[derive(Default, Debug)] pub struct Observers { - // Cached ECS observers to save a lookup most common events. + // Cached ECS observers to save a lookup most common triggers. on_add: CachedObservers, on_insert: CachedObservers, on_remove: CachedObservers, - // Map from event type to set of observers + // Map from trigger type to set of observers cache: HashMap, } impl Observers { - pub(crate) fn get_observers(&mut self, event: ComponentId) -> &mut CachedObservers { - match event { + pub(crate) fn get_observers(&mut self, trigger: ComponentId) -> &mut CachedObservers { + match trigger { ON_ADD => &mut self.on_add, ON_INSERT => &mut self.on_insert, ON_REMOVE => &mut self.on_remove, - _ => self.cache.entry(event).or_default(), + _ => self.cache.entry(trigger).or_default(), } } - pub(crate) fn try_get_observers(&self, event: ComponentId) -> Option<&CachedObservers> { - match event { + pub(crate) fn try_get_observers(&self, trigger: ComponentId) -> Option<&CachedObservers> { + match trigger { ON_ADD => Some(&self.on_add), ON_INSERT => Some(&self.on_insert), ON_REMOVE => Some(&self.on_remove), - _ => self.cache.get(&event), + _ => self.cache.get(&trigger), } } - pub(crate) fn invoke( + pub(crate) fn invoke( mut world: DeferredWorld, - event: ComponentId, + trigger: ComponentId, source: Entity, components: impl Iterator, - data: &mut E, + data: &mut T, ) { // SAFETY: You cannot get a mutable reference to `observers` from `DeferredWorld` let (mut world, observers) = unsafe { let world = world.as_unsafe_world_cell(); // SAFETY: There are no outstanding world references - world.increment_event_id(); + world.increment_trigger_id(); let observers = world.observers(); - let Some(observers) = observers.try_get_observers(event) else { + let Some(observers) = observers.try_get_observers(trigger) else { return; }; // SAFETY: The only outstanding reference to world is `observers` @@ -156,24 +159,24 @@ impl Observers { world.reborrow(), ObserverTrigger { observer, - event, + trigger, source, }, data.into(), ); }; - // Trigger observers listening for any kind of this event + // Trigger observers listening for any kind of this trigger observers.map.iter().for_each(&mut trigger_observer); - // Trigger entity observers listening for this kind of event + // Trigger entity observers listening for this kind of trigger if source != Entity::PLACEHOLDER { if let Some(map) = observers.entity_observers.get(&source) { map.iter().for_each(&mut trigger_observer); } } - // Trigger observers listening to this event targeting a specific component + // Trigger observers listening to this trigger targeting a specific component components.for_each(|id| { if let Some(component_observers) = observers.component_observers.get(&id) { component_observers @@ -190,8 +193,8 @@ impl Observers { }); } - pub(crate) fn is_archetype_cached(event: ComponentId) -> Option { - match event { + pub(crate) fn is_archetype_cached(trigger: ComponentId) -> Option { + match trigger { ON_ADD => Some(ArchetypeFlags::ON_ADD_OBSERVER), ON_INSERT => Some(ArchetypeFlags::ON_INSERT_OBSERVER), ON_REMOVE => Some(ArchetypeFlags::ON_REMOVE_OBSERVER), @@ -226,25 +229,31 @@ impl Observers { impl World { /// Construct an [`ObserverBuilder`] - pub fn observer_builder(&mut self) -> ObserverBuilder { - self.init_component::(); + pub fn observer_builder(&mut self) -> ObserverBuilder { + self.init_trigger::(); ObserverBuilder::new(self.commands()) } /// Spawn an [`Observer`] and returns it's [`Entity`]. - pub fn observer( + pub fn observer( &mut self, - system: impl IntoObserverSystem, + system: impl IntoObserverSystem, ) -> Entity { - // Ensure components are registered with the world + // Ensure triggers are registered with the world + // Note: this sidesteps init_trigger B::component_ids(&mut self.components, &mut self.storages, &mut |_| {}); ObserverBuilder::new(self.commands()).run(system) } - /// Constructs an [`EventBuilder`]. - pub fn ecs_event(&mut self, event: E) -> EventBuilder { - self.init_component::(); - EventBuilder::new(event, self.commands()) + /// Registers `T` as a [`Trigger`] + pub fn init_trigger(&mut self) { + self.init_component::(); + } + + /// Constructs a [`TriggerBuilder`]. + pub fn trigger(&mut self, trigger: T) -> TriggerBuilder { + self.init_component::(); + TriggerBuilder::new(trigger, self.commands()) } /// Register an observer to the cache, called when an observer is created @@ -261,8 +270,8 @@ impl World { }; let descriptor = &observer_component.descriptor; - for &event in &descriptor.events { - let cache = observers.get_observers(event); + for &trigger in &descriptor.triggers { + let cache = observers.get_observers(trigger); if descriptor.components.is_empty() && descriptor.sources.is_empty() { cache.map.insert(entity, observer_component.runner); @@ -280,13 +289,13 @@ impl World { .component_observers .entry(component) .or_insert_with(|| { - if let Some(flag) = Observers::is_archetype_cached(event) { + if let Some(flag) = Observers::is_archetype_cached(trigger) { archetypes.update_flags(component, flag, true); } CachedComponentObservers::default() }); if descriptor.sources.is_empty() { - // Register for all events targeting the component + // Register for all triggers targeting the component observers.map.insert(entity, observer_component.runner); } else { // Register for each targeted entity @@ -305,8 +314,8 @@ impl World { let archetypes = &mut self.archetypes; let observers = &mut self.observers; - for &event in &descriptor.events { - let cache = observers.get_observers(event); + for &trigger in &descriptor.triggers { + let cache = observers.get_observers(trigger); if descriptor.components.is_empty() && descriptor.sources.is_empty() { cache.map.remove(&entity); } else if descriptor.components.is_empty() { @@ -341,7 +350,7 @@ impl World { if observers.map.is_empty() && observers.entity_map.is_empty() { cache.component_observers.remove(component); - if let Some(flag) = Observers::is_archetype_cached(event) { + if let Some(flag) = Observers::is_archetype_cached(trigger) { archetypes.update_flags(*component, flag, false); } } @@ -357,7 +366,7 @@ mod tests { use crate as bevy_ecs; use crate::component::ComponentDescriptor; - use crate::observer::EventBuilder; + use crate::observer::TriggerBuilder; use crate::prelude::*; use super::ObserverBuilder; @@ -371,8 +380,8 @@ mod tests { #[derive(Component)] struct C; - #[derive(Component)] - struct EventA; + #[derive(Trigger)] + struct TriggerA; #[derive(Resource, Default)] struct R(usize); @@ -464,14 +473,14 @@ mod tests { } #[test] - fn observer_multiple_events() { + fn observer_multiple_triggerss() { let mut world = World::new(); world.init_resource::(); world.init_component::(); world .observer_builder::() - .on_event::() + .on_trigger::() .run(|_: Observer<_, A>, mut res: ResMut| res.0 += 1); let entity = world.spawn(A).id(); @@ -520,17 +529,17 @@ mod tests { fn observer_no_source() { let mut world = World::new(); world.init_resource::(); - world.init_component::(); + world.init_trigger::(); world .spawn_empty() - .observe(|_: Observer| panic!("Event routed to non-targeted entity.")); - world.observer(move |obs: Observer, mut res: ResMut| { + .observe(|_: Observer| panic!("Trigger routed to non-targeted entity.")); + world.observer(move |obs: Observer, mut res: ResMut| { assert_eq!(obs.source(), Entity::PLACEHOLDER); res.0 += 1; }); - world.ecs_event(EventA).emit(); + world.trigger(TriggerA).emit(); world.flush(); assert_eq!(1, world.resource::().0); } @@ -539,21 +548,21 @@ mod tests { fn observer_entity_routing() { let mut world = World::new(); world.init_resource::(); - world.init_component::(); + world.init_trigger::(); world .spawn_empty() - .observe(|_: Observer| panic!("Event routed to non-targeted entity.")); + .observe(|_: Observer| panic!("Trigger routed to non-targeted entity.")); let entity = world .spawn_empty() - .observe(|_: Observer, mut res: ResMut| res.0 += 1) + .observe(|_: Observer, mut res: ResMut| res.0 += 1) .id(); - world.observer(move |obs: Observer, mut res: ResMut| { + world.observer(move |obs: Observer, mut res: ResMut| { assert_eq!(obs.source(), entity); res.0 += 1; }); - world.ecs_event(EventA).entity(entity).emit(); + world.trigger(TriggerA).entity(entity).emit(); world.flush(); assert_eq!(2, world.resource::().0); } @@ -576,23 +585,23 @@ mod tests { }); let entity = entity.flush(); - world.ecs_event(EventA).entity(entity).emit(); + world.trigger(TriggerA).entity(entity).emit(); world.flush(); assert_eq!(1, world.resource::().0); } #[test] - fn observer_dynamic_event() { + fn observer_dynamic_trigger() { let mut world = World::new(); world.init_resource::(); - let event = world.init_component_with_descriptor(ComponentDescriptor::new::()); - // SAFETY: we registered `event` above - unsafe { ObserverBuilder::new_with_id(event, world.commands()) } - .run(|_: Observer, mut res: ResMut| res.0 += 1); + let trigger = world.init_component_with_descriptor(ComponentDescriptor::new::()); + // SAFETY: we registered `trigger` above + unsafe { ObserverBuilder::new_with_id(trigger, world.commands()) } + .run(|_: Observer, mut res: ResMut| res.0 += 1); - // SAFETY: we registered `event` above - unsafe { EventBuilder::new_with_id(event, EventA, world.commands()) }.emit(); + // SAFETY: we registered `trigger` above + unsafe { TriggerBuilder::new_with_id(trigger, TriggerA, world.commands()) }.emit(); world.flush(); assert_eq!(1, world.resource::().0); } diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index 27d9a4bd4aa80..e47f294975221 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -12,18 +12,18 @@ use super::*; pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut); /// Equivalent to [`BoxedSystem`](crate::system::BoxedSystem) for [`ObserverSystem`]. -pub type BoxedObserverSystem = Box>; +pub type BoxedObserverSystem = Box>; pub(crate) struct ObserverComponent { pub(crate) descriptor: ObserverDescriptor, pub(crate) runner: ObserverRunner, - pub(crate) last_event_id: u32, + pub(crate) last_trigger_id: u32, } #[derive(Component)] #[component(storage = "SparseSet")] // This used to be in `ObserverComponent` but MIRI recently got a new lint that complained about the type erasure -pub(crate) struct ObserverSystemComponent(BoxedObserverSystem); +pub(crate) struct ObserverSystemComponent(BoxedObserverSystem); impl Component for ObserverComponent { const STORAGE_TYPE: StorageType = StorageType::SparseSet; @@ -46,18 +46,18 @@ impl Component for ObserverComponent { } impl ObserverComponent { - pub(crate) fn from( + pub(crate) fn from( world: &mut World, descriptor: ObserverDescriptor, - system: impl IntoObserverSystem, - ) -> (Self, ObserverSystemComponent) { + system: impl IntoObserverSystem, + ) -> (Self, ObserverSystemComponent) { let mut system = IntoObserverSystem::into_system(system); assert!( !system.is_exclusive(), "Cannot run exclusive systems in Observers" ); system.initialize(world); - let system: BoxedObserverSystem = Box::new(system); + let system: BoxedObserverSystem = Box::new(system); ( Self { descriptor, @@ -75,19 +75,19 @@ impl ObserverComponent { // TODO: Move this check into the observer cache to avoid dynamic dispatch // SAFETY: We only access world metadata - let last_event = unsafe { world.world_metadata() }.last_event_id(); - if state.last_event_id == last_event { + let last_trigger = unsafe { world.world_metadata() }.last_trigger_id(); + if state.last_trigger_id == last_trigger { return; } - state.last_event_id = last_event; + state.last_trigger_id = last_trigger; - // SAFETY: Caller ensures `ptr` is castable to `&mut E` - let observer: Observer = + let observer: Observer = + // SAFETY: Caller ensures `ptr` is castable to `&mut T` Observer::new(unsafe { ptr.deref_mut() }, trigger); // SAFETY: Observer was triggered so must have an `ObserverSystemComponent` let system = unsafe { &mut observer_cell - .get_mut::>() + .get_mut::>() .debug_checked_unwrap() .0 }; @@ -104,7 +104,7 @@ impl ObserverComponent { system.queue_deferred(world.into_deferred()); } }, - last_event_id: 0, + last_trigger_id: 0, }, ObserverSystemComponent(system), ) @@ -114,7 +114,7 @@ impl ObserverComponent { Self { descriptor, runner, - last_event_id: 0, + last_trigger_id: 0, } } } diff --git a/crates/bevy_ecs/src/system/adapter_system.rs b/crates/bevy_ecs/src/system/adapter_system.rs index c44e19c24d709..855190525749b 100644 --- a/crates/bevy_ecs/src/system/adapter_system.rs +++ b/crates/bevy_ecs/src/system/adapter_system.rs @@ -123,6 +123,11 @@ where self.system.apply_deferred(world); } + #[inline] + fn queue_deferred(&mut self, world: crate::world::DeferredWorld) { + self.system.queue_deferred(world); + } + fn initialize(&mut self, world: &mut crate::prelude::World) { self.system.initialize(world); } diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index 8184cf76226fb..6b4a80a21b9d6 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -197,6 +197,12 @@ where self.b.apply_deferred(world); } + #[inline] + fn queue_deferred(&mut self, mut world: crate::world::DeferredWorld) { + self.a.queue_deferred(world.reborrow()); + self.b.queue_deferred(world); + } + fn initialize(&mut self, world: &mut World) { self.a.initialize(world); self.b.initialize(world); diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 42579a54ec790..edd4b59d8073d 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -1202,29 +1202,29 @@ impl> Command for Observe { - /// [`ComponentId`] for this event. - event: ComponentId, +pub(crate) struct EmitTrigger { + /// [`ComponentId`] for this trigger. + trigger: ComponentId, /// Entities to trigger observers for. entities: Vec, /// Components to trigger observers for. components: Vec, - /// Data for the event. - data: E, + /// Data for the trigger. + data: T, } -impl EmitEcsEvent { - // SAFETY: Caller must ensure the type represented by `event` is accessible as `E`. +impl EmitTrigger { + // SAFETY: Caller must ensure the type represented by `trigger` is accessible as `T`. pub(crate) unsafe fn new( - event: ComponentId, + trigger: ComponentId, entities: Vec, components: Vec, - data: E, + data: T, ) -> Self { Self { - event, + trigger, entities, components, data, @@ -1232,15 +1232,15 @@ impl EmitEcsEvent { } } -impl Command for EmitEcsEvent { +impl Command for EmitTrigger { fn apply(mut self, world: &mut World) { let mut world = DeferredWorld::from(world); if self.entities.is_empty() { - // SAFETY: E is accessible as the type represented by self.event, ensured in `Self::new` + // SAFETY: T is accessible as the type represented by self.trigger, ensured in `Self::new` unsafe { world.trigger_observers_with_data( - self.event, + self.trigger, Entity::PLACEHOLDER, self.components.iter().cloned(), &mut self.data, @@ -1248,10 +1248,10 @@ impl Command for EmitEcsEvent { }; } else { for &target in &self.entities { - // SAFETY: E is accessible as the type represented by self.event, ensured in `Self::new` + // SAFETY: T is accessible as the type represented by self.trigger, ensured in `Self::new` unsafe { world.trigger_observers_with_data( - self.event, + self.trigger, target, self.components.iter().cloned(), &mut self.data, diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index 5f48eec3173a4..8ce6ebd3dfd5e 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -126,6 +126,13 @@ where // might have buffers to apply, but this is handled by `PipeSystem`. } + #[inline] + fn queue_deferred(&mut self, _world: crate::world::DeferredWorld) { + // "pure" exclusive systems do not have any buffers to apply. + // Systems made by piping a normal system with an exclusive system + // might have buffers to apply, but this is handled by `PipeSystem`. + } + #[inline] fn initialize(&mut self, world: &mut World) { self.system_meta.last_run = world.change_tick().relative_to(Tick::MAX); diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index 9cf0e1d23f916..01bed428f1174 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -5,7 +5,7 @@ use crate::{ query::{Access, FilteredAccessSet}, schedule::{InternedSystemSet, SystemSet}, system::{check_system_change_tick, ReadOnlySystemParam, System, SystemParam, SystemParamItem}, - world::{unsafe_world_cell::UnsafeWorldCell, World, WorldId}, + world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World, WorldId}, }; use bevy_utils::all_tuples; @@ -515,6 +515,12 @@ where F::Param::apply(param_state, &self.system_meta, world); } + #[inline] + fn queue_deferred(&mut self, world: DeferredWorld) { + let param_state = self.param_state.as_mut().expect(Self::PARAM_MESSAGE); + F::Param::queue(param_state, &self.system_meta, world); + } + #[inline] fn initialize(&mut self, world: &mut World) { self.world_id = Some(world.id()); diff --git a/crates/bevy_ecs/src/system/observer_system.rs b/crates/bevy_ecs/src/system/observer_system.rs index d6f1b935bdbf5..1380e32be3413 100644 --- a/crates/bevy_ecs/src/system/observer_system.rs +++ b/crates/bevy_ecs/src/system/observer_system.rs @@ -1,52 +1,23 @@ +use bevy_utils::all_tuples; + use crate::{ prelude::{Bundle, Observer}, - query::{QueryData, QueryFilter}, system::{System, SystemParam, SystemParamFunction, SystemParamItem}, - world::DeferredWorld, }; -use super::{Commands, FunctionSystem, IntoSystem, Query, Res, ResMut, Resource}; -use bevy_utils::all_tuples; +use super::IntoSystem; -/// Implemented for systems that have an [`Observer`] as the first argument and only [`ObserverSystemParam`] implementors as parameters. +/// Implemented for systems that have an [`Observer`] as the first argument. pub trait ObserverSystem: System, Out = ()> + Send + 'static { - /// Queues any deferred mutations to be applied at the next [`apply_deferred`](crate::prelude::apply_deferred). - fn queue_deferred(&mut self, _world: DeferredWorld); } -/// Marker trait implemented for [`SystemParam`] that can be used in [`Observer`] systems. -/// `queue` may be called instead of `apply` for types implementing this trait. -/// -/// Note: this could be combined with [`SystemParam`] if we promote these restrictions to that trait. -pub trait ObserverSystemParam: SystemParam {} - -impl<'w, D: QueryData + 'static, F: QueryFilter + 'static> ObserverSystemParam - for Query<'w, 'w, D, F> +impl, Out = ()> + Send + 'static> + ObserverSystem for T { } -impl<'w, T: Resource> ObserverSystemParam for Res<'w, T> {} - -impl<'w, T: Resource> ObserverSystemParam for ResMut<'w, T> {} - -impl<'w> ObserverSystemParam for Commands<'w, 'w> {} - -impl<'w> ObserverSystemParam for DeferredWorld<'w> {} - -impl ObserverSystem for FunctionSystem -where - Marker: 'static, - F: SystemParamFunction, Out = ()>, - F::Param: ObserverSystemParam, -{ - fn queue_deferred(&mut self, world: DeferredWorld) { - let param_state = self.param_state.as_mut().unwrap(); - F::Param::queue(param_state, &self.system_meta, world); - } -} - /// Implemented for systems that convert into [`ObserverSystem`]. pub trait IntoObserverSystem: Send + 'static { /// The type of [`System`] that this instance converts into. @@ -68,17 +39,6 @@ where } } -macro_rules! impl_observer_system_param_tuple { - ($($param: ident),*) => { - #[allow(clippy::undocumented_unsafe_blocks)] - #[allow(non_snake_case)] - impl<$($param: ObserverSystemParam),*> ObserverSystemParam for ($($param,)*) { - } - }; -} - -all_tuples!(impl_observer_system_param_tuple, 0, 16, P); - macro_rules! impl_system_function { ($($param: ident),*) => { #[allow(non_snake_case)] diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index ec1b08707c70e..cf62615b53846 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -4,6 +4,7 @@ use core::fmt::Debug; use crate::component::Tick; use crate::schedule::InternedSystemSet; use crate::world::unsafe_world_cell::UnsafeWorldCell; +use crate::world::DeferredWorld; use crate::{archetype::ArchetypeComponentId, component::ComponentId, query::Access, world::World}; use std::any::TypeId; @@ -88,6 +89,10 @@ pub trait System: Send + Sync + 'static { /// This is where [`Commands`](crate::system::Commands) get applied. fn apply_deferred(&mut self, world: &mut World); + /// Enqueues any [`Deferred`](crate::system::Deferred) system parameters (or other system buffers) + /// of this system into the world's command buffer. + fn queue_deferred(&mut self, world: DeferredWorld); + /// Initialize the system. fn initialize(&mut self, _world: &mut World); diff --git a/crates/bevy_ecs/src/world/component_constants.rs b/crates/bevy_ecs/src/world/component_constants.rs index 4ef61c3b346d0..2aebd8cd93dd0 100644 --- a/crates/bevy_ecs/src/world/component_constants.rs +++ b/crates/bevy_ecs/src/world/component_constants.rs @@ -1,5 +1,5 @@ use super::*; -use crate as bevy_ecs; +use crate::{self as bevy_ecs, prelude::Trigger}; /// Internal components used by bevy with a fixed component id. /// Constants are used to skip [`TypeId`] lookups in hot paths. @@ -10,14 +10,14 @@ pub const ON_INSERT: ComponentId = ComponentId::new(1); /// [`ComponentId`] for [`OnRemove`] pub const ON_REMOVE: ComponentId = ComponentId::new(2); -/// Event emitted when a component is added to an entity. -#[derive(Component)] +/// Trigger emitted when a component is added to an entity. +#[derive(Trigger)] pub struct OnAdd; -/// Event emitted when a component is inserted on to to an entity. -#[derive(Component)] +/// Trigger emitted when a component is inserted on to to an entity. +#[derive(Trigger)] pub struct OnInsert; -/// Event emitted when a component is removed from an entity. -#[derive(Component)] +/// Trigger emitted when a component is removed from an entity. +#[derive(Trigger)] pub struct OnRemove; diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 9c1313a56efe4..97c6e12fa87ff 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -6,7 +6,7 @@ use crate::{ component::ComponentId, entity::Entity, event::{Event, EventId, Events, SendBatchIds}, - observer::{EventBuilder, Observers}, + observer::{Observers, Trigger, TriggerBuilder}, prelude::{Component, QueryState}, query::{QueryData, QueryFilter}, system::{Commands, Query, Resource}, @@ -367,9 +367,9 @@ impl<'w> DeferredWorld<'w> { Observers::invoke(self.reborrow(), event, source, components, data); } - /// Constructs an [`EventBuilder`] for an ECS event. - pub fn ecs_event(&mut self, data: E) -> EventBuilder { - EventBuilder::new(data, self.commands()) + /// Constructs an [`TriggerBuilder`] for an ECS [`Trigger`]. + pub fn trigger(&mut self, data: T) -> TriggerBuilder { + TriggerBuilder::new(data, self.commands()) } /// Gets an [`UnsafeWorldCell`] containing the underlying world. diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index c5badd419bb17..0703d9ff3967f 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -116,7 +116,7 @@ pub struct World { pub(crate) change_tick: AtomicU32, pub(crate) last_change_tick: Tick, pub(crate) last_check_tick: Tick, - pub(crate) last_event_id: u32, + pub(crate) last_trigger_id: u32, pub(crate) command_queue: CommandQueue, } @@ -136,7 +136,7 @@ impl Default for World { change_tick: AtomicU32::new(1), last_change_tick: Tick::new(0), last_check_tick: Tick::new(0), - last_event_id: 0, + last_trigger_id: 0, command_queue: CommandQueue::default(), }; world.bootstrap(); @@ -1935,8 +1935,8 @@ impl World { /// Returns the id of the last ECS event that was fired. /// Used internally to ensure observers don't trigger multiple times for the same event. #[inline] - pub(crate) fn last_event_id(&self) -> u32 { - self.last_event_id + pub(crate) fn last_trigger_id(&self) -> u32 { + self.last_trigger_id } /// Sets [`World::last_change_tick()`] to the specified value during a scope. diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 81ebc1ce41b9b..f3bc58e3adf98 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -581,10 +581,10 @@ impl<'w> UnsafeWorldCell<'w> { /// # Safety /// It is the callers responsibility to ensure that there are no outstanding - /// references to `last_event_id`. - pub(crate) unsafe fn increment_event_id(self) { + /// references to `last_trigger_id`. + pub(crate) unsafe fn increment_trigger_id(self) { // SAFETY: Caller ensure there are no outstanding references - unsafe { (*self.0).last_event_id += 1 } + unsafe { (*self.0).last_trigger_id += 1 } } } diff --git a/examples/ecs/observers.rs b/examples/ecs/observers.rs index dde88cca42418..0b211518ab597 100644 --- a/examples/ecs/observers.rs +++ b/examples/ecs/observers.rs @@ -19,13 +19,13 @@ struct Mine { size: f32, } -#[derive(Component)] +#[derive(Trigger)] struct TriggerMines { pos: Vec2, radius: f32, } -#[derive(Component)] +#[derive(Trigger)] struct Explode; fn setup(world: &mut World) { @@ -44,35 +44,35 @@ fn setup(world: &mut World) { }, )); - // Pre-register all our components, resource and event types. + // Pre-register all our components, resource and trigger types. world.init_resource::(); world.init_component::(); - world.init_component::(); - world.init_component::(); + world.init_trigger::(); + world.init_trigger::(); - // Observers are triggered when a certain event is fired, each event is represented by a component type. + // Observers run when a certain trigger is fired, each trigger is represented by a type. // This observer runs whenever `TriggerMines` is fired, observers run systems which can be defined as closures. world.observer( |observer: Observer, mines: Query<&Mine>, index: Res, mut commands: Commands| { - // You can access the event data via the `Observer` + // You can access the trigger data via the `Observer` let trigger = observer.data(); // Access resources for e in index.get_nearby(trigger.pos) { // Run queries let mine = mines.get(e).unwrap(); if mine.pos.distance(trigger.pos) < mine.size + trigger.radius { - // And queue commands, including firing additional events - // Here we fire the `Explode` event at entity `e` - commands.event(Explode).entity(e).emit(); + // And queue commands, including firing additional triggers + // Here we fire the `Explode` trigger at entity `e` + commands.trigger(Explode).entity(e).emit(); } } }, ); - // Observers can also listen for events triggering for a specific component. + // Observers can also listen for triggers for a specific component. // This observer runs whenever the `Mine` component is added to an entity, and places it in a simple spatial index. world.observer( |observer: Observer, query: Query<&Mine>, mut index: ResMut| { @@ -100,11 +100,11 @@ fn setup(world: &mut World) { ), size: 4.0 + rand::random::() * 16.0, }) - // Observers can also listen to events targeting a specific entity. - // This observer listens to `Explode` events targeted at our mine. + // Observers can also listen to triggers targeting a specific entity. + // This observer listens to `Explode` triggers targeted at our mine. .observe( |observer: Observer, query: Query<&Mine>, mut commands: Commands| { - // If an event is targeting a specific entity you can access it with `.source()` + // If a trigger is targeting a specific entity you can access it with `.source()` let source = observer.source(); let Some(mut entity) = commands.get_entity(source) else { return; @@ -112,9 +112,9 @@ fn setup(world: &mut World) { println!("Boom! {:?} exploded.", source.index()); entity.despawn(); let mine = query.get(source).unwrap(); - // Fire another event to cascade into other mines. + // Fire another trigger to cascade into other mines. commands - .event(TriggerMines { + .trigger(TriggerMines { pos: mine.pos, radius: mine.size, }) @@ -178,7 +178,7 @@ fn draw_shapes(mut gizmos: Gizmos, mines: Query<&Mine>) { } } -// Fire an initial `TriggerMines` event on click +// Fire an initial `TriggerMines` trigger on click fn handle_click( mouse_button_input: Res>, camera: Query<(&Camera, &GlobalTransform)>, @@ -193,7 +193,7 @@ fn handle_click( .map(|ray| ray.origin.truncate()) { if mouse_button_input.just_pressed(MouseButton::Left) { - commands.event(TriggerMines { pos, radius: 1.0 }).emit(); + commands.trigger(TriggerMines { pos, radius: 1.0 }).emit(); } } } From f2b29f88c615c3b0fb06c3d8f81df66bc858f78d Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Wed, 22 May 2024 21:04:03 -0700 Subject: [PATCH 084/109] Apply easy doc improvements Co-authored-by: Alice Cecile --- crates/bevy_ecs/src/archetype.rs | 6 +++--- crates/bevy_ecs/src/observer/builder.rs | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index dc6f60408e68a..4c7984af3caf5 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -605,19 +605,19 @@ impl Archetype { self.flags().contains(ArchetypeFlags::ON_REMOVE_HOOK) } - /// Returns true if any of the components in this archetype have `OnAdd` observer + /// Returns true if any of the components in this archetype have at least one `OnAdd` observer #[inline] pub fn has_add_observer(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_ADD_OBSERVER) } - /// Returns true if any of the components in this archetype have `OnInsert` observer + /// Returns true if any of the components in this archetype have at least one `OnInsert` observer #[inline] pub fn has_insert_observer(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_INSERT_OBSERVER) } - /// Returns true if any of the components in this archetype have `OnRemove` observer + /// Returns true if any of the components in this archetype have at least one `OnRemove` observer #[inline] pub fn has_remove_observer(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_REMOVE_OBSERVER) diff --git a/crates/bevy_ecs/src/observer/builder.rs b/crates/bevy_ecs/src/observer/builder.rs index 6c53117bfef3d..5e53bdc205e3e 100644 --- a/crates/bevy_ecs/src/observer/builder.rs +++ b/crates/bevy_ecs/src/observer/builder.rs @@ -32,6 +32,7 @@ impl<'w, T: 'static> ObserverBuilder<'w, T> { } /// Constructs an [`ObserverBuilder`] with a dynamic trigger id. + /// /// # Safety /// Caller must ensure that the component associated with `id` is accessible as T #[must_use] @@ -79,7 +80,7 @@ impl<'w, T: 'static> ObserverBuilder<'w, T> { unsafe { std::mem::transmute(self) } } - /// Add [`ComponentId`] in `T` to the list of components listened to by this observer. + /// Add all of the [`ComponentId`]s in `B` to the list of components listened to by this observer. /// For examples an `OnRemove` observer would trigger when any component in `B` was removed. pub fn components(&mut self) -> &mut Self { B::get_component_ids(self.commands.components(), &mut |id| { @@ -128,7 +129,7 @@ impl<'w, T: 'static> ObserverBuilder<'w, T> { entity } - /// Spawns the resulting observer into the world using an [`ObserverRunner`] callback. + /// Spawns the resulting observer into the world using a custom [`ObserverRunner`] callback. /// This is not advised unless you want to override the default runner behaviour. pub fn runner(&mut self, runner: ObserverRunner) -> Entity { let entity = self.commands.spawn_empty().id(); From 890d8fb2f38a291fc319f525aec374c089b577d0 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 30 May 2024 11:01:07 -0400 Subject: [PATCH 085/109] Improve `EntityCommands::observe` docs --- crates/bevy_ecs/src/system/commands/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index edd4b59d8073d..e9b7ee460cd5a 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -1022,7 +1022,7 @@ impl EntityCommands<'_> { self.commands.reborrow() } - /// Creates an [`Observer`](crate::observer::Observer) listening for `E` events targeting this entity. + /// Creates an [`Observer`](crate::observer::Observer) listening for any event of type `E` that targets this entity. pub fn observe( &mut self, system: impl IntoObserverSystem, From e0ac5dbe070815262763d5fe22209e35c8aea991 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 30 May 2024 14:15:39 -0400 Subject: [PATCH 086/109] Better notes Co-authored-by: MiniaczQ --- examples/ecs/observers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ecs/observers.rs b/examples/ecs/observers.rs index 0b211518ab597..8fd91c5fe4fa9 100644 --- a/examples/ecs/observers.rs +++ b/examples/ecs/observers.rs @@ -1,4 +1,4 @@ -//! This examples illustrates several ways you can employ observers +//! Demonstrates how to observe life-cycle triggers as well as define custom ones. use bevy::{ prelude::*, From a8bc2f0d8227a6f877853d311b47016b45bd942b Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 7 Jun 2024 18:30:48 -0700 Subject: [PATCH 087/109] Make Observers directly spawnable, auto-init, improve trigger API, App methods --- crates/bevy_app/src/app.rs | 11 +- crates/bevy_ecs/src/observer/builder.rs | 240 ---------------- crates/bevy_ecs/src/observer/emit_trigger.rs | 156 +++++++++++ .../bevy_ecs/src/observer/entity_observer.rs | 8 +- crates/bevy_ecs/src/observer/mod.rs | 190 +++++++------ crates/bevy_ecs/src/observer/runner.rs | 257 ++++++++++++------ crates/bevy_ecs/src/system/commands/mod.rs | 119 +++----- crates/bevy_ecs/src/world/deferred_world.rs | 13 +- crates/bevy_ecs/src/world/entity_ref.rs | 15 +- examples/ecs/observers.rs | 93 +++---- 10 files changed, 546 insertions(+), 556 deletions(-) delete mode 100644 crates/bevy_ecs/src/observer/builder.rs create mode 100644 crates/bevy_ecs/src/observer/emit_trigger.rs diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 176c5caf60a56..2464de2eeb126 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -8,7 +8,7 @@ use bevy_ecs::{ intern::Interned, prelude::*, schedule::{ScheduleBuildSettings, ScheduleLabel}, - system::SystemId, + system::{IntoObserverSystem, SystemId}, }; #[cfg(feature = "bevy_state")] use bevy_state::{prelude::*, state::FreelyMutableState}; @@ -883,6 +883,15 @@ impl App { None } + + /// Spawn the given [`Observer`]. + pub fn observe( + &mut self, + observer: impl IntoObserverSystem, + ) -> &mut Self { + self.world_mut().observe(observer); + self + } } type RunnerFn = Box AppExit>; diff --git a/crates/bevy_ecs/src/observer/builder.rs b/crates/bevy_ecs/src/observer/builder.rs deleted file mode 100644 index 5e53bdc205e3e..0000000000000 --- a/crates/bevy_ecs/src/observer/builder.rs +++ /dev/null @@ -1,240 +0,0 @@ -use std::any::TypeId; - -use super::*; - -/// Builder struct for [`Observer`]. -pub struct ObserverBuilder<'w, T = ()> { - commands: Commands<'w, 'w>, - descriptor: ObserverDescriptor, - _marker: PhantomData, -} - -impl<'w, T: 'static> ObserverBuilder<'w, T> { - /// Constructs a new [`ObserverBuilder`]. - pub fn new(commands: Commands<'w, 'w>) -> Self { - let mut descriptor = ObserverDescriptor::default(); - let trigger = commands - .components() - .get_id(TypeId::of::()) - .unwrap_or_else(|| { - panic!( - "Cannot observe trigger before it is registered with init_trigger: {}", - std::any::type_name::(), - ) - }); - descriptor.triggers.push(trigger); - - Self { - commands, - descriptor, - _marker: PhantomData, - } - } - - /// Constructs an [`ObserverBuilder`] with a dynamic trigger id. - /// - /// # Safety - /// Caller must ensure that the component associated with `id` is accessible as T - #[must_use] - pub unsafe fn new_with_id(trigger: ComponentId, commands: Commands<'w, 'w>) -> Self { - let mut descriptor = ObserverDescriptor::default(); - descriptor.triggers.push(trigger); - - Self { - commands, - descriptor, - _marker: PhantomData, - } - } - - /// Adds `NewT` to the list of triggers listened to by this observer. - /// After calling this function the observer will no longer have access to typed trigger data - /// to prtrigger accessing the data as the incorrect type. - /// Observing the same trigger multiple times has no effect. - pub fn on_trigger(&mut self) -> &mut ObserverBuilder<'w, ()> { - let trigger = self - .commands - .components() - .get_id(TypeId::of::()) - .unwrap_or_else(|| { - panic!( - "Cannot observe trigger before it is registered with init_component: {}", - std::any::type_name::(), - ) - }); - self.descriptor.triggers.push(trigger); - // SAFETY: () will not allow bad memory access as it has no size - unsafe { std::mem::transmute(self) } - } - - /// Add `triggers` to the list of triggers listened to by this observer. - /// After calling this function the observer will no longer have access to typed trigger data - /// to prevent accessing the data as the incorrect type. - /// Observing the same trigger multiple times has no effect. - pub fn on_trigger_ids( - &mut self, - triggers: impl IntoIterator, - ) -> &mut ObserverBuilder<'w, ()> { - self.descriptor.triggers.extend(triggers); - // SAFETY: () type will not allow bad memory access as it has no size - unsafe { std::mem::transmute(self) } - } - - /// Add all of the [`ComponentId`]s in `B` to the list of components listened to by this observer. - /// For examples an `OnRemove` observer would trigger when any component in `B` was removed. - pub fn components(&mut self) -> &mut Self { - B::get_component_ids(self.commands.components(), &mut |id| { - self.descriptor.components.push(id.unwrap_or_else(|| { - panic!( - "Cannot observe trigger before it is registered with init_component: {}", - std::any::type_name::(), - ) - })); - }); - self - } - - /// Add `ids` to the list of component sources listened to by this observer. - pub fn component_ids<'c>( - &mut self, - ids: impl IntoIterator, - ) -> &mut Self { - self.descriptor.components.extend(ids.into_iter().cloned()); - self - } - - /// Adds `source` as the list of entity sources listened to by this observer. - pub fn source(&mut self, source: Entity) -> &mut Self { - self.descriptor.sources.push(source); - self - } - - /// Spawns the resulting observer into the world. - pub fn run(&mut self, system: impl IntoObserverSystem) -> Entity { - B::get_component_ids(self.commands.components(), &mut |id| { - self.descriptor.components.push(id.unwrap_or_else(|| { - panic!( - "Cannot observe trigger before it is registered with init_component: {}", - std::any::type_name::(), - ) - })); - }); - let entity = self.commands.spawn_empty().id(); - let descriptor = self.descriptor.clone(); - self.commands.add(move |world: &mut World| { - let component = ObserverComponent::from(world, descriptor, system); - world.entity_mut(entity).insert(component); - world.register_observer(entity); - }); - entity - } - - /// Spawns the resulting observer into the world using a custom [`ObserverRunner`] callback. - /// This is not advised unless you want to override the default runner behaviour. - pub fn runner(&mut self, runner: ObserverRunner) -> Entity { - let entity = self.commands.spawn_empty().id(); - let descriptor: ObserverDescriptor = self.descriptor.clone(); - self.commands.add(move |world: &mut World| { - let component = ObserverComponent::from_runner(descriptor, runner); - world.entity_mut(entity).insert(component); - world.register_observer(entity); - }); - entity - } -} - -/// Type used to construct and emit an ECS trigger. -pub struct TriggerBuilder<'w, T = ()> { - trigger: ComponentId, - commands: Commands<'w, 'w>, - targets: Vec, - components: Vec, - data: Option, -} - -impl<'w, T: Send + 'static> TriggerBuilder<'w, T> { - /// Constructs a new builder that will write it's trigger to `world`'s command queue - #[must_use] - pub fn new(data: T, commands: Commands<'w, 'w>) -> Self { - let trigger = commands - .components() - .get_id(TypeId::of::()) - .unwrap_or_else(|| { - panic!( - "Cannot emit trigger before it is registered with init_component: {}", - std::any::type_name::() - ) - }); - Self { - trigger, - commands, - targets: Vec::new(), - components: Vec::new(), - data: Some(data), - } - } - - /// Sets the trigger id of the resulting trigger, used for dynamic triggers - /// # Safety - /// Caller must ensure that the component associated with `id` is accessible as E - #[must_use] - pub unsafe fn new_with_id(trigger: ComponentId, data: T, commands: Commands<'w, 'w>) -> Self { - Self { - trigger, - commands, - targets: Vec::new(), - components: Vec::new(), - data: Some(data), - } - } - - /// Adds `target` to the list of entities targeted by `self` - #[must_use] - pub fn entity(&mut self, target: Entity) -> &mut Self { - self.targets.push(target); - self - } - - /// Adds `component_id` to the list of components targeted by `self` - #[must_use] - pub fn component(&mut self, component_id: ComponentId) -> &mut Self { - self.components.push(component_id); - self - } - - /// Add the trigger to the command queue of world - pub fn emit(&mut self) { - // SAFETY: `self.trigger` is accessible as T, enforced in `Self::new` and `Self::new_with_id`. - self.commands.add(unsafe { - EmitTrigger::::new( - self.trigger, - std::mem::take(&mut self.targets), - std::mem::take(&mut self.components), - std::mem::take(&mut self.data) - .expect("triggerBuilder used to send more than one trigger."), - ) - }); - } -} - -impl<'w, 's> Commands<'w, 's> { - /// Constructs a [`TriggerBuilder`]. - #[must_use] - pub fn trigger(&mut self, trigger: T) -> TriggerBuilder { - TriggerBuilder::new(trigger, self.reborrow()) - } - - /// Construct an [`ObserverBuilder`]. - #[must_use] - pub fn observer_builder(&mut self) -> ObserverBuilder { - ObserverBuilder::new(self.reborrow()) - } - - /// Spawn an [`Observer`] and returns it's [`Entity`]. - pub fn observer( - &mut self, - callback: impl IntoObserverSystem, - ) -> Entity { - ObserverBuilder::new(self.reborrow()).run(callback) - } -} diff --git a/crates/bevy_ecs/src/observer/emit_trigger.rs b/crates/bevy_ecs/src/observer/emit_trigger.rs new file mode 100644 index 0000000000000..63aefb5684151 --- /dev/null +++ b/crates/bevy_ecs/src/observer/emit_trigger.rs @@ -0,0 +1,156 @@ +use crate::{ + component::ComponentId, + entity::Entity, + observer::Trigger, + world::{Command, DeferredWorld, World}, +}; + +/// A [`Command`] that emits a given trigger for a given set of targets. +pub struct EmitTrigger { + /// The trigger to emit. + pub trigger: T, + + /// The targets to emit the trigger for. + pub targets: Targets, +} + +impl Command for EmitTrigger { + fn apply(mut self, world: &mut World) { + let trigger_id = world.init_component::(); + apply_trigger(world, trigger_id, &mut self.trigger, self.targets); + } +} + +/// Emit a trigger for a dynamic component id. This is unsafe and must be verified manually. +pub struct EmitDynamicTrigger { + trigger: ComponentId, + data: T, + targets: Targets, +} + +impl EmitDynamicTrigger { + /// Sets the trigger id of the resulting trigger, used for dynamic triggers + /// # Safety + /// Caller must ensure that the component associated with `id` is accessible as E + pub unsafe fn new_with_id(trigger: ComponentId, data: T, targets: Targets) -> Self { + Self { + trigger, + data, + targets, + } + } +} + +impl Command for EmitDynamicTrigger { + fn apply(mut self, world: &mut World) { + apply_trigger(world, self.trigger, &mut self.data, self.targets); + } +} + +#[inline] +fn apply_trigger( + world: &mut World, + trigger_id: ComponentId, + data: &mut T, + targets: Targets, +) { + let mut world = DeferredWorld::from(world); + if targets.entities().len() == 0 { + eprintln!("triggering"); + // SAFETY: T is accessible as the type represented by self.trigger, ensured in `Self::new` + unsafe { + world.trigger_observers_with_data( + trigger_id, + Entity::PLACEHOLDER, + targets.components(), + data, + ); + }; + } else { + for target in targets.entities() { + // SAFETY: T is accessible as the type represented by self.trigger, ensured in `Self::new` + unsafe { + world.trigger_observers_with_data(trigger_id, target, targets.components(), data); + }; + } + } +} + +/// Represents a collection of targets, which can be of type [`Entity`] or [`ComponentId`]. +pub trait TriggerTargets: Send + Sync + 'static { + /// The components the trigger should target. + fn components(&self) -> impl ExactSizeIterator; + + /// The entities the trigger should target. + fn entities(&self) -> impl ExactSizeIterator; +} + +impl TriggerTargets for () { + fn components(&self) -> impl ExactSizeIterator { + [].into_iter() + } + + fn entities(&self) -> impl ExactSizeIterator { + [].into_iter() + } +} + +impl TriggerTargets for Entity { + fn components(&self) -> impl ExactSizeIterator { + [].into_iter() + } + + fn entities(&self) -> impl ExactSizeIterator { + std::iter::once(*self) + } +} + +impl TriggerTargets for Vec { + fn components(&self) -> impl ExactSizeIterator { + [].into_iter() + } + + fn entities(&self) -> impl ExactSizeIterator { + self.iter().copied() + } +} + +impl TriggerTargets for [Entity; N] { + fn components(&self) -> impl ExactSizeIterator { + [].into_iter() + } + + fn entities(&self) -> impl ExactSizeIterator { + self.iter().copied() + } +} + +impl TriggerTargets for ComponentId { + fn components(&self) -> impl ExactSizeIterator { + std::iter::once(*self) + } + + fn entities(&self) -> impl ExactSizeIterator { + [].into_iter() + } +} + +impl TriggerTargets for Vec { + fn components(&self) -> impl ExactSizeIterator { + self.iter().copied() + } + + fn entities(&self) -> impl ExactSizeIterator { + [].into_iter() + } +} + +impl TriggerTargets for [ComponentId; N] { + fn components(&self) -> impl ExactSizeIterator { + self.iter().copied() + } + + fn entities(&self) -> impl ExactSizeIterator { + [].into_iter() + } +} diff --git a/crates/bevy_ecs/src/observer/entity_observer.rs b/crates/bevy_ecs/src/observer/entity_observer.rs index bc3f5eb781c31..e8155c843d5f8 100644 --- a/crates/bevy_ecs/src/observer/entity_observer.rs +++ b/crates/bevy_ecs/src/observer/entity_observer.rs @@ -1,5 +1,9 @@ -use super::*; -use crate::component::{ComponentHooks, StorageType}; +use crate::{ + component::{Component, ComponentHooks, StorageType}, + entity::Entity, + observer::Command, + world::World, +}; /// Command to attach an entity observer to an entity pub(crate) struct AttachObserver { diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 9aef26d0c9483..49c237741d1d5 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -1,26 +1,18 @@ //! Types for creating and storing [`Observer`]s -mod builder; +mod emit_trigger; mod entity_observer; mod runner; -use std::marker::PhantomData; - -use crate::{ - archetype::ArchetypeFlags, - query::DebugCheckedUnwrap, - system::{EmitTrigger, IntoObserverSystem}, - world::*, -}; pub use bevy_ecs_macros::Trigger; -pub use builder::*; -pub(crate) use entity_observer::*; +pub use emit_trigger::*; pub use runner::*; -use bevy_ptr::{Ptr, PtrMut}; -use bevy_utils::{EntityHashMap, HashMap}; - +use crate::{archetype::ArchetypeFlags, system::IntoObserverSystem, world::*}; use crate::{component::ComponentId, prelude::*, world::DeferredWorld}; +use bevy_ptr::Ptr; +use bevy_utils::{EntityHashMap, HashMap}; +use std::marker::PhantomData; /// Trait implemented for types that are used as ECS triggers observed by [`Observer`] pub trait Trigger: Component {} @@ -33,7 +25,8 @@ pub struct Observer<'w, T, B: Bundle = ()> { } impl<'w, T, B: Bundle> Observer<'w, T, B> { - pub(crate) fn new(data: &'w mut T, trigger: ObserverTrigger) -> Self { + /// Creates a new observer for the given trigger. + pub fn new(data: &'w mut T, trigger: ObserverTrigger) -> Self { Self { data, trigger, @@ -67,18 +60,56 @@ impl<'w, T, B: Bundle> Observer<'w, T, B> { } } +/// A description of what an [`Observer`] observes. #[derive(Default, Clone)] -pub(crate) struct ObserverDescriptor { +pub struct ObserverDescriptor { + /// The triggers the observer is triggered for. triggers: Vec, + + /// The components the observer is watching. components: Vec, + + /// The entities the observer is watching. sources: Vec, } +impl ObserverDescriptor { + /// Add the given `triggers` to the descriptor. + pub fn with_triggers(mut self, triggers: Vec) -> Self { + self.triggers = triggers; + self + } + + /// Add the given `components` to the descriptor. + pub fn with_components(mut self, components: Vec) -> Self { + self.components = components; + self + } + + /// Add the given `sources` to the descriptor. + pub fn with_sources(mut self, sources: Vec) -> Self { + self.sources = sources; + self + } + + pub(crate) fn merge(&mut self, descriptor: &ObserverDescriptor) { + self.triggers.extend(descriptor.triggers.iter().copied()); + self.components + .extend(descriptor.components.iter().copied()); + self.sources.extend(descriptor.sources.iter().copied()); + } +} + /// Metadata for the source triggering an [`Observer`], pub struct ObserverTrigger { - observer: Entity, - trigger: ComponentId, - source: Entity, + /// The [`Entity`] of observer handling the trigger. + pub observer: Entity, + + /// The [`ComponentId`] for the given trigger. + pub trigger: ComponentId, + + /// The source where the trigger occurred. + pub source: Entity, } // Map between an observer entity and it's runner @@ -228,32 +259,26 @@ impl Observers { } impl World { - /// Construct an [`ObserverBuilder`] - pub fn observer_builder(&mut self) -> ObserverBuilder { - self.init_trigger::(); - ObserverBuilder::new(self.commands()) - } - /// Spawn an [`Observer`] and returns it's [`Entity`]. - pub fn observer( + pub fn observe( &mut self, system: impl IntoObserverSystem, - ) -> Entity { - // Ensure triggers are registered with the world - // Note: this sidesteps init_trigger - B::component_ids(&mut self.components, &mut self.storages, &mut |_| {}); - ObserverBuilder::new(self.commands()).run(system) + ) -> EntityWorldMut { + self.spawn(ObserverSystemComponent::new(system)) } - /// Registers `T` as a [`Trigger`] - pub fn init_trigger(&mut self) { - self.init_component::(); + /// Emits the given `trigger``. + pub fn trigger(&mut self, trigger: impl Trigger) { + EmitTrigger { + trigger, + targets: (), + } + .apply(self); } - /// Constructs a [`TriggerBuilder`]. - pub fn trigger(&mut self, trigger: T) -> TriggerBuilder { - self.init_component::(); - TriggerBuilder::new(trigger, self.commands()) + /// Emits the given `trigger` for the given `targets`. + pub fn trigger_targets(&mut self, trigger: impl Trigger, targets: impl TriggerTargets) { + EmitTrigger { trigger, targets }.apply(self); } /// Register an observer to the cache, called when an observer is created @@ -365,12 +390,11 @@ mod tests { use bevy_ptr::OwningPtr; use crate as bevy_ecs; - use crate::component::ComponentDescriptor; - use crate::observer::TriggerBuilder; + use crate::observer::{ + EmitDynamicTrigger, ObserverComponent, ObserverDescriptor, ObserverSystemComponent, + }; use crate::prelude::*; - use super::ObserverBuilder; - #[derive(Component)] struct A; @@ -399,9 +423,9 @@ mod tests { let mut world = World::new(); world.init_resource::(); - world.observer(|_: Observer, mut res: ResMut| res.assert_order(0)); - world.observer(|_: Observer, mut res: ResMut| res.assert_order(1)); - world.observer(|_: Observer, mut res: ResMut| res.assert_order(2)); + world.observe(|_: Observer, mut res: ResMut| res.assert_order(0)); + world.observe(|_: Observer, mut res: ResMut| res.assert_order(1)); + world.observe(|_: Observer, mut res: ResMut| res.assert_order(2)); let entity = world.spawn(A).id(); world.despawn(entity); @@ -413,9 +437,9 @@ mod tests { let mut world = World::new(); world.init_resource::(); - world.observer(|_: Observer, mut res: ResMut| res.assert_order(0)); - world.observer(|_: Observer, mut res: ResMut| res.assert_order(1)); - world.observer(|_: Observer, mut res: ResMut| res.assert_order(2)); + world.observe(|_: Observer, mut res: ResMut| res.assert_order(0)); + world.observe(|_: Observer, mut res: ResMut| res.assert_order(1)); + world.observe(|_: Observer, mut res: ResMut| res.assert_order(2)); let mut entity = world.spawn_empty(); entity.insert(A); @@ -428,26 +452,26 @@ mod tests { fn observer_order_recursive() { let mut world = World::new(); world.init_resource::(); - world.observer( + world.observe( |obs: Observer, mut res: ResMut, mut commands: Commands| { res.assert_order(0); commands.entity(obs.source()).insert(B); }, ); - world.observer( + world.observe( |obs: Observer, mut res: ResMut, mut commands: Commands| { res.assert_order(2); commands.entity(obs.source()).remove::(); }, ); - world.observer( + world.observe( |obs: Observer, mut res: ResMut, mut commands: Commands| { res.assert_order(1); commands.entity(obs.source()).remove::(); }, ); - world.observer(|_: Observer, mut res: ResMut| { + world.observe(|_: Observer, mut res: ResMut| { res.assert_order(3); }); @@ -463,8 +487,8 @@ mod tests { let mut world = World::new(); world.init_resource::(); - world.observer(|_: Observer, mut res: ResMut| res.0 += 1); - world.observer(|_: Observer, mut res: ResMut| res.0 += 1); + world.observe(|_: Observer, mut res: ResMut| res.0 += 1); + world.observe(|_: Observer, mut res: ResMut| res.0 += 1); world.spawn(A).flush(); assert_eq!(2, world.resource::().0); @@ -476,12 +500,11 @@ mod tests { fn observer_multiple_triggerss() { let mut world = World::new(); world.init_resource::(); - world.init_component::(); - - world - .observer_builder::() - .on_trigger::() - .run(|_: Observer<_, A>, mut res: ResMut| res.0 += 1); + let on_remove = world.init_component::(); + world.spawn( + ObserverSystemComponent::new(|_: Observer, mut res: ResMut| res.0 += 1) + .with_trigger(on_remove), + ); let entity = world.spawn(A).id(); world.despawn(entity); @@ -495,7 +518,7 @@ mod tests { world.init_component::(); world.init_component::(); - world.observer(|_: Observer, mut res: ResMut| res.0 += 1); + world.observe(|_: Observer, mut res: ResMut| res.0 += 1); let entity = world.spawn(A).id(); world.entity_mut(entity).insert(B); @@ -509,7 +532,8 @@ mod tests { world.init_resource::(); let observer = world - .observer(|_: Observer| panic!("Observer triggered after being despawned.")); + .observe(|_: Observer| panic!("Observer triggered after being despawned.")) + .id(); world.despawn(observer); world.spawn(A).flush(); } @@ -519,7 +543,7 @@ mod tests { let mut world = World::new(); world.init_resource::(); - world.observer(|_: Observer, mut res: ResMut| res.0 += 1); + world.observe(|_: Observer, mut res: ResMut| res.0 += 1); world.spawn((A, B)).flush(); assert_eq!(1, world.resource::().0); @@ -529,17 +553,16 @@ mod tests { fn observer_no_source() { let mut world = World::new(); world.init_resource::(); - world.init_trigger::(); world .spawn_empty() .observe(|_: Observer| panic!("Trigger routed to non-targeted entity.")); - world.observer(move |obs: Observer, mut res: ResMut| { + world.observe(move |obs: Observer, mut res: ResMut| { assert_eq!(obs.source(), Entity::PLACEHOLDER); res.0 += 1; }); - world.trigger(TriggerA).emit(); + world.trigger(TriggerA); world.flush(); assert_eq!(1, world.resource::().0); } @@ -548,7 +571,6 @@ mod tests { fn observer_entity_routing() { let mut world = World::new(); world.init_resource::(); - world.init_trigger::(); world .spawn_empty() @@ -557,12 +579,12 @@ mod tests { .spawn_empty() .observe(|_: Observer, mut res: ResMut| res.0 += 1) .id(); - world.observer(move |obs: Observer, mut res: ResMut| { + world.observe(move |obs: Observer, mut res: ResMut| { assert_eq!(obs.source(), entity); res.0 += 1; }); - world.trigger(TriggerA).entity(entity).emit(); + world.trigger_targets(TriggerA, entity); world.flush(); assert_eq!(2, world.resource::().0); } @@ -572,11 +594,11 @@ mod tests { let mut world = World::new(); world.init_resource::(); - let component_id = world.init_component_with_descriptor(ComponentDescriptor::new::()); - world - .observer_builder() - .component_ids(&[component_id]) - .run(|_: Observer, mut res: ResMut| res.0 += 1); + let component_id = world.init_component::(); + world.spawn( + ObserverSystemComponent::new(|_: Observer, mut res: ResMut| res.0 += 1) + .with_component(component_id), + ); let mut entity = world.spawn_empty(); OwningPtr::make(A, |ptr| { @@ -585,7 +607,7 @@ mod tests { }); let entity = entity.flush(); - world.trigger(TriggerA).entity(entity).emit(); + world.trigger_targets(TriggerA, entity); world.flush(); assert_eq!(1, world.resource::().0); } @@ -594,14 +616,20 @@ mod tests { fn observer_dynamic_trigger() { let mut world = World::new(); world.init_resource::(); + let trigger_a = world.init_component::(); - let trigger = world.init_component_with_descriptor(ComponentDescriptor::new::()); - // SAFETY: we registered `trigger` above - unsafe { ObserverBuilder::new_with_id(trigger, world.commands()) } - .run(|_: Observer, mut res: ResMut| res.0 += 1); + world.spawn(ObserverComponent { + descriptor: ObserverDescriptor::default().with_triggers(vec![trigger_a]), + runner: |mut world, _trigger, _ptr| { + world.resource_mut::().0 += 1; + }, + ..Default::default() + }); - // SAFETY: we registered `trigger` above - unsafe { TriggerBuilder::new_with_id(trigger, TriggerA, world.commands()) }.emit(); + world.commands().add( + // SAFETY: we registered `trigger` above and it matches the type of TriggerA + unsafe { EmitDynamicTrigger::new_with_id(trigger_a, TriggerA, ()) }, + ); world.flush(); assert_eq!(1, world.resource::().0); } diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index e47f294975221..1f6e9cd0afbf3 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -1,34 +1,66 @@ -use crate as bevy_ecs; use crate::{ - component::{ComponentHooks, StorageType}, - system::ObserverSystem, + component::{ComponentHooks, ComponentId, StorageType}, + observer::{ObserverDescriptor, ObserverTrigger}, + prelude::*, + query::DebugCheckedUnwrap, + system::{IntoObserverSystem, ObserverSystem}, + world::DeferredWorld, }; +use bevy_ptr::PtrMut; -use super::*; - -/// Type for function that is run when an observer is triggered. -/// Typically refers to the default runner that runs the system stored in the associated [`ObserverSystemComponent`], -/// but can be overridden for custom behaviour. -pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut); - -/// Equivalent to [`BoxedSystem`](crate::system::BoxedSystem) for [`ObserverSystem`]. -pub type BoxedObserverSystem = Box>; - -pub(crate) struct ObserverComponent { +/// Contains [`Observer`]` information. This defines how a given observer behaves. It is the +/// "source of truth" for a given observer entity's behavior. +pub struct ObserverComponent { pub(crate) descriptor: ObserverDescriptor, pub(crate) runner: ObserverRunner, pub(crate) last_trigger_id: u32, } -#[derive(Component)] -#[component(storage = "SparseSet")] -// This used to be in `ObserverComponent` but MIRI recently got a new lint that complained about the type erasure -pub(crate) struct ObserverSystemComponent(BoxedObserverSystem); +impl Default for ObserverComponent { + fn default() -> Self { + Self { + runner: |_, _, _| {}, + last_trigger_id: 0, + descriptor: Default::default(), + } + } +} + +impl ObserverComponent { + /// Adds the given `trigger` + pub fn with_trigger(mut self, trigger: ComponentId) -> Self { + self.descriptor.triggers.push(trigger); + self + } + + /// Adds the given `triggers` + pub fn with_triggers(mut self, triggers: impl IntoIterator) -> Self { + self.descriptor.triggers.extend(triggers); + self + } + + /// Adds the given [`Entity`] `sources` + pub fn with_sources(mut self, sources: impl IntoIterator) -> Self { + self.descriptor.sources.extend(sources); + self + } + + /// Adds the given [`ComponentId`] `components` + pub fn with_components(mut self, components: impl IntoIterator) -> Self { + self.descriptor.components.extend(components); + self + } +} impl Component for ObserverComponent { const STORAGE_TYPE: StorageType = StorageType::SparseSet; fn register_component_hooks(hooks: &mut ComponentHooks) { + hooks.on_add(|mut world, entity, _| { + world.commands().add(move |world: &mut World| { + world.register_observer(entity); + }); + }); hooks.on_remove(|mut world, entity, _| { let descriptor = std::mem::take( &mut world @@ -45,76 +77,137 @@ impl Component for ObserverComponent { } } -impl ObserverComponent { - pub(crate) fn from( - world: &mut World, - descriptor: ObserverDescriptor, - system: impl IntoObserverSystem, - ) -> (Self, ObserverSystemComponent) { - let mut system = IntoObserverSystem::into_system(system); - assert!( - !system.is_exclusive(), - "Cannot run exclusive systems in Observers" - ); - system.initialize(world); - let system: BoxedObserverSystem = Box::new(system); - ( - Self { - descriptor, - runner: |mut world, trigger, ptr| { - let world = world.as_unsafe_world_cell(); - let observer_cell = - // SAFETY: Observer was triggered so must still exist in world - unsafe { world.get_entity(trigger.observer).debug_checked_unwrap() }; - // SAFETY: Observer was triggered so must have an `ObserverComponent` - let mut state = unsafe { - observer_cell - .get_mut::() - .debug_checked_unwrap() - }; +/// Type for function that is run when an observer is triggered. +/// Typically refers to the default runner that runs the system stored in the associated [`ObserverSystemComponent`], +/// but can be overridden for custom behaviour. +pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut); - // TODO: Move this check into the observer cache to avoid dynamic dispatch - // SAFETY: We only access world metadata - let last_trigger = unsafe { world.world_metadata() }.last_trigger_id(); - if state.last_trigger_id == last_trigger { +/// An [`Observer`] system. Add this [`Component`] to an [`Entity`] to give it observer behaviors for the given system. +pub struct ObserverSystemComponent { + system: BoxedObserverSystem, + descriptor: ObserverDescriptor, +} + +impl ObserverSystemComponent { + /// Creates a new [`ObserverSystemComponent`], which defaults to a "global" observer. + pub fn new(system: impl IntoObserverSystem) -> Self { + Self { + system: Box::new(IntoObserverSystem::into_system(system)), + descriptor: Default::default(), + } + } + + /// Observe the given `entity`. + pub fn with_source(mut self, entity: Entity) -> Self { + self.descriptor.sources.push(entity); + self + } + + /// Observe the given `component`. + pub fn with_component(mut self, component: ComponentId) -> Self { + self.descriptor.components.push(component); + self + } + + /// Observe the given [`trigger`]. + pub fn with_trigger(mut self, component: ComponentId) -> Self { + self.descriptor.triggers.push(component); + self + } +} + +impl Component for ObserverSystemComponent { + const STORAGE_TYPE: StorageType = StorageType::SparseSet; + fn register_component_hooks(hooks: &mut ComponentHooks) { + hooks.on_add(|mut world, entity, _| { + world.commands().add(move |world: &mut World| { + let trigger_id = world.init_component::(); + let mut components = Vec::new(); + B::component_ids(&mut world.components, &mut world.storages, &mut |id| { + components.push(id); + }); + let mut descriptor = ObserverDescriptor { + triggers: vec![trigger_id], + components, + ..Default::default() + }; + + // Initialize System + let system: *mut dyn ObserverSystem = + if let Some(mut observe) = world.get_mut::(entity) { + descriptor.merge(&observe.descriptor); + &mut *observe.system + } else { return; - } - state.last_trigger_id = last_trigger; - - let observer: Observer = - // SAFETY: Caller ensures `ptr` is castable to `&mut T` - Observer::new(unsafe { ptr.deref_mut() }, trigger); - // SAFETY: Observer was triggered so must have an `ObserverSystemComponent` - let system = unsafe { - &mut observer_cell - .get_mut::>() - .debug_checked_unwrap() - .0 }; + // SAFETY: World reference is exclusive and initialize does not touch system, so references do not alias + unsafe { + (*system).initialize(world); + } - system.update_archetype_component_access(world); - - // SAFETY: - // - `update_archetype_component_access` was just called - // - there are no outstanding references to world except a private component - // - system is an `ObserverSystem` so won't mutate world beyond the access of a `DeferredWorld` - // - system is the same type erased system from above - unsafe { - system.run_unsafe(std::mem::transmute(observer), world); - system.queue_deferred(world.into_deferred()); + { + let mut entity = world.entity_mut(entity); + if let crate::world::Entry::Vacant(entry) = entity.entry::() + { + entry.insert(ObserverComponent { + descriptor, + runner: observer_system_runner::, + ..Default::default() + }); } - }, - last_trigger_id: 0, - }, - ObserverSystemComponent(system), - ) + } + }); + }); } +} - pub(crate) fn from_runner(descriptor: ObserverDescriptor, runner: ObserverRunner) -> Self { - Self { - descriptor, - runner, - last_trigger_id: 0, - } +/// Equivalent to [`BoxedSystem`](crate::system::BoxedSystem) for [`ObserverSystem`]. +pub type BoxedObserverSystem = Box>; + +fn observer_system_runner( + mut world: DeferredWorld, + trigger: ObserverTrigger, + ptr: PtrMut, +) { + let world = world.as_unsafe_world_cell(); + let observer_cell = + // SAFETY: Observer was triggered so must still exist in world + unsafe { world.get_entity(trigger.observer).debug_checked_unwrap() }; + // SAFETY: Observer was triggered so must have an `ObserverComponent` + let mut state = unsafe { + observer_cell + .get_mut::() + .debug_checked_unwrap() + }; + + // TODO: Move this check into the observer cache to avoid dynamic dispatch + // SAFETY: We only access world metadata + let last_trigger = unsafe { world.world_metadata() }.last_trigger_id(); + if state.last_trigger_id == last_trigger { + return; + } + state.last_trigger_id = last_trigger; + + let observer: Observer = + // SAFETY: Caller ensures `ptr` is castable to `&mut T` + Observer::new(unsafe { ptr.deref_mut() }, trigger); + // SAFETY: Observer was triggered so must have an `ObserverSystemComponent` + let system = unsafe { + &mut observer_cell + .get_mut::>() + .debug_checked_unwrap() + .system + }; + + system.update_archetype_component_access(world); + + // SAFETY: + // - `update_archetype_component_access` was just called + // - there are no outstanding references to world except a private component + // - system is an `ObserverSystem` so won't mutate world beyond the access of a `DeferredWorld` + // - system is the same type erased system from above + unsafe { + system.run_unsafe(std::mem::transmute(observer), world); + system.queue_deferred(world.into_deferred()); } } diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index e9b7ee460cd5a..e1abc9bda2512 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -1,14 +1,15 @@ mod parallel_scope; -use super::{Deferred, IntoObserverSystem, IntoSystem, ObserverSystem, RegisterSystem, Resource}; +use super::{Deferred, IntoObserverSystem, IntoSystem, RegisterSystem, Resource}; use crate::{ self as bevy_ecs, bundle::Bundle, component::{ComponentId, Components}, entity::{Entities, Entity}, - prelude::Component, + observer::{EmitTrigger, ObserverSystemComponent, TriggerTargets}, + prelude::Trigger, system::{RunSystemWithInput, SystemId}, - world::{Command, CommandQueue, DeferredWorld, EntityWorldMut, FromWorld, World}, + world::{Command, CommandQueue, EntityWorldMut, FromWorld, World}, }; use bevy_ecs_macros::SystemParam; use bevy_utils::tracing::{error, info}; @@ -633,6 +634,27 @@ impl<'w, 's> Commands<'w, 's> { pub fn components(&self) -> &Components { self.components } + + /// Sends a "global" [`Trigger`] without any targets. + pub fn trigger(&mut self, trigger: impl Trigger) { + self.add(EmitTrigger { + trigger, + targets: (), + }) + } + + /// Sends a [`Trigger`] with the given `targets`. + pub fn trigger_targets(&mut self, trigger: impl Trigger, targets: impl TriggerTargets) { + self.add(EmitTrigger { trigger, targets }) + } + + /// Spawn an [`Observer`] and returns it's [`Entity`]. + pub fn observe( + &mut self, + observer: impl IntoObserverSystem, + ) -> EntityCommands { + self.spawn(ObserverSystemComponent::new(observer)) + } } /// A [`Command`] which gets executed for a given [`Entity`]. @@ -1022,16 +1044,12 @@ impl EntityCommands<'_> { self.commands.reborrow() } - /// Creates an [`Observer`](crate::observer::Observer) listening for any event of type `E` that targets this entity. - pub fn observe( + /// Creates an [`Observer`](crate::observer::Observer) listening for a trigger of type `T` that targets this entity. + pub fn observe( &mut self, - system: impl IntoObserverSystem, + system: impl IntoObserverSystem, ) -> &mut Self { - self.commands.add(Observe:: { - entity: self.entity, - system: IntoObserverSystem::into_system(system), - marker: PhantomData, - }); + self.add(observe(system)); self } } @@ -1185,79 +1203,12 @@ fn log_components(entity: Entity, world: &mut World) { info!("Entity {:?}: {:?}", entity, debug_infos); } -/// A [`Command`] that spawns an observer attached to a specific entity. -#[derive(Debug)] -pub struct Observe> { - /// The entity that will be observed. - pub entity: Entity, - /// The callback to run when the event is observed. - pub system: C, - /// Marker for type parameters - pub marker: PhantomData<(E, B)>, -} - -impl> Command for Observe { - fn apply(self, world: &mut World) { - world.entity_mut(self.entity).observe(self.system); - } -} - -/// A [`Command`] that emits a trigger to be received by observers. -#[derive(Debug)] -pub(crate) struct EmitTrigger { - /// [`ComponentId`] for this trigger. - trigger: ComponentId, - /// Entities to trigger observers for. - entities: Vec, - /// Components to trigger observers for. - components: Vec, - /// Data for the trigger. - data: T, -} - -impl EmitTrigger { - // SAFETY: Caller must ensure the type represented by `trigger` is accessible as `T`. - pub(crate) unsafe fn new( - trigger: ComponentId, - entities: Vec, - components: Vec, - data: T, - ) -> Self { - Self { - trigger, - entities, - components, - data, - } - } -} - -impl Command for EmitTrigger { - fn apply(mut self, world: &mut World) { - let mut world = DeferredWorld::from(world); - - if self.entities.is_empty() { - // SAFETY: T is accessible as the type represented by self.trigger, ensured in `Self::new` - unsafe { - world.trigger_observers_with_data( - self.trigger, - Entity::PLACEHOLDER, - self.components.iter().cloned(), - &mut self.data, - ); - }; - } else { - for &target in &self.entities { - // SAFETY: T is accessible as the type represented by self.trigger, ensured in `Self::new` - unsafe { - world.trigger_observers_with_data( - self.trigger, - target, - self.components.iter().cloned(), - &mut self.data, - ); - }; - } +fn observe( + observer: impl IntoObserverSystem, +) -> impl EntityCommand { + move |entity, world: &mut World| { + if let Some(mut entity) = world.get_entity_mut(entity) { + entity.observe(observer); } } } diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 97c6e12fa87ff..9760d95d2f58e 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -6,7 +6,7 @@ use crate::{ component::ComponentId, entity::Entity, event::{Event, EventId, Events, SendBatchIds}, - observer::{Observers, Trigger, TriggerBuilder}, + observer::{Observers, Trigger, TriggerTargets}, prelude::{Component, QueryState}, query::{QueryData, QueryFilter}, system::{Commands, Query, Resource}, @@ -367,9 +367,14 @@ impl<'w> DeferredWorld<'w> { Observers::invoke(self.reborrow(), event, source, components, data); } - /// Constructs an [`TriggerBuilder`] for an ECS [`Trigger`]. - pub fn trigger(&mut self, data: T) -> TriggerBuilder { - TriggerBuilder::new(data, self.commands()) + /// Sends a "global" [`Trigger`] without any targets. + pub fn trigger(&mut self, trigger: impl Trigger) { + self.commands().trigger(trigger); + } + + /// Sends a [`Trigger`] with the given `targets`. + pub fn trigger_targets(&mut self, trigger: impl Trigger, targets: impl TriggerTargets) { + self.commands().trigger_targets(trigger, targets); } /// Gets an [`UnsafeWorldCell`] containing the underlying world. diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index cdc51353b6488..87a1f351b775c 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -4,7 +4,7 @@ use crate::{ change_detection::MutUntyped, component::{Component, ComponentId, ComponentTicks, Components, StorageType}, entity::{Entities, Entity, EntityLocation}, - observer::{AttachObserver, ObserverBuilder, Observers}, + observer::{ObserverSystemComponent, Observers, Trigger}, query::{Access, DebugCheckedUnwrap}, removal_detection::RemovedComponentEvents, storage::Storages, @@ -1399,18 +1399,11 @@ impl<'w> EntityWorldMut<'w> { /// Creates an [`Observer`](crate::observer::Observer) listening for `E` events targeting this entity. /// In order to trigger the callback the entity must also match the query when the event is fired. - pub fn observe( + pub fn observe( &mut self, - callback: impl IntoObserverSystem, + observer: impl IntoObserverSystem, ) -> &mut Self { - let observer = ObserverBuilder::new(self.world.commands()) - .source(self.entity) - .run(IntoObserverSystem::into_system(callback)); - self.world.commands().add(AttachObserver { - target: self.entity, - observer, - }); - self + self.insert(ObserverSystemComponent::new(observer).with_source(self.entity)) } } diff --git a/examples/ecs/observers.rs b/examples/ecs/observers.rs index 8fd91c5fe4fa9..5f1eb3d40ff80 100644 --- a/examples/ecs/observers.rs +++ b/examples/ecs/observers.rs @@ -8,8 +8,36 @@ use bevy::{ fn main() { App::new() .add_plugins(DefaultPlugins) + .init_resource::() .add_systems(Startup, setup) .add_systems(Update, (draw_shapes, handle_click)) + // Observers run when a certain trigger is fired, each trigger is represented by a type. + // This observer runs whenever `TriggerMines` is fired. + .observe( + |observer: Observer, + mines: Query<&Mine>, + index: Res, + mut commands: Commands| { + // You can access the trigger data via the `Observer` + let trigger = observer.data(); + // Access resources + for e in index.get_nearby(trigger.pos) { + // Run queries + let mine = mines.get(e).unwrap(); + if mine.pos.distance(trigger.pos) < mine.size + trigger.radius { + // And queue commands, including firing additional triggers + // Here we fire the `Explode` trigger at entity `e` + commands.trigger_targets(Explode, e); + } + } + }, + ) + // This observer runs whenever the `Mine` component is added to an entity, and places it in a simple spatial index. + .observe(add_mine) + // Since observers run systems you can also define them as standalone functions rather than closures. + // This observer runs whenever the `Mine` component is removed from an entity (including despawning it) + // and removes it from the spatial index. + .observe(remove_mine) .run(); } @@ -44,51 +72,7 @@ fn setup(world: &mut World) { }, )); - // Pre-register all our components, resource and trigger types. - world.init_resource::(); - world.init_component::(); - world.init_trigger::(); - world.init_trigger::(); - - // Observers run when a certain trigger is fired, each trigger is represented by a type. - // This observer runs whenever `TriggerMines` is fired, observers run systems which can be defined as closures. - world.observer( - |observer: Observer, - mines: Query<&Mine>, - index: Res, - mut commands: Commands| { - // You can access the trigger data via the `Observer` - let trigger = observer.data(); - // Access resources - for e in index.get_nearby(trigger.pos) { - // Run queries - let mine = mines.get(e).unwrap(); - if mine.pos.distance(trigger.pos) < mine.size + trigger.radius { - // And queue commands, including firing additional triggers - // Here we fire the `Explode` trigger at entity `e` - commands.trigger(Explode).entity(e).emit(); - } - } - }, - ); - // Observers can also listen for triggers for a specific component. - // This observer runs whenever the `Mine` component is added to an entity, and places it in a simple spatial index. - world.observer( - |observer: Observer, query: Query<&Mine>, mut index: ResMut| { - let mine = query.get(observer.source()).unwrap(); - let tile = ( - (mine.pos.x / CELL_SIZE).floor() as i32, - (mine.pos.y / CELL_SIZE).floor() as i32, - ); - index.map.entry(tile).or_default().insert(observer.source()); - }, - ); - - // Since observers run systems you can also define them as standalone functions rather than closures. - // This observer runs whenever the `Mine` component is removed from an entity (including despawning it) - // and removes it from the spatial index. - world.observer(remove_mine); // Now we spawn a set of random mines. for _ in 0..1000 { @@ -113,12 +97,10 @@ fn setup(world: &mut World) { entity.despawn(); let mine = query.get(source).unwrap(); // Fire another trigger to cascade into other mines. - commands - .trigger(TriggerMines { - pos: mine.pos, - radius: mine.size, - }) - .emit(); + commands.trigger(TriggerMines { + pos: mine.pos, + radius: mine.size, + }); }, ); } @@ -151,6 +133,15 @@ impl SpatialIndex { } } +fn add_mine(observer: Observer, query: Query<&Mine>, mut index: ResMut) { + let mine = query.get(observer.source()).unwrap(); + let tile = ( + (mine.pos.x / CELL_SIZE).floor() as i32, + (mine.pos.y / CELL_SIZE).floor() as i32, + ); + index.map.entry(tile).or_default().insert(observer.source()); +} + // Remove despawned mines from our index fn remove_mine( observer: Observer, @@ -193,7 +184,7 @@ fn handle_click( .map(|ray| ray.origin.truncate()) { if mouse_button_input.just_pressed(MouseButton::Left) { - commands.trigger(TriggerMines { pos, radius: 1.0 }).emit(); + commands.trigger(TriggerMines { pos, radius: 1.0 }); } } } From b5d3304ae972f95acf4ed2b411ef900a5d8b35bf Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 7 Jun 2024 18:38:27 -0700 Subject: [PATCH 088/109] Remove println --- crates/bevy_ecs/src/observer/emit_trigger.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/bevy_ecs/src/observer/emit_trigger.rs b/crates/bevy_ecs/src/observer/emit_trigger.rs index 63aefb5684151..802a063e8e77a 100644 --- a/crates/bevy_ecs/src/observer/emit_trigger.rs +++ b/crates/bevy_ecs/src/observer/emit_trigger.rs @@ -56,7 +56,6 @@ fn apply_trigger( ) { let mut world = DeferredWorld::from(world); if targets.entities().len() == 0 { - eprintln!("triggering"); // SAFETY: T is accessible as the type represented by self.trigger, ensured in `Self::new` unsafe { world.trigger_observers_with_data( From 39c90002b315e475a015af54fbf8cd751de96a49 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 7 Jun 2024 19:20:12 -0700 Subject: [PATCH 089/109] Event + Trigger unification renames. --- crates/bevy_app/src/app.rs | 4 +- crates/bevy_ecs/macros/src/component.rs | 27 +-- crates/bevy_ecs/macros/src/lib.rs | 5 - crates/bevy_ecs/src/event.rs | 9 +- crates/bevy_ecs/src/lib.rs | 2 +- crates/bevy_ecs/src/observer/emit_trigger.rs | 51 +++--- crates/bevy_ecs/src/observer/mod.rs | 168 +++++++++--------- crates/bevy_ecs/src/observer/runner.rs | 40 ++--- crates/bevy_ecs/src/system/commands/mod.rs | 27 ++- crates/bevy_ecs/src/system/observer_system.rs | 24 +-- .../bevy_ecs/src/world/component_constants.rs | 8 +- crates/bevy_ecs/src/world/deferred_world.rs | 6 +- crates/bevy_ecs/src/world/entity_ref.rs | 7 +- examples/ecs/observers.rs | 49 +++-- 14 files changed, 198 insertions(+), 229 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 2464de2eeb126..0721e88910b75 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -885,9 +885,9 @@ impl App { } /// Spawn the given [`Observer`]. - pub fn observe( + pub fn observe( &mut self, - observer: impl IntoObserverSystem, + observer: impl IntoObserverSystem, ) -> &mut Self { self.world_mut().observe(observer); self diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index 120d73f67effc..dbd28e4b28ac6 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -18,6 +18,10 @@ pub fn derive_event(input: TokenStream) -> TokenStream { TokenStream::from(quote! { impl #impl_generics #bevy_ecs_path::event::Event for #struct_name #type_generics #where_clause { } + + impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause { + const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #bevy_ecs_path::component::StorageType::SparseSet; + } }) } @@ -65,29 +69,6 @@ pub fn derive_component(input: TokenStream) -> TokenStream { }) } -pub fn derive_trigger(input: TokenStream) -> TokenStream { - let mut ast = parse_macro_input!(input as DeriveInput); - let bevy_ecs_path: Path = crate::bevy_ecs_path(); - - let storage = storage_path(&bevy_ecs_path, StorageTy::SparseSet); - - 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 { - const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage; - } - - impl #bevy_ecs_path::observer::Trigger for #struct_name #type_generics {} - }) -} - pub const COMPONENT: &str = "component"; pub const STORAGE: &str = "storage"; diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 6a234dff32a9d..60e8027756731 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -532,11 +532,6 @@ pub fn derive_component(input: TokenStream) -> TokenStream { component::derive_component(input) } -#[proc_macro_derive(Trigger)] -pub fn derive_trigger(input: TokenStream) -> TokenStream { - component::derive_trigger(input) -} - #[proc_macro_derive(States)] pub fn derive_states(input: TokenStream) -> TokenStream { states::derive_states(input) diff --git a/crates/bevy_ecs/src/event.rs b/crates/bevy_ecs/src/event.rs index 5f5b7092f047e..d77560d134489 100644 --- a/crates/bevy_ecs/src/event.rs +++ b/crates/bevy_ecs/src/event.rs @@ -1,11 +1,10 @@ //! Event handling types. use crate as bevy_ecs; -use crate::batching::BatchingStrategy; -use crate::change_detection::MutUntyped; use crate::{ - change_detection::{DetectChangesMut, Mut}, - component::{ComponentId, Tick}, + batching::BatchingStrategy, + change_detection::{DetectChangesMut, Mut, MutUntyped}, + component::{Component, ComponentId, Tick}, system::{Local, Res, ResMut, Resource, SystemParam}, world::World, }; @@ -28,7 +27,7 @@ use std::{ /// You can conveniently access events using the [`EventReader`] and [`EventWriter`] system parameter. /// /// Events must be thread-safe. -pub trait Event: Send + Sync + 'static {} +pub trait Event: Component {} /// An `EventId` uniquely identifies an event stored in a specific [`World`]. /// diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 307c7a94a16e5..73218f2718aad 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -47,7 +47,7 @@ pub mod prelude { component::Component, entity::{Entity, EntityMapper}, event::{Event, EventReader, EventWriter, Events}, - observer::{Observer, Trigger}, + observer::Trigger, query::{Added, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without}, removal_detection::RemovedComponents, schedule::{ diff --git a/crates/bevy_ecs/src/observer/emit_trigger.rs b/crates/bevy_ecs/src/observer/emit_trigger.rs index 802a063e8e77a..20aa0e5027e12 100644 --- a/crates/bevy_ecs/src/observer/emit_trigger.rs +++ b/crates/bevy_ecs/src/observer/emit_trigger.rs @@ -1,57 +1,57 @@ use crate::{ component::ComponentId, entity::Entity, - observer::Trigger, + event::Event, world::{Command, DeferredWorld, World}, }; /// A [`Command`] that emits a given trigger for a given set of targets. -pub struct EmitTrigger { - /// The trigger to emit. - pub trigger: T, +pub struct TriggerEvent { + /// The event to trigger. + pub event: E, - /// The targets to emit the trigger for. + /// The targets to trigger the event for. pub targets: Targets, } -impl Command for EmitTrigger { +impl Command for TriggerEvent { fn apply(mut self, world: &mut World) { - let trigger_id = world.init_component::(); - apply_trigger(world, trigger_id, &mut self.trigger, self.targets); + let event_type = world.init_component::(); + trigger_event(world, event_type, &mut self.event, self.targets); } } /// Emit a trigger for a dynamic component id. This is unsafe and must be verified manually. pub struct EmitDynamicTrigger { - trigger: ComponentId, - data: T, + event_type: ComponentId, + event_data: T, targets: Targets, } -impl EmitDynamicTrigger { +impl EmitDynamicTrigger { /// Sets the trigger id of the resulting trigger, used for dynamic triggers /// # Safety - /// Caller must ensure that the component associated with `id` is accessible as E - pub unsafe fn new_with_id(trigger: ComponentId, data: T, targets: Targets) -> Self { + /// Caller must ensure that the component associated with `event_type` is accessible as E + pub unsafe fn new_with_id(event_type: ComponentId, event_data: E, targets: Targets) -> Self { Self { - trigger, - data, + event_type, + event_data, targets, } } } -impl Command for EmitDynamicTrigger { +impl Command for EmitDynamicTrigger { fn apply(mut self, world: &mut World) { - apply_trigger(world, self.trigger, &mut self.data, self.targets); + trigger_event(world, self.event_type, &mut self.event_data, self.targets); } } #[inline] -fn apply_trigger( +fn trigger_event( world: &mut World, - trigger_id: ComponentId, - data: &mut T, + event_type: ComponentId, + event_data: &mut E, targets: Targets, ) { let mut world = DeferredWorld::from(world); @@ -59,17 +59,22 @@ fn apply_trigger( // SAFETY: T is accessible as the type represented by self.trigger, ensured in `Self::new` unsafe { world.trigger_observers_with_data( - trigger_id, + event_type, Entity::PLACEHOLDER, targets.components(), - data, + event_data, ); }; } else { for target in targets.entities() { // SAFETY: T is accessible as the type represented by self.trigger, ensured in `Self::new` unsafe { - world.trigger_observers_with_data(trigger_id, target, targets.components(), data); + world.trigger_observers_with_data( + event_type, + target, + targets.components(), + event_data, + ); }; } } diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 49c237741d1d5..a1aa7e9e16597 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -4,7 +4,6 @@ mod emit_trigger; mod entity_observer; mod runner; -pub use bevy_ecs_macros::Trigger; pub use emit_trigger::*; pub use runner::*; @@ -14,44 +13,41 @@ use bevy_ptr::Ptr; use bevy_utils::{EntityHashMap, HashMap}; use std::marker::PhantomData; -/// Trait implemented for types that are used as ECS triggers observed by [`Observer`] -pub trait Trigger: Component {} - /// Type used in callbacks registered for observers. -pub struct Observer<'w, T, B: Bundle = ()> { - data: &'w mut T, +pub struct Trigger<'w, E, B: Bundle = ()> { + event: &'w mut E, trigger: ObserverTrigger, _marker: PhantomData, } -impl<'w, T, B: Bundle> Observer<'w, T, B> { - /// Creates a new observer for the given trigger. - pub fn new(data: &'w mut T, trigger: ObserverTrigger) -> Self { +impl<'w, E, B: Bundle> Trigger<'w, E, B> { + /// Creates a new trigger for the given event and observer information. + pub fn new(event: &'w mut E, trigger: ObserverTrigger) -> Self { Self { - data, + event, trigger, _marker: PhantomData, } } - /// Returns the trigger id for the triggering trigger - pub fn trigger(&self) -> ComponentId { - self.trigger.trigger + /// Returns the event type of this trigger. + pub fn event_type(&self) -> ComponentId { + self.trigger.event_type } - /// Returns a reference to the data associated with the trigger that triggered the observer. - pub fn data(&self) -> &T { - self.data + /// Returns a reference to the triggered event. + pub fn event(&self) -> &E { + self.event } - /// Returns a mutable reference to the data associated with the trigger that triggered the observer. - pub fn data_mut(&mut self) -> &mut T { - self.data + /// Returns a mutable reference to the triggered event. + pub fn event_mut(&mut self) -> &mut E { + self.event } - /// Returns a pointer to the data associated with the trigger that triggered the observer. - pub fn data_ptr(&self) -> Ptr { - Ptr::from(&self.data) + /// Returns a pointer to the triggered event. + pub fn event_ptr(&self) -> Ptr { + Ptr::from(&self.event) } /// Returns the entity that triggered the observer, could be [`Entity::PLACEHOLDER`]. @@ -63,8 +59,8 @@ impl<'w, T, B: Bundle> Observer<'w, T, B> { /// A description of what an [`Observer`] observes. #[derive(Default, Clone)] pub struct ObserverDescriptor { - /// The triggers the observer is triggered for. - triggers: Vec, + /// The events the observer is watching. + events: Vec, /// The components the observer is watching. components: Vec, @@ -76,7 +72,7 @@ pub struct ObserverDescriptor { impl ObserverDescriptor { /// Add the given `triggers` to the descriptor. pub fn with_triggers(mut self, triggers: Vec) -> Self { - self.triggers = triggers; + self.events = triggers; self } @@ -93,7 +89,7 @@ impl ObserverDescriptor { } pub(crate) fn merge(&mut self, descriptor: &ObserverDescriptor) { - self.triggers.extend(descriptor.triggers.iter().copied()); + self.events.extend(descriptor.events.iter().copied()); self.components .extend(descriptor.components.iter().copied()); self.sources.extend(descriptor.sources.iter().copied()); @@ -106,7 +102,7 @@ pub struct ObserverTrigger { pub observer: Entity, /// The [`ComponentId`] for the given trigger. - pub trigger: ComponentId, + pub event_type: ComponentId, /// The source where the trigger occurred. pub source: Entity, @@ -147,27 +143,27 @@ pub struct Observers { } impl Observers { - pub(crate) fn get_observers(&mut self, trigger: ComponentId) -> &mut CachedObservers { - match trigger { + pub(crate) fn get_observers(&mut self, event_type: ComponentId) -> &mut CachedObservers { + match event_type { ON_ADD => &mut self.on_add, ON_INSERT => &mut self.on_insert, ON_REMOVE => &mut self.on_remove, - _ => self.cache.entry(trigger).or_default(), + _ => self.cache.entry(event_type).or_default(), } } - pub(crate) fn try_get_observers(&self, trigger: ComponentId) -> Option<&CachedObservers> { - match trigger { + pub(crate) fn try_get_observers(&self, event_type: ComponentId) -> Option<&CachedObservers> { + match event_type { ON_ADD => Some(&self.on_add), ON_INSERT => Some(&self.on_insert), ON_REMOVE => Some(&self.on_remove), - _ => self.cache.get(&trigger), + _ => self.cache.get(&event_type), } } pub(crate) fn invoke( mut world: DeferredWorld, - trigger: ComponentId, + event_type: ComponentId, source: Entity, components: impl Iterator, data: &mut T, @@ -178,7 +174,7 @@ impl Observers { // SAFETY: There are no outstanding world references world.increment_trigger_id(); let observers = world.observers(); - let Some(observers) = observers.try_get_observers(trigger) else { + let Some(observers) = observers.try_get_observers(event_type) else { return; }; // SAFETY: The only outstanding reference to world is `observers` @@ -190,7 +186,7 @@ impl Observers { world.reborrow(), ObserverTrigger { observer, - trigger, + event_type, source, }, data.into(), @@ -224,8 +220,8 @@ impl Observers { }); } - pub(crate) fn is_archetype_cached(trigger: ComponentId) -> Option { - match trigger { + pub(crate) fn is_archetype_cached(event_type: ComponentId) -> Option { + match event_type { ON_ADD => Some(ArchetypeFlags::ON_ADD_OBSERVER), ON_INSERT => Some(ArchetypeFlags::ON_INSERT_OBSERVER), ON_REMOVE => Some(ArchetypeFlags::ON_REMOVE_OBSERVER), @@ -260,25 +256,21 @@ impl Observers { impl World { /// Spawn an [`Observer`] and returns it's [`Entity`]. - pub fn observe( + pub fn observe( &mut self, - system: impl IntoObserverSystem, + system: impl IntoObserverSystem, ) -> EntityWorldMut { self.spawn(ObserverSystemComponent::new(system)) } - /// Emits the given `trigger``. - pub fn trigger(&mut self, trigger: impl Trigger) { - EmitTrigger { - trigger, - targets: (), - } - .apply(self); + /// Triggers the given `event`, which will run any observers watching for it. + pub fn trigger(&mut self, event: impl Event) { + TriggerEvent { event, targets: () }.apply(self); } - /// Emits the given `trigger` for the given `targets`. - pub fn trigger_targets(&mut self, trigger: impl Trigger, targets: impl TriggerTargets) { - EmitTrigger { trigger, targets }.apply(self); + /// Triggers the given `event` for the given `targets`, which will run any observers watching for it. + pub fn trigger_targets(&mut self, event: impl Event, targets: impl TriggerTargets) { + TriggerEvent { event, targets }.apply(self); } /// Register an observer to the cache, called when an observer is created @@ -295,8 +287,8 @@ impl World { }; let descriptor = &observer_component.descriptor; - for &trigger in &descriptor.triggers { - let cache = observers.get_observers(trigger); + for &event_type in &descriptor.events { + let cache = observers.get_observers(event_type); if descriptor.components.is_empty() && descriptor.sources.is_empty() { cache.map.insert(entity, observer_component.runner); @@ -314,7 +306,7 @@ impl World { .component_observers .entry(component) .or_insert_with(|| { - if let Some(flag) = Observers::is_archetype_cached(trigger) { + if let Some(flag) = Observers::is_archetype_cached(event_type) { archetypes.update_flags(component, flag, true); } CachedComponentObservers::default() @@ -339,8 +331,8 @@ impl World { let archetypes = &mut self.archetypes; let observers = &mut self.observers; - for &trigger in &descriptor.triggers { - let cache = observers.get_observers(trigger); + for &event_type in &descriptor.events { + let cache = observers.get_observers(event_type); if descriptor.components.is_empty() && descriptor.sources.is_empty() { cache.map.remove(&entity); } else if descriptor.components.is_empty() { @@ -375,7 +367,7 @@ impl World { if observers.map.is_empty() && observers.entity_map.is_empty() { cache.component_observers.remove(component); - if let Some(flag) = Observers::is_archetype_cached(trigger) { + if let Some(flag) = Observers::is_archetype_cached(event_type) { archetypes.update_flags(*component, flag, false); } } @@ -404,8 +396,8 @@ mod tests { #[derive(Component)] struct C; - #[derive(Trigger)] - struct TriggerA; + #[derive(Event)] + struct EventA; #[derive(Resource, Default)] struct R(usize); @@ -423,9 +415,9 @@ mod tests { let mut world = World::new(); world.init_resource::(); - world.observe(|_: Observer, mut res: ResMut| res.assert_order(0)); - world.observe(|_: Observer, mut res: ResMut| res.assert_order(1)); - world.observe(|_: Observer, mut res: ResMut| res.assert_order(2)); + world.observe(|_: Trigger, mut res: ResMut| res.assert_order(0)); + world.observe(|_: Trigger, mut res: ResMut| res.assert_order(1)); + world.observe(|_: Trigger, mut res: ResMut| res.assert_order(2)); let entity = world.spawn(A).id(); world.despawn(entity); @@ -437,9 +429,9 @@ mod tests { let mut world = World::new(); world.init_resource::(); - world.observe(|_: Observer, mut res: ResMut| res.assert_order(0)); - world.observe(|_: Observer, mut res: ResMut| res.assert_order(1)); - world.observe(|_: Observer, mut res: ResMut| res.assert_order(2)); + world.observe(|_: Trigger, mut res: ResMut| res.assert_order(0)); + world.observe(|_: Trigger, mut res: ResMut| res.assert_order(1)); + world.observe(|_: Trigger, mut res: ResMut| res.assert_order(2)); let mut entity = world.spawn_empty(); entity.insert(A); @@ -453,25 +445,25 @@ mod tests { let mut world = World::new(); world.init_resource::(); world.observe( - |obs: Observer, mut res: ResMut, mut commands: Commands| { + |obs: Trigger, mut res: ResMut, mut commands: Commands| { res.assert_order(0); commands.entity(obs.source()).insert(B); }, ); world.observe( - |obs: Observer, mut res: ResMut, mut commands: Commands| { + |obs: Trigger, mut res: ResMut, mut commands: Commands| { res.assert_order(2); commands.entity(obs.source()).remove::(); }, ); world.observe( - |obs: Observer, mut res: ResMut, mut commands: Commands| { + |obs: Trigger, mut res: ResMut, mut commands: Commands| { res.assert_order(1); commands.entity(obs.source()).remove::(); }, ); - world.observe(|_: Observer, mut res: ResMut| { + world.observe(|_: Trigger, mut res: ResMut| { res.assert_order(3); }); @@ -487,8 +479,8 @@ mod tests { let mut world = World::new(); world.init_resource::(); - world.observe(|_: Observer, mut res: ResMut| res.0 += 1); - world.observe(|_: Observer, mut res: ResMut| res.0 += 1); + world.observe(|_: Trigger, mut res: ResMut| res.0 += 1); + world.observe(|_: Trigger, mut res: ResMut| res.0 += 1); world.spawn(A).flush(); assert_eq!(2, world.resource::().0); @@ -497,13 +489,13 @@ mod tests { } #[test] - fn observer_multiple_triggerss() { + fn observer_multiple_events() { let mut world = World::new(); world.init_resource::(); let on_remove = world.init_component::(); world.spawn( - ObserverSystemComponent::new(|_: Observer, mut res: ResMut| res.0 += 1) - .with_trigger(on_remove), + ObserverSystemComponent::new(|_: Trigger, mut res: ResMut| res.0 += 1) + .with_event(on_remove), ); let entity = world.spawn(A).id(); @@ -518,7 +510,7 @@ mod tests { world.init_component::(); world.init_component::(); - world.observe(|_: Observer, mut res: ResMut| res.0 += 1); + world.observe(|_: Trigger, mut res: ResMut| res.0 += 1); let entity = world.spawn(A).id(); world.entity_mut(entity).insert(B); @@ -532,7 +524,7 @@ mod tests { world.init_resource::(); let observer = world - .observe(|_: Observer| panic!("Observer triggered after being despawned.")) + .observe(|_: Trigger| panic!("Observer triggered after being despawned.")) .id(); world.despawn(observer); world.spawn(A).flush(); @@ -543,7 +535,7 @@ mod tests { let mut world = World::new(); world.init_resource::(); - world.observe(|_: Observer, mut res: ResMut| res.0 += 1); + world.observe(|_: Trigger, mut res: ResMut| res.0 += 1); world.spawn((A, B)).flush(); assert_eq!(1, world.resource::().0); @@ -556,13 +548,13 @@ mod tests { world .spawn_empty() - .observe(|_: Observer| panic!("Trigger routed to non-targeted entity.")); - world.observe(move |obs: Observer, mut res: ResMut| { + .observe(|_: Trigger| panic!("Trigger routed to non-targeted entity.")); + world.observe(move |obs: Trigger, mut res: ResMut| { assert_eq!(obs.source(), Entity::PLACEHOLDER); res.0 += 1; }); - world.trigger(TriggerA); + world.trigger(EventA); world.flush(); assert_eq!(1, world.resource::().0); } @@ -574,17 +566,17 @@ mod tests { world .spawn_empty() - .observe(|_: Observer| panic!("Trigger routed to non-targeted entity.")); + .observe(|_: Trigger| panic!("Trigger routed to non-targeted entity.")); let entity = world .spawn_empty() - .observe(|_: Observer, mut res: ResMut| res.0 += 1) + .observe(|_: Trigger, mut res: ResMut| res.0 += 1) .id(); - world.observe(move |obs: Observer, mut res: ResMut| { + world.observe(move |obs: Trigger, mut res: ResMut| { assert_eq!(obs.source(), entity); res.0 += 1; }); - world.trigger_targets(TriggerA, entity); + world.trigger_targets(EventA, entity); world.flush(); assert_eq!(2, world.resource::().0); } @@ -596,7 +588,7 @@ mod tests { let component_id = world.init_component::(); world.spawn( - ObserverSystemComponent::new(|_: Observer, mut res: ResMut| res.0 += 1) + ObserverSystemComponent::new(|_: Trigger, mut res: ResMut| res.0 += 1) .with_component(component_id), ); @@ -607,7 +599,7 @@ mod tests { }); let entity = entity.flush(); - world.trigger_targets(TriggerA, entity); + world.trigger_targets(EventA, entity); world.flush(); assert_eq!(1, world.resource::().0); } @@ -616,10 +608,10 @@ mod tests { fn observer_dynamic_trigger() { let mut world = World::new(); world.init_resource::(); - let trigger_a = world.init_component::(); + let event_a = world.init_component::(); world.spawn(ObserverComponent { - descriptor: ObserverDescriptor::default().with_triggers(vec![trigger_a]), + descriptor: ObserverDescriptor::default().with_triggers(vec![event_a]), runner: |mut world, _trigger, _ptr| { world.resource_mut::().0 += 1; }, @@ -628,7 +620,7 @@ mod tests { world.commands().add( // SAFETY: we registered `trigger` above and it matches the type of TriggerA - unsafe { EmitDynamicTrigger::new_with_id(trigger_a, TriggerA, ()) }, + unsafe { EmitDynamicTrigger::new_with_id(event_a, EventA, ()) }, ); world.flush(); assert_eq!(1, world.resource::().0); diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index 1f6e9cd0afbf3..aa737096fe5c0 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -27,15 +27,15 @@ impl Default for ObserverComponent { } impl ObserverComponent { - /// Adds the given `trigger` - pub fn with_trigger(mut self, trigger: ComponentId) -> Self { - self.descriptor.triggers.push(trigger); + /// Adds the given `event` + pub fn with_event(mut self, event: ComponentId) -> Self { + self.descriptor.events.push(event); self } - /// Adds the given `triggers` - pub fn with_triggers(mut self, triggers: impl IntoIterator) -> Self { - self.descriptor.triggers.extend(triggers); + /// Adds the given `events` + pub fn with_events(mut self, events: impl IntoIterator) -> Self { + self.descriptor.events.extend(events); self } @@ -88,9 +88,9 @@ pub struct ObserverSystemComponent { descriptor: ObserverDescriptor, } -impl ObserverSystemComponent { +impl ObserverSystemComponent { /// Creates a new [`ObserverSystemComponent`], which defaults to a "global" observer. - pub fn new(system: impl IntoObserverSystem) -> Self { + pub fn new(system: impl IntoObserverSystem) -> Self { Self { system: Box::new(IntoObserverSystem::into_system(system)), descriptor: Default::default(), @@ -110,30 +110,30 @@ impl ObserverSystemComponent { } /// Observe the given [`trigger`]. - pub fn with_trigger(mut self, component: ComponentId) -> Self { - self.descriptor.triggers.push(component); + pub fn with_event(mut self, event: ComponentId) -> Self { + self.descriptor.events.push(event); self } } -impl Component for ObserverSystemComponent { +impl Component for ObserverSystemComponent { const STORAGE_TYPE: StorageType = StorageType::SparseSet; fn register_component_hooks(hooks: &mut ComponentHooks) { hooks.on_add(|mut world, entity, _| { world.commands().add(move |world: &mut World| { - let trigger_id = world.init_component::(); + let event_type = world.init_component::(); let mut components = Vec::new(); B::component_ids(&mut world.components, &mut world.storages, &mut |id| { components.push(id); }); let mut descriptor = ObserverDescriptor { - triggers: vec![trigger_id], + events: vec![event_type], components, ..Default::default() }; // Initialize System - let system: *mut dyn ObserverSystem = + let system: *mut dyn ObserverSystem = if let Some(mut observe) = world.get_mut::(entity) { descriptor.merge(&observe.descriptor); &mut *observe.system @@ -151,7 +151,7 @@ impl Component for ObserverSystemComponent { { entry.insert(ObserverComponent { descriptor, - runner: observer_system_runner::, + runner: observer_system_runner::, ..Default::default() }); } @@ -162,9 +162,9 @@ impl Component for ObserverSystemComponent { } /// Equivalent to [`BoxedSystem`](crate::system::BoxedSystem) for [`ObserverSystem`]. -pub type BoxedObserverSystem = Box>; +pub type BoxedObserverSystem = Box>; -fn observer_system_runner( +fn observer_system_runner( mut world: DeferredWorld, trigger: ObserverTrigger, ptr: PtrMut, @@ -188,13 +188,13 @@ fn observer_system_runner( } state.last_trigger_id = last_trigger; - let observer: Observer = + let observer: Trigger = // SAFETY: Caller ensures `ptr` is castable to `&mut T` - Observer::new(unsafe { ptr.deref_mut() }, trigger); + Trigger::new(unsafe { ptr.deref_mut() }, trigger); // SAFETY: Observer was triggered so must have an `ObserverSystemComponent` let system = unsafe { &mut observer_cell - .get_mut::>() + .get_mut::>() .debug_checked_unwrap() .system }; diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index e1abc9bda2512..56e55ac909656 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -6,8 +6,8 @@ use crate::{ bundle::Bundle, component::{ComponentId, Components}, entity::{Entities, Entity}, - observer::{EmitTrigger, ObserverSystemComponent, TriggerTargets}, - prelude::Trigger, + event::Event, + observer::{ObserverSystemComponent, TriggerEvent, TriggerTargets}, system::{RunSystemWithInput, SystemId}, world::{Command, CommandQueue, EntityWorldMut, FromWorld, World}, }; @@ -636,22 +636,19 @@ impl<'w, 's> Commands<'w, 's> { } /// Sends a "global" [`Trigger`] without any targets. - pub fn trigger(&mut self, trigger: impl Trigger) { - self.add(EmitTrigger { - trigger, - targets: (), - }) + pub fn trigger(&mut self, event: impl Event) { + self.add(TriggerEvent { event, targets: () }) } /// Sends a [`Trigger`] with the given `targets`. - pub fn trigger_targets(&mut self, trigger: impl Trigger, targets: impl TriggerTargets) { - self.add(EmitTrigger { trigger, targets }) + pub fn trigger_targets(&mut self, event: impl Event, targets: impl TriggerTargets) { + self.add(TriggerEvent { event, targets }) } /// Spawn an [`Observer`] and returns it's [`Entity`]. - pub fn observe( + pub fn observe( &mut self, - observer: impl IntoObserverSystem, + observer: impl IntoObserverSystem, ) -> EntityCommands { self.spawn(ObserverSystemComponent::new(observer)) } @@ -1045,9 +1042,9 @@ impl EntityCommands<'_> { } /// Creates an [`Observer`](crate::observer::Observer) listening for a trigger of type `T` that targets this entity. - pub fn observe( + pub fn observe( &mut self, - system: impl IntoObserverSystem, + system: impl IntoObserverSystem, ) -> &mut Self { self.add(observe(system)); self @@ -1203,8 +1200,8 @@ fn log_components(entity: Entity, world: &mut World) { info!("Entity {:?}: {:?}", entity, debug_infos); } -fn observe( - observer: impl IntoObserverSystem, +fn observe( + observer: impl IntoObserverSystem, ) -> impl EntityCommand { move |entity, world: &mut World| { if let Some(mut entity) = world.get_entity_mut(entity) { diff --git a/crates/bevy_ecs/src/system/observer_system.rs b/crates/bevy_ecs/src/system/observer_system.rs index 1380e32be3413..0e13100f68d9b 100644 --- a/crates/bevy_ecs/src/system/observer_system.rs +++ b/crates/bevy_ecs/src/system/observer_system.rs @@ -1,7 +1,7 @@ use bevy_utils::all_tuples; use crate::{ - prelude::{Bundle, Observer}, + prelude::{Bundle, Trigger}, system::{System, SystemParam, SystemParamFunction, SystemParamItem}, }; @@ -9,11 +9,11 @@ use super::IntoSystem; /// Implemented for systems that have an [`Observer`] as the first argument. pub trait ObserverSystem: - System, Out = ()> + Send + 'static + System, Out = ()> + Send + 'static { } -impl, Out = ()> + Send + 'static> +impl, Out = ()> + Send + 'static> ObserverSystem for T { } @@ -27,12 +27,12 @@ pub trait IntoObserverSystem: Send + 'static { fn into_system(this: Self) -> Self::System; } -impl, (), M> + Send + 'static, M, E: 'static, B: Bundle> +impl, (), M> + Send + 'static, M, E: 'static, B: Bundle> IntoObserverSystem for S where S::System: ObserverSystem, { - type System = , (), M>>::System; + type System = , (), M>>::System; fn into_system(this: Self) -> Self::System { IntoSystem::into_system(this) @@ -42,21 +42,21 @@ where macro_rules! impl_system_function { ($($param: ident),*) => { #[allow(non_snake_case)] - impl SystemParamFunction, $($param,)*)> for Func + impl SystemParamFunction, $($param,)*)> for Func where for <'a> &'a mut Func: - FnMut(Observer, $($param),*) + - FnMut(Observer, $(SystemParamItem<$param>),*) + FnMut(Trigger, $($param),*) + + FnMut(Trigger, $(SystemParamItem<$param>),*) { - type In = Observer<'static, E, B>; + type In = Trigger<'static, E, B>; type Out = (); type Param = ($($param,)*); #[inline] - fn run(&mut self, input: Observer<'static, E, B>, param_value: SystemParamItem< ($($param,)*)>) { + fn run(&mut self, input: Trigger<'static, E, B>, param_value: SystemParamItem< ($($param,)*)>) { #[allow(clippy::too_many_arguments)] fn call_inner( - mut f: impl FnMut(Observer<'static, E, B>, $($param,)*), - input: Observer<'static, E, B>, + mut f: impl FnMut(Trigger<'static, E, B>, $($param,)*), + input: Trigger<'static, E, B>, $($param: $param,)* ){ f(input, $($param,)*) diff --git a/crates/bevy_ecs/src/world/component_constants.rs b/crates/bevy_ecs/src/world/component_constants.rs index 2aebd8cd93dd0..640ff1929d7a6 100644 --- a/crates/bevy_ecs/src/world/component_constants.rs +++ b/crates/bevy_ecs/src/world/component_constants.rs @@ -1,5 +1,5 @@ use super::*; -use crate::{self as bevy_ecs, prelude::Trigger}; +use crate::{self as bevy_ecs}; /// Internal components used by bevy with a fixed component id. /// Constants are used to skip [`TypeId`] lookups in hot paths. @@ -11,13 +11,13 @@ pub const ON_INSERT: ComponentId = ComponentId::new(1); pub const ON_REMOVE: ComponentId = ComponentId::new(2); /// Trigger emitted when a component is added to an entity. -#[derive(Trigger)] +#[derive(Event)] pub struct OnAdd; /// Trigger emitted when a component is inserted on to to an entity. -#[derive(Trigger)] +#[derive(Event)] pub struct OnInsert; /// Trigger emitted when a component is removed from an entity. -#[derive(Trigger)] +#[derive(Event)] pub struct OnRemove; diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 9760d95d2f58e..b747ffa472ded 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -6,7 +6,7 @@ use crate::{ component::ComponentId, entity::Entity, event::{Event, EventId, Events, SendBatchIds}, - observer::{Observers, Trigger, TriggerTargets}, + observer::{Observers, TriggerTargets}, prelude::{Component, QueryState}, query::{QueryData, QueryFilter}, system::{Commands, Query, Resource}, @@ -368,12 +368,12 @@ impl<'w> DeferredWorld<'w> { } /// Sends a "global" [`Trigger`] without any targets. - pub fn trigger(&mut self, trigger: impl Trigger) { + pub fn trigger(&mut self, trigger: impl Event) { self.commands().trigger(trigger); } /// Sends a [`Trigger`] with the given `targets`. - pub fn trigger_targets(&mut self, trigger: impl Trigger, targets: impl TriggerTargets) { + pub fn trigger_targets(&mut self, trigger: impl Event, targets: impl TriggerTargets) { self.commands().trigger_targets(trigger, targets); } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 87a1f351b775c..63d40547f77af 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -4,7 +4,8 @@ use crate::{ change_detection::MutUntyped, component::{Component, ComponentId, ComponentTicks, Components, StorageType}, entity::{Entities, Entity, EntityLocation}, - observer::{ObserverSystemComponent, Observers, Trigger}, + event::Event, + observer::{ObserverSystemComponent, Observers}, query::{Access, DebugCheckedUnwrap}, removal_detection::RemovedComponentEvents, storage::Storages, @@ -1399,9 +1400,9 @@ impl<'w> EntityWorldMut<'w> { /// Creates an [`Observer`](crate::observer::Observer) listening for `E` events targeting this entity. /// In order to trigger the callback the entity must also match the query when the event is fired. - pub fn observe( + pub fn observe( &mut self, - observer: impl IntoObserverSystem, + observer: impl IntoObserverSystem, ) -> &mut Self { self.insert(ObserverSystemComponent::new(observer).with_source(self.entity)) } diff --git a/examples/ecs/observers.rs b/examples/ecs/observers.rs index 5f1eb3d40ff80..e1e8c3b4a33c8 100644 --- a/examples/ecs/observers.rs +++ b/examples/ecs/observers.rs @@ -11,22 +11,21 @@ fn main() { .init_resource::() .add_systems(Startup, setup) .add_systems(Update, (draw_shapes, handle_click)) - // Observers run when a certain trigger is fired, each trigger is represented by a type. - // This observer runs whenever `TriggerMines` is fired. + // Observers run when a certain event is triggered. This observer runs whenever `ExplodeMines` is triggered. .observe( - |observer: Observer, + |trigger: Trigger, mines: Query<&Mine>, index: Res, mut commands: Commands| { // You can access the trigger data via the `Observer` - let trigger = observer.data(); + let event = trigger.event(); // Access resources - for e in index.get_nearby(trigger.pos) { + for e in index.get_nearby(event.pos) { // Run queries let mine = mines.get(e).unwrap(); - if mine.pos.distance(trigger.pos) < mine.size + trigger.radius { - // And queue commands, including firing additional triggers - // Here we fire the `Explode` trigger at entity `e` + if mine.pos.distance(event.pos) < mine.size + event.radius { + // And queue commands, including triggering additional events + // Here we trigger the `Explode` event for entity `e` commands.trigger_targets(Explode, e); } } @@ -47,13 +46,13 @@ struct Mine { size: f32, } -#[derive(Trigger)] -struct TriggerMines { +#[derive(Event)] +struct ExplodeMines { pos: Vec2, radius: f32, } -#[derive(Trigger)] +#[derive(Event)] struct Explode; fn setup(world: &mut World) { @@ -84,12 +83,12 @@ fn setup(world: &mut World) { ), size: 4.0 + rand::random::() * 16.0, }) - // Observers can also listen to triggers targeting a specific entity. - // This observer listens to `Explode` triggers targeted at our mine. + // Observers can also listen to events targeting a specific entity. + // This observer listens to `Explode` event triggers targeted at our mine. .observe( - |observer: Observer, query: Query<&Mine>, mut commands: Commands| { - // If a trigger is targeting a specific entity you can access it with `.source()` - let source = observer.source(); + |trigger: Trigger, query: Query<&Mine>, mut commands: Commands| { + // If a triggered event is targeting a specific entity you can access it with `.source()` + let source = trigger.source(); let Some(mut entity) = commands.get_entity(source) else { return; }; @@ -97,7 +96,7 @@ fn setup(world: &mut World) { entity.despawn(); let mine = query.get(source).unwrap(); // Fire another trigger to cascade into other mines. - commands.trigger(TriggerMines { + commands.trigger(ExplodeMines { pos: mine.pos, radius: mine.size, }); @@ -133,28 +132,28 @@ impl SpatialIndex { } } -fn add_mine(observer: Observer, query: Query<&Mine>, mut index: ResMut) { - let mine = query.get(observer.source()).unwrap(); +fn add_mine(trigger: Trigger, query: Query<&Mine>, mut index: ResMut) { + let mine = query.get(trigger.source()).unwrap(); let tile = ( (mine.pos.x / CELL_SIZE).floor() as i32, (mine.pos.y / CELL_SIZE).floor() as i32, ); - index.map.entry(tile).or_default().insert(observer.source()); + index.map.entry(tile).or_default().insert(trigger.source()); } // Remove despawned mines from our index fn remove_mine( - observer: Observer, + trigger: Trigger, query: Query<&Mine>, mut index: ResMut, ) { - let mine = query.get(observer.source()).unwrap(); + let mine = query.get(trigger.source()).unwrap(); let tile = ( (mine.pos.x / CELL_SIZE).floor() as i32, (mine.pos.y / CELL_SIZE).floor() as i32, ); index.map.entry(tile).and_modify(|set| { - set.remove(&observer.source()); + set.remove(&trigger.source()); }); } @@ -169,7 +168,7 @@ fn draw_shapes(mut gizmos: Gizmos, mines: Query<&Mine>) { } } -// Fire an initial `TriggerMines` trigger on click +// Fire an initial `ExplodeMines` trigger on click fn handle_click( mouse_button_input: Res>, camera: Query<(&Camera, &GlobalTransform)>, @@ -184,7 +183,7 @@ fn handle_click( .map(|ray| ray.origin.truncate()) { if mouse_button_input.just_pressed(MouseButton::Left) { - commands.trigger(TriggerMines { pos, radius: 1.0 }); + commands.trigger(ExplodeMines { pos, radius: 1.0 }); } } } From 65f75877a1fa09695a06394d2c46db97305d4586 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Sun, 9 Jun 2024 17:40:48 -0700 Subject: [PATCH 090/109] Renames: ObserverSystemComponent -> Observer, ObserverComponent -> ObserverState --- crates/bevy_ecs/src/observer/emit_trigger.rs | 2 +- crates/bevy_ecs/src/observer/mod.rs | 16 +++++------ crates/bevy_ecs/src/observer/runner.rs | 29 ++++++++++---------- crates/bevy_ecs/src/system/commands/mod.rs | 4 +-- crates/bevy_ecs/src/world/entity_ref.rs | 4 +-- 5 files changed, 26 insertions(+), 29 deletions(-) diff --git a/crates/bevy_ecs/src/observer/emit_trigger.rs b/crates/bevy_ecs/src/observer/emit_trigger.rs index 20aa0e5027e12..8f706d4838d44 100644 --- a/crates/bevy_ecs/src/observer/emit_trigger.rs +++ b/crates/bevy_ecs/src/observer/emit_trigger.rs @@ -29,7 +29,7 @@ pub struct EmitDynamicTrigger { } impl EmitDynamicTrigger { - /// Sets the trigger id of the resulting trigger, used for dynamic triggers + /// Sets the event type of the resulting trigger, used for dynamic triggers /// # Safety /// Caller must ensure that the component associated with `event_type` is accessible as E pub unsafe fn new_with_id(event_type: ComponentId, event_data: E, targets: Targets) -> Self { diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index a1aa7e9e16597..13e5b557cb603 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -260,7 +260,7 @@ impl World { &mut self, system: impl IntoObserverSystem, ) -> EntityWorldMut { - self.spawn(ObserverSystemComponent::new(system)) + self.spawn(Observer::new(system)) } /// Triggers the given `event`, which will run any observers watching for it. @@ -277,8 +277,8 @@ impl World { pub(crate) fn register_observer(&mut self, entity: Entity) { // SAFETY: References do not alias. let (observer_component, archetypes, observers) = unsafe { - let observer_component: *const ObserverComponent = - self.get::(entity).unwrap(); + let observer_component: *const ObserverState = + self.get::(entity).unwrap(); ( &*observer_component, &mut self.archetypes, @@ -382,9 +382,7 @@ mod tests { use bevy_ptr::OwningPtr; use crate as bevy_ecs; - use crate::observer::{ - EmitDynamicTrigger, ObserverComponent, ObserverDescriptor, ObserverSystemComponent, - }; + use crate::observer::{EmitDynamicTrigger, Observer, ObserverDescriptor, ObserverState}; use crate::prelude::*; #[derive(Component)] @@ -494,7 +492,7 @@ mod tests { world.init_resource::(); let on_remove = world.init_component::(); world.spawn( - ObserverSystemComponent::new(|_: Trigger, mut res: ResMut| res.0 += 1) + Observer::new(|_: Trigger, mut res: ResMut| res.0 += 1) .with_event(on_remove), ); @@ -588,7 +586,7 @@ mod tests { let component_id = world.init_component::(); world.spawn( - ObserverSystemComponent::new(|_: Trigger, mut res: ResMut| res.0 += 1) + Observer::new(|_: Trigger, mut res: ResMut| res.0 += 1) .with_component(component_id), ); @@ -610,7 +608,7 @@ mod tests { world.init_resource::(); let event_a = world.init_component::(); - world.spawn(ObserverComponent { + world.spawn(ObserverState { descriptor: ObserverDescriptor::default().with_triggers(vec![event_a]), runner: |mut world, _trigger, _ptr| { world.resource_mut::().0 += 1; diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index aa737096fe5c0..ef640f6b8e05d 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -10,13 +10,13 @@ use bevy_ptr::PtrMut; /// Contains [`Observer`]` information. This defines how a given observer behaves. It is the /// "source of truth" for a given observer entity's behavior. -pub struct ObserverComponent { +pub struct ObserverState { pub(crate) descriptor: ObserverDescriptor, pub(crate) runner: ObserverRunner, pub(crate) last_trigger_id: u32, } -impl Default for ObserverComponent { +impl Default for ObserverState { fn default() -> Self { Self { runner: |_, _, _| {}, @@ -26,7 +26,7 @@ impl Default for ObserverComponent { } } -impl ObserverComponent { +impl ObserverState { /// Adds the given `event` pub fn with_event(mut self, event: ComponentId) -> Self { self.descriptor.events.push(event); @@ -52,7 +52,7 @@ impl ObserverComponent { } } -impl Component for ObserverComponent { +impl Component for ObserverState { const STORAGE_TYPE: StorageType = StorageType::SparseSet; fn register_component_hooks(hooks: &mut ComponentHooks) { @@ -65,7 +65,7 @@ impl Component for ObserverComponent { let descriptor = std::mem::take( &mut world .entity_mut(entity) - .get_mut::() + .get_mut::() .unwrap() .as_mut() .descriptor, @@ -83,13 +83,13 @@ impl Component for ObserverComponent { pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut); /// An [`Observer`] system. Add this [`Component`] to an [`Entity`] to give it observer behaviors for the given system. -pub struct ObserverSystemComponent { +pub struct Observer { system: BoxedObserverSystem, descriptor: ObserverDescriptor, } -impl ObserverSystemComponent { - /// Creates a new [`ObserverSystemComponent`], which defaults to a "global" observer. +impl Observer { + /// Creates a new [`Observer`], which defaults to a "global" observer. pub fn new(system: impl IntoObserverSystem) -> Self { Self { system: Box::new(IntoObserverSystem::into_system(system)), @@ -116,7 +116,7 @@ impl ObserverSystemComponent { } } -impl Component for ObserverSystemComponent { +impl Component for Observer { const STORAGE_TYPE: StorageType = StorageType::SparseSet; fn register_component_hooks(hooks: &mut ComponentHooks) { hooks.on_add(|mut world, entity, _| { @@ -147,9 +147,8 @@ impl Component for ObserverSystemComponent { { let mut entity = world.entity_mut(entity); - if let crate::world::Entry::Vacant(entry) = entity.entry::() - { - entry.insert(ObserverComponent { + if let crate::world::Entry::Vacant(entry) = entity.entry::() { + entry.insert(ObserverState { descriptor, runner: observer_system_runner::, ..Default::default() @@ -173,10 +172,10 @@ fn observer_system_runner( let observer_cell = // SAFETY: Observer was triggered so must still exist in world unsafe { world.get_entity(trigger.observer).debug_checked_unwrap() }; - // SAFETY: Observer was triggered so must have an `ObserverComponent` + // SAFETY: Observer was triggered so must have an `ObserverState` let mut state = unsafe { observer_cell - .get_mut::() + .get_mut::() .debug_checked_unwrap() }; @@ -194,7 +193,7 @@ fn observer_system_runner( // SAFETY: Observer was triggered so must have an `ObserverSystemComponent` let system = unsafe { &mut observer_cell - .get_mut::>() + .get_mut::>() .debug_checked_unwrap() .system }; diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 56e55ac909656..4818cf2f963a3 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -7,7 +7,7 @@ use crate::{ component::{ComponentId, Components}, entity::{Entities, Entity}, event::Event, - observer::{ObserverSystemComponent, TriggerEvent, TriggerTargets}, + observer::{Observer, TriggerEvent, TriggerTargets}, system::{RunSystemWithInput, SystemId}, world::{Command, CommandQueue, EntityWorldMut, FromWorld, World}, }; @@ -650,7 +650,7 @@ impl<'w, 's> Commands<'w, 's> { &mut self, observer: impl IntoObserverSystem, ) -> EntityCommands { - self.spawn(ObserverSystemComponent::new(observer)) + self.spawn(Observer::new(observer)) } } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 63d40547f77af..49ee6887262cd 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -5,7 +5,7 @@ use crate::{ component::{Component, ComponentId, ComponentTicks, Components, StorageType}, entity::{Entities, Entity, EntityLocation}, event::Event, - observer::{ObserverSystemComponent, Observers}, + observer::{Observer, Observers}, query::{Access, DebugCheckedUnwrap}, removal_detection::RemovedComponentEvents, storage::Storages, @@ -1404,7 +1404,7 @@ impl<'w> EntityWorldMut<'w> { &mut self, observer: impl IntoObserverSystem, ) -> &mut Self { - self.insert(ObserverSystemComponent::new(observer).with_source(self.entity)) + self.insert(Observer::new(observer).with_source(self.entity)) } } From e91dfb79c3aa2b8ae045c165d81254b9428c8df9 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Sun, 9 Jun 2024 18:26:02 -0700 Subject: [PATCH 091/109] Despawn Observers if all of their entity targets have been despawned --- .../bevy_ecs/src/observer/entity_observer.rs | 54 +++++++++---------- crates/bevy_ecs/src/observer/mod.rs | 30 ++++++----- crates/bevy_ecs/src/observer/runner.rs | 8 +++ 3 files changed, 50 insertions(+), 42 deletions(-) diff --git a/crates/bevy_ecs/src/observer/entity_observer.rs b/crates/bevy_ecs/src/observer/entity_observer.rs index e8155c843d5f8..1f1abd2686881 100644 --- a/crates/bevy_ecs/src/observer/entity_observer.rs +++ b/crates/bevy_ecs/src/observer/entity_observer.rs @@ -1,44 +1,42 @@ use crate::{ component::{Component, ComponentHooks, StorageType}, entity::Entity, - observer::Command, - world::World, + observer::ObserverState, }; -/// Command to attach an entity observer to an entity -pub(crate) struct AttachObserver { - pub(crate) target: Entity, - pub(crate) observer: Entity, -} - -impl Command for AttachObserver { - fn apply(self, world: &mut World) { - if let Some(mut target) = world.get_entity_mut(self.target) { - let mut observed_by = target - .entry::() - .or_insert_with(|| ObservedBy(vec![])); - observed_by.0.push(self.observer); - } else { - world.despawn(self.observer); - } - } -} - /// Tracks a list of entity observers for the attached entity -pub(crate) struct ObservedBy(Vec); +#[derive(Default)] +pub(crate) struct ObservedBy(pub(crate) Vec); impl Component for ObservedBy { const STORAGE_TYPE: StorageType = StorageType::SparseSet; fn register_component_hooks(hooks: &mut ComponentHooks) { hooks.on_remove(|mut world, entity, _| { - let mut component = world.get_mut::(entity).unwrap(); - let observed_by = std::mem::take(&mut component.0); - observed_by.iter().for_each(|&e| { - if let Some(mut entity) = world.commands().get_entity(e) { - entity.despawn(); + let observed_by = { + let mut component = world.get_mut::(entity).unwrap(); + std::mem::take(&mut component.0) + }; + for e in observed_by { + let (total_sources, despawned_sources) = { + let Some(mut entity_mut) = world.get_entity_mut(e) else { + continue; + }; + let Some(mut state) = entity_mut.get_mut::() else { + continue; + }; + state.despawned_sources += 1; + ( + state.descriptor.sources.len(), + state.despawned_sources as usize, + ) }; - }); + + // Despawn Observer if it has no more active sources. + if total_sources == despawned_sources { + world.commands().entity(e).despawn(); + } + } }); } } diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 13e5b557cb603..9cbd214e254c3 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -7,6 +7,7 @@ mod runner; pub use emit_trigger::*; pub use runner::*; +use crate::observer::entity_observer::ObservedBy; use crate::{archetype::ArchetypeFlags, system::IntoObserverSystem, world::*}; use crate::{component::ComponentId, prelude::*, world::DeferredWorld}; use bevy_ptr::Ptr; @@ -276,27 +277,28 @@ impl World { /// Register an observer to the cache, called when an observer is created pub(crate) fn register_observer(&mut self, entity: Entity) { // SAFETY: References do not alias. - let (observer_component, archetypes, observers) = unsafe { - let observer_component: *const ObserverState = - self.get::(entity).unwrap(); - ( - &*observer_component, - &mut self.archetypes, - &mut self.observers, - ) + let (observer_state, archetypes, observers) = unsafe { + let observer_state: *const ObserverState = self.get::(entity).unwrap(); + // Populate ObservedBy for each observed source. + for source in &(*observer_state).descriptor.sources { + let mut source_mut = self.entity_mut(*source); + let mut observed_by = source_mut.entry::().or_default(); + observed_by.0.push(entity); + } + (&*observer_state, &mut self.archetypes, &mut self.observers) }; - let descriptor = &observer_component.descriptor; + let descriptor = &observer_state.descriptor; for &event_type in &descriptor.events { let cache = observers.get_observers(event_type); if descriptor.components.is_empty() && descriptor.sources.is_empty() { - cache.map.insert(entity, observer_component.runner); + cache.map.insert(entity, observer_state.runner); } else if descriptor.components.is_empty() { // Observer is not targeting any components so register it as an entity observer - for &source in &observer_component.descriptor.sources { + for &source in &observer_state.descriptor.sources { let map = cache.entity_observers.entry(source).or_default(); - map.insert(entity, observer_component.runner); + map.insert(entity, observer_state.runner); } } else { // Register observer for each source component @@ -313,12 +315,12 @@ impl World { }); if descriptor.sources.is_empty() { // Register for all triggers targeting the component - observers.map.insert(entity, observer_component.runner); + observers.map.insert(entity, observer_state.runner); } else { // Register for each targeted entity for &source in &descriptor.sources { let map = observers.entity_map.entry(source).or_default(); - map.insert(entity, observer_component.runner); + map.insert(entity, observer_state.runner); } } } diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index ef640f6b8e05d..3fb9908a63d52 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -14,6 +14,7 @@ pub struct ObserverState { pub(crate) descriptor: ObserverDescriptor, pub(crate) runner: ObserverRunner, pub(crate) last_trigger_id: u32, + pub(crate) despawned_sources: u32, } impl Default for ObserverState { @@ -21,6 +22,7 @@ impl Default for ObserverState { Self { runner: |_, _, _| {}, last_trigger_id: 0, + despawned_sources: 0, descriptor: Default::default(), } } @@ -62,6 +64,7 @@ impl Component for ObserverState { }); }); hooks.on_remove(|mut world, entity, _| { + println!("Despawned observer {entity:?}"); let descriptor = std::mem::take( &mut world .entity_mut(entity) @@ -103,6 +106,11 @@ impl Observer { self } + /// Observe the given `entity`. Note that if this is called _after_ an [`Observer`] is spawned, it will produce no effects. + pub fn add_source(&mut self, entity: Entity) { + self.descriptor.sources.push(entity); + } + /// Observe the given `component`. pub fn with_component(mut self, component: ComponentId) -> Self { self.descriptor.components.push(component); From d27ccfe08a9535d4917f0e6ac734315b7d76a104 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Sun, 9 Jun 2024 19:15:12 -0700 Subject: [PATCH 092/109] Remove unused Components field on Commands --- crates/bevy_ecs/src/system/commands/mod.rs | 18 +++--------------- .../src/system/commands/parallel_scope.rs | 4 +--- crates/bevy_ecs/src/world/deferred_world.rs | 2 +- crates/bevy_ecs/src/world/mod.rs | 2 +- 4 files changed, 6 insertions(+), 20 deletions(-) diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 4818cf2f963a3..3ed335aa4b433 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -4,7 +4,7 @@ use super::{Deferred, IntoObserverSystem, IntoSystem, RegisterSystem, Resource}; use crate::{ self as bevy_ecs, bundle::Bundle, - component::{ComponentId, Components}, + component::ComponentId, entity::{Entities, Entity}, event::Event, observer::{Observer, TriggerEvent, TriggerTargets}, @@ -71,7 +71,6 @@ use std::marker::PhantomData; pub struct Commands<'w, 's> { queue: Deferred<'s, CommandQueue>, entities: &'w Entities, - components: &'w Components, } impl<'w, 's> Commands<'w, 's> { @@ -81,7 +80,7 @@ impl<'w, 's> Commands<'w, 's> { /// /// [system parameter]: crate::system::SystemParam pub fn new(queue: &'s mut CommandQueue, world: &'w World) -> Self { - Self::new_from_entities(queue, &world.entities, &world.components) + Self::new_from_entities(queue, &world.entities) } /// Returns a new `Commands` instance from a [`CommandQueue`] and an [`Entities`] reference. @@ -89,14 +88,9 @@ impl<'w, 's> Commands<'w, 's> { /// It is not required to call this constructor when using `Commands` as a [system parameter]. /// /// [system parameter]: crate::system::SystemParam - pub fn new_from_entities( - queue: &'s mut CommandQueue, - entities: &'w Entities, - components: &'w Components, - ) -> Self { + pub fn new_from_entities(queue: &'s mut CommandQueue, entities: &'w Entities) -> Self { Self { queue: Deferred(queue), - components, entities, } } @@ -123,7 +117,6 @@ impl<'w, 's> Commands<'w, 's> { Commands { queue: self.queue.reborrow(), entities: self.entities, - components: self.components, } } @@ -630,11 +623,6 @@ impl<'w, 's> Commands<'w, 's> { self.queue.push(command); } - /// Returns a reference to the [`World`]'s [`Components`]. - pub fn components(&self) -> &Components { - self.components - } - /// Sends a "global" [`Trigger`] without any targets. pub fn trigger(&mut self, event: impl Event) { self.add(TriggerEvent { event, targets: () }) diff --git a/crates/bevy_ecs/src/system/commands/parallel_scope.rs b/crates/bevy_ecs/src/system/commands/parallel_scope.rs index b54545edb53ca..865b6eb2b882f 100644 --- a/crates/bevy_ecs/src/system/commands/parallel_scope.rs +++ b/crates/bevy_ecs/src/system/commands/parallel_scope.rs @@ -2,7 +2,6 @@ use bevy_utils::Parallel; use crate::{ self as bevy_ecs, - component::Components, entity::Entities, prelude::World, system::{Deferred, SystemBuffer, SystemMeta, SystemParam}, @@ -45,7 +44,6 @@ struct ParallelCommandQueue { pub struct ParallelCommands<'w, 's> { state: Deferred<'s, ParallelCommandQueue>, entities: &'w Entities, - components: &'w Components, } impl SystemBuffer for ParallelCommandQueue { @@ -65,7 +63,7 @@ impl<'w, 's> ParallelCommands<'w, 's> { /// For an example, see the type-level documentation for [`ParallelCommands`]. pub fn command_scope(&self, f: impl FnOnce(Commands) -> R) -> R { self.state.thread_queues.scope(|queue| { - let commands = Commands::new_from_entities(queue, self.entities, self.components); + let commands = Commands::new_from_entities(queue, self.entities); f(commands) }) } diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index b747ffa472ded..739eddac8b98b 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -65,7 +65,7 @@ impl<'w> DeferredWorld<'w> { pub fn commands(&mut self) -> Commands { // SAFETY: &mut self ensure that there are no outstanding accesses to the queue let queue = unsafe { self.world.get_command_queue() }; - Commands::new_from_entities(queue, self.world.entities(), self.world.components()) + Commands::new_from_entities(queue, self.world.entities()) } /// Retrieves a mutable reference to the given `entity`'s [`Component`] of the given type. diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 0703d9ff3967f..b60e8515b8480 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -231,7 +231,7 @@ impl World { /// Use [`World::flush`] to apply all queued commands #[inline] pub fn commands(&mut self) -> Commands { - Commands::new_from_entities(&mut self.command_queue, &self.entities, &self.components) + Commands::new_from_entities(&mut self.command_queue, &self.entities) } /// Initializes a new [`Component`] type and returns the [`ComponentId`] created for it. From b5d9ef00ea6259fdb4a1243803a9b074742e9a59 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Mon, 10 Jun 2024 12:48:21 -0700 Subject: [PATCH 093/109] Implement SystemParam::queue for Commands --- crates/bevy_ecs/src/world/command_queue.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/bevy_ecs/src/world/command_queue.rs b/crates/bevy_ecs/src/world/command_queue.rs index fafa357f641fe..205d2b82a3b38 100644 --- a/crates/bevy_ecs/src/world/command_queue.rs +++ b/crates/bevy_ecs/src/world/command_queue.rs @@ -292,6 +292,11 @@ impl SystemBuffer for CommandQueue { #[inline] fn queue(&mut self, _system_meta: &SystemMeta, mut world: DeferredWorld) { + println!( + "Queuing commands for {} {}", + _system_meta.name(), + self.bytes.len() + ); world.commands().append(self); } } From 5be1dbe27c9eafc0f4e8b2a59e0cbe27a8919c73 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Mon, 10 Jun 2024 13:02:51 -0700 Subject: [PATCH 094/109] Commit correct file for SystemParam::queue Commands impl :) --- crates/bevy_ecs/src/system/commands/mod.rs | 11 +++++++++++ crates/bevy_ecs/src/world/command_queue.rs | 5 ----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 90e94c61de1f6..17c048da33756 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -118,6 +118,17 @@ const _: () = { world, ); } + fn queue( + state: &mut Self::State, + system_meta: &bevy_ecs::system::SystemMeta, + world: bevy_ecs::world::DeferredWorld, + ) { + <__StructFieldsAlias<'_, '_> as bevy_ecs::system::SystemParam>::queue( + &mut state.state, + system_meta, + world, + ); + } unsafe fn get_param<'w, 's>( state: &'s mut Self::State, system_meta: &bevy_ecs::system::SystemMeta, diff --git a/crates/bevy_ecs/src/world/command_queue.rs b/crates/bevy_ecs/src/world/command_queue.rs index 205d2b82a3b38..fafa357f641fe 100644 --- a/crates/bevy_ecs/src/world/command_queue.rs +++ b/crates/bevy_ecs/src/world/command_queue.rs @@ -292,11 +292,6 @@ impl SystemBuffer for CommandQueue { #[inline] fn queue(&mut self, _system_meta: &SystemMeta, mut world: DeferredWorld) { - println!( - "Queuing commands for {} {}", - _system_meta.name(), - self.bytes.len() - ); world.commands().append(self); } } From 9303a4ae9cc91989984b9a8ee4cd4a195f887c6a Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Mon, 10 Jun 2024 17:12:11 -0700 Subject: [PATCH 095/109] Rework CommandQueue application logic to apply commands at the right time in the right order. Fix some tests. --- crates/bevy_ecs/src/bundle.rs | 4 +- crates/bevy_ecs/src/observer/mod.rs | 6 +++ crates/bevy_ecs/src/observer/runner.rs | 1 - crates/bevy_ecs/src/world/command_queue.rs | 50 +++++++++++----------- 4 files changed, 34 insertions(+), 27 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 83371528b2dc9..5693e03095425 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -1242,13 +1242,13 @@ mod tests { world .register_component_hooks::() .on_add(|mut world, _, _| { - world.resource_mut::().assert_order(2); + world.resource_mut::().assert_order(3); }); world .register_component_hooks::() .on_add(|mut world, _, _| { - world.resource_mut::().assert_order(3); + world.resource_mut::().assert_order(2); }); world.spawn(A).flush(); diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 9cbd214e254c3..00447359dcfaa 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -554,6 +554,9 @@ mod tests { res.0 += 1; }); + // TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut + // and therefore does not automatically flush. + world.flush(); world.trigger(EventA); world.flush(); assert_eq!(1, world.resource::().0); @@ -576,6 +579,9 @@ mod tests { res.0 += 1; }); + // TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut + // and therefore does not automatically flush. + world.flush(); world.trigger_targets(EventA, entity); world.flush(); assert_eq!(2, world.resource::().0); diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index 3fb9908a63d52..2779ec2800d75 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -64,7 +64,6 @@ impl Component for ObserverState { }); }); hooks.on_remove(|mut world, entity, _| { - println!("Despawned observer {entity:?}"); let descriptor = std::mem::take( &mut world .entity_mut(entity) diff --git a/crates/bevy_ecs/src/world/command_queue.rs b/crates/bevy_ecs/src/world/command_queue.rs index fafa357f641fe..5763b446974d4 100644 --- a/crates/bevy_ecs/src/world/command_queue.rs +++ b/crates/bevy_ecs/src/world/command_queue.rs @@ -20,11 +20,8 @@ struct CommandMeta { /// `world` is optional to allow this one function pointer to perform double-duty as a drop. /// /// Advances `cursor` by the size of `T` in bytes. - consume_command_and_get_size: unsafe fn( - value: OwningPtr, - world: Option>, - cursor: NonNull, - ), + consume_command_and_get_size: + unsafe fn(value: OwningPtr, world: Option>, cursor: &mut usize), } /// Densely and efficiently stores a queue of heterogenous types implementing [`Command`]. @@ -166,9 +163,8 @@ impl RawCommandQueue { } let meta = CommandMeta { - consume_command_and_get_size: |command, world, mut cursor| { - // SAFETY: Pointer is assured to be valid in `CommandQueue.apply_or_drop_queued` - unsafe { *cursor.as_mut() += std::mem::size_of::() } + consume_command_and_get_size: |command, world, cursor| { + *cursor += std::mem::size_of::(); // SAFETY: According to the invariants of `CommandMeta.consume_command_and_get_size`, // `command` must point to a value of type `C`. @@ -224,34 +220,35 @@ impl RawCommandQueue { pub(crate) unsafe fn apply_or_drop_queued(&mut self, world: Option>) { // SAFETY: If this is the command queue on world, world will not be dropped as we have a mutable reference // If this is not the command queue on world we have exclusive ownership and self will not be mutated - while *self.cursor.as_ref() < self.bytes.as_ref().len() { + let start = *self.cursor.as_ref(); + let stop = self.bytes.as_ref().len(); + let length = stop - start; + let mut local_cursor = start; + // SAFETY: we are setting the global cursor to the current length to prevent the executing commands from applying + // the remaining commands currently in this list. This is safe. + *self.cursor.as_mut() = stop; + + while local_cursor < stop { // SAFETY: The cursor is either at the start of the buffer, or just after the previous command. // Since we know that the cursor is in bounds, it must point to the start of a new command. let meta = unsafe { self.bytes .as_mut() .as_mut_ptr() - .add(*self.cursor.as_ref()) + .add(local_cursor) .cast::() .read_unaligned() }; // Advance to the bytes just after `meta`, which represent a type-erased command. - // SAFETY: For most types of `Command`, the pointer immediately following the metadata - // is guaranteed to be in bounds. If the command is a zero-sized type (ZST), then the cursor - // might be 1 byte past the end of the buffer, which is safe. - unsafe { *self.cursor.as_mut() += std::mem::size_of::() }; + local_cursor += std::mem::size_of::(); // Construct an owned pointer to the command. // SAFETY: It is safe to transfer ownership out of `self.bytes`, since the increment of `cursor` above // guarantees that nothing stored in the buffer will get observed after this function ends. // `cmd` points to a valid address of a stored command, so it must be non-null. let cmd = unsafe { OwningPtr::::new(std::ptr::NonNull::new_unchecked( - self.bytes - .as_mut() - .as_mut_ptr() - .add(*self.cursor.as_ref()) - .cast(), + self.bytes.as_mut().as_mut_ptr().add(local_cursor).cast(), )) }; // SAFETY: The data underneath the cursor must correspond to the type erased in metadata, @@ -260,14 +257,19 @@ impl RawCommandQueue { // This also advances the cursor past the command. For ZSTs, the cursor will not move. // At this point, it will either point to the next `CommandMeta`, // or the cursor will be out of bounds and the loop will end. - unsafe { (meta.consume_command_and_get_size)(cmd, world, self.cursor) }; + unsafe { (meta.consume_command_and_get_size)(cmd, world, &mut local_cursor) }; + + // We call this again, with the cursor still at `stop`, to ensure that any new Commands queued by the command + // are also applied. + self.apply_or_drop_queued(world); } - // Reset the buffer, so it can be reused after this function ends. - // SAFETY: `set_len(0)` is always valid. + // Reset the buffer: all commands past the original `start` cursor have been applied. + // SAFETY: we are setting the length of bytes to the original length, minus the length of the original + // list of commands being considered. All bytes remaining in the Vec are still valid, unapplied commands. unsafe { - self.bytes.as_mut().set_len(0); - *self.cursor.as_mut() = 0; + self.bytes.as_mut().set_len(stop - length); + *self.cursor.as_mut() = start; }; } } From de76720d2cc06f5e86f423a4f88fc9d103701788 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Tue, 11 Jun 2024 15:22:26 -0700 Subject: [PATCH 096/109] Add panic recovery --- crates/bevy_ecs/src/world/command_queue.rs | 90 ++++++++++++++++++---- crates/bevy_ecs/src/world/entity_ref.rs | 2 +- crates/bevy_ecs/src/world/mod.rs | 5 +- 3 files changed, 81 insertions(+), 16 deletions(-) diff --git a/crates/bevy_ecs/src/world/command_queue.rs b/crates/bevy_ecs/src/world/command_queue.rs index 5763b446974d4..a6b1a6e5a95bc 100644 --- a/crates/bevy_ecs/src/world/command_queue.rs +++ b/crates/bevy_ecs/src/world/command_queue.rs @@ -3,6 +3,7 @@ use crate::system::{SystemBuffer, SystemMeta}; use std::{ fmt::Debug, mem::MaybeUninit, + panic::{self, AssertUnwindSafe}, ptr::{addr_of_mut, NonNull}, }; @@ -40,6 +41,7 @@ pub struct CommandQueue { // be passed to the corresponding `CommandMeta.apply_command_and_get_size` fn pointer. pub(crate) bytes: Vec>, pub(crate) cursor: usize, + pub(crate) panic_recovery: Vec>, } /// Wraps pointers to a [`CommandQueue`], used internally to avoid stacked borrow rules when @@ -48,6 +50,7 @@ pub struct CommandQueue { pub(crate) struct RawCommandQueue { pub(crate) bytes: NonNull>>, pub(crate) cursor: NonNull, + pub(crate) panic_recovery: NonNull>>, } // CommandQueue needs to implement Debug manually, rather than deriving it, because the derived impl just prints @@ -116,6 +119,7 @@ impl CommandQueue { RawCommandQueue { bytes: NonNull::new_unchecked(addr_of_mut!(self.bytes)), cursor: NonNull::new_unchecked(addr_of_mut!(self.cursor)), + panic_recovery: NonNull::new_unchecked(addr_of_mut!(self.panic_recovery)), } } } @@ -129,6 +133,7 @@ impl RawCommandQueue { Self { bytes: NonNull::new_unchecked(Box::into_raw(Box::default())), cursor: NonNull::new_unchecked(Box::into_raw(Box::new(0usize))), + panic_recovery: NonNull::new_unchecked(Box::into_raw(Box::default())), } } } @@ -171,8 +176,15 @@ impl RawCommandQueue { let command: C = unsafe { command.read_unaligned() }; match world { // Apply command to the provided world... - // SAFETY: Calller ensures pointer is not null - Some(mut world) => command.apply(unsafe { world.as_mut() }), + Some(mut world) => { + // SAFETY: Caller ensures pointer is not null + let world = unsafe { world.as_mut() }; + command.apply(world); + // The command may have queued up world commands, which we flush here to ensure they are also picked up. + // If the current command queue already the World Command queue, this will still behave appropriately because the global cursor + // is still at the current `stop`, ensuring only the newly queued Commands will be applied. + world.flush_commands(); + } // ...or discard it. None => drop(command), } @@ -222,7 +234,6 @@ impl RawCommandQueue { // If this is not the command queue on world we have exclusive ownership and self will not be mutated let start = *self.cursor.as_ref(); let stop = self.bytes.as_ref().len(); - let length = stop - start; let mut local_cursor = start; // SAFETY: we are setting the global cursor to the current length to prevent the executing commands from applying // the remaining commands currently in this list. This is safe. @@ -257,18 +268,42 @@ impl RawCommandQueue { // This also advances the cursor past the command. For ZSTs, the cursor will not move. // At this point, it will either point to the next `CommandMeta`, // or the cursor will be out of bounds and the loop will end. - unsafe { (meta.consume_command_and_get_size)(cmd, world, &mut local_cursor) }; - - // We call this again, with the cursor still at `stop`, to ensure that any new Commands queued by the command - // are also applied. - self.apply_or_drop_queued(world); + let result = panic::catch_unwind(AssertUnwindSafe(|| { + unsafe { (meta.consume_command_and_get_size)(cmd, world, &mut local_cursor) }; + })); + + if let Err(payload) = result { + // local_cursor now points to the location _after_ the panicked command. + // Add the remaining commands that _would have_ been applied to the + // panic_recovery queue. + // + // This uses `current_stop` instead of `stop` to account for any commands + // that were queued _during_ this panic. + // + // This is implemented in such a way that if apply_or_drop_queued() are nested recursively in, + // an applied Command, the correct command order will be retained. + let panic_recovery = self.panic_recovery.as_mut(); + let bytes = self.bytes.as_mut(); + let current_stop = bytes.len(); + panic_recovery.extend_from_slice(&bytes[local_cursor..current_stop]); + bytes.set_len(start); + *self.cursor.as_mut() = start; + + // This was the "top of the apply stack". If we are _not_ at the top of the apply stack, + // when we call`resume_unwind" the caller "closer to the top" will catch the unwind and do this check, + // until we reach the top. + if start == 0 { + bytes.extend(panic_recovery.drain(..)) + } + panic::resume_unwind(payload); + } } // Reset the buffer: all commands past the original `start` cursor have been applied. // SAFETY: we are setting the length of bytes to the original length, minus the length of the original // list of commands being considered. All bytes remaining in the Vec are still valid, unapplied commands. unsafe { - self.bytes.as_mut().set_len(stop - length); + self.bytes.as_mut().set_len(start); *self.cursor.as_mut() = start; }; } @@ -301,6 +336,8 @@ impl SystemBuffer for CommandQueue { #[cfg(test)] mod test { use super::*; + use crate as bevy_ecs; + use crate::system::Resource; use std::{ panic::AssertUnwindSafe, sync::{ @@ -421,10 +458,6 @@ mod test { queue.apply(&mut world); })); - // even though the first command panicking. - // the cursor was incremented. - assert!(queue.cursor > 0); - // Even though the first command panicked, it's still ok to push // more commands. queue.push(SpawnCommand); @@ -433,6 +466,37 @@ mod test { assert_eq!(world.entities().len(), 3); } + #[test] + fn test_command_queue_inner_nested_panic_safe() { + std::panic::set_hook(Box::new(|_| {})); + + #[derive(Resource, Default)] + struct Order(Vec); + + let mut world = World::new(); + world.init_resource::(); + + fn add_index(index: usize) -> impl Command { + move |world: &mut World| world.resource_mut::().0.push(index) + } + world.commands().add(add_index(1)); + world.commands().add(|world: &mut World| { + world.commands().add(add_index(2)); + world.commands().add(PanicCommand("I panic!".to_owned())); + world.commands().add(add_index(3)); + world.flush_commands(); + }); + world.commands().add(add_index(4)); + + let _ = std::panic::catch_unwind(AssertUnwindSafe(|| { + world.flush_commands(); + })); + + world.commands().add(add_index(5)); + world.flush_commands(); + assert_eq!(&world.resource::().0, &[1, 2, 3, 4, 5]); + } + // NOTE: `CommandQueue` is `Send` because `Command` is send. // If the `Command` trait gets reworked to be non-send, `CommandQueue` // should be reworked. diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index f7e7884b02348..97a586a610512 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -6,7 +6,7 @@ use crate::{ entity::{Entities, Entity, EntityLocation}, event::Event, observer::{Observer, Observers}, - query::{Access, DebugCheckedUnwrap}, + query::Access, removal_detection::RemovedComponentEvents, storage::Storages, system::IntoObserverSystem, diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 2d0f22a43563a..6628a49c05fee 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -2045,14 +2045,15 @@ impl World { } } - /// Applies any commands in the world's internal [`CommandQueue`]. - /// This does not apply commands from any systems, only those stored in the world. + /// Calls both [`World::flush_entities`] and [`World::flush_commands`]. #[inline] pub fn flush(&mut self) { self.flush_entities(); self.flush_commands(); } + /// Applies any commands in the world's internal [`CommandQueue`]. + /// This does not apply commands from any systems, only those stored in the world. pub fn flush_commands(&mut self) { // SAFETY: `self.command_queue` is only de-allocated in `World`'s `Drop` if !unsafe { self.command_queue.is_empty() } { From c2a43af99d0332fcf721fdd1b1ab45b902d4930f Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Tue, 11 Jun 2024 16:43:54 -0700 Subject: [PATCH 097/109] Clippy --- crates/bevy_ecs/src/observer/runner.rs | 19 +++++++++++++------ crates/bevy_ecs/src/system/commands/mod.rs | 4 ++-- crates/bevy_ecs/src/world/command_queue.rs | 14 +++++++------- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index 2779ec2800d75..651707d606c3e 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -8,7 +8,7 @@ use crate::{ }; use bevy_ptr::PtrMut; -/// Contains [`Observer`]` information. This defines how a given observer behaves. It is the +/// Contains [`Observer`] information. This defines how a given observer behaves. It is the /// "source of truth" for a given observer entity's behavior. pub struct ObserverState { pub(crate) descriptor: ObserverDescriptor, @@ -172,13 +172,13 @@ pub type BoxedObserverSystem = Box>; fn observer_system_runner( mut world: DeferredWorld, - trigger: ObserverTrigger, + observer_trigger: ObserverTrigger, ptr: PtrMut, ) { let world = world.as_unsafe_world_cell(); let observer_cell = // SAFETY: Observer was triggered so must still exist in world - unsafe { world.get_entity(trigger.observer).debug_checked_unwrap() }; + unsafe { world.get_entity(observer_trigger.observer).debug_checked_unwrap() }; // SAFETY: Observer was triggered so must have an `ObserverState` let mut state = unsafe { observer_cell @@ -194,9 +194,16 @@ fn observer_system_runner( } state.last_trigger_id = last_trigger; - let observer: Trigger = + let trigger: Trigger = // SAFETY: Caller ensures `ptr` is castable to `&mut T` - Trigger::new(unsafe { ptr.deref_mut() }, trigger); + Trigger::new(unsafe { ptr.deref_mut() }, observer_trigger); + // SAFETY: the static lifetime is encapsulated in Trigger / cannot leak out. + // Additionally, IntoObserverSystem is only implemented for functions starting + // with for<'a> Trigger<'a>, meaning users cannot specify Trigger<'static> manually, + // allowing the Trigger<'static> to be moved outside of the context of the system. + // This transmute is obviously not ideal, but it is safe. Ideally we can remove the + // static constraint from ObserverSystem, but so far we have not found a way. + let trigger: Trigger<'static, E, B> = unsafe { std::mem::transmute(trigger) }; // SAFETY: Observer was triggered so must have an `ObserverSystemComponent` let system = unsafe { &mut observer_cell @@ -213,7 +220,7 @@ fn observer_system_runner( // - system is an `ObserverSystem` so won't mutate world beyond the access of a `DeferredWorld` // - system is the same type erased system from above unsafe { - system.run_unsafe(std::mem::transmute(observer), world); + system.run_unsafe(trigger, world); system.queue_deferred(world.into_deferred()); } } diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 17c048da33756..4c9890fea7a98 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -751,12 +751,12 @@ impl<'w, 's> Commands<'w, 's> { /// Sends a "global" [`Trigger`] without any targets. pub fn trigger(&mut self, event: impl Event) { - self.add(TriggerEvent { event, targets: () }) + self.add(TriggerEvent { event, targets: () }); } /// Sends a [`Trigger`] with the given `targets`. pub fn trigger_targets(&mut self, event: impl Event, targets: impl TriggerTargets) { - self.add(TriggerEvent { event, targets }) + self.add(TriggerEvent { event, targets }); } /// Spawn an [`Observer`] and returns it's [`Entity`]. diff --git a/crates/bevy_ecs/src/world/command_queue.rs b/crates/bevy_ecs/src/world/command_queue.rs index a6b1a6e5a95bc..ecf42a3a7e0a6 100644 --- a/crates/bevy_ecs/src/world/command_queue.rs +++ b/crates/bevy_ecs/src/world/command_queue.rs @@ -262,13 +262,13 @@ impl RawCommandQueue { self.bytes.as_mut().as_mut_ptr().add(local_cursor).cast(), )) }; - // SAFETY: The data underneath the cursor must correspond to the type erased in metadata, - // since they were stored next to each other by `.push()`. - // For ZSTs, the type doesn't matter as long as the pointer is non-null. - // This also advances the cursor past the command. For ZSTs, the cursor will not move. - // At this point, it will either point to the next `CommandMeta`, - // or the cursor will be out of bounds and the loop will end. let result = panic::catch_unwind(AssertUnwindSafe(|| { + // SAFETY: The data underneath the cursor must correspond to the type erased in metadata, + // since they were stored next to each other by `.push()`. + // For ZSTs, the type doesn't matter as long as the pointer is non-null. + // This also advances the cursor past the command. For ZSTs, the cursor will not move. + // At this point, it will either point to the next `CommandMeta`, + // or the cursor will be out of bounds and the loop will end. unsafe { (meta.consume_command_and_get_size)(cmd, world, &mut local_cursor) }; })); @@ -293,7 +293,7 @@ impl RawCommandQueue { // when we call`resume_unwind" the caller "closer to the top" will catch the unwind and do this check, // until we reach the top. if start == 0 { - bytes.extend(panic_recovery.drain(..)) + bytes.append(panic_recovery); } panic::resume_unwind(payload); } From 76c6543463a0c87fe2f0f47f103d54b599402c93 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Tue, 11 Jun 2024 17:58:07 -0700 Subject: [PATCH 098/109] Update observers example to illustrate Observer components and reuse --- examples/ecs/observers.rs | 149 +++++++++++++++++++++----------------- 1 file changed, 83 insertions(+), 66 deletions(-) diff --git a/examples/ecs/observers.rs b/examples/ecs/observers.rs index e1e8c3b4a33c8..857a7c33a8e34 100644 --- a/examples/ecs/observers.rs +++ b/examples/ecs/observers.rs @@ -1,6 +1,7 @@ //! Demonstrates how to observe life-cycle triggers as well as define custom ones. use bevy::{ + ecs::observer::Observer, prelude::*, utils::{HashMap, HashSet}, }; @@ -11,7 +12,8 @@ fn main() { .init_resource::() .add_systems(Startup, setup) .add_systems(Update, (draw_shapes, handle_click)) - // Observers run when a certain event is triggered. This observer runs whenever `ExplodeMines` is triggered. + // Observers are systems that run when an event is "triggered". This observer runs whenever + // `ExplodeMines` is triggered. .observe( |trigger: Trigger, mines: Query<&Mine>, @@ -33,7 +35,6 @@ fn main() { ) // This observer runs whenever the `Mine` component is added to an entity, and places it in a simple spatial index. .observe(add_mine) - // Since observers run systems you can also define them as standalone functions rather than closures. // This observer runs whenever the `Mine` component is removed from an entity (including despawning it) // and removes it from the spatial index. .observe(remove_mine) @@ -46,6 +47,18 @@ struct Mine { size: f32, } +impl Mine { + fn random() -> Self { + Mine { + pos: Vec2::new( + (rand::random::() - 0.5) * 1200.0, + (rand::random::() - 0.5) * 600.0, + ), + size: 4.0 + rand::random::() * 16.0, + } + } +} + #[derive(Event)] struct ExplodeMines { pos: Vec2, @@ -55,81 +68,42 @@ struct ExplodeMines { #[derive(Event)] struct Explode; -fn setup(world: &mut World) { - world.spawn(Camera2dBundle::default()); - - let font = world - .resource::() - .load("fonts/FiraMono-Medium.ttf"); - world.spawn(TextBundle::from_section( +fn setup(mut commands: Commands, asset_server: Res) { + commands.spawn(Camera2dBundle::default()); + commands.spawn(TextBundle::from_section( "Click on a \"Mine\" to trigger it.\n\ When it explodes it will trigger all overlapping mines.", TextStyle { - font, + font: asset_server.load("fonts/FiraMono-Medium.ttf"), font_size: 24., color: Color::WHITE, }, )); - // Observers can also listen for triggers for a specific component. - - // Now we spawn a set of random mines. + commands + .spawn(Mine::random()) + // Observers can watch for events targeting a specific entity. + // This will create a new observer that runs whenever the Explode event + // is triggered for this spawned entity. + .observe(explode_mine); + + // We want to spawn a bunch of mines. We could just call the code above for each of them. + // That would create a new observer instance for every Mine entity. Having duplicate observers + // generally isn't worth worrying about as the overhead is low. But if you want to be maximally efficient, + // you can reuse observers across entities. + // + // First, observers are actually just entities with the Observer component! The `observe()` functions + // you've seen so far in this example are just shorthand for manually spawning an observer. + let mut observer = Observer::new(explode_mine); + + // As we spawn entities, we can make this observer watch each of them: for _ in 0..1000 { - world - .spawn(Mine { - pos: Vec2::new( - (rand::random::() - 0.5) * 1200.0, - (rand::random::() - 0.5) * 600.0, - ), - size: 4.0 + rand::random::() * 16.0, - }) - // Observers can also listen to events targeting a specific entity. - // This observer listens to `Explode` event triggers targeted at our mine. - .observe( - |trigger: Trigger, query: Query<&Mine>, mut commands: Commands| { - // If a triggered event is targeting a specific entity you can access it with `.source()` - let source = trigger.source(); - let Some(mut entity) = commands.get_entity(source) else { - return; - }; - println!("Boom! {:?} exploded.", source.index()); - entity.despawn(); - let mine = query.get(source).unwrap(); - // Fire another trigger to cascade into other mines. - commands.trigger(ExplodeMines { - pos: mine.pos, - radius: mine.size, - }); - }, - ); + let entity = commands.spawn(Mine::random()).id(); + observer.add_source(entity); } -} - -#[derive(Resource, Default)] -struct SpatialIndex { - map: HashMap<(i32, i32), HashSet>, -} -/// Cell size has to be bigger than any `TriggerMine::radius` -const CELL_SIZE: f32 = 64.0; - -impl SpatialIndex { - // Lookup all entities within adjacent cells of our spatial index - fn get_nearby(&self, pos: Vec2) -> Vec { - let tile = ( - (pos.x / CELL_SIZE).floor() as i32, - (pos.y / CELL_SIZE).floor() as i32, - ); - let mut nearby = Vec::new(); - for x in -1..2 { - for y in -1..2 { - if let Some(mines) = self.map.get(&(tile.0 + x, tile.1 + y)) { - nearby.extend(mines.iter()); - } - } - } - nearby - } + // By spawning the Observer component, it becomes active! + commands.spawn(observer); } fn add_mine(trigger: Trigger, query: Query<&Mine>, mut index: ResMut) { @@ -157,6 +131,22 @@ fn remove_mine( }); } +fn explode_mine(trigger: Trigger, query: Query<&Mine>, mut commands: Commands) { + // If a triggered event is targeting a specific entity you can access it with `.source()` + let source = trigger.source(); + let Some(mut entity) = commands.get_entity(source) else { + return; + }; + println!("Boom! {:?} exploded.", source.index()); + entity.despawn(); + let mine = query.get(source).unwrap(); + // Fire another trigger to cascade into other mines. + commands.trigger(ExplodeMines { + pos: mine.pos, + radius: mine.size, + }); +} + // Draw a circle for each mine using `Gizmos` fn draw_shapes(mut gizmos: Gizmos, mines: Query<&Mine>) { for mine in mines.iter() { @@ -187,3 +177,30 @@ fn handle_click( } } } + +#[derive(Resource, Default)] +struct SpatialIndex { + map: HashMap<(i32, i32), HashSet>, +} + +/// Cell size has to be bigger than any `TriggerMine::radius` +const CELL_SIZE: f32 = 64.0; + +impl SpatialIndex { + // Lookup all entities within adjacent cells of our spatial index + fn get_nearby(&self, pos: Vec2) -> Vec { + let tile = ( + (pos.x / CELL_SIZE).floor() as i32, + (pos.y / CELL_SIZE).floor() as i32, + ); + let mut nearby = Vec::new(); + for x in -1..2 { + for y in -1..2 { + if let Some(mines) = self.map.get(&(tile.0 + x, tile.1 + y)) { + nearby.extend(mines.iter()); + } + } + } + nearby + } +} From ed58fabba9cdd3bd0032fc3918b4f8a7e3bedccf Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Tue, 11 Jun 2024 18:17:55 -0700 Subject: [PATCH 099/109] Rename "source" concept to "entity" --- .../bevy_ecs/src/observer/entity_observer.rs | 10 +- crates/bevy_ecs/src/observer/mod.rs | 103 +++++++++--------- crates/bevy_ecs/src/observer/runner.rs | 20 ++-- crates/bevy_ecs/src/world/deferred_world.rs | 8 +- crates/bevy_ecs/src/world/entity_ref.rs | 2 +- examples/ecs/observers.rs | 20 ++-- 6 files changed, 82 insertions(+), 81 deletions(-) diff --git a/crates/bevy_ecs/src/observer/entity_observer.rs b/crates/bevy_ecs/src/observer/entity_observer.rs index 1f1abd2686881..f7fea1fb550ab 100644 --- a/crates/bevy_ecs/src/observer/entity_observer.rs +++ b/crates/bevy_ecs/src/observer/entity_observer.rs @@ -18,22 +18,22 @@ impl Component for ObservedBy { std::mem::take(&mut component.0) }; for e in observed_by { - let (total_sources, despawned_sources) = { + let (total_entities, despawned_watched_entities) = { let Some(mut entity_mut) = world.get_entity_mut(e) else { continue; }; let Some(mut state) = entity_mut.get_mut::() else { continue; }; - state.despawned_sources += 1; + state.despawned_watched_entities += 1; ( - state.descriptor.sources.len(), - state.despawned_sources as usize, + state.descriptor.entities.len(), + state.despawned_watched_entities as usize, ) }; // Despawn Observer if it has no more active sources. - if total_sources == despawned_sources { + if total_entities == despawned_watched_entities { world.commands().entity(e).despawn(); } } diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 00447359dcfaa..0651d0e4c318d 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -52,8 +52,8 @@ impl<'w, E, B: Bundle> Trigger<'w, E, B> { } /// Returns the entity that triggered the observer, could be [`Entity::PLACEHOLDER`]. - pub fn source(&self) -> Entity { - self.trigger.source + pub fn entity(&self) -> Entity { + self.trigger.entity } } @@ -67,7 +67,7 @@ pub struct ObserverDescriptor { components: Vec, /// The entities the observer is watching. - sources: Vec, + entities: Vec, } impl ObserverDescriptor { @@ -83,9 +83,9 @@ impl ObserverDescriptor { self } - /// Add the given `sources` to the descriptor. - pub fn with_sources(mut self, sources: Vec) -> Self { - self.sources = sources; + /// Add the given `entities` to the descriptor. + pub fn with_entities(mut self, entities: Vec) -> Self { + self.entities = entities; self } @@ -93,20 +93,20 @@ impl ObserverDescriptor { self.events.extend(descriptor.events.iter().copied()); self.components .extend(descriptor.components.iter().copied()); - self.sources.extend(descriptor.sources.iter().copied()); + self.entities.extend(descriptor.entities.iter().copied()); } } -/// Metadata for the source triggering an [`Observer`], +/// Event trigger metadata for a given [`Observer`], pub struct ObserverTrigger { - /// The [`Entity`] of observer handling the trigger. + /// The [`Entity`] of the observer handling the trigger. pub observer: Entity, - /// The [`ComponentId`] for the given trigger. + /// The [`ComponentId`] the trigger targeted. pub event_type: ComponentId, - /// The source where the trigger occurred. - pub source: Entity, + /// The entity the trigger targeted. + pub entity: Entity, } // Map between an observer entity and it's runner @@ -165,7 +165,7 @@ impl Observers { pub(crate) fn invoke( mut world: DeferredWorld, event_type: ComponentId, - source: Entity, + entity: Entity, components: impl Iterator, data: &mut T, ) { @@ -188,7 +188,7 @@ impl Observers { ObserverTrigger { observer, event_type, - source, + entity, }, data.into(), ); @@ -198,8 +198,8 @@ impl Observers { observers.map.iter().for_each(&mut trigger_observer); // Trigger entity observers listening for this kind of trigger - if source != Entity::PLACEHOLDER { - if let Some(map) = observers.entity_observers.get(&source) { + if entity != Entity::PLACEHOLDER { + if let Some(map) = observers.entity_observers.get(&entity) { map.iter().for_each(&mut trigger_observer); } } @@ -212,8 +212,8 @@ impl Observers { .iter() .for_each(&mut trigger_observer); - if source != Entity::PLACEHOLDER { - if let Some(map) = component_observers.entity_map.get(&source) { + if entity != Entity::PLACEHOLDER { + if let Some(map) = component_observers.entity_map.get(&entity) { map.iter().for_each(&mut trigger_observer); } } @@ -275,15 +275,16 @@ impl World { } /// Register an observer to the cache, called when an observer is created - pub(crate) fn register_observer(&mut self, entity: Entity) { + pub(crate) fn register_observer(&mut self, observer_entity: Entity) { // SAFETY: References do not alias. let (observer_state, archetypes, observers) = unsafe { - let observer_state: *const ObserverState = self.get::(entity).unwrap(); - // Populate ObservedBy for each observed source. - for source in &(*observer_state).descriptor.sources { - let mut source_mut = self.entity_mut(*source); - let mut observed_by = source_mut.entry::().or_default(); - observed_by.0.push(entity); + let observer_state: *const ObserverState = + self.get::(observer_entity).unwrap(); + // Populate ObservedBy for each observed entity. + for watched_entity in &(*observer_state).descriptor.entities { + let mut entity_mut = self.entity_mut(*watched_entity); + let mut observed_by = entity_mut.entry::().or_default(); + observed_by.0.push(observer_entity); } (&*observer_state, &mut self.archetypes, &mut self.observers) }; @@ -292,16 +293,16 @@ impl World { for &event_type in &descriptor.events { let cache = observers.get_observers(event_type); - if descriptor.components.is_empty() && descriptor.sources.is_empty() { - cache.map.insert(entity, observer_state.runner); + if descriptor.components.is_empty() && descriptor.entities.is_empty() { + cache.map.insert(observer_entity, observer_state.runner); } else if descriptor.components.is_empty() { // Observer is not targeting any components so register it as an entity observer - for &source in &observer_state.descriptor.sources { - let map = cache.entity_observers.entry(source).or_default(); - map.insert(entity, observer_state.runner); + for &watched_entity in &observer_state.descriptor.entities { + let map = cache.entity_observers.entry(watched_entity).or_default(); + map.insert(observer_entity, observer_state.runner); } } else { - // Register observer for each source component + // Register observer for each watched component for &component in &descriptor.components { let observers = cache @@ -313,14 +314,14 @@ impl World { } CachedComponentObservers::default() }); - if descriptor.sources.is_empty() { + if descriptor.entities.is_empty() { // Register for all triggers targeting the component - observers.map.insert(entity, observer_state.runner); + observers.map.insert(observer_entity, observer_state.runner); } else { - // Register for each targeted entity - for &source in &descriptor.sources { - let map = observers.entity_map.entry(source).or_default(); - map.insert(entity, observer_state.runner); + // Register for each watched entity + for &watched_entity in &descriptor.entities { + let map = observers.entity_map.entry(watched_entity).or_default(); + map.insert(observer_entity, observer_state.runner); } } } @@ -335,17 +336,17 @@ impl World { for &event_type in &descriptor.events { let cache = observers.get_observers(event_type); - if descriptor.components.is_empty() && descriptor.sources.is_empty() { + if descriptor.components.is_empty() && descriptor.entities.is_empty() { cache.map.remove(&entity); } else if descriptor.components.is_empty() { - for source in &descriptor.sources { + for watched_entity in &descriptor.entities { // This check should be unnecessary since this observer hasn't been unregistered yet - let Some(observers) = cache.entity_observers.get_mut(source) else { + let Some(observers) = cache.entity_observers.get_mut(watched_entity) else { continue; }; observers.remove(&entity); if observers.is_empty() { - cache.entity_observers.remove(source); + cache.entity_observers.remove(watched_entity); } } } else { @@ -353,16 +354,16 @@ impl World { let Some(observers) = cache.component_observers.get_mut(component) else { continue; }; - if descriptor.sources.is_empty() { + if descriptor.entities.is_empty() { observers.map.remove(&entity); } else { - for source in &descriptor.sources { - let Some(map) = observers.entity_map.get_mut(source) else { + for watched_entity in &descriptor.entities { + let Some(map) = observers.entity_map.get_mut(watched_entity) else { continue; }; map.remove(&entity); if map.is_empty() { - observers.entity_map.remove(source); + observers.entity_map.remove(watched_entity); } } } @@ -447,20 +448,20 @@ mod tests { world.observe( |obs: Trigger, mut res: ResMut, mut commands: Commands| { res.assert_order(0); - commands.entity(obs.source()).insert(B); + commands.entity(obs.entity()).insert(B); }, ); world.observe( |obs: Trigger, mut res: ResMut, mut commands: Commands| { res.assert_order(2); - commands.entity(obs.source()).remove::(); + commands.entity(obs.entity()).remove::(); }, ); world.observe( |obs: Trigger, mut res: ResMut, mut commands: Commands| { res.assert_order(1); - commands.entity(obs.source()).remove::(); + commands.entity(obs.entity()).remove::(); }, ); world.observe(|_: Trigger, mut res: ResMut| { @@ -542,7 +543,7 @@ mod tests { } #[test] - fn observer_no_source() { + fn observer_no_target() { let mut world = World::new(); world.init_resource::(); @@ -550,7 +551,7 @@ mod tests { .spawn_empty() .observe(|_: Trigger| panic!("Trigger routed to non-targeted entity.")); world.observe(move |obs: Trigger, mut res: ResMut| { - assert_eq!(obs.source(), Entity::PLACEHOLDER); + assert_eq!(obs.entity(), Entity::PLACEHOLDER); res.0 += 1; }); @@ -575,7 +576,7 @@ mod tests { .observe(|_: Trigger, mut res: ResMut| res.0 += 1) .id(); world.observe(move |obs: Trigger, mut res: ResMut| { - assert_eq!(obs.source(), entity); + assert_eq!(obs.entity(), entity); res.0 += 1; }); diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index 651707d606c3e..dd05ee3603c86 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -14,7 +14,7 @@ pub struct ObserverState { pub(crate) descriptor: ObserverDescriptor, pub(crate) runner: ObserverRunner, pub(crate) last_trigger_id: u32, - pub(crate) despawned_sources: u32, + pub(crate) despawned_watched_entities: u32, } impl Default for ObserverState { @@ -22,7 +22,7 @@ impl Default for ObserverState { Self { runner: |_, _, _| {}, last_trigger_id: 0, - despawned_sources: 0, + despawned_watched_entities: 0, descriptor: Default::default(), } } @@ -41,13 +41,13 @@ impl ObserverState { self } - /// Adds the given [`Entity`] `sources` - pub fn with_sources(mut self, sources: impl IntoIterator) -> Self { - self.descriptor.sources.extend(sources); + /// Watches the given [`Entity`] list. + pub fn with_entities(mut self, entities: impl IntoIterator) -> Self { + self.descriptor.entities.extend(entities); self } - /// Adds the given [`ComponentId`] `components` + /// Watches the given [`ComponentId`] list. pub fn with_components(mut self, components: impl IntoIterator) -> Self { self.descriptor.components.extend(components); self @@ -100,14 +100,14 @@ impl Observer { } /// Observe the given `entity`. - pub fn with_source(mut self, entity: Entity) -> Self { - self.descriptor.sources.push(entity); + pub fn with_entity(mut self, entity: Entity) -> Self { + self.descriptor.entities.push(entity); self } /// Observe the given `entity`. Note that if this is called _after_ an [`Observer`] is spawned, it will produce no effects. - pub fn add_source(&mut self, entity: Entity) { - self.descriptor.sources.push(entity); + pub fn watch_entity(&mut self, entity: Entity) { + self.descriptor.entities.push(entity); } /// Observe the given `component`. diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index ae6e547774d83..71f5a059aed20 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -347,10 +347,10 @@ impl<'w> DeferredWorld<'w> { pub(crate) unsafe fn trigger_observers( &mut self, event: ComponentId, - source: Entity, + entity: Entity, components: impl Iterator, ) { - Observers::invoke(self.reborrow(), event, source, components, &mut ()); + Observers::invoke(self.reborrow(), event, entity, components, &mut ()); } /// Triggers all event observers for [`ComponentId`] in target. @@ -361,11 +361,11 @@ impl<'w> DeferredWorld<'w> { pub(crate) unsafe fn trigger_observers_with_data( &mut self, event: ComponentId, - source: Entity, + entity: Entity, components: impl Iterator, data: &mut E, ) { - Observers::invoke(self.reborrow(), event, source, components, data); + Observers::invoke(self.reborrow(), event, entity, components, data); } /// Sends a "global" [`Trigger`] without any targets. diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 97a586a610512..6ac5e9c1b1b86 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -1420,7 +1420,7 @@ impl<'w> EntityWorldMut<'w> { &mut self, observer: impl IntoObserverSystem, ) -> &mut Self { - self.insert(Observer::new(observer).with_source(self.entity)) + self.insert(Observer::new(observer).with_entity(self.entity)) } } diff --git a/examples/ecs/observers.rs b/examples/ecs/observers.rs index 857a7c33a8e34..802be88d7b30c 100644 --- a/examples/ecs/observers.rs +++ b/examples/ecs/observers.rs @@ -99,7 +99,7 @@ fn setup(mut commands: Commands, asset_server: Res) { // As we spawn entities, we can make this observer watch each of them: for _ in 0..1000 { let entity = commands.spawn(Mine::random()).id(); - observer.add_source(entity); + observer.watch_entity(entity); } // By spawning the Observer component, it becomes active! @@ -107,12 +107,12 @@ fn setup(mut commands: Commands, asset_server: Res) { } fn add_mine(trigger: Trigger, query: Query<&Mine>, mut index: ResMut) { - let mine = query.get(trigger.source()).unwrap(); + let mine = query.get(trigger.entity()).unwrap(); let tile = ( (mine.pos.x / CELL_SIZE).floor() as i32, (mine.pos.y / CELL_SIZE).floor() as i32, ); - index.map.entry(tile).or_default().insert(trigger.source()); + index.map.entry(tile).or_default().insert(trigger.entity()); } // Remove despawned mines from our index @@ -121,25 +121,25 @@ fn remove_mine( query: Query<&Mine>, mut index: ResMut, ) { - let mine = query.get(trigger.source()).unwrap(); + let mine = query.get(trigger.entity()).unwrap(); let tile = ( (mine.pos.x / CELL_SIZE).floor() as i32, (mine.pos.y / CELL_SIZE).floor() as i32, ); index.map.entry(tile).and_modify(|set| { - set.remove(&trigger.source()); + set.remove(&trigger.entity()); }); } fn explode_mine(trigger: Trigger, query: Query<&Mine>, mut commands: Commands) { // If a triggered event is targeting a specific entity you can access it with `.source()` - let source = trigger.source(); - let Some(mut entity) = commands.get_entity(source) else { + let id = trigger.entity(); + let Some(mut entity) = commands.get_entity(id) else { return; }; - println!("Boom! {:?} exploded.", source.index()); + println!("Boom! {:?} exploded.", id.index()); entity.despawn(); - let mine = query.get(source).unwrap(); + let mine = query.get(id).unwrap(); // Fire another trigger to cascade into other mines. commands.trigger(ExplodeMines { pos: mine.pos, @@ -149,7 +149,7 @@ fn explode_mine(trigger: Trigger, query: Query<&Mine>, mut commands: Co // Draw a circle for each mine using `Gizmos` fn draw_shapes(mut gizmos: Gizmos, mines: Query<&Mine>) { - for mine in mines.iter() { + for mine in &mines { gizmos.circle_2d( mine.pos, mine.size, From cfb5ca136543f8df8b2c197b8182380a96f4cad1 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 12 Jun 2024 11:36:10 -0700 Subject: [PATCH 100/109] Don't spawn observers on their target entities (this was an accident) --- crates/bevy_ecs/src/world/entity_ref.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 6ac5e9c1b1b86..45f2c9e34cdcd 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -1420,7 +1420,9 @@ impl<'w> EntityWorldMut<'w> { &mut self, observer: impl IntoObserverSystem, ) -> &mut Self { - self.insert(Observer::new(observer).with_entity(self.entity)) + self.world + .spawn(Observer::new(observer).with_entity(self.entity)); + self } } From 8040b94052fb75eee9a9cad2a8f672878df9b5f4 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 12 Jun 2024 13:28:08 -0700 Subject: [PATCH 101/109] Resolve some comments --- Cargo.toml | 2 +- crates/bevy_app/src/app.rs | 2 +- crates/bevy_ecs/src/archetype.rs | 12 ++++++-- crates/bevy_ecs/src/bundle.rs | 2 +- crates/bevy_ecs/src/event/base.rs | 16 ++++++++++- crates/bevy_ecs/src/lib.rs | 2 +- crates/bevy_ecs/src/observer/emit_trigger.rs | 7 ++++- .../bevy_ecs/src/observer/entity_observer.rs | 2 +- crates/bevy_ecs/src/observer/mod.rs | 9 ++++-- crates/bevy_ecs/src/observer/runner.rs | 28 +++++++++++++------ crates/bevy_ecs/src/query/access.rs | 2 +- crates/bevy_ecs/src/system/commands/mod.rs | 8 ++++-- crates/bevy_ecs/src/system/system_param.rs | 2 +- crates/bevy_ecs/src/world/entity_ref.rs | 2 +- crates/bevy_ecs/src/world/mod.rs | 2 ++ examples/ecs/observers.rs | 19 +++++++------ 16 files changed, 81 insertions(+), 36 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e63ff485637fb..88d860d75b83a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2541,7 +2541,7 @@ doc-scrape-examples = true [package.metadata.example.observers] name = "Observers" -description = "Define observers to react to ECS events" +description = "Demonstrates observers that react to events (both built-in life-cycle events and custom events)" category = "ECS (Entity Component System)" wasm = false diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index b8e1c44cb7599..43d2d0f8528ec 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -832,7 +832,7 @@ impl App { None } - /// Spawn the given [`Observer`]. + /// Spawns an [`Observer`] entity, which will watch for and respond to the given event. pub fn observe( &mut self, observer: impl IntoObserverSystem, diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 4c7984af3caf5..05b0a260cfe4e 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -605,19 +605,25 @@ impl Archetype { self.flags().contains(ArchetypeFlags::ON_REMOVE_HOOK) } - /// Returns true if any of the components in this archetype have at least one `OnAdd` observer + /// Returns true if any of the components in this archetype have at least one [`OnAdd`] observer + /// + /// [`OnAdd`]: crate::world::OnAdd #[inline] pub fn has_add_observer(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_ADD_OBSERVER) } - /// Returns true if any of the components in this archetype have at least one `OnInsert` observer + /// Returns true if any of the components in this archetype have at least one [`OnInsert`] observer + /// + /// [`OnInsert`]: crate::world::OnInsert #[inline] pub fn has_insert_observer(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_INSERT_OBSERVER) } - /// Returns true if any of the components in this archetype have at least one `OnRemove` observer + /// Returns true if any of the components in this archetype have at least one [`OnRemove`] observer + /// + /// [`OnRemove`]: crate::world::OnRemove #[inline] pub fn has_remove_observer(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_REMOVE_OBSERVER) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 5693e03095425..384f7d7a99c76 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -157,7 +157,7 @@ pub unsafe trait Bundle: DynamicBundle + Send + Sync + 'static { ids: &mut impl FnMut(ComponentId), ); - /// Gets this [`Bundle`]'s component ids, will be `None` if the component has not been registered. + /// Gets this [`Bundle`]'s component ids. This will be [`None`] if the component has not been registered. fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option)); /// Calls `func`, which should return data for each component in the bundle, in the order of diff --git a/crates/bevy_ecs/src/event/base.rs b/crates/bevy_ecs/src/event/base.rs index ee2b6d2f78661..acc830da447bd 100644 --- a/crates/bevy_ecs/src/event/base.rs +++ b/crates/bevy_ecs/src/event/base.rs @@ -8,10 +8,24 @@ use std::{ marker::PhantomData, }; -/// A type that can be stored in an [`Events`] resource +/// Something that "happens" and might be read / observed by app logic. +/// +/// Events can be stored in an [`Events`] resource /// You can conveniently access events using the [`EventReader`] and [`EventWriter`] system parameter. /// +/// Events can also be "triggered" on a [`World`], which will then cause any [`Observer`] of that trigger to run. +/// +/// This trait can be derived. +/// +/// Events implement the [`Component`] type (and they automatically do when they are derived). Events are (generally) +/// not directly inserted as components. More often, the [`ComponentId`] is used to identify the event type within the +/// context of the ECS. +/// /// Events must be thread-safe. +/// +/// [`World`]: crate::world::World +/// [`ComponentId`]: crate::component::ComponentId +/// [`Observer`]: crate::observer::Observer #[diagnostic::on_unimplemented( message = "`{Self}` is not an `Event`", label = "invalid `Event`", diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 73218f2718aad..307c7a94a16e5 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -47,7 +47,7 @@ pub mod prelude { component::Component, entity::{Entity, EntityMapper}, event::{Event, EventReader, EventWriter, Events}, - observer::Trigger, + observer::{Observer, Trigger}, query::{Added, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without}, removal_detection::RemovedComponents, schedule::{ diff --git a/crates/bevy_ecs/src/observer/emit_trigger.rs b/crates/bevy_ecs/src/observer/emit_trigger.rs index 8f706d4838d44..ff4000c8ee688 100644 --- a/crates/bevy_ecs/src/observer/emit_trigger.rs +++ b/crates/bevy_ecs/src/observer/emit_trigger.rs @@ -80,7 +80,12 @@ fn trigger_event( } } -/// Represents a collection of targets, which can be of type [`Entity`] or [`ComponentId`]. +/// Represents a collection of targets for a specific [`Trigger`] of an [`Event`]. Targets can be of type [`Entity`] or [`ComponentId`]. +/// When a trigger occurs for a given event and [`TriggerTargets`], any [`Observer`] that watches for that specific event-target combination +/// will run. +/// +/// [`Trigger`]: crate::observer::Trigger +/// [`Observer`]: crate::observer::Observer pub trait TriggerTargets: Send + Sync + 'static { /// The components the trigger should target. fn components(&self) -> impl ExactSizeIterator; diff --git a/crates/bevy_ecs/src/observer/entity_observer.rs b/crates/bevy_ecs/src/observer/entity_observer.rs index f7fea1fb550ab..ce30201785758 100644 --- a/crates/bevy_ecs/src/observer/entity_observer.rs +++ b/crates/bevy_ecs/src/observer/entity_observer.rs @@ -4,7 +4,7 @@ use crate::{ observer::ObserverState, }; -/// Tracks a list of entity observers for the attached entity +/// Tracks a list of entity observers for the [`Entity`] [`ObservedBy`] is added to. #[derive(Default)] pub(crate) struct ObservedBy(pub(crate) Vec); diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 0651d0e4c318d..976869fc8d0ee 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -14,7 +14,8 @@ use bevy_ptr::Ptr; use bevy_utils::{EntityHashMap, HashMap}; use std::marker::PhantomData; -/// Type used in callbacks registered for observers. +/// Type containing triggered [`Event`] information for a given run of an [`Observer`]. This contains the +/// [`Event`] data itself. If it was triggered for a specific [`Entity`], it includes that as well. pub struct Trigger<'w, E, B: Bundle = ()> { event: &'w mut E, trigger: ObserverTrigger, @@ -98,6 +99,7 @@ impl ObserverDescriptor { } /// Event trigger metadata for a given [`Observer`], +#[derive(Debug)] pub struct ObserverTrigger { /// The [`Entity`] of the observer handling the trigger. pub observer: Entity, @@ -109,7 +111,7 @@ pub struct ObserverTrigger { pub entity: Entity, } -// Map between an observer entity and it's runner +// Map between an observer entity and its runner type ObserverMap = EntityHashMap; /// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular trigger targeted at a specific component. @@ -162,6 +164,7 @@ impl Observers { } } + /// This will run the observers of the given `event_type`, targeting the given `entity` and `components`. pub(crate) fn invoke( mut world: DeferredWorld, event_type: ComponentId, @@ -256,7 +259,7 @@ impl Observers { } impl World { - /// Spawn an [`Observer`] and returns it's [`Entity`]. + /// Spawn a "global" [`Observer`] and returns it's [`Entity`]. pub fn observe( &mut self, system: impl IntoObserverSystem, diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index dd05ee3603c86..1bed428a6a047 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -29,25 +29,29 @@ impl Default for ObserverState { } impl ObserverState { - /// Adds the given `event` + /// Observe the given `event`. This will cause the [`Observer`] to run whenever an event with the given [`ComponentId`] + /// is triggered. pub fn with_event(mut self, event: ComponentId) -> Self { self.descriptor.events.push(event); self } - /// Adds the given `events` + /// Observe the given event list. This will cause the [`Observer`] to run whenever an event with any of the given [`ComponentId`]s + /// is triggered. pub fn with_events(mut self, events: impl IntoIterator) -> Self { self.descriptor.events.extend(events); self } - /// Watches the given [`Entity`] list. + /// Observe the given [`Entity`] list. This will cause the [`Observer`] to run whenever the [`Event`] is triggered + /// for any [`Entity`] target in the list. pub fn with_entities(mut self, entities: impl IntoIterator) -> Self { self.descriptor.entities.extend(entities); self } - /// Watches the given [`ComponentId`] list. + /// Observe the given [`ComponentId`] list. This will cause the [`Observer`] to run whenever the [`Event`] is triggered + /// for any [`ComponentId`] target in the list. pub fn with_components(mut self, components: impl IntoIterator) -> Self { self.descriptor.components.extend(components); self @@ -91,7 +95,8 @@ pub struct Observer { } impl Observer { - /// Creates a new [`Observer`], which defaults to a "global" observer. + /// Creates a new [`Observer`], which defaults to a "global" observer. This means it will run whenever the event `E` is triggered + /// for _any_ entity (or no entity). pub fn new(system: impl IntoObserverSystem) -> Self { Self { system: Box::new(IntoObserverSystem::into_system(system)), @@ -99,24 +104,29 @@ impl Observer { } } - /// Observe the given `entity`. + /// Observe the given `entity`. This will cause the [`Observer`] to run whenever the [`Event`] is triggered + /// for the `entity`. pub fn with_entity(mut self, entity: Entity) -> Self { self.descriptor.entities.push(entity); self } - /// Observe the given `entity`. Note that if this is called _after_ an [`Observer`] is spawned, it will produce no effects. + /// Observe the given `entity`. This will cause the [`Observer`] to run whenever the [`Event`] is triggered + /// for the `entity`. + /// Note that if this is called _after_ an [`Observer`] is spawned, it will produce no effects. pub fn watch_entity(&mut self, entity: Entity) { self.descriptor.entities.push(entity); } - /// Observe the given `component`. + /// Observe the given `component`. This will cause the [`Observer`] to run whenever the [`Event`] is triggered + /// with the given component target. pub fn with_component(mut self, component: ComponentId) -> Self { self.descriptor.components.push(component); self } - /// Observe the given [`trigger`]. + /// Observe the given `event`. This will cause the [`Observer`] to run whenever an event with the given [`ComponentId`] + /// is triggered. pub fn with_event(mut self, event: ComponentId) -> Self { self.descriptor.events.push(event); self diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index de903603c18cf..3ee0e484ab292 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -647,7 +647,7 @@ impl FilteredAccessSet { .extend(filtered_access_set.filtered_accesses); } - /// Marks the set as reading all T. + /// Marks the set as reading all possible indices of type T. pub fn read_all(&mut self) { self.combined_access.read_all(); } diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 4c9890fea7a98..028182e69f00c 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -749,17 +749,19 @@ impl<'w, 's> Commands<'w, 's> { self.push(command); } - /// Sends a "global" [`Trigger`] without any targets. + /// Sends a "global" [`Trigger`] without any targets. This will run any [`Observer`] of the `event` that + /// isn't scoped to specific targets. pub fn trigger(&mut self, event: impl Event) { self.add(TriggerEvent { event, targets: () }); } - /// Sends a [`Trigger`] with the given `targets`. + /// Sends a [`Trigger`] for the given targets. This will run any [`Observer`] of the `event` that + /// watches those targets. pub fn trigger_targets(&mut self, event: impl Event, targets: impl TriggerTargets) { self.add(TriggerEvent { event, targets }); } - /// Spawn an [`Observer`] and returns it's [`Entity`]. + /// Spawn an [`Observer`] and returns the [`EntityCommands`] associated with the entity that stores the observer. pub fn observe( &mut self, observer: impl IntoObserverSystem, diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index ce562ab55cf56..aae8e4e2e26f3 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -717,7 +717,7 @@ unsafe impl SystemParam for &'_ World { } } -/// # Safety: `DeferredWorld` can read all components and resources but cannot be used to gain any other mutable references. +/// SAFETY: `DeferredWorld` can read all components and resources but cannot be used to gain any other mutable references. unsafe impl<'w> SystemParam for DeferredWorld<'w> { type State = (); type Item<'world, 'state> = DeferredWorld<'world>; diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 45f2c9e34cdcd..2f81c03514469 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -1414,7 +1414,7 @@ impl<'w> EntityWorldMut<'w> { } } - /// Creates an [`Observer`](crate::observer::Observer) listening for `E` events targeting this entity. + /// Creates an [`Observer`](crate::observer::Observer) listening for events of type `E` targeting this entity. /// In order to trigger the callback the entity must also match the query when the event is fired. pub fn observe( &mut self, diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index fbb0f057ec319..d509eaa349c65 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -158,6 +158,8 @@ impl Drop for World { } impl World { + /// This performs initialization that _must_ happen for every [`World`] immediately upon creation (such as claiming specific component ids). + /// This _must_ be run as part of constructing a [`World`], before it is returned to the caller. #[inline] fn bootstrap(&mut self) { assert_eq!(ON_ADD, self.init_component::()); diff --git a/examples/ecs/observers.rs b/examples/ecs/observers.rs index 802be88d7b30c..0ebdfac701afd 100644 --- a/examples/ecs/observers.rs +++ b/examples/ecs/observers.rs @@ -1,7 +1,6 @@ //! Demonstrates how to observe life-cycle triggers as well as define custom ones. use bevy::{ - ecs::observer::Observer, prelude::*, utils::{HashMap, HashSet}, }; @@ -34,10 +33,10 @@ fn main() { }, ) // This observer runs whenever the `Mine` component is added to an entity, and places it in a simple spatial index. - .observe(add_mine) + .observe(on_add_mine) // This observer runs whenever the `Mine` component is removed from an entity (including despawning it) // and removes it from the spatial index. - .observe(remove_mine) + .observe(on_remove_mine) .run(); } @@ -106,7 +105,11 @@ fn setup(mut commands: Commands, asset_server: Res) { commands.spawn(observer); } -fn add_mine(trigger: Trigger, query: Query<&Mine>, mut index: ResMut) { +fn on_add_mine( + trigger: Trigger, + query: Query<&Mine>, + mut index: ResMut, +) { let mine = query.get(trigger.entity()).unwrap(); let tile = ( (mine.pos.x / CELL_SIZE).floor() as i32, @@ -116,7 +119,7 @@ fn add_mine(trigger: Trigger, query: Query<&Mine>, mut index: ResMu } // Remove despawned mines from our index -fn remove_mine( +fn on_remove_mine( trigger: Trigger, query: Query<&Mine>, mut index: ResMut, @@ -132,7 +135,7 @@ fn remove_mine( } fn explode_mine(trigger: Trigger, query: Query<&Mine>, mut commands: Commands) { - // If a triggered event is targeting a specific entity you can access it with `.source()` + // If a triggered event is targeting a specific entity you can access it with `.entity()` let id = trigger.entity(); let Some(mut entity) = commands.get_entity(id) else { return; @@ -140,7 +143,7 @@ fn explode_mine(trigger: Trigger, query: Query<&Mine>, mut commands: Co println!("Boom! {:?} exploded.", id.index()); entity.despawn(); let mine = query.get(id).unwrap(); - // Fire another trigger to cascade into other mines. + // Trigger another explosion cascade. commands.trigger(ExplodeMines { pos: mine.pos, radius: mine.size, @@ -158,7 +161,7 @@ fn draw_shapes(mut gizmos: Gizmos, mines: Query<&Mine>) { } } -// Fire an initial `ExplodeMines` trigger on click +// Trigger `ExplodeMines` at the position of a given click fn handle_click( mouse_button_input: Res>, camera: Query<(&Camera, &GlobalTransform)>, From 949a3d498e72dcb456c9624f14afda69f1cbdc74 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 12 Jun 2024 13:41:55 -0700 Subject: [PATCH 102/109] Remove some duplicate code --- crates/bevy_ecs/src/world/entity_ref.rs | 47 +++++++++++++++---------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 2f81c03514469..9fa1021923522 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -10,7 +10,7 @@ use crate::{ removal_detection::RemovedComponentEvents, storage::Storages, system::IntoObserverSystem, - world::{Mut, World}, + world::{DeferredWorld, Mut, World}, }; use bevy_ptr::{OwningPtr, Ptr}; use std::{any::TypeId, marker::PhantomData}; @@ -902,16 +902,14 @@ impl<'w> EntityWorldMut<'w> { ) }; - // SAFETY: All components in the archetype exist in world + // SAFETY: all bundle components exist in World unsafe { - deferred_world.trigger_on_remove(old_archetype, entity, bundle_info.iter_components()); - if old_archetype.has_remove_observer() { - deferred_world.trigger_observers( - ON_REMOVE, - self.entity, - bundle_info.iter_components(), - ); - } + trigger_on_remove_hooks_and_observers( + &mut deferred_world, + old_archetype, + entity, + bundle_info, + ); } let archetypes = &mut world.archetypes; @@ -1085,16 +1083,14 @@ impl<'w> EntityWorldMut<'w> { ) }; - // SAFETY: All components in the archetype exist in world + // SAFETY: all bundle components exist in World unsafe { - deferred_world.trigger_on_remove(old_archetype, entity, bundle_info.iter_components()); - if old_archetype.has_remove_observer() { - deferred_world.trigger_observers( - ON_REMOVE, - self.entity, - bundle_info.iter_components(), - ); - } + trigger_on_remove_hooks_and_observers( + &mut deferred_world, + old_archetype, + entity, + bundle_info, + ); } let old_archetype = &world.archetypes[location.archetype_id]; @@ -1426,6 +1422,19 @@ impl<'w> EntityWorldMut<'w> { } } +/// SAFETY: all components in the archetype must exist in world +unsafe fn trigger_on_remove_hooks_and_observers( + deferred_world: &mut DeferredWorld, + archetype: &Archetype, + entity: Entity, + bundle_info: &BundleInfo, +) { + deferred_world.trigger_on_remove(archetype, entity, bundle_info.iter_components()); + if archetype.has_remove_observer() { + deferred_world.trigger_observers(ON_REMOVE, entity, bundle_info.iter_components()); + } +} + /// A view into a single entity and component in a world, which may either be vacant or occupied. /// /// This `enum` can only be constructed from the [`entry`] method on [`EntityWorldMut`]. From 9b2c982f1432b5029ab1b48b1e90b7a9359443a7 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 12 Jun 2024 13:53:56 -0700 Subject: [PATCH 103/109] Rename emit_trigger module to trigger_event --- crates/bevy_ecs/src/observer/mod.rs | 4 ++-- .../src/observer/{emit_trigger.rs => trigger_event.rs} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename crates/bevy_ecs/src/observer/{emit_trigger.rs => trigger_event.rs} (100%) diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 976869fc8d0ee..08e87854c4c56 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -1,11 +1,11 @@ //! Types for creating and storing [`Observer`]s -mod emit_trigger; mod entity_observer; mod runner; +mod trigger_event; -pub use emit_trigger::*; pub use runner::*; +pub use trigger_event::*; use crate::observer::entity_observer::ObservedBy; use crate::{archetype::ArchetypeFlags, system::IntoObserverSystem, world::*}; diff --git a/crates/bevy_ecs/src/observer/emit_trigger.rs b/crates/bevy_ecs/src/observer/trigger_event.rs similarity index 100% rename from crates/bevy_ecs/src/observer/emit_trigger.rs rename to crates/bevy_ecs/src/observer/trigger_event.rs From af4c0824b1116c4ac4f1a71a85d19dac561a2bba Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 14 Jun 2024 12:26:44 -0700 Subject: [PATCH 104/109] Do a full flush (including entities) in command queue application instead of just a flush_commands. --- crates/bevy_ecs/src/world/command_queue.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/world/command_queue.rs b/crates/bevy_ecs/src/world/command_queue.rs index ecf42a3a7e0a6..721248ea859ad 100644 --- a/crates/bevy_ecs/src/world/command_queue.rs +++ b/crates/bevy_ecs/src/world/command_queue.rs @@ -183,7 +183,7 @@ impl RawCommandQueue { // The command may have queued up world commands, which we flush here to ensure they are also picked up. // If the current command queue already the World Command queue, this will still behave appropriately because the global cursor // is still at the current `stop`, ensuring only the newly queued Commands will be applied. - world.flush_commands(); + world.flush(); } // ...or discard it. None => drop(command), From 1017f39a4aa285fd2b9911f775cab71b1a98994e Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 14 Jun 2024 18:00:39 -0700 Subject: [PATCH 105/109] Add primary / top level observer docs --- crates/bevy_ecs/README.md | 46 ++++++ crates/bevy_ecs/src/event/writer.rs | 7 + crates/bevy_ecs/src/observer/runner.rs | 185 ++++++++++++++++++++++++- 3 files changed, 232 insertions(+), 6 deletions(-) diff --git a/crates/bevy_ecs/README.md b/crates/bevy_ecs/README.md index 81cc9104dfe93..930faed1fcb3f 100644 --- a/crates/bevy_ecs/README.md +++ b/crates/bevy_ecs/README.md @@ -307,4 +307,50 @@ fn reader(mut reader: EventReader) { A minimal set up using events can be seen in [`events.rs`](examples/events.rs). +### Observers + +Observers are systems that listen for a "trigger" of a specific `Event`: + +```rust +use bevy_ecs::prelude::*; + +#[derive(Event)] +struct MyEvent { + message: String +} + +world.observe(|trigger: Trigger| { + println!("{}", trigger.event().message); +}); + +world.flush(); + +world.trigger(MyEvent { + message: "hello!".to_string(), +}); +``` + +These differ from `EventReader` and `EventWriter` in that they are "reactive". Rather than happening at a specific point in a schedule, they happen _immediately_ whenever a trigger happens. Triggers can trigger other triggers, and they all will be evaluated at the same time! + +Events can also be triggered to target specific entities: + +```rust +use bevy_ecs::prelude::*; + +let mut world = World::new(); +let entity = world.spawn_empty().id(); + +#[derive(Event)] +struct Explode; + +world.observe(|trigger: Trigger, mut commands: Commands| { + println!("Entity {:?} goes BOOM!", trigger.entity()); + commands.entity(trigger.entity()).despawn(); +}); + +world.flush(); + +world.trigger_targets(Explode, entity); +``` + [bevy]: https://bevyengine.org/ diff --git a/crates/bevy_ecs/src/event/writer.rs b/crates/bevy_ecs/src/event/writer.rs index c5d8e5b9b6d29..0ba8d80778452 100644 --- a/crates/bevy_ecs/src/event/writer.rs +++ b/crates/bevy_ecs/src/event/writer.rs @@ -20,6 +20,11 @@ use bevy_ecs::{ /// /// # bevy_ecs::system::assert_is_system(my_system); /// ``` +/// # Observers +/// +/// "Buffered" Events, such as those sent directly in [`Events`] or sent using [`EventWriter`], do _not_ automatically +/// trigger any [`Observer`]s watching for that event, as each [`Event`] has different requirements regarding _if_ it will +/// be triggered, and if so, _when_ it will be triggered in the schedule. /// /// # Concurrency /// @@ -52,6 +57,8 @@ use bevy_ecs::{ /// } /// ``` /// Note that this is considered *non-idiomatic*, and should only be used when `EventWriter` will not work. +/// +/// [`Observer`]: crate::observer::Observer #[derive(SystemParam)] pub struct EventWriter<'w, E: Event> { events: ResMut<'w, Events>, diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index 1bed428a6a047..f4d5384348230 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -88,7 +88,178 @@ impl Component for ObserverState { /// but can be overridden for custom behaviour. pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut); -/// An [`Observer`] system. Add this [`Component`] to an [`Entity`] to give it observer behaviors for the given system. +/// An [`Observer`] system. Add this [`Component`] to an [`Entity`] to turn it into an "observer". +/// +/// Observers listen for a "trigger" of a specific [`Event`]. Events are triggered by calling [`World::trigger`] or [`World::trigger_targets`]. +/// +/// Note that "buffered" events sent using [`EventReader`] and [`EventWriter`] are _not_ automatically triggered. They must be triggered at a specific +/// point in the schedule. +/// +/// # Usage +/// +/// The simplest usage +/// of the observer pattern looks like this: +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # let mut world = World::default(); +/// #[derive(Event)] +/// struct Speak { +/// message: String, +/// } +/// +/// world.observe(|trigger: Trigger| { +/// println!("{}", trigger.event().message); +/// }); +/// +/// // Observers currently require a flush() to be registered. In the context of schedules, +/// // this will generally be done for you. +/// world.flush(); +/// +/// world.trigger(Speak { +/// message: "Hello!".into(), +/// }); +/// ``` +/// +/// Notice that we used [`World::observe`]. This is just a shorthand for spawning an [`Observer`] manually: +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # let mut world = World::default(); +/// # #[derive(Event)] +/// # struct Speak; +/// // These are functionally the same: +/// world.observe(|trigger: Trigger| {}); +/// world.spawn(Observer::new(|trigger: Trigger| {})); +/// ``` +/// +/// Observers are systems. They can access arbitrary [`World`] data by adding [`SystemParam`]s: +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # let mut world = World::default(); +/// # #[derive(Event)] +/// # struct PrintNames; +/// # #[derive(Component, Debug)] +/// # struct Name; +/// world.observe(|trigger: Trigger, names: Query<&Name>| { +/// for name in &names { +/// println!("{name:?}"); +/// } +/// }); +/// ``` +/// +/// Note that [`Trigger`] must always be the first parameter. +/// +/// You can also add [`Commands`], which means you can spawn new entities, insert new components, etc: +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # let mut world = World::default(); +/// # #[derive(Event)] +/// # struct SpawnThing; +/// # #[derive(Component, Debug)] +/// # struct Thing; +/// world.observe(|trigger: Trigger, mut commands: Commands| { +/// commands.spawn(Thing); +/// }); +/// ``` +/// +/// Observers can also trigger new events: +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # let mut world = World::default(); +/// # #[derive(Event)] +/// # struct A; +/// # #[derive(Event)] +/// # struct B; +/// world.observe(|trigger: Trigger, mut commands: Commands| { +/// commands.trigger(B); +/// }); +/// ``` +/// +/// When the commands are flushed (including these "nested triggers") they will be +/// recursively evaluated until there are no commands left, meaning nested triggers all +/// evaluate at the same time! +/// +/// Events can be triggered for entities, which will be passed to the [`Observer`]: +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # let mut world = World::default(); +/// # let entity = world.spawn_empty().id(); +/// #[derive(Event)] +/// struct Explode; +/// +/// world.observe(|trigger: Trigger, mut commands: Commands| { +/// println!("Entity {:?} goes BOOM!", trigger.entity()); +/// commands.entity(trigger.entity()).despawn(); +/// }); +/// +/// world.flush(); +/// +/// world.trigger_targets(Explode, entity); +/// ``` +/// +/// You can trigger multiple entities at once: +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # let mut world = World::default(); +/// # let e1 = world.spawn_empty().id(); +/// # let e2 = world.spawn_empty().id(); +/// # #[derive(Event)] +/// # struct Explode; +/// world.trigger_targets(Explode, [e1, e2]); +/// ``` +/// +/// Observers can also watch _specific_ entities, which enables you to assign entity-specific logic: +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # #[derive(Component, Debug)] +/// # struct Name(String); +/// # let mut world = World::default(); +/// # let e1 = world.spawn_empty().id(); +/// # let e2 = world.spawn_empty().id(); +/// # #[derive(Event)] +/// # struct Explode; +/// world.entity_mut(e1).observe(|trigger: Trigger, mut commands: Commands| { +/// println!("Boom!"); +/// commands.entity(trigger.entity()).despawn(); +/// }); +/// +/// world.entity_mut(e2).observe(|trigger: Trigger, mut commands: Commands| { +/// println!("The explosion fizzles! This entity is immune!"); +/// }); +/// ``` +/// +/// If all entities watched by a given [`Observer`] are despawned, the [`Observer`] entity will also be despawned. +/// This protects against observer "garbage" building up over time. +/// +/// The examples above calling [`EntityWorldMut::observe`] to add entity-specific observer logic are (once again) +/// just shorthand for spawning an [`Observer`] directly: +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # let mut world = World::default(); +/// # let entity = world.spawn_empty().id(); +/// # #[derive(Event)] +/// # struct Explode; +/// let mut observer = Observer::new(|trigger: Trigger| {}); +/// observer.watch_entity(entity); +/// world.spawn(observer); +/// ``` +/// +/// Note that the [`Observer`] component is not added to the entity it is observing. Observers should always be their own entities! +/// +/// You can call [`Observer::watch_entity`] more than once, which allows you to watch multiple entities with the same [`Observer`]. +/// +/// When first added, [`Observer`] will also create an [`ObserverState`] component, which registers the observer with the [`World`] and +/// serves as the "source of truth" of the observer. +/// +/// [`SystemParam`]: crate::system::SystemParam pub struct Observer { system: BoxedObserverSystem, descriptor: ObserverDescriptor, @@ -186,9 +357,12 @@ fn observer_system_runner( ptr: PtrMut, ) { let world = world.as_unsafe_world_cell(); - let observer_cell = // SAFETY: Observer was triggered so must still exist in world - unsafe { world.get_entity(observer_trigger.observer).debug_checked_unwrap() }; + let observer_cell = unsafe { + world + .get_entity(observer_trigger.observer) + .debug_checked_unwrap() + }; // SAFETY: Observer was triggered so must have an `ObserverState` let mut state = unsafe { observer_cell @@ -204,9 +378,8 @@ fn observer_system_runner( } state.last_trigger_id = last_trigger; - let trigger: Trigger = - // SAFETY: Caller ensures `ptr` is castable to `&mut T` - Trigger::new(unsafe { ptr.deref_mut() }, observer_trigger); + // SAFETY: Caller ensures `ptr` is castable to `&mut T` + let trigger: Trigger = Trigger::new(unsafe { ptr.deref_mut() }, observer_trigger); // SAFETY: the static lifetime is encapsulated in Trigger / cannot leak out. // Additionally, IntoObserverSystem is only implemented for functions starting // with for<'a> Trigger<'a>, meaning users cannot specify Trigger<'static> manually, From 85004b47c96829a613038073d8682d0ea8c5e7d4 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 14 Jun 2024 18:01:22 -0700 Subject: [PATCH 106/109] Update example readme --- examples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/README.md b/examples/README.md index 67be1f638eeba..78b909ff4a4ee 100644 --- a/examples/README.md +++ b/examples/README.md @@ -276,7 +276,7 @@ Example | Description [Hierarchy](../examples/ecs/hierarchy.rs) | Creates a hierarchy of parents and children entities [Iter Combinations](../examples/ecs/iter_combinations.rs) | Shows how to iterate over combinations of query results [Nondeterministic System Order](../examples/ecs/nondeterministic_system_order.rs) | Systems run in parallel, but their order isn't always deterministic. Here's how to detect and fix this. -[Observers](../examples/ecs/observers.rs) | Define observers to react to ECS events +[Observers](../examples/ecs/observers.rs) | Demonstrates observers that react to events (both built-in life-cycle events and custom events) [One Shot Systems](../examples/ecs/one_shot_systems.rs) | Shows how to flexibly run systems without scheduling them [Parallel Query](../examples/ecs/parallel_query.rs) | Illustrates parallel queries with `ParallelIterator` [Removal Detection](../examples/ecs/removal_detection.rs) | Query for entities that had a specific component removed earlier in the current frame From 1a5846a7f739fe33367bdfc0d40f6cd669d933ed Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 14 Jun 2024 18:11:29 -0700 Subject: [PATCH 107/109] Add missing World to readme example --- crates/bevy_ecs/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/README.md b/crates/bevy_ecs/README.md index 930faed1fcb3f..138b10d52ee26 100644 --- a/crates/bevy_ecs/README.md +++ b/crates/bevy_ecs/README.md @@ -319,6 +319,8 @@ struct MyEvent { message: String } +let mut world = World::new(); + world.observe(|trigger: Trigger| { println!("{}", trigger.event().message); }); @@ -337,12 +339,12 @@ Events can also be triggered to target specific entities: ```rust use bevy_ecs::prelude::*; -let mut world = World::new(); -let entity = world.spawn_empty().id(); - #[derive(Event)] struct Explode; +let mut world = World::new(); +let entity = world.spawn_empty().id(); + world.observe(|trigger: Trigger, mut commands: Commands| { println!("Entity {:?} goes BOOM!", trigger.entity()); commands.entity(trigger.entity()).despawn(); From 6277f00a5b2b9f87d37c7cb95f57970772c87d34 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 14 Jun 2024 18:16:01 -0700 Subject: [PATCH 108/109] Flag observer example for wasm builds --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 88d860d75b83a..da29dd26e2676 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2543,7 +2543,6 @@ doc-scrape-examples = true name = "Observers" description = "Demonstrates observers that react to events (both built-in life-cycle events and custom events)" category = "ECS (Entity Component System)" -wasm = false [[example]] name = "3d_rotation" From 85ff016415a0e080c5a146c1103125dce472bca6 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 14 Jun 2024 18:17:11 -0700 Subject: [PATCH 109/109] Set wasm=true explicitly --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index da29dd26e2676..27ebefbe940ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2543,6 +2543,7 @@ doc-scrape-examples = true name = "Observers" description = "Demonstrates observers that react to events (both built-in life-cycle events and custom events)" category = "ECS (Entity Component System)" +wasm = true [[example]] name = "3d_rotation"