Generalised ECS reactivity with Observers (#10839)

# Objective

- Provide an expressive way to register dynamic behavior in response to
ECS changes that is consistent with existing bevy types and traits as to
provide a smooth user experience.
- Provide a mechanism for immediate changes in response to events during
command application in order to facilitate improved query caching on the
path to relations.

## Solution

- A new fundamental ECS construct, the `Observer`; inspired by flec's
observers but adapted to better fit bevy's access patterns and rust's
type system.

---

## Examples
There are 3 main ways to register observers. The first is a "component
observer" that looks like this:
```rust
world.observe(|trigger: Trigger<OnAdd, Transform>, query: Query<&Transform>| {
    let transform = query.get(trigger.entity()).unwrap();
});
```
The above code will spawn a new entity representing the observer that
will run it's callback whenever the `Transform` component is added to an
entity. This is a system-like function that supports dependency
injection for all the standard bevy types: `Query`, `Res`, `Commands`
etc. It also has a `Trigger` parameter that provides information about
the trigger such as the target entity, and the event being triggered.
Importantly these systems run during command application which is key
for their future use to keep ECS internals up to date. There are similar
events for `OnInsert` and `OnRemove`, and this will be expanded with
things such as `ArchetypeCreated`, `TableEmpty` etc. in follow up PRs.

Another way to register an observer is an "entity observer" that looks
like this:
```rust
world.entity_mut(entity).observe(|trigger: Trigger<Resize>| {
    // ...
});
```
Entity observers run whenever an event of their type is triggered
targeting that specific entity. This type of observer will de-spawn
itself if the entity (or entities) it is observing is ever de-spawned so
as to not leave dangling observers.

Entity observers can also be spawned from deferred contexts such as
other observers, systems, or hooks using commands:
```rust
commands.entity(entity).observe(|trigger: Trigger<Resize>| {
    // ...
});
```

Observers are not limited to in built event types, they can be used with
any type that implements `Event` (which has been extended to implement
Component). This means events can also carry data:

```rust
#[derive(Event)]
struct Resize { x: u32, y: u32 }

commands.entity(entity).observe(|trigger: Trigger<Resize>, query: Query<&mut Size>| {
    let event = trigger.event();
    // ...
});

// Will trigger the observer when commands are applied.
commands.trigger_targets(Resize { x: 10, y: 10 }, entity);
```

You can also trigger events that target more than one entity at a time:

```rust
commands.trigger_targets(Resize { x: 10, y: 10 }, [e1, e2]);
```

Additionally, Observers don't _need_ entity targets:

```rust
app.observe(|trigger: Trigger<Quit>| {
})

commands.trigger(Quit);
```

In these cases, `trigger.entity()` will be a placeholder.

Observers are actually just normal entities with an `ObserverState` and
`Observer` component! The `observe()` functions above are just shorthand
for:

```rust
world.spawn(Observer::new(|trigger: Trigger<Resize>| {});
```

This will spawn the `Observer` system and use an `on_add` hook to add
the `ObserverState` component.

Dynamic components and trigger types are also fully supported allowing
for runtime defined trigger types.

## Possible Follow-ups
1. Deprecate `RemovedComponents`, observers should fulfill all use cases
while being more flexible and performant.
2. Queries as entities: Swap queries to entities and begin using
observers listening to archetype creation triggers to keep their caches
in sync, this allows unification of `ObserverState` and `QueryState` as
well as unlocking several API improvements for `Query` and the
management of `QueryState`.
3. Trigger bubbling: For some UI use cases in particular users are
likely to want some form of bubbling for entity observers, this is
trivial to implement naively but ideally this includes an acceleration
structure to cache hierarchy traversals.
4. All kinds of other in-built trigger types.
5. Optimization; in order to not bloat the complexity of the PR I have
kept the implementation straightforward, there are several areas where
performance can be improved. The focus for this PR is to get the
behavior implemented and not incur a performance cost for users who
don't use observers.

I am leaving each of these to follow up PR's in order to keep each of
them reviewable as this already includes significant changes.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: MiniaczQ <xnetroidpl@gmail.com>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
James O'Brien 2024-06-14 18:33:26 -07:00 committed by GitHub
parent 1a1b22ede8
commit eb3c81374a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 2261 additions and 162 deletions

View file

@ -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"

View file

@ -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<E: Event, B: Bundle, M>(
&mut self,
observer: impl IntoObserverSystem<E, B, M>,
) -> &mut Self {
self.world_mut().observe(observer);
self
}
}
type RunnerFn = Box<dyn FnOnce(App) -> AppExit>;

View file

@ -307,4 +307,52 @@ fn reader(mut reader: EventReader<MyEvent>) {
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<MyEvent>| {
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<Explode>, 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/

View file

@ -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;
}
})
}

View file

@ -74,6 +74,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
.collect::<Vec<_>>();
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,

View file

@ -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<ComponentStatus>,
pub added: Vec<ComponentId>,
}
/// 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<ComponentStatus>,
added: Vec<ComponentId>,
) {
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<Item = (ComponentId, ArchetypeComponentId)>,
@ -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<ComponentId>,
sparse_set_components: Vec<ComponentId>,
@ -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<RangeFrom<ArchetypeGeneration>> for Archetypes {

View file

@ -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<ComponentId>));
/// 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<C: Component> 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<ComponentId>)) {
ids(components.get_id(TypeId::of::<C>()));
}
}
impl<C: Component> 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<ComponentId>)){
$(<$name as Bundle>::get_component_ids(components, ids);)*
}
#[allow(unused_variables, unused_mut)]
#[allow(clippy::unused_unit)]
unsafe fn from_components<T, F>(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::<C>()
.on_add(|mut world, _, _| {
world.resource_mut::<R>().assert_order(2);
world.resource_mut::<R>().assert_order(3);
});
world
.register_component_hooks::<D>()
.on_add(|mut world, _, _| {
world.resource_mut::<R>().assert_order(3);
world.resource_mut::<R>().assert_order(2);
});
world.spawn(A).flush();

View file

@ -1,3 +1,4 @@
use crate::component::Component;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
use std::{
@ -7,16 +8,30 @@ use std::{
marker::PhantomData,
};
/// A type that can be stored in an [`Events<E>`] resource
/// Something that "happens" and might be read / observed by app logic.
///
/// Events can be stored in an [`Events<E>`] 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`].
///

View file

@ -20,6 +20,11 @@ use bevy_ecs::{
///
/// # bevy_ecs::system::assert_is_system(my_system);
/// ```
/// # Observers
///
/// "Buffered" Events, such as those sent directly in [`Events`] or sent using [`EventWriter`], do _not_ automatically
/// trigger any [`Observer`]s watching for that event, as each [`Event`] has different requirements regarding _if_ it will
/// be triggered, and if so, _when_ it will be triggered in the schedule.
///
/// # Concurrency
///
@ -52,6 +57,8 @@ use bevy_ecs::{
/// }
/// ```
/// Note that this is considered *non-idiomatic*, and should only be used when `EventWriter` will not work.
///
/// [`Observer`]: crate::observer::Observer
#[derive(SystemParam)]
pub struct EventWriter<'w, E: Event> {
events: ResMut<'w, Events<E>>,

View file

@ -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},
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,
},
};
}

View file

@ -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<Entity>);
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::<ObservedBy>(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::<ObserverState>() 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();
}
}
});
}
}

View file

@ -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<B>,
}
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<ComponentId>,
/// The components the observer is watching.
components: Vec<ComponentId>,
/// The entities the observer is watching.
entities: Vec<Entity>,
}
impl ObserverDescriptor {
/// Add the given `triggers` to the descriptor.
pub fn with_triggers(mut self, triggers: Vec<ComponentId>) -> Self {
self.events = triggers;
self
}
/// Add the given `components` to the descriptor.
pub fn with_components(mut self, components: Vec<ComponentId>) -> Self {
self.components = components;
self
}
/// Add the given `entities` to the descriptor.
pub fn with_entities(mut self, entities: Vec<Entity>) -> 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<Entity, ObserverRunner>;
/// 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<Entity, ObserverMap>,
}
/// 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<ComponentId, CachedComponentObservers>,
// Observers listening for this trigger fired at a specific entity
entity_observers: EntityHashMap<Entity, ObserverMap>,
}
/// 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<ComponentId, CachedObservers>,
}
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<T>(
mut world: DeferredWorld,
event_type: ComponentId,
entity: Entity,
components: impl Iterator<Item = ComponentId>,
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<ArchetypeFlags> {
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<E: Event, B: Bundle, M>(
&mut self,
system: impl IntoObserverSystem<E, B, M>,
) -> 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::<ObserverState>(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::<ObservedBy>().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::<R>();
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.assert_order(0));
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<R>| res.assert_order(1));
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<R>| res.assert_order(2));
let entity = world.spawn(A).id();
world.despawn(entity);
assert_eq!(3, world.resource::<R>().0);
}
#[test]
fn observer_order_insert_remove() {
let mut world = World::new();
world.init_resource::<R>();
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.assert_order(0));
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<R>| res.assert_order(1));
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<R>| res.assert_order(2));
let mut entity = world.spawn_empty();
entity.insert(A);
entity.remove::<A>();
entity.flush();
assert_eq!(3, world.resource::<R>().0);
}
#[test]
fn observer_order_recursive() {
let mut world = World::new();
world.init_resource::<R>();
world.observe(
|obs: Trigger<OnAdd, A>, mut res: ResMut<R>, mut commands: Commands| {
res.assert_order(0);
commands.entity(obs.entity()).insert(B);
},
);
world.observe(
|obs: Trigger<OnRemove, A>, mut res: ResMut<R>, mut commands: Commands| {
res.assert_order(2);
commands.entity(obs.entity()).remove::<B>();
},
);
world.observe(
|obs: Trigger<OnAdd, B>, mut res: ResMut<R>, mut commands: Commands| {
res.assert_order(1);
commands.entity(obs.entity()).remove::<A>();
},
);
world.observe(|_: Trigger<OnRemove, B>, mut res: ResMut<R>| {
res.assert_order(3);
});
let entity = world.spawn(A).flush();
let entity = world.get_entity(entity).unwrap();
assert!(!entity.contains::<A>());
assert!(!entity.contains::<B>());
assert_eq!(4, world.resource::<R>().0);
}
#[test]
fn observer_multiple_listeners() {
let mut world = World::new();
world.init_resource::<R>();
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.0 += 1);
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.0 += 1);
world.spawn(A).flush();
assert_eq!(2, world.resource::<R>().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::<R>();
let on_remove = world.init_component::<OnRemove>();
world.spawn(
Observer::new(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.0 += 1)
.with_event(on_remove),
);
let entity = world.spawn(A).id();
world.despawn(entity);
assert_eq!(2, world.resource::<R>().0);
}
#[test]
fn observer_multiple_components() {
let mut world = World::new();
world.init_resource::<R>();
world.init_component::<A>();
world.init_component::<B>();
world.observe(|_: Trigger<OnAdd, (A, B)>, mut res: ResMut<R>| res.0 += 1);
let entity = world.spawn(A).id();
world.entity_mut(entity).insert(B);
world.flush();
assert_eq!(2, world.resource::<R>().0);
}
#[test]
fn observer_despawn() {
let mut world = World::new();
world.init_resource::<R>();
let observer = world
.observe(|_: Trigger<OnAdd, A>| 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::<R>();
world.observe(|_: Trigger<OnAdd, (A, B)>, mut res: ResMut<R>| res.0 += 1);
world.spawn((A, B)).flush();
assert_eq!(1, world.resource::<R>().0);
}
#[test]
fn observer_no_target() {
let mut world = World::new();
world.init_resource::<R>();
world
.spawn_empty()
.observe(|_: Trigger<EventA>| panic!("Trigger routed to non-targeted entity."));
world.observe(move |obs: Trigger<EventA>, mut res: ResMut<R>| {
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::<R>().0);
}
#[test]
fn observer_entity_routing() {
let mut world = World::new();
world.init_resource::<R>();
world
.spawn_empty()
.observe(|_: Trigger<EventA>| panic!("Trigger routed to non-targeted entity."));
let entity = world
.spawn_empty()
.observe(|_: Trigger<EventA>, mut res: ResMut<R>| res.0 += 1)
.id();
world.observe(move |obs: Trigger<EventA>, mut res: ResMut<R>| {
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::<R>().0);
}
#[test]
fn observer_dynamic_component() {
let mut world = World::new();
world.init_resource::<R>();
let component_id = world.init_component::<A>();
world.spawn(
Observer::new(|_: Trigger<OnAdd>, mut res: ResMut<R>| 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::<R>().0);
}
#[test]
fn observer_dynamic_trigger() {
let mut world = World::new();
world.init_resource::<R>();
let event_a = world.init_component::<EventA>();
world.spawn(ObserverState {
descriptor: ObserverDescriptor::default().with_triggers(vec![event_a]),
runner: |mut world, _trigger, _ptr| {
world.resource_mut::<R>().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::<R>().0);
}
}

View file

@ -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<Item = ComponentId>) -> 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<Item = Entity>) -> 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<Item = ComponentId>) -> 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::<ObserverState>()
.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<Speak>| {
/// 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<Speak>| {});
/// world.spawn(Observer::new(|trigger: Trigger<Speak>| {}));
/// ```
///
/// 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<PrintNames>, 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<SpawnThing>, 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<A>, 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<Explode>, 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<Explode>, mut commands: Commands| {
/// println!("Boom!");
/// commands.entity(trigger.entity()).despawn();
/// });
///
/// world.entity_mut(e2).observe(|trigger: Trigger<Explode>, 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<Explode>| {});
/// 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<T: 'static, B: Bundle> {
system: BoxedObserverSystem<T, B>,
descriptor: ObserverDescriptor,
}
impl<E: Event, B: Bundle> Observer<E, B> {
/// 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<M>(system: impl IntoObserverSystem<E, B, M>) -> 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<E: Event, B: Bundle> Component for Observer<E, B> {
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::<E>();
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<E, B> =
if let Some(mut observe) = world.get_mut::<Self>(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::<ObserverState>() {
entry.insert(ObserverState {
descriptor,
runner: observer_system_runner::<E, B>,
..Default::default()
});
}
}
});
});
}
}
/// Equivalent to [`BoxedSystem`](crate::system::BoxedSystem) for [`ObserverSystem`].
pub type BoxedObserverSystem<E = (), B = ()> = Box<dyn ObserverSystem<E, B>>;
fn observer_system_runner<E: Event, B: Bundle>(
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::<ObserverState>()
.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<E, B> = 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::<Observer<E, B>>()
.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());
}
}

View file

@ -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<E, Targets: TriggerTargets = ()> {
/// The event to trigger.
pub event: E,
/// The targets to trigger the event for.
pub targets: Targets,
}
impl<E: Event, Targets: TriggerTargets> Command for TriggerEvent<E, Targets> {
fn apply(mut self, world: &mut World) {
let event_type = world.init_component::<E>();
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<T, Targets: TriggerTargets = ()> {
event_type: ComponentId,
event_data: T,
targets: Targets,
}
impl<E, Targets: TriggerTargets> EmitDynamicTrigger<E, Targets> {
/// 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<E: Event, Targets: TriggerTargets> Command for EmitDynamicTrigger<E, Targets> {
fn apply(mut self, world: &mut World) {
trigger_event(world, self.event_type, &mut self.event_data, self.targets);
}
}
#[inline]
fn trigger_event<E, Targets: TriggerTargets>(
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<Item = ComponentId>;
/// The entities the trigger should target.
fn entities(&self) -> impl ExactSizeIterator<Item = Entity>;
}
impl TriggerTargets for () {
fn components(&self) -> impl ExactSizeIterator<Item = ComponentId> {
[].into_iter()
}
fn entities(&self) -> impl ExactSizeIterator<Item = Entity> {
[].into_iter()
}
}
impl TriggerTargets for Entity {
fn components(&self) -> impl ExactSizeIterator<Item = ComponentId> {
[].into_iter()
}
fn entities(&self) -> impl ExactSizeIterator<Item = Entity> {
std::iter::once(*self)
}
}
impl TriggerTargets for Vec<Entity> {
fn components(&self) -> impl ExactSizeIterator<Item = ComponentId> {
[].into_iter()
}
fn entities(&self) -> impl ExactSizeIterator<Item = Entity> {
self.iter().copied()
}
}
impl<const N: usize> TriggerTargets for [Entity; N] {
fn components(&self) -> impl ExactSizeIterator<Item = ComponentId> {
[].into_iter()
}
fn entities(&self) -> impl ExactSizeIterator<Item = Entity> {
self.iter().copied()
}
}
impl TriggerTargets for ComponentId {
fn components(&self) -> impl ExactSizeIterator<Item = ComponentId> {
std::iter::once(*self)
}
fn entities(&self) -> impl ExactSizeIterator<Item = Entity> {
[].into_iter()
}
}
impl TriggerTargets for Vec<ComponentId> {
fn components(&self) -> impl ExactSizeIterator<Item = ComponentId> {
self.iter().copied()
}
fn entities(&self) -> impl ExactSizeIterator<Item = Entity> {
[].into_iter()
}
}
impl<const N: usize> TriggerTargets for [ComponentId; N] {
fn components(&self) -> impl ExactSizeIterator<Item = ComponentId> {
self.iter().copied()
}
fn entities(&self) -> impl ExactSizeIterator<Item = Entity> {
[].into_iter()
}
}

View file

@ -647,6 +647,16 @@ impl<T: SparseSetIndex> FilteredAccessSet<T> {
.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();

View file

@ -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);
}

View file

@ -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);

View file

@ -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<C: Command>(&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<E: Event, B: Bundle, M>(
&mut self,
observer: impl IntoObserverSystem<E, B, M>,
) -> 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<E: Event, B: Bundle, M>(
&mut self,
system: impl IntoObserverSystem<E, B, M>,
) -> &mut Self {
self.add(observe(system));
self
}
}
impl<F> Command for F
@ -1285,6 +1327,16 @@ fn log_components(entity: Entity, world: &mut World) {
info!("Entity {:?}: {:?}", entity, debug_infos);
}
fn observe<E: Event, B: Bundle, M>(
observer: impl IntoObserverSystem<E, B, M>,
) -> 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 {

View file

@ -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);

View file

@ -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<Marker>,
{
func: F,
param_state: Option<<F::Param as SystemParam>::State>,
system_meta: SystemMeta,
pub(crate) param_state: Option<<F::Param as SystemParam>::State>,
pub(crate) system_meta: SystemMeta,
world_id: Option<WorldId>,
archetype_generation: ArchetypeGeneration,
// NOTE: PhantomData<fn()-> 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 {

View file

@ -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::*;

View file

@ -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<E: 'static, B: Bundle>:
System<In = Trigger<'static, E, B>, Out = ()> + Send + 'static
{
}
impl<E: 'static, B: Bundle, T: System<In = Trigger<'static, E, B>, Out = ()> + Send + 'static>
ObserverSystem<E, B> for T
{
}
/// Implemented for systems that convert into [`ObserverSystem`].
pub trait IntoObserverSystem<E: 'static, B: Bundle, M>: Send + 'static {
/// The type of [`System`] that this instance converts into.
type System: ObserverSystem<E, B>;
/// Turns this value into its corresponding [`System`].
fn into_system(this: Self) -> Self::System;
}
impl<S: IntoSystem<Trigger<'static, E, B>, (), M> + Send + 'static, M, E: 'static, B: Bundle>
IntoObserverSystem<E, B, M> for S
where
S::System: ObserverSystem<E, B>,
{
type System = <S as IntoSystem<Trigger<'static, E, B>, (), 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<E: 'static, B: Bundle, Func: Send + Sync + 'static, $($param: SystemParam),*> SystemParamFunction<fn(Trigger<E, B>, $($param,)*)> for Func
where
for <'a> &'a mut Func:
FnMut(Trigger<E, B>, $($param),*) +
FnMut(Trigger<E, B>, $(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<E: 'static, B: Bundle, $($param,)*>(
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);

View file

@ -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);

View file

@ -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<T: SystemBuffer> 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>(

View file

@ -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<Unaligned>,
world: Option<NonNull<World>>,
cursor: NonNull<usize>,
),
consume_command_and_get_size:
unsafe fn(value: OwningPtr<Unaligned>, world: Option<NonNull<World>>, 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<MaybeUninit<u8>>,
pub(crate) cursor: usize,
pub(crate) panic_recovery: Vec<MaybeUninit<u8>>,
}
/// 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<Vec<MaybeUninit<u8>>>,
pub(crate) cursor: NonNull<usize>,
pub(crate) panic_recovery: NonNull<Vec<MaybeUninit<u8>>>,
}
// 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::<C>() }
consume_command_and_get_size: |command, world, cursor| {
*cursor += std::mem::size_of::<C>();
// 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<NonNull<World>>) {
// 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::<CommandMeta>()
.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::<CommandMeta>() };
local_cursor += std::mem::size_of::<CommandMeta>();
// 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::<Unaligned>::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<usize>);
let mut world = World::new();
world.init_resource::<Order>();
fn add_index(index: usize) -> impl Command {
move |world: &mut World| world.resource_mut::<Order>().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::<Order>().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.

View file

@ -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;

View file

@ -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,37 +73,42 @@ impl<'w> DeferredWorld<'w> {
/// Returns `None` if the `entity` does not have a [`Component`] of the given type.
#[inline]
pub fn get_mut<T: Component>(&mut self, entity: Entity) -> Option<Mut<T>> {
// 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`.
/// This will panic if the `entity` does not exist. Use [`Self::get_entity_mut`] if you want
/// to check for entity existence instead of implicitly panic-ing.
#[inline]
#[track_caller]
pub fn entity_mut(&mut self, entity: Entity) -> EntityMut {
#[inline(never)]
#[cold]
#[track_caller]
fn panic_no_entity(entity: Entity) -> ! {
panic!("Entity {entity:?} does not exist");
}
match self.get_entity_mut(entity) {
Some(entity) => entity,
None => panic_no_entity(entity),
}
}
/// Retrieves an [`EntityMut`] that exposes read and write operations for the given `entity`.
/// Returns [`None`] if the `entity` does not exist.
/// Instead of unwrapping the value returned from this function, prefer [`Self::entity_mut`].
#[inline]
pub fn get_entity_mut(&mut self, entity: Entity) -> Option<EntityMut> {
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)) })
// 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]
pub fn entity_mut(&mut self, entity: Entity) -> EntityMut {
#[inline(never)]
#[cold]
fn panic_no_entity(entity: Entity) -> ! {
panic!("Entity {entity:?} does not exist");
}
match self.get_entity_mut(entity) {
Some(entity) => entity,
None => panic_no_entity(entity),
}
}
/// Returns [`Query`] for the given [`QueryState`], which is used to efficiently
@ -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<Item = ComponentId>,
) {
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<Item = ComponentId>,
) {
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<Item = ComponentId>,
) {
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<Item = ComponentId>,
) {
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<E>(
&mut self,
event: ComponentId,
entity: Entity,
components: impl Iterator<Item = ComponentId>,
data: &mut E,
) {
Observers::invoke(self.reborrow(), event, entity, components, data);
}
/// Sends a "global" [`Trigger`] without any targets.
pub fn trigger<T: Event>(&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
}
}

View file

@ -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<E: Event, B: Bundle, M>(
&mut self,
observer: impl IntoObserverSystem<E, B, M>,
) -> &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,

View file

@ -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::<OnAdd>());
assert_eq!(ON_INSERT, self.init_component::<OnInsert>());
assert_eq!(ON_REMOVE, self.init_component::<OnRemove>());
}
/// 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<EntityWorldMut> {
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<B: Bundle>(&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<Item = (Entity, B)>,
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.
///

View file

@ -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();

View file

@ -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<'_> {

View file

@ -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

209
examples/ecs/observers.rs Normal file
View file

@ -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::<SpatialIndex>()
.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<ExplodeMines>,
mines: Query<&Mine>,
index: Res<SpatialIndex>,
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::<f32>() - 0.5) * 1200.0,
(rand::random::<f32>() - 0.5) * 600.0,
),
size: 4.0 + rand::random::<f32>() * 16.0,
}
}
}
#[derive(Event)]
struct ExplodeMines {
pos: Vec2,
radius: f32,
}
#[derive(Event)]
struct Explode;
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
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<OnAdd, Mine>,
query: Query<&Mine>,
mut index: ResMut<SpatialIndex>,
) {
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<OnRemove, Mine>,
query: Query<&Mine>,
mut index: ResMut<SpatialIndex>,
) {
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<Explode>, 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<ButtonInput<MouseButton>>,
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<Entity>>,
}
/// 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<Entity> {
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
}
}