# Objective - Fixes #13825 ## Solution - Cherry picked and fixed non-trivial conflicts to be able to merge #10839 into the 0.14 release branch. Link to PR: https://github.com/bevyengine/bevy/pull/10839 Co-authored-by: James O'Brien <james.obrien@drafly.net> 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:
parent
5ed296ff03
commit
eca8220761
11
Cargo.toml
11
Cargo.toml
@ -2534,6 +2534,17 @@ description = "Systems run in parallel, but their order isn't always determinist
|
||||
category = "ECS (Entity Component System)"
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
name = "observers"
|
||||
path = "examples/ecs/observers.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.observers]
|
||||
name = "Observers"
|
||||
description = "Demonstrates observers that react to events (both built-in life-cycle events and custom events)"
|
||||
category = "ECS (Entity Component System)"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "3d_rotation"
|
||||
path = "examples/transforms/3d_rotation.rs"
|
||||
|
@ -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>;
|
||||
|
@ -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/
|
||||
|
@ -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;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
|
@ -5,7 +5,7 @@ use crate::batching::BatchingStrategy;
|
||||
use crate::change_detection::MutUntyped;
|
||||
use crate::{
|
||||
change_detection::{DetectChangesMut, Mut},
|
||||
component::{ComponentId, Tick},
|
||||
component::{Component, ComponentId, Tick},
|
||||
system::{Local, Res, ResMut, Resource, SystemParam},
|
||||
world::World,
|
||||
};
|
||||
@ -24,16 +24,30 @@ use std::{
|
||||
slice::Iter,
|
||||
};
|
||||
|
||||
/// A type that can be stored in an [`Events<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`].
|
||||
///
|
||||
@ -556,6 +570,11 @@ impl<'w, 's, E: Event> EventReader<'w, 's, E> {
|
||||
///
|
||||
/// # bevy_ecs::system::assert_is_system(my_system);
|
||||
/// ```
|
||||
/// # Observers
|
||||
///
|
||||
/// "Buffered" Events, such as those sent directly in [`Events`] or sent using [`EventWriter`], do _not_ automatically
|
||||
/// trigger any [`Observer`]s watching for that event, as each [`Event`] has different requirements regarding _if_ it will
|
||||
/// be triggered, and if so, _when_ it will be triggered in the schedule.
|
||||
///
|
||||
/// # Concurrency
|
||||
///
|
||||
@ -588,6 +607,8 @@ impl<'w, 's, E: Event> EventReader<'w, 's, E> {
|
||||
/// }
|
||||
/// ```
|
||||
/// Note that this is considered *non-idiomatic*, and should only be used when `EventWriter` will not work.
|
||||
///
|
||||
/// [`Observer`]: crate::observer::Observer
|
||||
#[derive(SystemParam)]
|
||||
pub struct EventWriter<'w, E: Event> {
|
||||
events: ResMut<'w, Events<E>>,
|
||||
|
@ -21,6 +21,7 @@ pub mod event;
|
||||
pub mod identifier;
|
||||
pub mod intern;
|
||||
pub mod label;
|
||||
pub mod observer;
|
||||
pub mod query;
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
pub mod reflect;
|
||||
@ -46,6 +47,7 @@ pub mod prelude {
|
||||
component::Component,
|
||||
entity::{Entity, EntityMapper},
|
||||
event::{Event, EventReader, EventWriter, Events, ShouldUpdateEvents},
|
||||
observer::{Observer, Trigger},
|
||||
query::{Added, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without},
|
||||
removal_detection::RemovedComponents,
|
||||
schedule::{
|
||||
@ -57,7 +59,9 @@ pub mod prelude {
|
||||
ParamSet, Query, ReadOnlySystem, Res, ResMut, Resource, System, SystemBuilder,
|
||||
SystemParamFunction,
|
||||
},
|
||||
world::{EntityMut, EntityRef, EntityWorldMut, FromWorld, World},
|
||||
world::{
|
||||
EntityMut, EntityRef, EntityWorldMut, FromWorld, OnAdd, OnInsert, OnRemove, World,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
42
crates/bevy_ecs/src/observer/entity_observer.rs
Normal file
42
crates/bevy_ecs/src/observer/entity_observer.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
638
crates/bevy_ecs/src/observer/mod.rs
Normal file
638
crates/bevy_ecs/src/observer/mod.rs
Normal 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);
|
||||
}
|
||||
}
|
409
crates/bevy_ecs/src/observer/runner.rs
Normal file
409
crates/bevy_ecs/src/observer/runner.rs
Normal 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());
|
||||
}
|
||||
}
|
165
crates/bevy_ecs/src/observer/trigger_event.rs
Normal file
165
crates/bevy_ecs/src/observer/trigger_event.rs
Normal 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()
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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::*;
|
||||
|
71
crates/bevy_ecs/src/system/observer_system.rs
Normal file
71
crates/bevy_ecs/src/system/observer_system.rs
Normal 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);
|
@ -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);
|
||||
|
||||
|
@ -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>(
|
||||
|
@ -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.
|
||||
|
23
crates/bevy_ecs/src/world/component_constants.rs
Normal file
23
crates/bevy_ecs/src/world/component_constants.rs
Normal 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;
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
///
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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<'_> {
|
||||
|
@ -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
209
examples/ecs/observers.rs
Normal 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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user