diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index 1706ead914..2b5c8f5077 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -1,5 +1,7 @@ +use std::any::Any; + use crate::{ - component::{ComponentHooks, ComponentId, StorageType}, + component::{ComponentHook, ComponentHooks, ComponentId, StorageType}, observer::{ObserverDescriptor, ObserverTrigger}, prelude::*, query::DebugCheckedUnwrap, @@ -257,22 +259,23 @@ pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate: /// 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. [`ObserverState`] can be used to filter for [`Observer`] [`Entity`]s, e.g. -/// [`With`]. +/// serves as the "source of truth" of the observer. /// /// [`SystemParam`]: crate::system::SystemParam -pub struct Observer { - system: BoxedObserverSystem, +pub struct Observer { + system: Box, descriptor: ObserverDescriptor, + hook_on_add: ComponentHook, } -impl Observer { +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 { + pub fn new>(system: I) -> Self { Self { system: Box::new(IntoObserverSystem::into_system(system)), descriptor: Default::default(), + hook_on_add: hook_on_add::, } } @@ -308,54 +311,20 @@ impl Observer { } } -impl Component for Observer { +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() - }); - } - } - }); + hooks.on_add(|world, entity, _id| { + let Some(observe) = world.get::(entity) else { + return; + }; + let hook = observe.hook_on_add; + hook(world, entity, _id); }); } } -/// Equivalent to [`BoxedSystem`](crate::system::BoxedSystem) for [`ObserverSystem`]. -pub type BoxedObserverSystem = Box>; - -fn observer_system_runner( +fn observer_system_runner>( mut world: DeferredWorld, observer_trigger: ObserverTrigger, ptr: PtrMut, @@ -395,23 +364,75 @@ fn observer_system_runner( // 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 `Observer` component. - let system = unsafe { - &mut observer_cell - .get_mut::>() - .debug_checked_unwrap() - .system + // SAFETY: + // - observer was triggered so must have an `Observer` component. + // - observer cannot be dropped or mutated until after the system pointer is already dropped. + let system: *mut dyn ObserverSystem = unsafe { + let mut observe = observer_cell.get_mut::().debug_checked_unwrap(); + let system = observe.system.downcast_mut::().unwrap(); + &mut *system }; - system.update_archetype_component_access(world); - // SAFETY: - // - `update_archetype_component_access` was just called + // - `update_archetype_component_access` is called first // - 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()); + (*system).update_archetype_component_access(world); + (*system).run_unsafe(trigger, world); + (*system).queue_deferred(world.into_deferred()); } } + +/// A [`ComponentHook`] used by [`Observer`] to handle its [`on-add`](`ComponentHooks::on_add`). +/// +/// This function exists separate from [`Observer`] to allow [`Observer`] to have its type parameters +/// erased. +/// +/// The type parameters of this function _must_ match those used to create the [`Observer`]. +/// As such, it is recommended to only use this function within the [`Observer::new`] method to +/// ensure type parameters match. +fn hook_on_add>( + mut world: DeferredWorld<'_>, + entity: Entity, + _: ComponentId, +) { + 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); + let system = observe.system.downcast_mut::().unwrap(); + &mut *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() + }); + } + } + }); +}