From eca8220761606850d733e2cb5e1e2ce220cb1d58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Maita?= <47983254+mnmaita@users.noreply.github.com> Date: Sun, 16 Jun 2024 17:44:08 +0200 Subject: [PATCH] Generalised ECS reactivity with Observers (#10839) (#13873) # Objective - Fixes #13825 ## Solution - Cherry picked and fixed non-trivial conflicts to be able to merge #10839 into the 0.14 release branch. Link to PR: https://github.com/bevyengine/bevy/pull/10839 Co-authored-by: James O'Brien Co-authored-by: Alice Cecile Co-authored-by: MiniaczQ Co-authored-by: Carter Anderson --- Cargo.toml | 11 + crates/bevy_app/src/app.rs | 11 +- crates/bevy_ecs/README.md | 48 ++ crates/bevy_ecs/macros/src/component.rs | 4 + crates/bevy_ecs/macros/src/lib.rs | 15 + crates/bevy_ecs/src/archetype.rs | 56 +- crates/bevy_ecs/src/bundle.rs | 97 +-- crates/bevy_ecs/src/event.rs | 27 +- crates/bevy_ecs/src/lib.rs | 6 +- .../bevy_ecs/src/observer/entity_observer.rs | 42 ++ crates/bevy_ecs/src/observer/mod.rs | 638 ++++++++++++++++++ crates/bevy_ecs/src/observer/runner.rs | 409 +++++++++++ crates/bevy_ecs/src/observer/trigger_event.rs | 165 +++++ crates/bevy_ecs/src/query/access.rs | 10 + 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 | 56 +- .../src/system/exclusive_function_system.rs | 9 +- crates/bevy_ecs/src/system/function_system.rs | 12 +- crates/bevy_ecs/src/system/mod.rs | 2 + crates/bevy_ecs/src/system/observer_system.rs | 71 ++ crates/bevy_ecs/src/system/system.rs | 5 + crates/bevy_ecs/src/system/system_param.rs | 39 +- crates/bevy_ecs/src/world/command_queue.rs | 145 +++- .../bevy_ecs/src/world/component_constants.rs | 23 + crates/bevy_ecs/src/world/deferred_world.rs | 127 +++- crates/bevy_ecs/src/world/entity_ref.rs | 76 ++- crates/bevy_ecs/src/world/mod.rs | 63 +- crates/bevy_ecs/src/world/spawn_batch.rs | 2 +- .../bevy_ecs/src/world/unsafe_world_cell.rs | 16 + examples/README.md | 1 + examples/ecs/observers.rs | 209 ++++++ 32 files changed, 2252 insertions(+), 154 deletions(-) create mode 100644 crates/bevy_ecs/src/observer/entity_observer.rs create mode 100644 crates/bevy_ecs/src/observer/mod.rs create mode 100644 crates/bevy_ecs/src/observer/runner.rs create mode 100644 crates/bevy_ecs/src/observer/trigger_event.rs create mode 100644 crates/bevy_ecs/src/system/observer_system.rs create mode 100644 crates/bevy_ecs/src/world/component_constants.rs create mode 100644 examples/ecs/observers.rs diff --git a/Cargo.toml b/Cargo.toml index c93235f4c0d01..6e0f45a6480df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2534,6 +2534,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 = "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" path = "examples/transforms/3d_rotation.rs" diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 555e30a9928a2..fdd7db483beaa 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 = "trace")] use bevy_utils::tracing::info_span; @@ -829,6 +829,15 @@ impl App { None } + + /// Spawns an [`Observer`] entity, which will watch for and respond to the given event. + 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/README.md b/crates/bevy_ecs/README.md index 81cc9104dfe93..138b10d52ee26 100644 --- a/crates/bevy_ecs/README.md +++ b/crates/bevy_ecs/README.md @@ -307,4 +307,52 @@ 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 +} + +let mut world = World::new(); + +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::*; + +#[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(); +}); + +world.flush(); + +world.trigger_targets(Explode, entity); +``` + [bevy]: https://bevyengine.org/ diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index b7b7e1582c8b3..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; + } }) } diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index dbca96db17e76..60e8027756731 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -74,6 +74,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { .collect::>(); let mut field_component_ids = Vec::new(); + let mut field_get_component_ids = Vec::new(); let mut field_get_components = Vec::new(); let mut field_from_components = Vec::new(); for (((i, field_type), field_kind), field) in field_type @@ -87,6 +88,9 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { field_component_ids.push(quote! { <#field_type as #ecs_path::bundle::Bundle>::component_ids(components, storages, &mut *ids); }); + field_get_component_ids.push(quote! { + <#field_type as #ecs_path::bundle::Bundle>::get_component_ids(components, &mut *ids); + }); match field { Some(field) => { field_get_components.push(quote! { @@ -133,6 +137,13 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { #(#field_component_ids)* } + fn get_component_ids( + components: &#ecs_path::component::Components, + ids: &mut impl FnMut(Option<#ecs_path::component::ComponentId>) + ){ + #(#field_get_component_ids)* + } + #[allow(unused_variables, non_snake_case)] unsafe fn from_components<__T, __F>(ctx: &mut __T, func: &mut __F) -> Self where @@ -435,6 +446,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 5f6302a2e1628..05b0a260cfe4e 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::{ @@ -119,6 +120,7 @@ pub(crate) struct AddBundle { /// For each component iterated in the same order as the source [`Bundle`](crate::bundle::Bundle), /// indicate if the component is newly added to the target archetype or if it already existed pub bundle_status: Vec, + pub added: Vec, } /// This trait is used to report the status of [`Bundle`](crate::bundle::Bundle) components @@ -202,12 +204,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, }, ); } @@ -314,6 +318,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); } } @@ -335,6 +342,7 @@ pub struct Archetype { impl Archetype { pub(crate) fn new( components: &Components, + observers: &Observers, id: ArchetypeId, table_id: TableId, table_components: impl Iterator, @@ -348,6 +356,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 { @@ -580,21 +589,45 @@ impl Archetype { /// Returns true if any of the components in this archetype have `on_add` hooks #[inline] - pub(crate) 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(crate) 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(crate) 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 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 + /// + /// [`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 + /// + /// [`OnRemove`]: crate::world::OnRemove + #[inline] + pub fn has_remove_observer(&self) -> bool { + self.flags().contains(ArchetypeFlags::ON_REMOVE_OBSERVER) + } } /// The next [`ArchetypeId`] in an [`Archetypes`] collection. @@ -681,6 +714,7 @@ impl Archetypes { unsafe { archetypes.get_id_or_insert( &Components::default(), + &Observers::default(), TableId::empty(), Vec::new(), Vec::new(), @@ -782,6 +816,7 @@ impl Archetypes { pub(crate) unsafe fn get_id_or_insert( &mut self, components: &Components, + observers: &Observers, table_id: TableId, table_components: Vec, sparse_set_components: Vec, @@ -808,6 +843,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), @@ -832,6 +868,20 @@ impl Archetypes { archetype.clear_entities(); } } + + pub(crate) fn update_flags( + &mut self, + component_id: ComponentId, + flags: ArchetypeFlags, + set: bool, + ) { + // TODO: Refactor component index to speed this up. + 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 8c67ae32dd46d..384f7d7a99c76 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -2,8 +2,9 @@ //! //! This module contains the [`Bundle`] trait and some other helper types. +use std::any::TypeId; + pub use bevy_ecs_macros::Bundle; -use bevy_utils::{HashMap, HashSet, TypeIdMap}; use crate::{ archetype::{ @@ -12,14 +13,15 @@ use crate::{ }, component::{Component, ComponentId, 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}, }; + use bevy_ptr::{ConstNonNull, OwningPtr}; -use bevy_utils::all_tuples; -use std::any::TypeId; +use bevy_utils::{all_tuples, HashMap, HashSet, TypeIdMap}; use std::ptr::NonNull; /// The `Bundle` trait enables insertion and removal of [`Component`]s from an entity. @@ -155,6 +157,9 @@ pub unsafe trait Bundle: DynamicBundle + Send + Sync + 'static { ids: &mut impl FnMut(ComponentId), ); + /// 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 /// this bundle's [`Component`]s /// @@ -204,6 +209,10 @@ unsafe impl Bundle for C { // Safety: The id given in `component_ids` is for `Self` unsafe { ptr.read() } } + + fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option)) { + ids(components.get_id(TypeId::of::())); + } } impl DynamicBundle for C { @@ -227,6 +236,11 @@ macro_rules! tuple_impl { $(<$name as Bundle>::component_ids(components, storages, ids);)* } + #[allow(unused_variables)] + fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option)){ + $(<$name as Bundle>::get_component_ids(components, ids);)* + } + #[allow(unused_variables, unused_mut)] #[allow(clippy::unused_unit)] unsafe fn from_components(ctx: &mut T, func: &mut F) -> Self @@ -432,6 +446,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) { @@ -440,6 +455,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() { @@ -447,6 +463,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() { @@ -459,7 +476,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; @@ -498,6 +515,7 @@ impl BundleInfo { // SAFETY: ids in self must be valid let new_archetype_id = archetypes.get_id_or_insert( components, + observers, table_id, table_components, sparse_set_components, @@ -507,6 +525,7 @@ impl BundleInfo { self.id, new_archetype_id, bundle_status, + added, ); new_archetype_id } @@ -567,6 +586,7 @@ impl<'w> BundleInserter<'w> { &mut world.archetypes, &mut world.storages, &world.components, + &world.observers, archetype_id, ); if new_archetype_id == archetype_id { @@ -786,27 +806,21 @@ 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() }; - if new_archetype.has_on_add() { - // 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( - entity, - bundle_info - .iter_components() - .zip(add_bundle.bundle_status.iter()) - .filter(|(_, &status)| status == ComponentStatus::Added) - .map(|(id, _)| id), - ); + // 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(new_archetype, entity, add_bundle.added.iter().cloned()); + if new_archetype.has_add_observer() { + 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, entity, bundle_info.iter_components()); } - } - if new_archetype.has_on_insert() { - // 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_insert(entity, bundle_info.iter_components()) } } new_location @@ -853,6 +867,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]; @@ -882,12 +897,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(); @@ -910,16 +925,20 @@ 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() }; - if archetype.has_on_add() { - // 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(entity, bundle_info.iter_components()) }; - } - if archetype.has_on_insert() { - // 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_insert(entity, bundle_info.iter_components()) }; - } + // 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 { + deferred_world.trigger_on_add(archetype, entity, bundle_info.iter_components()); + if archetype.has_add_observer() { + 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, entity, bundle_info.iter_components()); + } + }; location } @@ -947,7 +966,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(); } } @@ -1223,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/event.rs b/crates/bevy_ecs/src/event.rs index 25954c44edbdd..66c6b361ae7c3 100644 --- a/crates/bevy_ecs/src/event.rs +++ b/crates/bevy_ecs/src/event.rs @@ -5,7 +5,7 @@ use crate::batching::BatchingStrategy; use crate::change_detection::MutUntyped; use crate::{ change_detection::{DetectChangesMut, Mut}, - component::{ComponentId, Tick}, + component::{Component, ComponentId, Tick}, system::{Local, Res, ResMut, Resource, SystemParam}, world::World, }; @@ -24,16 +24,30 @@ use std::{ slice::Iter, }; -/// 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`", note = "consider annotating `{Self}` with `#[derive(Event)]`" )] -pub trait Event: Send + Sync + 'static {} +pub trait Event: Component {} /// An `EventId` uniquely identifies an event stored in a specific [`World`]. /// @@ -556,6 +570,11 @@ impl<'w, 's, E: Event> EventReader<'w, 's, E> { /// /// # 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 /// @@ -588,6 +607,8 @@ impl<'w, 's, E: Event> EventReader<'w, 's, E> { /// } /// ``` /// 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/lib.rs b/crates/bevy_ecs/src/lib.rs index 689ee8967e5d6..8d459913c5fca 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -21,6 +21,7 @@ pub mod event; pub mod identifier; pub mod intern; pub mod label; +pub mod observer; pub mod query; #[cfg(feature = "bevy_reflect")] pub mod reflect; @@ -46,6 +47,7 @@ pub mod prelude { component::Component, entity::{Entity, EntityMapper}, event::{Event, EventReader, EventWriter, Events, ShouldUpdateEvents}, + observer::{Observer, Trigger}, query::{Added, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without}, removal_detection::RemovedComponents, schedule::{ @@ -57,7 +59,9 @@ pub mod prelude { ParamSet, Query, ReadOnlySystem, Res, ResMut, Resource, System, SystemBuilder, SystemParamFunction, }, - world::{EntityMut, EntityRef, EntityWorldMut, FromWorld, World}, + world::{ + EntityMut, EntityRef, EntityWorldMut, FromWorld, OnAdd, OnInsert, OnRemove, World, + }, }; } diff --git a/crates/bevy_ecs/src/observer/entity_observer.rs b/crates/bevy_ecs/src/observer/entity_observer.rs new file mode 100644 index 0000000000000..ce30201785758 --- /dev/null +++ b/crates/bevy_ecs/src/observer/entity_observer.rs @@ -0,0 +1,42 @@ +use crate::{ + component::{Component, ComponentHooks, StorageType}, + entity::Entity, + observer::ObserverState, +}; + +/// Tracks a list of entity observers for the [`Entity`] [`ObservedBy`] is added to. +#[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 observed_by = { + let mut component = world.get_mut::(entity).unwrap(); + std::mem::take(&mut component.0) + }; + for e in observed_by { + 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_watched_entities += 1; + ( + state.descriptor.entities.len(), + state.despawned_watched_entities as usize, + ) + }; + + // Despawn Observer if it has no more active 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 new file mode 100644 index 0000000000000..08e87854c4c56 --- /dev/null +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -0,0 +1,638 @@ +//! Types for creating and storing [`Observer`]s + +mod entity_observer; +mod runner; +mod trigger_event; + +pub use runner::*; +pub use trigger_event::*; + +use crate::observer::entity_observer::ObservedBy; +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; + +/// 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, + _marker: PhantomData, +} + +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 { + event, + trigger, + _marker: PhantomData, + } + } + + /// Returns the event type of this trigger. + pub fn event_type(&self) -> ComponentId { + self.trigger.event_type + } + + /// Returns a reference to the triggered event. + pub fn event(&self) -> &E { + self.event + } + + /// Returns a mutable reference to the triggered event. + pub fn event_mut(&mut self) -> &mut E { + self.event + } + + /// 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`]. + pub fn entity(&self) -> Entity { + self.trigger.entity + } +} + +/// A description of what an [`Observer`] observes. +#[derive(Default, Clone)] +pub struct ObserverDescriptor { + /// The events the observer is watching. + events: Vec, + + /// The components the observer is watching. + components: Vec, + + /// The entities the observer is watching. + entities: Vec, +} + +impl ObserverDescriptor { + /// Add the given `triggers` to the descriptor. + pub fn with_triggers(mut self, triggers: Vec) -> Self { + self.events = 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 `entities` to the descriptor. + pub fn with_entities(mut self, entities: Vec) -> Self { + self.entities = entities; + self + } + + pub(crate) fn merge(&mut self, descriptor: &ObserverDescriptor) { + self.events.extend(descriptor.events.iter().copied()); + self.components + .extend(descriptor.components.iter().copied()); + self.entities.extend(descriptor.entities.iter().copied()); + } +} + +/// Event trigger metadata for a given [`Observer`], +#[derive(Debug)] +pub struct ObserverTrigger { + /// The [`Entity`] of the observer handling the trigger. + pub observer: Entity, + + /// The [`ComponentId`] the trigger targeted. + pub event_type: ComponentId, + + /// The entity the trigger targeted. + pub entity: Entity, +} + +// 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. +#[derive(Default, Debug)] +pub struct CachedComponentObservers { + // Observers listening to triggers targeting this component + map: ObserverMap, + // Observers listening to triggers targeting this component on a specific entity + entity_map: EntityHashMap, +} + +/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular trigger. +#[derive(Default, Debug)] +pub struct CachedObservers { + // Observers listening for any time this trigger is fired + map: ObserverMap, + // Observers listening for this trigger fired at a specific component + component_observers: HashMap, + // Observers listening for this trigger fired at a specific entity + entity_observers: EntityHashMap, +} + +/// 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 triggers. + on_add: CachedObservers, + on_insert: CachedObservers, + on_remove: CachedObservers, + // Map from trigger type to set of observers + cache: HashMap, +} + +impl Observers { + 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(event_type).or_default(), + } + } + + 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(&event_type), + } + } + + /// 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, + entity: Entity, + components: impl Iterator, + 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_trigger_id(); + let observers = world.observers(); + let Some(observers) = observers.try_get_observers(event_type) else { + return; + }; + // SAFETY: The only outstanding reference to world is `observers` + (world.into_deferred(), observers) + }; + + let mut trigger_observer = |(&observer, runner): (&Entity, &ObserverRunner)| { + (runner)( + world.reborrow(), + ObserverTrigger { + observer, + event_type, + entity, + }, + data.into(), + ); + }; + + // 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 trigger + if entity != Entity::PLACEHOLDER { + if let Some(map) = observers.entity_observers.get(&entity) { + map.iter().for_each(&mut trigger_observer); + } + } + + // 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 + .map + .iter() + .for_each(&mut trigger_observer); + + if entity != Entity::PLACEHOLDER { + if let Some(map) = component_observers.entity_map.get(&entity) { + map.iter().for_each(&mut trigger_observer); + } + } + } + }); + } + + 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), + _ => 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); + } + } +} + +impl World { + /// Spawn a "global" [`Observer`] and returns it's [`Entity`]. + pub fn observe( + &mut self, + system: impl IntoObserverSystem, + ) -> EntityWorldMut { + self.spawn(Observer::new(system)) + } + + /// 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); + } + + /// 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 + 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::(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) + }; + let descriptor = &observer_state.descriptor; + + for &event_type in &descriptor.events { + let cache = observers.get_observers(event_type); + + 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 &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 watched component + for &component in &descriptor.components { + let observers = + cache + .component_observers + .entry(component) + .or_insert_with(|| { + if let Some(flag) = Observers::is_archetype_cached(event_type) { + archetypes.update_flags(component, flag, true); + } + CachedComponentObservers::default() + }); + if descriptor.entities.is_empty() { + // Register for all triggers targeting the component + observers.map.insert(observer_entity, observer_state.runner); + } else { + // 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); + } + } + } + } + } + } + + /// 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; + + for &event_type in &descriptor.events { + let cache = observers.get_observers(event_type); + if descriptor.components.is_empty() && descriptor.entities.is_empty() { + cache.map.remove(&entity); + } else if descriptor.components.is_empty() { + 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(watched_entity) else { + continue; + }; + observers.remove(&entity); + if observers.is_empty() { + cache.entity_observers.remove(watched_entity); + } + } + } else { + for component in &descriptor.components { + let Some(observers) = cache.component_observers.get_mut(component) else { + continue; + }; + if descriptor.entities.is_empty() { + observers.map.remove(&entity); + } 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(watched_entity); + } + } + } + + if observers.map.is_empty() && observers.entity_map.is_empty() { + cache.component_observers.remove(component); + if let Some(flag) = Observers::is_archetype_cached(event_type) { + archetypes.update_flags(*component, flag, false); + } + } + } + } + } + } +} + +#[cfg(test)] +mod tests { + use bevy_ptr::OwningPtr; + + use crate as bevy_ecs; + use crate::observer::{EmitDynamicTrigger, Observer, ObserverDescriptor, ObserverState}; + use crate::prelude::*; + + #[derive(Component)] + struct A; + + #[derive(Component)] + struct B; + + #[derive(Component)] + struct C; + + #[derive(Event)] + 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.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); + assert_eq!(3, world.resource::().0); + } + + #[test] + fn observer_order_insert_remove() { + let mut world = World::new(); + world.init_resource::(); + + 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); + entity.remove::(); + entity.flush(); + assert_eq!(3, world.resource::().0); + } + + #[test] + fn observer_order_recursive() { + let mut world = World::new(); + world.init_resource::(); + world.observe( + |obs: Trigger, mut res: ResMut, mut commands: Commands| { + res.assert_order(0); + commands.entity(obs.entity()).insert(B); + }, + ); + world.observe( + |obs: Trigger, mut res: ResMut, mut commands: Commands| { + res.assert_order(2); + commands.entity(obs.entity()).remove::(); + }, + ); + + world.observe( + |obs: Trigger, mut res: ResMut, mut commands: Commands| { + res.assert_order(1); + commands.entity(obs.entity()).remove::(); + }, + ); + world.observe(|_: Trigger, 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.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); + // Our A entity plus our two observers + assert_eq!(world.entities().len(), 3); + } + + #[test] + fn observer_multiple_events() { + let mut world = World::new(); + world.init_resource::(); + let on_remove = world.init_component::(); + world.spawn( + Observer::new(|_: Trigger, mut res: ResMut| res.0 += 1) + .with_event(on_remove), + ); + + 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.observe(|_: Trigger, 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 + .observe(|_: Trigger| panic!("Observer triggered after being despawned.")) + .id(); + world.despawn(observer); + world.spawn(A).flush(); + } + + #[test] + fn observer_multiple_matches() { + let mut world = World::new(); + world.init_resource::(); + + world.observe(|_: Trigger, mut res: ResMut| res.0 += 1); + + world.spawn((A, B)).flush(); + assert_eq!(1, world.resource::().0); + } + + #[test] + fn observer_no_target() { + let mut world = World::new(); + world.init_resource::(); + + world + .spawn_empty() + .observe(|_: Trigger| panic!("Trigger routed to non-targeted entity.")); + world.observe(move |obs: Trigger, mut res: ResMut| { + assert_eq!(obs.entity(), Entity::PLACEHOLDER); + 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); + } + + #[test] + fn observer_entity_routing() { + let mut world = World::new(); + world.init_resource::(); + + world + .spawn_empty() + .observe(|_: Trigger| panic!("Trigger routed to non-targeted entity.")); + let entity = world + .spawn_empty() + .observe(|_: Trigger, mut res: ResMut| res.0 += 1) + .id(); + world.observe(move |obs: Trigger, mut res: ResMut| { + assert_eq!(obs.entity(), entity); + 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); + } + + #[test] + fn observer_dynamic_component() { + let mut world = World::new(); + world.init_resource::(); + + let component_id = world.init_component::(); + world.spawn( + Observer::new(|_: Trigger, mut res: ResMut| res.0 += 1) + .with_component(component_id), + ); + + 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.trigger_targets(EventA, entity); + world.flush(); + assert_eq!(1, world.resource::().0); + } + + #[test] + fn observer_dynamic_trigger() { + let mut world = World::new(); + world.init_resource::(); + let event_a = world.init_component::(); + + world.spawn(ObserverState { + descriptor: ObserverDescriptor::default().with_triggers(vec![event_a]), + runner: |mut world, _trigger, _ptr| { + world.resource_mut::().0 += 1; + }, + ..Default::default() + }); + + world.commands().add( + // SAFETY: we registered `trigger` above and it matches the type of 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 new file mode 100644 index 0000000000000..f4d5384348230 --- /dev/null +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -0,0 +1,409 @@ +use crate::{ + component::{ComponentHooks, ComponentId, StorageType}, + observer::{ObserverDescriptor, ObserverTrigger}, + prelude::*, + query::DebugCheckedUnwrap, + system::{IntoObserverSystem, ObserverSystem}, + world::DeferredWorld, +}; +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 ObserverState { + pub(crate) descriptor: ObserverDescriptor, + pub(crate) runner: ObserverRunner, + pub(crate) last_trigger_id: u32, + pub(crate) despawned_watched_entities: u32, +} + +impl Default for ObserverState { + fn default() -> Self { + Self { + runner: |_, _, _| {}, + last_trigger_id: 0, + despawned_watched_entities: 0, + descriptor: Default::default(), + } + } +} + +impl ObserverState { + /// 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 + } + + /// 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 + } + + /// 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 + } + + /// 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 + } +} + +impl Component for ObserverState { + 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 + .entity_mut(entity) + .get_mut::() + .unwrap() + .as_mut() + .descriptor, + ); + world.commands().add(move |world: &mut World| { + world.unregister_observer(entity, descriptor); + }); + }); + } +} + +/// 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); + +/// 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, +} + +impl 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)), + descriptor: Default::default(), + } + } + + /// 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`. 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`. 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 `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 + } +} + +impl Component for Observer { + 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 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 { + events: vec![event_type], + 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; + }; + // SAFETY: World reference is exclusive and initialize does not touch system, so references do not alias + unsafe { + (*system).initialize(world); + } + + { + let mut entity = world.entity_mut(entity); + if let crate::world::Entry::Vacant(entry) = entity.entry::() { + entry.insert(ObserverState { + descriptor, + runner: observer_system_runner::, + ..Default::default() + }); + } + } + }); + }); + } +} + +/// Equivalent to [`BoxedSystem`](crate::system::BoxedSystem) for [`ObserverSystem`]. +pub type BoxedObserverSystem = Box>; + +fn observer_system_runner( + mut world: DeferredWorld, + observer_trigger: ObserverTrigger, + ptr: PtrMut, +) { + let world = world.as_unsafe_world_cell(); + // SAFETY: Observer was triggered so must still exist in world + 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 + .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; + + // 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, + // 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 + .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(trigger, world); + system.queue_deferred(world.into_deferred()); + } +} diff --git a/crates/bevy_ecs/src/observer/trigger_event.rs b/crates/bevy_ecs/src/observer/trigger_event.rs new file mode 100644 index 0000000000000..ff4000c8ee688 --- /dev/null +++ b/crates/bevy_ecs/src/observer/trigger_event.rs @@ -0,0 +1,165 @@ +use crate::{ + component::ComponentId, + entity::Entity, + event::Event, + world::{Command, DeferredWorld, World}, +}; + +/// A [`Command`] that emits a given trigger for a given set of targets. +pub struct TriggerEvent { + /// The event to trigger. + pub event: E, + + /// The targets to trigger the event for. + pub targets: Targets, +} + +impl Command for TriggerEvent { + fn apply(mut self, world: &mut World) { + 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 { + event_type: ComponentId, + event_data: T, + targets: Targets, +} + +impl EmitDynamicTrigger { + /// 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 { + Self { + event_type, + event_data, + targets, + } + } +} + +impl Command for EmitDynamicTrigger { + fn apply(mut self, world: &mut World) { + trigger_event(world, self.event_type, &mut self.event_data, self.targets); + } +} + +#[inline] +fn trigger_event( + world: &mut World, + event_type: ComponentId, + event_data: &mut E, + targets: Targets, +) { + let mut world = DeferredWorld::from(world); + if targets.entities().len() == 0 { + // SAFETY: T is accessible as the type represented by self.trigger, ensured in `Self::new` + unsafe { + world.trigger_observers_with_data( + event_type, + Entity::PLACEHOLDER, + targets.components(), + 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( + event_type, + target, + targets.components(), + event_data, + ); + }; + } + } +} + +/// 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; + + /// 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/query/access.rs b/crates/bevy_ecs/src/query/access.rs index e4880c33ad5f2..3ee0e484ab292 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -647,6 +647,16 @@ impl FilteredAccessSet { .extend(filtered_access_set.filtered_accesses); } + /// Marks the set as reading all possible indices of type 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/adapter_system.rs b/crates/bevy_ecs/src/system/adapter_system.rs index 794db43936506..33ad9aa78a226 100644 --- a/crates/bevy_ecs/src/system/adapter_system.rs +++ b/crates/bevy_ecs/src/system/adapter_system.rs @@ -127,6 +127,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 dca72127dc9a5..e1de10b3943f3 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -202,6 +202,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 4b74a121ff2d1..028182e69f00c 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -1,11 +1,13 @@ mod parallel_scope; -use super::{Deferred, IntoSystem, RegisterSystem, Resource}; +use super::{Deferred, IntoObserverSystem, IntoSystem, RegisterSystem, Resource}; use crate::{ self as bevy_ecs, bundle::Bundle, component::ComponentId, entity::{Entities, Entity}, + event::Event, + observer::{Observer, TriggerEvent, TriggerTargets}, system::{RunSystemWithInput, SystemId}, world::command_queue::RawCommandQueue, world::{Command, CommandQueue, EntityWorldMut, FromWorld, World}, @@ -116,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, @@ -150,7 +163,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()) + Self::new_from_entities(queue, &world.entities) } /// Returns a new `Commands` instance from a [`CommandQueue`] and an [`Entities`] reference. @@ -735,6 +748,26 @@ impl<'w, 's> Commands<'w, 's> { pub fn add(&mut self, command: C) { self.push(command); } + + /// 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`] 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 the [`EntityCommands`] associated with the entity that stores the observer. + pub fn observe( + &mut self, + observer: impl IntoObserverSystem, + ) -> EntityCommands { + self.spawn(Observer::new(observer)) + } } /// A [`Command`] which gets executed for a given [`Entity`]. @@ -1125,6 +1158,15 @@ impl EntityCommands<'_> { pub fn commands(&mut self) -> Commands { self.commands.reborrow() } + + /// 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, + ) -> &mut Self { + self.add(observe(system)); + self + } } impl Command for F @@ -1285,6 +1327,16 @@ fn log_components(entity: Entity, world: &mut World) { info!("Entity {:?}: {:?}", entity, debug_infos); } +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); + } + } +} + #[cfg(test)] #[allow(clippy::float_cmp, clippy::approx_constant)] mod tests { diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index 85f3af12097e3..3d54e882b5d59 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); @@ -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 e9d7f71736154..bf2b2c4fd5dbc 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; @@ -399,8 +399,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 @@ -542,6 +542,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) { if let Some(id) = self.world_id { diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index c06e71e15924f..003a312789320 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -108,6 +108,7 @@ mod commands; mod exclusive_function_system; mod exclusive_system_param; mod function_system; +mod observer_system; mod query; #[allow(clippy::module_inception)] mod system; @@ -124,6 +125,7 @@ pub use commands::*; pub use exclusive_function_system::*; pub use exclusive_system_param::*; pub use function_system::*; +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..0e13100f68d9b --- /dev/null +++ b/crates/bevy_ecs/src/system/observer_system.rs @@ -0,0 +1,71 @@ +use bevy_utils::all_tuples; + +use crate::{ + prelude::{Bundle, Trigger}, + system::{System, SystemParam, SystemParamFunction, SystemParamItem}, +}; + +use super::IntoSystem; + +/// Implemented for systems that have an [`Observer`] as the first argument. +pub trait ObserverSystem: + System, Out = ()> + Send + 'static +{ +} + +impl, Out = ()> + Send + 'static> + ObserverSystem for T +{ +} + +/// Implemented for systems that convert into [`ObserverSystem`]. +pub trait IntoObserverSystem: Send + 'static { + /// The type of [`System`] that this instance converts into. + type System: ObserverSystem; + + /// Turns this value into its corresponding [`System`]. + fn into_system(this: Self) -> Self::System; +} + +impl, (), M> + Send + 'static, M, E: 'static, B: Bundle> + 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_system_function { + ($($param: ident),*) => { + #[allow(non_snake_case)] + impl SystemParamFunction, $($param,)*)> for Func + where + for <'a> &'a mut Func: + FnMut(Trigger, $($param),*) + + FnMut(Trigger, $(SystemParamItem<$param>),*) + { + type In = Trigger<'static, E, B>; + type Out = (); + type Param = ($($param,)*); + #[inline] + 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(Trigger<'static, E, B>, $($param,)*), + input: Trigger<'static, E, B>, + $($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/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index f8321a5d93389..a36900a353824 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; @@ -89,6 +90,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/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 7ec42cfb6acd3..aae8e4e2e26f3 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -11,7 +11,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; @@ -159,6 +159,11 @@ 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 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) {} + /// Creates a parameter to be passed into a [`SystemParamFunction`]. /// /// [`SystemParamFunction`]: super::SystemParamFunction @@ -712,6 +717,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. @@ -848,6 +874,8 @@ impl<'w, T: FromWorld + Send + 'static> BuildableSystemParam for Local<'w, 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 be applied at the next [`apply_deferred`](crate::prelude::apply_deferred). + fn queue(&mut self, _system_meta: &SystemMeta, _world: DeferredWorld) {} } /// A [`SystemParam`] that stores a buffer which gets applied to the [`World`] during @@ -1012,6 +1040,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, @@ -1424,6 +1456,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/command_queue.rs b/crates/bevy_ecs/src/world/command_queue.rs index 0c783be27fdff..721248ea859ad 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}, }; @@ -11,6 +12,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. @@ -18,11 +21,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`]. @@ -41,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 @@ -49,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 @@ -117,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)), } } } @@ -130,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())), } } } @@ -164,17 +168,23 @@ 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`. 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(); + } // ...or discard it. None => drop(command), } @@ -222,50 +232,79 @@ 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 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, - // 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, self.cursor) }; + 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) }; + })); + + 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.append(panic_recovery); + } + panic::resume_unwind(payload); + } } - // 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(start); + *self.cursor.as_mut() = start; }; } } @@ -287,11 +326,18 @@ 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)] mod test { use super::*; + use crate as bevy_ecs; + use crate::system::Resource; use std::{ panic::AssertUnwindSafe, sync::{ @@ -412,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); @@ -424,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/component_constants.rs b/crates/bevy_ecs/src/world/component_constants.rs new file mode 100644 index 0000000000000..640ff1929d7a6 --- /dev/null +++ b/crates/bevy_ecs/src/world/component_constants.rs @@ -0,0 +1,23 @@ +use super::*; +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. + +/// [`ComponentId`] for [`OnAdd`] +pub const ON_ADD: ComponentId = ComponentId::new(0); +/// [`ComponentId`] for [`OnInsert`] +pub const ON_INSERT: ComponentId = ComponentId::new(1); +/// [`ComponentId`] for [`OnRemove`] +pub const ON_REMOVE: ComponentId = ComponentId::new(2); + +/// Trigger emitted when a component is added to an entity. +#[derive(Event)] +pub struct OnAdd; + +/// Trigger emitted when a component is inserted on to to an entity. +#[derive(Event)] +pub struct OnInsert; + +/// Trigger emitted when a component is removed from an entity. +#[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 5bf02b3c8bd2a..71f5a059aed20 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -1,10 +1,12 @@ use std::ops::Deref; use crate::{ + archetype::Archetype, change_detection::MutUntyped, component::ComponentId, entity::Entity, event::{Event, EventId, Events, SendBatchIds}, + observer::{Observers, TriggerTargets}, prelude::{Component, QueryState}, query::{QueryData, QueryFilter}, system::{Commands, Query, Resource}, @@ -52,6 +54,12 @@ impl<'w> From<&'w mut World> 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 } + } + /// Creates a [`Commands`] instance that pushes to the world's command queue #[inline] pub fn commands(&mut self) -> Commands { @@ -65,19 +73,34 @@ impl<'w> DeferredWorld<'w> { /// 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: &mut self ensure that there are no outstanding accesses to the component + // 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() } } + /// 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: 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) + } + /// 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"); } @@ -88,16 +111,6 @@ impl<'w> DeferredWorld<'w> { } } - /// 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, entity, location)) }) - } - /// Returns [`Query`] for the given [`QueryState`], which is used to efficiently /// run queries on the [`World`] by storing and reusing the [`QueryState`]. /// @@ -266,14 +279,17 @@ impl<'w> DeferredWorld<'w> { #[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); + } } } } @@ -285,14 +301,17 @@ impl<'w> DeferredWorld<'w> { #[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); + } } } } @@ -304,15 +323,67 @@ impl<'w> DeferredWorld<'w> { #[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 { + 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); + } } } } + + /// Triggers all event observers for [`ComponentId`] in target. + /// + /// # Safety + /// Caller must ensure observers listening for `event` can accept ZST pointers + #[inline] + pub(crate) unsafe fn trigger_observers( + &mut self, + event: ComponentId, + entity: Entity, + components: impl Iterator, + ) { + Observers::invoke(self.reborrow(), event, entity, components, &mut ()); + } + + /// Triggers all event observers for [`ComponentId`] in target. + /// + /// # Safety + /// Caller must ensure `E` is accessible as the type represented by `event` + #[inline] + pub(crate) unsafe fn trigger_observers_with_data( + &mut self, + event: ComponentId, + entity: Entity, + components: impl Iterator, + data: &mut E, + ) { + Observers::invoke(self.reborrow(), event, entity, components, data); + } + + /// Sends a "global" [`Trigger`] without any targets. + 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 Event, targets: impl TriggerTargets) { + self.commands().trigger_targets(trigger, targets); + } + + /// 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 e7396bf3c7bc7..9fa1021923522 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -4,16 +4,19 @@ use crate::{ change_detection::MutUntyped, component::{Component, ComponentId, ComponentTicks, Components, StorageType}, entity::{Entities, Entity, EntityLocation}, + event::Event, + observer::{Observer, Observers}, query::Access, removal_detection::RemovedComponentEvents, storage::Storages, - world::{Mut, World}, + system::IntoObserverSystem, + world::{DeferredWorld, Mut, World}, }; use bevy_ptr::{OwningPtr, Ptr}; use std::{any::TypeId, marker::PhantomData}; use thiserror::Error; -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. /// @@ -876,6 +879,7 @@ impl<'w> EntityWorldMut<'w> { &mut world.archetypes, storages, components, + &world.observers, old_location.archetype_id, bundle_info, false, @@ -898,11 +902,14 @@ impl<'w> EntityWorldMut<'w> { ) }; - if old_archetype.has_on_remove() { - // SAFETY: All components in the archetype exist in world - unsafe { - deferred_world.trigger_on_remove(entity, bundle_info.iter_components()); - } + // SAFETY: all bundle components exist in World + unsafe { + trigger_on_remove_hooks_and_observers( + &mut deferred_world, + old_archetype, + entity, + bundle_info, + ); } let archetypes = &mut world.archetypes; @@ -1053,6 +1060,7 @@ impl<'w> EntityWorldMut<'w> { &mut world.archetypes, &mut world.storages, &world.components, + &world.observers, location.archetype_id, bundle_info, // components from the bundle that are not present on the entity are ignored @@ -1075,11 +1083,14 @@ impl<'w> EntityWorldMut<'w> { ) }; - if old_archetype.has_on_remove() { - // SAFETY: All components in the archetype exist in world - unsafe { - deferred_world.trigger_on_remove(entity, bundle_info.iter_components()); - } + // SAFETY: all bundle components exist in World + unsafe { + trigger_on_remove_hooks_and_observers( + &mut deferred_world, + old_archetype, + entity, + bundle_info, + ); } let old_archetype = &world.archetypes[location.archetype_id]; @@ -1209,10 +1220,11 @@ impl<'w> EntityWorldMut<'w> { (&*archetype, world.into_deferred()) }; - if archetype.has_on_remove() { - // SAFETY: All components in the archetype exist in world - unsafe { - deferred_world.trigger_on_remove(self.entity, archetype.components()); + // 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()); } } @@ -1276,12 +1288,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`] + /// Ensures any commands triggered by the actions of Self are applied, equivalent to [`World::flush`] pub fn flush(self) -> Entity { - self.world.flush_commands(); + self.world.flush(); self.entity } @@ -1397,6 +1409,30 @@ impl<'w> EntityWorldMut<'w> { }) } } + + /// 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, + observer: impl IntoObserverSystem, + ) -> &mut Self { + self.world + .spawn(Observer::new(observer).with_entity(self.entity)); + self + } +} + +/// 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. @@ -2292,6 +2328,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, @@ -2362,6 +2399,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 099534a4272fe..d509eaa349c65 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1,20 +1,25 @@ //! Defines the [`World`] and APIs for accessing it directly. pub(crate) mod command_queue; +mod component_constants; mod deferred_world; mod entity_ref; pub mod error; +mod identifier; mod spawn_batch; pub mod unsafe_world_cell; -pub use crate::change_detection::{Mut, Ref, CHECK_TICK_THRESHOLD}; -use crate::entity::EntityHashSet; -pub use crate::world::command_queue::CommandQueue; +pub use crate::{ + change_detection::{Mut, Ref, CHECK_TICK_THRESHOLD}, + world::command_queue::CommandQueue, +}; +pub use component_constants::*; pub use deferred_world::DeferredWorld; pub use entity_ref::{ EntityMut, EntityRef, EntityWorldMut, Entry, FilteredEntityMut, FilteredEntityRef, OccupiedEntry, VacantEntry, }; +pub use identifier::WorldId; pub use spawn_batch::*; use crate::{ @@ -25,8 +30,9 @@ use crate::{ Component, ComponentDescriptor, ComponentHooks, ComponentId, ComponentInfo, ComponentTicks, Components, Tick, }, - entity::{AllocAtWithoutReplacement, Entities, Entity, EntityLocation}, + entity::{AllocAtWithoutReplacement, Entities, Entity, EntityHashSet, EntityLocation}, event::{Event, EventId, Events, SendBatchIds}, + observer::Observers, query::{DebugCheckedUnwrap, QueryData, QueryEntityError, QueryFilter, QueryState}, removal_detection::RemovedComponentEvents, schedule::{Schedule, ScheduleLabel, Schedules}, @@ -43,10 +49,7 @@ use std::{ mem::MaybeUninit, sync::atomic::{AtomicU32, Ordering}, }; -mod identifier; - -use self::unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell}; -pub use identifier::WorldId; +use unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell}; /// A [`World`] mutation. /// @@ -110,30 +113,36 @@ pub struct World { pub(crate) archetypes: Archetypes, pub(crate) storages: Storages, pub(crate) bundles: Bundles, + pub(crate) observers: Observers, pub(crate) removed_components: RemovedComponentEvents, pub(crate) change_tick: AtomicU32, pub(crate) last_change_tick: Tick, pub(crate) last_check_tick: Tick, + pub(crate) last_trigger_id: u32, pub(crate) command_queue: RawCommandQueue, } 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(), // Default value is `1`, and `last_change_tick`s default to `0`, such that changes // are detected on first system runs and for direct world queries. change_tick: AtomicU32::new(1), last_change_tick: Tick::new(0), last_check_tick: Tick::new(0), + last_trigger_id: 0, command_queue: RawCommandQueue::new(), - } + }; + world.bootstrap(); + world } } @@ -149,6 +158,14 @@ 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::()); + assert_eq!(ON_INSERT, self.init_component::()); + assert_eq!(ON_REMOVE, self.init_component::()); + } /// Creates a new empty [`World`]. /// /// # Panics @@ -226,7 +243,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 { // SAFETY: command_queue is stored on world and always valid while the world exists @@ -493,7 +510,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 @@ -886,7 +903,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) } @@ -952,7 +969,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 = { @@ -1083,6 +1100,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 @@ -1734,7 +1752,7 @@ impl World { I::IntoIter: Iterator, B: Bundle, { - self.flush_entities(); + self.flush(); let change_tick = self.change_tick(); @@ -2020,9 +2038,15 @@ impl 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. - #[inline] 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() } { @@ -2073,6 +2097,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_trigger_id(&self) -> u32 { + self.last_trigger_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/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(); diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index d6c400171c88c..227846340c36e 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -9,6 +9,7 @@ use crate::{ change_detection::{MutUntyped, Ticks, TicksMut}, component::{ComponentId, ComponentTicks, Components, StorageType, Tick, TickCells}, entity::{Entities, Entity, EntityLocation}, + observer::Observers, prelude::Component, removal_detection::RemovedComponentEvents, storage::{Column, ComponentSparseSet, Storages}, @@ -231,6 +232,13 @@ impl<'w> UnsafeWorldCell<'w> { &unsafe { self.world_metadata() }.removed_components } + /// Retrieves this world's [`Observers`] collection. + pub(crate) unsafe fn observers(self) -> &'w Observers { + // SAFETY: + // - we only access world metadata + &unsafe { self.world_metadata() }.observers + } + /// Retrieves this world's [`Bundles`] collection. #[inline] pub fn bundles(self) -> &'w Bundles { @@ -571,6 +579,14 @@ impl<'w> UnsafeWorldCell<'w> { // - caller ensures that we have permission to access the queue unsafe { (*self.0).command_queue.clone() } } + + /// # Safety + /// It is the callers responsibility to ensure that there are no outstanding + /// 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_trigger_id += 1 } + } } impl Debug for UnsafeWorldCell<'_> { diff --git a/examples/README.md b/examples/README.md index f9857d60b272a..df2d906d2f2fd 100644 --- a/examples/README.md +++ b/examples/README.md @@ -276,6 +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) | 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 diff --git a/examples/ecs/observers.rs b/examples/ecs/observers.rs new file mode 100644 index 0000000000000..0ebdfac701afd --- /dev/null +++ b/examples/ecs/observers.rs @@ -0,0 +1,209 @@ +//! Demonstrates how to observe life-cycle triggers as well as define custom ones. + +use bevy::{ + prelude::*, + utils::{HashMap, HashSet}, +}; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .init_resource::() + .add_systems(Startup, setup) + .add_systems(Update, (draw_shapes, handle_click)) + // Observers are systems that run when an event is "triggered". This observer runs whenever + // `ExplodeMines` is triggered. + .observe( + |trigger: Trigger, + mines: Query<&Mine>, + index: Res, + mut commands: Commands| { + // You can access the trigger data via the `Observer` + let event = trigger.event(); + // Access resources + for e in index.get_nearby(event.pos) { + // Run queries + let mine = mines.get(e).unwrap(); + 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); + } + } + }, + ) + // This observer runs whenever the `Mine` component is added to an entity, and places it in a simple spatial index. + .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(on_remove_mine) + .run(); +} + +#[derive(Component)] +struct Mine { + pos: Vec2, + 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, + radius: f32, +} + +#[derive(Event)] +struct Explode; + +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: asset_server.load("fonts/FiraMono-Medium.ttf"), + font_size: 24., + color: Color::WHITE, + }, + )); + + 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 { + let entity = commands.spawn(Mine::random()).id(); + observer.watch_entity(entity); + } + + // By spawning the Observer component, it becomes active! + commands.spawn(observer); +} + +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, + (mine.pos.y / CELL_SIZE).floor() as i32, + ); + index.map.entry(tile).or_default().insert(trigger.entity()); +} + +// Remove despawned mines from our index +fn on_remove_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, + (mine.pos.y / CELL_SIZE).floor() as i32, + ); + index.map.entry(tile).and_modify(|set| { + 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 `.entity()` + let id = trigger.entity(); + let Some(mut entity) = commands.get_entity(id) else { + return; + }; + println!("Boom! {:?} exploded.", id.index()); + entity.despawn(); + let mine = query.get(id).unwrap(); + // Trigger another explosion cascade. + 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 { + gizmos.circle_2d( + mine.pos, + mine.size, + Color::hsl((mine.size - 4.0) / 16.0 * 360.0, 1.0, 0.8), + ); + } +} + +// Trigger `ExplodeMines` at the position of a given 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.trigger(ExplodeMines { pos, radius: 1.0 }); + } + } +} + +#[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 + } +}