mirror of
https://github.com/bevyengine/bevy.git
synced 2026-05-06 06:06:42 -04:00
Enable dynamic triggers for observers (#23870)
# Objective Finish the bevy_ecs dynamic story. Currently you can register observers with dynamic triggers and runners, but there's no way to actually trigger these observers dynamically. ## Solution Adds three new unsafe `World` functions: - `trigger_dynamic()` - `trigger_dynamic_targets()` - `trigger_dynamic_targets_components()` These enable observers to be triggered with untyped events and trigger data. Their implementations are just wiring up some existing internal structure. Structurally, they are based on their non-dynamic counterparts. Also exposes `EventKey::new()` and `EventKey::component_id()` for constructing event keys from dynamic `ComponentId`s. ## Testing Several new tests ## Showcase See updated example
This commit is contained in:
@@ -386,10 +386,40 @@ struct EventWrapperComponent<E: Event>(PhantomData<E>);
|
||||
///
|
||||
/// You can look up the key for your event by calling the [`World::event_key`] method.
|
||||
///
|
||||
/// For dynamic events not backed by a Rust type, create an `EventKey` from
|
||||
/// a [`ComponentId`] using [`EventKey::new`]. Obtain a [`ComponentId`] via
|
||||
/// [`World::register_component_with_descriptor`].
|
||||
///
|
||||
/// [observers]: crate::observer
|
||||
#[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub struct EventKey(pub(crate) ComponentId);
|
||||
|
||||
impl EventKey {
|
||||
/// Creates a new [`EventKey`] from a [`ComponentId`].
|
||||
///
|
||||
/// Useful for dynamic events not backed by a Rust type. Obtain a
|
||||
/// [`ComponentId`] via [`World::register_component_with_descriptor`].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure that `component_id` was registered for use as
|
||||
/// an event (e.g. via [`World::register_component_with_descriptor`]).
|
||||
/// Using an unrelated [`ComponentId`] may cause observers to receive
|
||||
/// data with an unexpected layout.
|
||||
///
|
||||
/// [`World::register_component_with_descriptor`]: crate::world::World::register_component_with_descriptor
|
||||
#[inline]
|
||||
pub const unsafe fn new(component_id: ComponentId) -> Self {
|
||||
Self(component_id)
|
||||
}
|
||||
|
||||
/// Returns the underlying [`ComponentId`] for this event key.
|
||||
#[inline]
|
||||
pub const fn component_id(self) -> ComponentId {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::{vec, vec::Vec};
|
||||
|
||||
@@ -111,6 +111,212 @@ impl World {
|
||||
}
|
||||
}
|
||||
|
||||
/// Splits `&mut self` into a [`DeferredWorld`] and the [`CachedObservers`]
|
||||
/// registered for `event_key`, or returns `None` if no observers exist.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Caller must not use the returned [`DeferredWorld`] to access observer
|
||||
/// storage, as it aliases with the returned [`CachedObservers`] reference.
|
||||
unsafe fn split_for_event(
|
||||
&mut self,
|
||||
event_key: crate::event::EventKey,
|
||||
) -> Option<(DeferredWorld<'_>, &CachedObservers)> {
|
||||
let world_cell = self.as_unsafe_world_cell();
|
||||
let observers = world_cell.observers();
|
||||
let observers = observers.try_get_observers(event_key)?;
|
||||
// SAFETY: The caller guarantees the returned `DeferredWorld` will not
|
||||
// be used to access observer storage (which `observers` borrows).
|
||||
Some((unsafe { world_cell.into_deferred() }, observers))
|
||||
}
|
||||
|
||||
/// Triggers global [`Observer`]s for `event_key` with untyped event and
|
||||
/// trigger data.
|
||||
///
|
||||
/// Dynamic equivalent of [`World::trigger`]. Only fires global observers,
|
||||
/// not entity- or component-scoped ones.
|
||||
///
|
||||
/// Use [`World::trigger_dynamic_targets`] to also fire entity-scoped
|
||||
/// observers.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - `event_data` must point to a valid, aligned value whose layout matches
|
||||
/// what observers registered for this `event_key` expect.
|
||||
/// - `trigger_data` must point to a valid, aligned value whose layout
|
||||
/// matches what observers registered for this `event_key` expect.
|
||||
#[track_caller]
|
||||
pub unsafe fn trigger_dynamic(
|
||||
&mut self,
|
||||
event_key: crate::event::EventKey,
|
||||
mut event_data: bevy_ptr::PtrMut,
|
||||
mut trigger_data: bevy_ptr::PtrMut,
|
||||
) {
|
||||
// SAFETY: We have exclusive access via `&mut self` and will not
|
||||
// access observer storage through the returned `DeferredWorld`.
|
||||
let Some((mut world, observers)) = (unsafe { self.split_for_event(event_key) }) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let context = TriggerContext {
|
||||
event_key,
|
||||
caller: MaybeLocation::caller(),
|
||||
};
|
||||
|
||||
// SAFETY: no outstanding world references besides `observers`
|
||||
unsafe {
|
||||
world.as_unsafe_world_cell().increment_trigger_id();
|
||||
}
|
||||
|
||||
for (observer, runner) in observers.global_observers() {
|
||||
// SAFETY:
|
||||
// - `observers` come from `world` and correspond to `event_key`
|
||||
// - caller guarantees `event_data` and `trigger_data` are valid
|
||||
unsafe {
|
||||
(runner)(
|
||||
world.reborrow(),
|
||||
*observer,
|
||||
&context,
|
||||
event_data.reborrow(),
|
||||
trigger_data.reborrow(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Triggers [`Observer`]s for `event_key` targeting `entity`, with untyped
|
||||
/// event and trigger data.
|
||||
///
|
||||
/// Fires global and entity-scoped observers. Dynamic equivalent of
|
||||
/// [`EntityWorldMut::trigger`].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - `event_data` must point to a valid, aligned value whose layout matches
|
||||
/// what observers registered for this `event_key` expect.
|
||||
/// - `trigger_data` must point to a valid, aligned value whose layout
|
||||
/// matches what observers registered for this `event_key` expect.
|
||||
#[track_caller]
|
||||
pub unsafe fn trigger_dynamic_targets(
|
||||
&mut self,
|
||||
event_key: crate::event::EventKey,
|
||||
entity: Entity,
|
||||
event_data: bevy_ptr::PtrMut,
|
||||
trigger_data: bevy_ptr::PtrMut,
|
||||
) {
|
||||
// SAFETY: We have exclusive access via `&mut self` and will not
|
||||
// access observer storage through the returned `DeferredWorld`.
|
||||
let Some((world, observers)) = (unsafe { self.split_for_event(event_key) }) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let context = TriggerContext {
|
||||
event_key,
|
||||
caller: MaybeLocation::caller(),
|
||||
};
|
||||
|
||||
// SAFETY:
|
||||
// - `observers` come from `world` and correspond to `event_key`
|
||||
// - caller guarantees `event_data` and `trigger_data` are valid
|
||||
// - `trigger_entity_internal` increments the trigger id
|
||||
unsafe {
|
||||
crate::event::trigger_entity_internal(
|
||||
world,
|
||||
observers,
|
||||
event_data,
|
||||
trigger_data,
|
||||
entity,
|
||||
&context,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Triggers [`Observer`]s for `event_key` targeting `entity` and
|
||||
/// `components`, with untyped event and trigger data.
|
||||
///
|
||||
/// Fires global, entity-scoped, and component-scoped observers.
|
||||
/// Dynamic equivalent of [`EntityComponentsTrigger`].
|
||||
///
|
||||
/// [`EntityComponentsTrigger`]: crate::event::EntityComponentsTrigger
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - `event_data` must point to a valid, aligned value whose layout matches
|
||||
/// what observers registered for this `event_key` expect.
|
||||
/// - `trigger_data` must point to a valid, aligned value whose layout
|
||||
/// matches what observers registered for this `event_key` expect.
|
||||
#[track_caller]
|
||||
pub unsafe fn trigger_dynamic_targets_components(
|
||||
&mut self,
|
||||
event_key: crate::event::EventKey,
|
||||
entity: Entity,
|
||||
components: &[crate::component::ComponentId],
|
||||
mut event_data: bevy_ptr::PtrMut,
|
||||
mut trigger_data: bevy_ptr::PtrMut,
|
||||
) {
|
||||
// SAFETY: We have exclusive access via `&mut self` and will not
|
||||
// access observer storage through the returned `DeferredWorld`.
|
||||
let Some((mut world, observers)) = (unsafe { self.split_for_event(event_key) }) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let context = TriggerContext {
|
||||
event_key,
|
||||
caller: MaybeLocation::caller(),
|
||||
};
|
||||
|
||||
// SAFETY:
|
||||
// - `observers` come from `world` and correspond to `event_key`
|
||||
// - caller guarantees `event_data` and `trigger_data` are valid
|
||||
// - `trigger_entity_internal` increments the trigger id
|
||||
unsafe {
|
||||
crate::event::trigger_entity_internal(
|
||||
world.reborrow(),
|
||||
observers,
|
||||
event_data.reborrow(),
|
||||
trigger_data.reborrow(),
|
||||
entity,
|
||||
&context,
|
||||
);
|
||||
}
|
||||
|
||||
// Trigger observers watching for specific components.
|
||||
for id in components {
|
||||
if let Some(component_observers) = observers.component_observers().get(id) {
|
||||
for (observer, runner) in component_observers.global_observers() {
|
||||
// SAFETY: same as above, caller guarantees data validity
|
||||
unsafe {
|
||||
(runner)(
|
||||
world.reborrow(),
|
||||
*observer,
|
||||
&context,
|
||||
event_data.reborrow(),
|
||||
trigger_data.reborrow(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(map) = component_observers
|
||||
.entity_component_observers()
|
||||
.get(&entity)
|
||||
{
|
||||
for (observer, runner) in map {
|
||||
// SAFETY: same as above, caller guarantees data validity
|
||||
unsafe {
|
||||
(runner)(
|
||||
world.reborrow(),
|
||||
*observer,
|
||||
&context,
|
||||
event_data.reborrow(),
|
||||
trigger_data.reborrow(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
@@ -705,6 +911,262 @@ mod tests {
|
||||
assert_eq!(vec!["event_a"], world.resource::<Order>().0);
|
||||
}
|
||||
|
||||
/// Collects `u32` values read by dynamic observers through `PtrMut`.
|
||||
#[derive(Resource, Default)]
|
||||
struct DynamicValues(Vec<u32>);
|
||||
|
||||
#[test]
|
||||
fn observer_fully_dynamic_trigger() {
|
||||
use core::alloc::Layout;
|
||||
|
||||
let mut world = World::new();
|
||||
world.init_resource::<Order>();
|
||||
world.init_resource::<DynamicValues>();
|
||||
|
||||
// Register a dynamic event whose data is a u32.
|
||||
let event_id = world.register_component_with_descriptor(
|
||||
// SAFETY: u32 layout with no drop
|
||||
unsafe {
|
||||
crate::component::ComponentDescriptor::new_with_layout(
|
||||
"DynamicEvent",
|
||||
crate::component::StorageType::Table,
|
||||
Layout::new::<u32>(),
|
||||
None,
|
||||
false,
|
||||
crate::component::ComponentCloneBehavior::Ignore,
|
||||
None,
|
||||
)
|
||||
},
|
||||
);
|
||||
// SAFETY: event_id was just registered for use as an event
|
||||
let event_key = unsafe { crate::event::EventKey::new(event_id) };
|
||||
|
||||
// SAFETY: event_key was just created, observer reads event_data as u32
|
||||
let observe = unsafe {
|
||||
Observer::with_dynamic_runner(
|
||||
|mut world, _observer, _trigger_context, event, _trigger| {
|
||||
// SAFETY: caller passes a valid u32 pointer as event data
|
||||
let value = *event.as_ref().deref::<u32>();
|
||||
world.resource_mut::<Order>().observed("dynamic_event");
|
||||
world.resource_mut::<DynamicValues>().0.push(value);
|
||||
},
|
||||
)
|
||||
.with_event_key(event_key)
|
||||
};
|
||||
world.spawn(observe);
|
||||
|
||||
let mut event_data: u32 = 42;
|
||||
let mut trigger_data: u32 = 0;
|
||||
// SAFETY: pointers are valid u32s matching the registered layout
|
||||
unsafe {
|
||||
world.trigger_dynamic(
|
||||
event_key,
|
||||
bevy_ptr::PtrMut::from(&mut event_data),
|
||||
bevy_ptr::PtrMut::from(&mut trigger_data),
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(vec!["dynamic_event"], world.resource::<Order>().0);
|
||||
assert_eq!(vec![42], world.resource::<DynamicValues>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_fully_dynamic_trigger_targets() {
|
||||
use core::alloc::Layout;
|
||||
|
||||
let mut world = World::new();
|
||||
world.init_resource::<Order>();
|
||||
world.init_resource::<DynamicValues>();
|
||||
|
||||
let event_id = world.register_component_with_descriptor(
|
||||
// SAFETY: u32 layout with no drop
|
||||
unsafe {
|
||||
crate::component::ComponentDescriptor::new_with_layout(
|
||||
"DynamicEntityEvent",
|
||||
crate::component::StorageType::Table,
|
||||
Layout::new::<u32>(),
|
||||
None,
|
||||
false,
|
||||
crate::component::ComponentCloneBehavior::Ignore,
|
||||
None,
|
||||
)
|
||||
},
|
||||
);
|
||||
// SAFETY: event_id was just registered for use as an event
|
||||
let event_key = unsafe { crate::event::EventKey::new(event_id) };
|
||||
|
||||
let target = world.spawn_empty().id();
|
||||
let other = world.spawn_empty().id();
|
||||
|
||||
// SAFETY: event_key was just created, observer reads event_data as u32
|
||||
let global = unsafe {
|
||||
Observer::with_dynamic_runner(
|
||||
|mut world, _observer, _trigger_context, event, _trigger| {
|
||||
let value = *event.as_ref().deref::<u32>();
|
||||
world.resource_mut::<Order>().observed("global");
|
||||
world.resource_mut::<DynamicValues>().0.push(value);
|
||||
},
|
||||
)
|
||||
.with_event_key(event_key)
|
||||
};
|
||||
world.spawn(global);
|
||||
|
||||
// SAFETY: event_key was just created, observer reads event_data as u32
|
||||
let entity_scoped = unsafe {
|
||||
Observer::with_dynamic_runner(
|
||||
|mut world, _observer, _trigger_context, event, _trigger| {
|
||||
let value = *event.as_ref().deref::<u32>();
|
||||
world.resource_mut::<Order>().observed("entity_scoped");
|
||||
world.resource_mut::<DynamicValues>().0.push(value);
|
||||
},
|
||||
)
|
||||
.with_event_key(event_key)
|
||||
.with_entity(target)
|
||||
};
|
||||
world.spawn(entity_scoped);
|
||||
|
||||
// Trigger targeting `target`: both global and entity-scoped should fire.
|
||||
let mut event_data: u32 = 7;
|
||||
let mut trigger_data: u32 = 0;
|
||||
// SAFETY: pointers are valid u32s matching the registered layout
|
||||
unsafe {
|
||||
world.trigger_dynamic_targets(
|
||||
event_key,
|
||||
target,
|
||||
bevy_ptr::PtrMut::from(&mut event_data),
|
||||
bevy_ptr::PtrMut::from(&mut trigger_data),
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(vec!["global", "entity_scoped"], world.resource::<Order>().0);
|
||||
assert_eq!(vec![7, 7], world.resource::<DynamicValues>().0);
|
||||
|
||||
// Trigger targeting `other`: only global should fire.
|
||||
world.resource_mut::<Order>().0.clear();
|
||||
world.resource_mut::<DynamicValues>().0.clear();
|
||||
let mut event_data: u32 = 99;
|
||||
let mut trigger_data: u32 = 0;
|
||||
// SAFETY: pointers are valid u32s matching the registered layout
|
||||
unsafe {
|
||||
world.trigger_dynamic_targets(
|
||||
event_key,
|
||||
other,
|
||||
bevy_ptr::PtrMut::from(&mut event_data),
|
||||
bevy_ptr::PtrMut::from(&mut trigger_data),
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(vec!["global"], world.resource::<Order>().0);
|
||||
assert_eq!(vec![99], world.resource::<DynamicValues>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_fully_dynamic_trigger_targets_components() {
|
||||
use core::alloc::Layout;
|
||||
|
||||
let mut world = World::new();
|
||||
world.init_resource::<Order>();
|
||||
world.init_resource::<DynamicValues>();
|
||||
|
||||
let event_id = world.register_component_with_descriptor(
|
||||
// SAFETY: u32 layout with no drop
|
||||
unsafe {
|
||||
crate::component::ComponentDescriptor::new_with_layout(
|
||||
"DynamicComponentEvent",
|
||||
crate::component::StorageType::Table,
|
||||
Layout::new::<u32>(),
|
||||
None,
|
||||
false,
|
||||
crate::component::ComponentCloneBehavior::Ignore,
|
||||
None,
|
||||
)
|
||||
},
|
||||
);
|
||||
// SAFETY: event_id was just registered for use as an event
|
||||
let event_key = unsafe { crate::event::EventKey::new(event_id) };
|
||||
|
||||
// Register a dynamic component to scope an observer to.
|
||||
let comp_id = world.register_component_with_descriptor(
|
||||
// SAFETY: ZST layout with no drop
|
||||
unsafe {
|
||||
crate::component::ComponentDescriptor::new_with_layout(
|
||||
"DynamicComp",
|
||||
crate::component::StorageType::Table,
|
||||
Layout::new::<()>(),
|
||||
None,
|
||||
false,
|
||||
crate::component::ComponentCloneBehavior::Ignore,
|
||||
None,
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
let target = world.spawn_empty().id();
|
||||
|
||||
// SAFETY: event_key was just created, observer reads event_data as u32
|
||||
let global = unsafe {
|
||||
Observer::with_dynamic_runner(
|
||||
|mut world, _observer, _trigger_context, event, _trigger| {
|
||||
let value = *event.as_ref().deref::<u32>();
|
||||
world.resource_mut::<Order>().observed("global");
|
||||
world.resource_mut::<DynamicValues>().0.push(value);
|
||||
},
|
||||
)
|
||||
.with_event_key(event_key)
|
||||
};
|
||||
world.spawn(global);
|
||||
|
||||
// SAFETY: event_key was just created, observer reads event_data as u32
|
||||
let comp_scoped = unsafe {
|
||||
Observer::with_dynamic_runner(
|
||||
|mut world, _observer, _trigger_context, event, _trigger| {
|
||||
let value = *event.as_ref().deref::<u32>();
|
||||
world.resource_mut::<Order>().observed("comp_scoped");
|
||||
world.resource_mut::<DynamicValues>().0.push(value);
|
||||
},
|
||||
)
|
||||
.with_event_key(event_key)
|
||||
.with_component(comp_id)
|
||||
};
|
||||
world.spawn(comp_scoped);
|
||||
|
||||
// Trigger with `comp_id` in the components list: both should fire.
|
||||
let mut event_data: u32 = 5;
|
||||
let mut trigger_data: u32 = 0;
|
||||
// SAFETY: pointers are valid u32s matching the registered layout
|
||||
unsafe {
|
||||
world.trigger_dynamic_targets_components(
|
||||
event_key,
|
||||
target,
|
||||
&[comp_id],
|
||||
bevy_ptr::PtrMut::from(&mut event_data),
|
||||
bevy_ptr::PtrMut::from(&mut trigger_data),
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(vec!["global", "comp_scoped"], world.resource::<Order>().0);
|
||||
assert_eq!(vec![5, 5], world.resource::<DynamicValues>().0);
|
||||
|
||||
// Trigger without components: only global should fire.
|
||||
world.resource_mut::<Order>().0.clear();
|
||||
world.resource_mut::<DynamicValues>().0.clear();
|
||||
let mut event_data: u32 = 10;
|
||||
let mut trigger_data: u32 = 0;
|
||||
// SAFETY: pointers are valid u32s matching the registered layout
|
||||
unsafe {
|
||||
world.trigger_dynamic_targets_components(
|
||||
event_key,
|
||||
target,
|
||||
&[],
|
||||
bevy_ptr::PtrMut::from(&mut event_data),
|
||||
bevy_ptr::PtrMut::from(&mut trigger_data),
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(vec!["global"], world.resource::<Order>().0);
|
||||
assert_eq!(vec![10], world.resource::<DynamicValues>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_propagating() {
|
||||
let mut world = World::new();
|
||||
|
||||
+100
-4
@@ -5,6 +5,9 @@
|
||||
|
||||
//! This example show how you can create components dynamically, spawn entities with those components
|
||||
//! as well as query for entities with those components.
|
||||
//!
|
||||
//! It also demonstrates dynamic observers: registering events and observers at
|
||||
//! runtime without compile-time event types, and triggering them with raw data.
|
||||
|
||||
use std::{alloc::Layout, collections::HashMap, io::Write, ptr::NonNull};
|
||||
|
||||
@@ -13,18 +16,22 @@ use bevy::{
|
||||
component::{
|
||||
ComponentCloneBehavior, ComponentDescriptor, ComponentId, ComponentInfo, StorageType,
|
||||
},
|
||||
event::EventKey,
|
||||
observer::{Observer, ObserverRunner},
|
||||
query::{ComponentAccessKind, QueryData},
|
||||
world::FilteredEntityMut,
|
||||
},
|
||||
prelude::*,
|
||||
ptr::{Aligned, OwningPtr},
|
||||
ptr::{Aligned, OwningPtr, PtrMut},
|
||||
};
|
||||
|
||||
const PROMPT: &str = "
|
||||
Commands:
|
||||
comp, c Create new components
|
||||
spawn, s Spawn entities
|
||||
query, q Query for entities
|
||||
comp, c Create new components
|
||||
spawn, s Spawn entities
|
||||
query, q Query for entities
|
||||
event, e Register dynamic events and observers
|
||||
emit, t Trigger a dynamic event
|
||||
Enter a command with no parameters for usage.";
|
||||
|
||||
const COMPONENT_PROMPT: &str = "
|
||||
@@ -48,11 +55,23 @@ query, q Query for entities
|
||||
|
||||
e.g. &A || &B, &mut C, D, ?E";
|
||||
|
||||
const EVENT_PROMPT: &str = "
|
||||
event, e Register dynamic events and observers
|
||||
Enter a comma separated list of event names.
|
||||
Each event gets a dynamic observer that prints when fired.
|
||||
e.g. OnDamage, OnHeal, OnDeath";
|
||||
|
||||
const EMIT_PROMPT: &str = "
|
||||
emit, t Trigger a dynamic event
|
||||
Enter the name of a previously registered event.
|
||||
e.g. OnDamage";
|
||||
|
||||
fn main() {
|
||||
let mut world = World::new();
|
||||
let mut lines = std::io::stdin().lines();
|
||||
let mut component_names = HashMap::<String, ComponentId>::new();
|
||||
let mut component_info = HashMap::<ComponentId, ComponentInfo>::new();
|
||||
let mut event_names = HashMap::<String, EventKey>::new();
|
||||
|
||||
println!("{PROMPT}");
|
||||
loop {
|
||||
@@ -71,6 +90,8 @@ fn main() {
|
||||
Some('c') => println!("{COMPONENT_PROMPT}"),
|
||||
Some('s') => println!("{ENTITY_PROMPT}"),
|
||||
Some('q') => println!("{QUERY_PROMPT}"),
|
||||
Some('e') => println!("{EVENT_PROMPT}"),
|
||||
Some('t') => println!("{EMIT_PROMPT}"),
|
||||
_ => println!("{PROMPT}"),
|
||||
}
|
||||
continue;
|
||||
@@ -191,11 +212,86 @@ fn main() {
|
||||
println!("{}: {}", filtered_entity.id(), terms);
|
||||
});
|
||||
}
|
||||
"e" => {
|
||||
rest.split(',').for_each(|event| {
|
||||
let name = event.trim();
|
||||
if name.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Register a ComponentId for this event, no Rust type needed.
|
||||
// SAFETY: ZST with no drop
|
||||
let event_component_id = world.register_component_with_descriptor(unsafe {
|
||||
ComponentDescriptor::new_with_layout(
|
||||
format!("event:{name}"),
|
||||
StorageType::Table,
|
||||
Layout::new::<()>(),
|
||||
None,
|
||||
false,
|
||||
ComponentCloneBehavior::Ignore,
|
||||
None,
|
||||
)
|
||||
});
|
||||
// SAFETY: event_component_id was just registered for this event
|
||||
let event_key = unsafe { EventKey::new(event_component_id) };
|
||||
event_names.insert(name.to_string(), event_key);
|
||||
|
||||
// Build a dynamic observer that prints when the event fires.
|
||||
let runner: ObserverRunner = |mut world, _observer, ctx, _event, _trigger| {
|
||||
println!(" Observer fired!");
|
||||
if let Some(mut counts) = world.get_resource_mut::<EventFireCount>() {
|
||||
*counts.0.entry(ctx.event_key).or_insert(0) += 1;
|
||||
}
|
||||
};
|
||||
|
||||
// SAFETY: event_key was just registered, runner ignores pointers
|
||||
let observer =
|
||||
unsafe { Observer::with_dynamic_runner(runner).with_event_key(event_key) };
|
||||
world.spawn(observer);
|
||||
|
||||
println!(
|
||||
"Event '{name}' registered (key: {}) with a dynamic observer",
|
||||
event_component_id.index()
|
||||
);
|
||||
});
|
||||
|
||||
// Ensure the counter resource exists.
|
||||
world.init_resource::<EventFireCount>();
|
||||
}
|
||||
"t" => {
|
||||
let name = rest.trim();
|
||||
let Some(&event_key) = event_names.get(name) else {
|
||||
println!(
|
||||
"Event '{name}' does not exist. Register it first with 'event {name}'"
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
let mut event_data = ();
|
||||
let mut trigger_data = ();
|
||||
// SAFETY: event_key was registered in this world, both pointers are valid ZSTs
|
||||
unsafe {
|
||||
world.trigger_dynamic(
|
||||
event_key,
|
||||
PtrMut::from(&mut event_data),
|
||||
PtrMut::from(&mut trigger_data),
|
||||
);
|
||||
}
|
||||
|
||||
let count = world
|
||||
.get_resource::<EventFireCount>()
|
||||
.map_or(0, |c| c.0.get(&event_key).copied().unwrap_or(0));
|
||||
println!("Event '{name}' triggered ({count} fires)");
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tracks how many times each dynamic event's observer has fired.
|
||||
#[derive(Resource, Default)]
|
||||
struct EventFireCount(HashMap<EventKey, u32>);
|
||||
|
||||
// Constructs `OwningPtr` for each item in `components`
|
||||
// By sharing the lifetime of `components` with the resulting ptrs we ensure we don't drop the data before use
|
||||
fn to_owning_ptrs(components: &mut [Vec<u64>]) -> Vec<OwningPtr<'_, Aligned>> {
|
||||
|
||||
Reference in New Issue
Block a user