
# Objective Currently, the observer API looks like this: ```rust app.add_observer(|trigger: Trigger<Explode>| { info!("Entity {} exploded!", trigger.target()); }); ``` Future plans for observers also include "multi-event observers" with a trigger that looks like this (see [Cart's example](https://github.com/bevyengine/bevy/issues/14649#issuecomment-2960402508)): ```rust trigger: Trigger<( OnAdd<Pressed>, OnRemove<Pressed>, OnAdd<InteractionDisabled>, OnRemove<InteractionDisabled>, OnInsert<Hovered>, )>, ``` In scenarios like this, there is a lot of repetition of `On`. These are expected to be very high-traffic APIs especially in UI contexts, so ergonomics and readability are critical. By renaming `Trigger` to `On`, we can make these APIs read more cleanly and get rid of the repetition: ```rust app.add_observer(|trigger: On<Explode>| { info!("Entity {} exploded!", trigger.target()); }); ``` ```rust trigger: On<( Add<Pressed>, Remove<Pressed>, Add<InteractionDisabled>, Remove<InteractionDisabled>, Insert<Hovered>, )>, ``` Names like `On<Add<Pressed>>` emphasize the actual event listener nature more than `Trigger<OnAdd<Pressed>>`, and look cleaner. This *also* frees up the `Trigger` name if we want to use it for the observer event type, splitting them out from buffered events (bikeshedding this is out of scope for this PR though). For prior art: [`bevy_eventlistener`](https://github.com/aevyrie/bevy_eventlistener) used [`On`](https://docs.rs/bevy_eventlistener/latest/bevy_eventlistener/event_listener/struct.On.html) for its event listener type. Though in our case, the observer is the event listener, and `On` is just a type containing information about the triggered event. ## Solution Steal from `bevy_event_listener` by @aevyrie and use `On`. - Rename `Trigger` to `On` - Rename `OnAdd` to `Add` - Rename `OnInsert` to `Insert` - Rename `OnReplace` to `Replace` - Rename `OnRemove` to `Remove` - Rename `OnDespawn` to `Despawn` ## Discussion ### Naming Conflicts?? Using a name like `Add` might initially feel like a very bad idea, since it risks conflict with `core::ops::Add`. However, I don't expect this to be a big problem in practice. - You rarely need to actually implement the `Add` trait, especially in modules that would use the Bevy ECS. - In the rare cases where you *do* get a conflict, it is very easy to fix by just disambiguating, for example using `ops::Add`. - The `Add` event is a struct while the `Add` trait is a trait (duh), so the compiler error should be very obvious. For the record, renaming `OnAdd` to `Add`, I got exactly *zero* errors or conflicts within Bevy itself. But this is of course not entirely representative of actual projects *using* Bevy. You might then wonder, why not use `Added`? This would conflict with the `Added` query filter, so it wouldn't work. Additionally, the current naming convention for observer events does not use past tense. ### Documentation This does make documentation slightly more awkward when referring to `On` or its methods. Previous docs often referred to `Trigger::target` or "sends a `Trigger`" (which is... a bit strange anyway), which would now be `On::target` and "sends an observer `Event`". You can see the diff in this PR to see some of the effects. I think it should be fine though, we may just need to reword more documentation to read better.
204 lines
7.1 KiB
Rust
204 lines
7.1 KiB
Rust
//! This example demonstrates immutable components.
|
|
|
|
use bevy::{
|
|
ecs::{
|
|
component::{ComponentCloneBehavior, ComponentDescriptor, ComponentId, StorageType},
|
|
lifecycle::HookContext,
|
|
world::DeferredWorld,
|
|
},
|
|
platform::collections::HashMap,
|
|
prelude::*,
|
|
ptr::OwningPtr,
|
|
};
|
|
use core::alloc::Layout;
|
|
|
|
/// This component is mutable, the default case. This is indicated by components
|
|
/// implementing [`Component`] where [`Component::Mutability`] is [`Mutable`](bevy::ecs::component::Mutable).
|
|
#[derive(Component)]
|
|
pub struct MyMutableComponent(bool);
|
|
|
|
/// This component is immutable. Once inserted into the ECS, it can only be viewed,
|
|
/// or removed. Replacement is also permitted, as this is equivalent to removal
|
|
/// and insertion.
|
|
///
|
|
/// Adding the `#[component(immutable)]` attribute prevents the implementation of [`Component<Mutability = Mutable>`]
|
|
/// in the derive macro.
|
|
#[derive(Component)]
|
|
#[component(immutable)]
|
|
pub struct MyImmutableComponent(bool);
|
|
|
|
fn demo_1(world: &mut World) {
|
|
// Immutable components can be inserted just like mutable components.
|
|
let mut entity = world.spawn((MyMutableComponent(false), MyImmutableComponent(false)));
|
|
|
|
// But where mutable components can be mutated...
|
|
let mut my_mutable_component = entity.get_mut::<MyMutableComponent>().unwrap();
|
|
my_mutable_component.0 = true;
|
|
|
|
// ...immutable ones cannot. The below fails to compile as `MyImmutableComponent`
|
|
// is declared as immutable.
|
|
// let mut my_immutable_component = entity.get_mut::<MyImmutableComponent>().unwrap();
|
|
|
|
// Instead, you could take or replace the immutable component to update its value.
|
|
let mut my_immutable_component = entity.take::<MyImmutableComponent>().unwrap();
|
|
my_immutable_component.0 = true;
|
|
entity.insert(my_immutable_component);
|
|
}
|
|
|
|
/// This is an example of a component like [`Name`](bevy::prelude::Name), but immutable.
|
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Component, Reflect)]
|
|
#[reflect(Hash, Component)]
|
|
#[component(
|
|
immutable,
|
|
// Since this component is immutable, we can fully capture all mutations through
|
|
// these component hooks. This allows for keeping other parts of the ECS synced
|
|
// to a component's value at all times.
|
|
on_insert = on_insert_name,
|
|
on_replace = on_replace_name,
|
|
)]
|
|
pub struct Name(pub &'static str);
|
|
|
|
/// This index allows for O(1) lookups of an [`Entity`] by its [`Name`].
|
|
#[derive(Resource, Default)]
|
|
struct NameIndex {
|
|
name_to_entity: HashMap<Name, Entity>,
|
|
}
|
|
|
|
impl NameIndex {
|
|
fn get_entity(&self, name: &'static str) -> Option<Entity> {
|
|
self.name_to_entity.get(&Name(name)).copied()
|
|
}
|
|
}
|
|
|
|
/// When a [`Name`] is inserted, we will add it to our [`NameIndex`].
|
|
///
|
|
/// Since all mutations to [`Name`] are captured by hooks, we know it is not currently
|
|
/// inserted in the index, and its value will not change without triggering a hook.
|
|
fn on_insert_name(mut world: DeferredWorld<'_>, HookContext { entity, .. }: HookContext) {
|
|
let Some(&name) = world.entity(entity).get::<Name>() else {
|
|
unreachable!("Insert hook guarantees `Name` is available on entity")
|
|
};
|
|
let Some(mut index) = world.get_resource_mut::<NameIndex>() else {
|
|
return;
|
|
};
|
|
|
|
index.name_to_entity.insert(name, entity);
|
|
}
|
|
|
|
/// When a [`Name`] is removed or replaced, remove it from our [`NameIndex`].
|
|
///
|
|
/// Since all mutations to [`Name`] are captured by hooks, we know it is currently
|
|
/// inserted in the index.
|
|
fn on_replace_name(mut world: DeferredWorld<'_>, HookContext { entity, .. }: HookContext) {
|
|
let Some(&name) = world.entity(entity).get::<Name>() else {
|
|
unreachable!("Replace hook guarantees `Name` is available on entity")
|
|
};
|
|
let Some(mut index) = world.get_resource_mut::<NameIndex>() else {
|
|
return;
|
|
};
|
|
|
|
index.name_to_entity.remove(&name);
|
|
}
|
|
|
|
fn demo_2(world: &mut World) {
|
|
// Setup our name index
|
|
world.init_resource::<NameIndex>();
|
|
|
|
// Spawn some entities!
|
|
let alyssa = world.spawn(Name("Alyssa")).id();
|
|
let javier = world.spawn(Name("Javier")).id();
|
|
|
|
// Check our index
|
|
let index = world.resource::<NameIndex>();
|
|
|
|
assert_eq!(index.get_entity("Alyssa"), Some(alyssa));
|
|
assert_eq!(index.get_entity("Javier"), Some(javier));
|
|
|
|
// Changing the name of an entity is also fully capture by our index
|
|
world.entity_mut(javier).insert(Name("Steven"));
|
|
|
|
// Javier changed their name to Steven
|
|
let steven = javier;
|
|
|
|
// Check our index
|
|
let index = world.resource::<NameIndex>();
|
|
|
|
assert_eq!(index.get_entity("Javier"), None);
|
|
assert_eq!(index.get_entity("Steven"), Some(steven));
|
|
}
|
|
|
|
/// This example demonstrates how to work with _dynamic_ immutable components.
|
|
#[expect(
|
|
unsafe_code,
|
|
reason = "Unsafe code is needed to work with dynamic components"
|
|
)]
|
|
fn demo_3(world: &mut World) {
|
|
// This is a list of dynamic components we will create.
|
|
// The first item is the name of the component, and the second is the size
|
|
// in bytes.
|
|
let my_dynamic_components = [("Foo", 1), ("Bar", 2), ("Baz", 4)];
|
|
|
|
// This pipeline takes our component descriptions, registers them, and gets
|
|
// their ComponentId's.
|
|
let my_registered_components = my_dynamic_components
|
|
.into_iter()
|
|
.map(|(name, size)| {
|
|
// SAFETY:
|
|
// - No drop command is required
|
|
// - The component will store [u8; size], which is Send + Sync
|
|
let descriptor = unsafe {
|
|
ComponentDescriptor::new_with_layout(
|
|
name.to_string(),
|
|
StorageType::Table,
|
|
Layout::array::<u8>(size).unwrap(),
|
|
None,
|
|
false,
|
|
ComponentCloneBehavior::Default,
|
|
)
|
|
};
|
|
|
|
(name, size, descriptor)
|
|
})
|
|
.map(|(name, size, descriptor)| {
|
|
let component_id = world.register_component_with_descriptor(descriptor);
|
|
|
|
(name, size, component_id)
|
|
})
|
|
.collect::<Vec<(&str, usize, ComponentId)>>();
|
|
|
|
// Now that our components are registered, let's add them to an entity
|
|
let mut entity = world.spawn_empty();
|
|
|
|
for (_name, size, component_id) in &my_registered_components {
|
|
// We're just storing some zeroes for the sake of demonstration.
|
|
let data = core::iter::repeat_n(0, *size).collect::<Vec<u8>>();
|
|
|
|
OwningPtr::make(data, |ptr| {
|
|
// SAFETY:
|
|
// - ComponentId has been taken from the same world
|
|
// - Array is created to the layout specified in the world
|
|
unsafe {
|
|
entity.insert_by_id(*component_id, ptr);
|
|
}
|
|
});
|
|
}
|
|
|
|
for (_name, _size, component_id) in &my_registered_components {
|
|
// With immutable components, we can read the values...
|
|
assert!(entity.get_by_id(*component_id).is_ok());
|
|
|
|
// ...but we cannot gain a mutable reference.
|
|
assert!(entity.get_mut_by_id(*component_id).is_err());
|
|
|
|
// Instead, you must either remove or replace the value.
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
App::new()
|
|
.add_systems(Startup, demo_1)
|
|
.add_systems(Startup, demo_2)
|
|
.add_systems(Startup, demo_3)
|
|
.run();
|
|
}
|