Event Split: Event, EntityEvent, and BufferedEvent (#19647)
# Objective Closes #19564. The current `Event` trait looks like this: ```rust pub trait Event: Send + Sync + 'static { type Traversal: Traversal<Self>; const AUTO_PROPAGATE: bool = false; fn register_component_id(world: &mut World) -> ComponentId { ... } fn component_id(world: &World) -> Option<ComponentId> { ... } } ``` The `Event` trait is used by both buffered events (`EventReader`/`EventWriter`) and observer events. If they are observer events, they can optionally be targeted at specific `Entity`s or `ComponentId`s, and can even be propagated to other entities. However, there has long been a desire to split the trait semantically for a variety of reasons, see #14843, #14272, and #16031 for discussion. Some reasons include: - It's very uncommon to use a single event type as both a buffered event and targeted observer event. They are used differently and tend to have distinct semantics. - A common footgun is using buffered events with observers or event readers with observer events, as there is no type-level error that prevents this kind of misuse. - #19440 made `Trigger::target` return an `Option<Entity>`. This *seriously* hurts ergonomics for the general case of entity observers, as you need to `.unwrap()` each time. If we could statically determine whether the event is expected to have an entity target, this would be unnecessary. There's really two main ways that we can categorize events: push vs. pull (i.e. "observer event" vs. "buffered event") and global vs. targeted: | | Push | Pull | | ------------ | --------------- | --------------------------- | | **Global** | Global observer | `EventReader`/`EventWriter` | | **Targeted** | Entity observer | - | There are many ways to approach this, each with their tradeoffs. Ultimately, we kind of want to split events both ways: - A type-level distinction between observer events and buffered events, to prevent people from using the wrong kind of event in APIs - A statically designated entity target for observer events to avoid accidentally using untargeted events for targeted APIs This PR achieves these goals by splitting event traits into `Event`, `EntityEvent`, and `BufferedEvent`, with `Event` being the shared trait implemented by all events. ## `Event`, `EntityEvent`, and `BufferedEvent` `Event` is now a very simple trait shared by all events. ```rust pub trait Event: Send + Sync + 'static { // Required for observer APIs fn register_component_id(world: &mut World) -> ComponentId { ... } fn component_id(world: &World) -> Option<ComponentId> { ... } } ``` You can call `trigger` for *any* event, and use a global observer for listening to the event. ```rust #[derive(Event)] struct Speak { message: String, } // ... app.add_observer(|trigger: On<Speak>| { println!("{}", trigger.message); }); // ... commands.trigger(Speak { message: "Y'all like these reworked events?".to_string(), }); ``` To allow an event to be targeted at entities and even propagated further, you can additionally implement the `EntityEvent` trait: ```rust pub trait EntityEvent: Event { type Traversal: Traversal<Self>; const AUTO_PROPAGATE: bool = false; } ``` This lets you call `trigger_targets`, and to use targeted observer APIs like `EntityCommands::observe`: ```rust #[derive(Event, EntityEvent)] #[entity_event(traversal = &'static ChildOf, auto_propagate)] struct Damage { amount: f32, } // ... let enemy = commands.spawn((Enemy, Health(100.0))).id(); // Spawn some armor as a child of the enemy entity. // When the armor takes damage, it will bubble the event up to the enemy. let armor_piece = commands .spawn((ArmorPiece, Health(25.0), ChildOf(enemy))) .observe(|trigger: On<Damage>, mut query: Query<&mut Health>| { // Note: `On::target` only exists because this is an `EntityEvent`. let mut health = query.get(trigger.target()).unwrap(); health.0 -= trigger.amount(); }); commands.trigger_targets(Damage { amount: 10.0 }, armor_piece); ``` > [!NOTE] > You *can* still also trigger an `EntityEvent` without targets using `trigger`. We probably *could* make this an either-or thing, but I'm not sure that's actually desirable. To allow an event to be used with the buffered API, you can implement `BufferedEvent`: ```rust pub trait BufferedEvent: Event {} ``` The event can then be used with `EventReader`/`EventWriter`: ```rust #[derive(Event, BufferedEvent)] struct Message(String); fn write_hello(mut writer: EventWriter<Message>) { writer.write(Message("I hope these examples are alright".to_string())); } fn read_messages(mut reader: EventReader<Message>) { // Process all buffered events of type `Message`. for Message(message) in reader.read() { println!("{message}"); } } ``` In summary: - Need a basic event you can trigger and observe? Derive `Event`! - Need the event to be targeted at an entity? Derive `EntityEvent`! - Need the event to be buffered and support the `EventReader`/`EventWriter` API? Derive `BufferedEvent`! ## Alternatives I'll now cover some of the alternative approaches I have considered and briefly explored. I made this section collapsible since it ended up being quite long :P <details> <summary>Expand this to see alternatives</summary> ### 1. Unified `Event` Trait One option is not to have *three* separate traits (`Event`, `EntityEvent`, `BufferedEvent`), and to instead just use associated constants on `Event` to determine whether an event supports targeting and buffering or not: ```rust pub trait Event: Send + Sync + 'static { type Traversal: Traversal<Self>; const AUTO_PROPAGATE: bool = false; const TARGETED: bool = false; const BUFFERED: bool = false; fn register_component_id(world: &mut World) -> ComponentId { ... } fn component_id(world: &World) -> Option<ComponentId> { ... } } ``` Methods can then use bounds like `where E: Event<TARGETED = true>` or `where E: Event<BUFFERED = true>` to limit APIs to specific kinds of events. This would keep everything under one `Event` trait, but I don't think it's necessarily a good idea. It makes APIs harder to read, and docs can't easily refer to specific types of events. You can also create weird invariants: what if you specify `TARGETED = false`, but have `Traversal` and/or `AUTO_PROPAGATE` enabled? ### 2. `Event` and `Trigger` Another option is to only split the traits between buffered events and observer events, since that is the main thing people have been asking for, and they have the largest API difference. If we did this, I think we would need to make the terms *clearly* separate. We can't really use `Event` and `BufferedEvent` as the names, since it would be strange that `BufferedEvent` doesn't implement `Event`. Something like `ObserverEvent` and `BufferedEvent` could work, but it'd be more verbose. For this approach, I would instead keep `Event` for the current `EventReader`/`EventWriter` API, and call the observer event a `Trigger`, since the "trigger" terminology is already used in the observer context within Bevy (both as a noun and a verb). This is also what a long [bikeshed on Discord](https://discord.com/channels/691052431525675048/749335865876021248/1298057661878898791) seemed to land on at the end of last year. ```rust // For `EventReader`/`EventWriter` pub trait Event: Send + Sync + 'static {} // For observers pub trait Trigger: Send + Sync + 'static { type Traversal: Traversal<Self>; const AUTO_PROPAGATE: bool = false; const TARGETED: bool = false; fn register_component_id(world: &mut World) -> ComponentId { ... } fn component_id(world: &World) -> Option<ComponentId> { ... } } ``` The problem is that "event" is just a really good term for something that "happens". Observers are rapidly becoming the more prominent API, so it'd be weird to give them the `Trigger` name and leave the good `Event` name for the less common API. So, even though a split like this seems neat on the surface, I think it ultimately wouldn't really work. We want to keep the `Event` name for observer events, and there is no good alternative for the buffered variant. (`Message` was suggested, but saying stuff like "sends a collision message" is weird.) ### 3. `GlobalEvent` + `TargetedEvent` What if instead of focusing on the buffered vs. observed split, we *only* make a distinction between global and targeted events? ```rust // A shared event trait to allow global observers to work pub trait Event: Send + Sync + 'static { fn register_component_id(world: &mut World) -> ComponentId { ... } fn component_id(world: &World) -> Option<ComponentId> { ... } } // For buffered events and non-targeted observer events pub trait GlobalEvent: Event {} // For targeted observer events pub trait TargetedEvent: Event { type Traversal: Traversal<Self>; const AUTO_PROPAGATE: bool = false; } ``` This is actually the first approach I implemented, and it has the neat characteristic that you can only use non-targeted APIs like `trigger` with a `GlobalEvent` and targeted APIs like `trigger_targets` with a `TargetedEvent`. You have full control over whether the entity should or should not have a target, as they are fully distinct at the type-level. However, there's a few problems: - There is no type-level indication of whether a `GlobalEvent` supports buffered events or just non-targeted observer events - An `Event` on its own does literally nothing, it's just a shared trait required to make global observers accept both non-targeted and targeted events - If an event is both a `GlobalEvent` and `TargetedEvent`, global observers again have ambiguity on whether an event has a target or not, undermining some of the benefits - The names are not ideal ### 4. `Event` and `EntityEvent` We can fix some of the problems of Alternative 3 by accepting that targeted events can also be used in non-targeted contexts, and simply having the `Event` and `EntityEvent` traits: ```rust // For buffered events and non-targeted observer events pub trait Event: Send + Sync + 'static { fn register_component_id(world: &mut World) -> ComponentId { ... } fn component_id(world: &World) -> Option<ComponentId> { ... } } // For targeted observer events pub trait EntityEvent: Event { type Traversal: Traversal<Self>; const AUTO_PROPAGATE: bool = false; } ``` This is essentially identical to this PR, just without a dedicated `BufferedEvent`. The remaining major "problem" is that there is still zero type-level indication of whether an `Event` event *actually* supports the buffered API. This leads us to the solution proposed in this PR, using `Event`, `EntityEvent`, and `BufferedEvent`. </details> ## Conclusion The `Event` + `EntityEvent` + `BufferedEvent` split proposed in this PR aims to solve all the common problems with Bevy's current event model while keeping the "weirdness" factor minimal. It splits in terms of both the push vs. pull *and* global vs. targeted aspects, while maintaining a shared concept for an "event". ### Why I Like This - The term "event" remains as a single concept for all the different kinds of events in Bevy. - Despite all event types being "events", they use fundamentally different APIs. Instead of assuming that you can use an event type with any pattern (when only one is typically supported), you explicitly opt in to each one with dedicated traits. - Using separate traits for each type of event helps with documentation and clearer function signatures. - I can safely make assumptions on expected usage. - If I see that an event is an `EntityEvent`, I can assume that I can use `observe` on it and get targeted events. - If I see that an event is a `BufferedEvent`, I can assume that I can use `EventReader` to read events. - If I see both `EntityEvent` and `BufferedEvent`, I can assume that both APIs are supported. In summary: This allows for a unified concept for events, while limiting the different ways to use them with opt-in traits. No more guess-work involved when using APIs. ### Problems? - Because `BufferedEvent` implements `Event` (for more consistent semantics etc.), you can still use all buffered events for non-targeted observers. I think this is fine/good. The important part is that if you see that an event implements `BufferedEvent`, you know that the `EventReader`/`EventWriter` API should be supported. Whether it *also* supports other APIs is secondary. - I currently only support `trigger_targets` for an `EntityEvent`. However, you can technically target components too, without targeting any entities. I consider that such a niche and advanced use case that it's not a huge problem to only support it for `EntityEvent`s, but we could also split `trigger_targets` into `trigger_entities` and `trigger_components` if we wanted to (or implement components as entities :P). - You can still trigger an `EntityEvent` *without* targets. I consider this correct, since `Event` implements the non-targeted behavior, and it'd be weird if implementing another trait *removed* behavior. However, it does mean that global observers for entity events can technically return `Entity::PLACEHOLDER` again (since I got rid of the `Option<Entity>` added in #19440 for ergonomics). I think that's enough of an edge case that it's not a huge problem, but it is worth keeping in mind. - ~~Deriving both `EntityEvent` and `BufferedEvent` for the same type currently duplicates the `Event` implementation, so you instead need to manually implement one of them.~~ Changed to always requiring `Event` to be derived. ## Related Work There are plans to implement multi-event support for observers, especially for UI contexts. [Cart's example](https://github.com/bevyengine/bevy/issues/14649#issuecomment-2960402508) API looked like this: ```rust // Truncated for brevity trigger: Trigger<( OnAdd<Pressed>, OnRemove<Pressed>, OnAdd<InteractionDisabled>, OnRemove<InteractionDisabled>, OnInsert<Hovered>, )>, ``` I believe this shouldn't be in conflict with this PR. If anything, this PR might *help* achieve the multi-event pattern for entity observers with fewer footguns: by statically enforcing that all of these events are `EntityEvent`s in the context of `EntityCommands::observe`, we can avoid misuse or weird cases where *some* events inside the trigger are targeted while others are not.
This commit is contained in:
parent
98c14e5917
commit
38c3423693
@ -1,6 +1,6 @@
|
||||
use bevy_ecs::prelude::*;
|
||||
|
||||
#[derive(Event)]
|
||||
#[derive(Event, BufferedEvent)]
|
||||
struct BenchEvent<const SIZE: usize>([u8; SIZE]);
|
||||
|
||||
pub struct Benchmark<const SIZE: usize>(Events<BenchEvent<SIZE>>);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use bevy_ecs::prelude::*;
|
||||
|
||||
#[derive(Event)]
|
||||
#[derive(Event, BufferedEvent)]
|
||||
struct BenchEvent<const SIZE: usize>([u8; SIZE]);
|
||||
|
||||
impl<const SIZE: usize> Default for BenchEvent<SIZE> {
|
||||
|
||||
@ -61,14 +61,10 @@ pub fn event_propagation(criterion: &mut Criterion) {
|
||||
group.finish();
|
||||
}
|
||||
|
||||
#[derive(Clone, Component)]
|
||||
#[derive(Event, EntityEvent, Clone, Component)]
|
||||
#[entity_event(traversal = &'static ChildOf, auto_propagate)]
|
||||
struct TestEvent<const N: usize> {}
|
||||
|
||||
impl<const N: usize> Event for TestEvent<N> {
|
||||
type Traversal = &'static ChildOf;
|
||||
const AUTO_PROPAGATE: bool = true;
|
||||
}
|
||||
|
||||
fn send_events<const N: usize, const N_EVENTS: usize>(world: &mut World, leaves: &[Entity]) {
|
||||
let target = leaves.iter().choose(&mut rand::thread_rng()).unwrap();
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use core::hint::black_box;
|
||||
|
||||
use bevy_ecs::{
|
||||
event::Event,
|
||||
event::{EntityEvent, Event},
|
||||
observer::{On, TriggerTargets},
|
||||
world::World,
|
||||
};
|
||||
@ -13,7 +13,7 @@ fn deterministic_rand() -> ChaCha8Rng {
|
||||
ChaCha8Rng::seed_from_u64(42)
|
||||
}
|
||||
|
||||
#[derive(Clone, Event)]
|
||||
#[derive(Clone, Event, EntityEvent)]
|
||||
struct EventBase;
|
||||
|
||||
pub fn observe_simple(criterion: &mut Criterion) {
|
||||
|
||||
@ -26,7 +26,8 @@ use accesskit::Node;
|
||||
use bevy_app::Plugin;
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::{
|
||||
prelude::{Component, Event},
|
||||
component::Component,
|
||||
event::{BufferedEvent, Event},
|
||||
resource::Resource,
|
||||
schedule::SystemSet,
|
||||
};
|
||||
@ -44,7 +45,7 @@ use serde::{Deserialize, Serialize};
|
||||
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
||||
|
||||
/// Wrapper struct for [`accesskit::ActionRequest`]. Required to allow it to be used as an `Event`.
|
||||
#[derive(Event, Deref, DerefMut)]
|
||||
#[derive(Event, BufferedEvent, Deref, DerefMut)]
|
||||
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
|
||||
pub struct ActionRequest(pub accesskit::ActionRequest);
|
||||
|
||||
|
||||
@ -324,13 +324,13 @@ impl AnimationClip {
|
||||
.push(variable_curve);
|
||||
}
|
||||
|
||||
/// Add a untargeted [`Event`] to this [`AnimationClip`].
|
||||
/// Add an [`EntityEvent`] with no [`AnimationTarget`] to this [`AnimationClip`].
|
||||
///
|
||||
/// The `event` will be cloned and triggered on the [`AnimationPlayer`] entity once the `time` (in seconds)
|
||||
/// is reached in the animation.
|
||||
///
|
||||
/// See also [`add_event_to_target`](Self::add_event_to_target).
|
||||
pub fn add_event(&mut self, time: f32, event: impl Event + Clone) {
|
||||
pub fn add_event(&mut self, time: f32, event: impl EntityEvent + Clone) {
|
||||
self.add_event_fn(
|
||||
time,
|
||||
move |commands: &mut Commands, entity: Entity, _time: f32, _weight: f32| {
|
||||
@ -339,7 +339,7 @@ impl AnimationClip {
|
||||
);
|
||||
}
|
||||
|
||||
/// Add an [`Event`] to an [`AnimationTarget`] named by an [`AnimationTargetId`].
|
||||
/// Add an [`EntityEvent`] to an [`AnimationTarget`] named by an [`AnimationTargetId`].
|
||||
///
|
||||
/// The `event` will be cloned and triggered on the entity matching the target once the `time` (in seconds)
|
||||
/// is reached in the animation.
|
||||
@ -349,7 +349,7 @@ impl AnimationClip {
|
||||
&mut self,
|
||||
target_id: AnimationTargetId,
|
||||
time: f32,
|
||||
event: impl Event + Clone,
|
||||
event: impl EntityEvent + Clone,
|
||||
) {
|
||||
self.add_event_fn_to_target(
|
||||
target_id,
|
||||
@ -360,19 +360,19 @@ impl AnimationClip {
|
||||
);
|
||||
}
|
||||
|
||||
/// Add a untargeted event function to this [`AnimationClip`].
|
||||
/// Add an event function with no [`AnimationTarget`] to this [`AnimationClip`].
|
||||
///
|
||||
/// The `func` will trigger on the [`AnimationPlayer`] entity once the `time` (in seconds)
|
||||
/// is reached in the animation.
|
||||
///
|
||||
/// For a simpler [`Event`]-based alternative, see [`AnimationClip::add_event`].
|
||||
/// For a simpler [`EntityEvent`]-based alternative, see [`AnimationClip::add_event`].
|
||||
/// See also [`add_event_to_target`](Self::add_event_to_target).
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_animation::AnimationClip;
|
||||
/// # let mut clip = AnimationClip::default();
|
||||
/// clip.add_event_fn(1.0, |commands, entity, time, weight| {
|
||||
/// println!("Animation Event Triggered {entity:#?} at time {time} with weight {weight}");
|
||||
/// println!("Animation event triggered {entity:#?} at time {time} with weight {weight}");
|
||||
/// })
|
||||
/// ```
|
||||
pub fn add_event_fn(
|
||||
@ -388,14 +388,14 @@ impl AnimationClip {
|
||||
/// The `func` will trigger on the entity matching the target once the `time` (in seconds)
|
||||
/// is reached in the animation.
|
||||
///
|
||||
/// For a simpler [`Event`]-based alternative, see [`AnimationClip::add_event_to_target`].
|
||||
/// For a simpler [`EntityEvent`]-based alternative, see [`AnimationClip::add_event_to_target`].
|
||||
/// Use [`add_event`](Self::add_event) instead if you don't have a specific target.
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_animation::{AnimationClip, AnimationTargetId};
|
||||
/// # let mut clip = AnimationClip::default();
|
||||
/// clip.add_event_fn_to_target(AnimationTargetId::from_iter(["Arm", "Hand"]), 1.0, |commands, entity, time, weight| {
|
||||
/// println!("Animation Event Triggered {entity:#?} at time {time} with weight {weight}");
|
||||
/// println!("Animation event triggered {entity:#?} at time {time} with weight {weight}");
|
||||
/// })
|
||||
/// ```
|
||||
pub fn add_event_fn_to_target(
|
||||
@ -1534,7 +1534,7 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Event, Reflect, Clone)]
|
||||
#[derive(Event, EntityEvent, Reflect, Clone)]
|
||||
struct A;
|
||||
|
||||
#[track_caller]
|
||||
|
||||
@ -344,7 +344,7 @@ impl App {
|
||||
self
|
||||
}
|
||||
|
||||
/// Initializes `T` event handling by inserting an event queue resource ([`Events::<T>`])
|
||||
/// Initializes [`BufferedEvent`] handling for `T` by inserting an event queue resource ([`Events::<T>`])
|
||||
/// and scheduling an [`event_update_system`] in [`First`].
|
||||
///
|
||||
/// See [`Events`] for information on how to define events.
|
||||
@ -355,7 +355,7 @@ impl App {
|
||||
/// # use bevy_app::prelude::*;
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #
|
||||
/// # #[derive(Event)]
|
||||
/// # #[derive(Event, BufferedEvent)]
|
||||
/// # struct MyEvent;
|
||||
/// # let mut app = App::new();
|
||||
/// #
|
||||
@ -363,7 +363,7 @@ impl App {
|
||||
/// ```
|
||||
pub fn add_event<T>(&mut self) -> &mut Self
|
||||
where
|
||||
T: Event,
|
||||
T: BufferedEvent,
|
||||
{
|
||||
self.main_mut().add_event::<T>();
|
||||
self
|
||||
@ -1325,7 +1325,7 @@ impl App {
|
||||
/// # friends_allowed: bool,
|
||||
/// # };
|
||||
/// #
|
||||
/// # #[derive(Event)]
|
||||
/// # #[derive(Event, EntityEvent)]
|
||||
/// # struct Invite;
|
||||
/// #
|
||||
/// # #[derive(Component)]
|
||||
@ -1407,7 +1407,7 @@ fn run_once(mut app: App) -> AppExit {
|
||||
app.should_exit().unwrap_or(AppExit::Success)
|
||||
}
|
||||
|
||||
/// An event that indicates the [`App`] should exit. If one or more of these are present at the end of an update,
|
||||
/// A [`BufferedEvent`] that indicates the [`App`] should exit. If one or more of these are present at the end of an update,
|
||||
/// the [runner](App::set_runner) will end and ([maybe](App::run)) return control to the caller.
|
||||
///
|
||||
/// This event can be used to detect when an exit is requested. Make sure that systems listening
|
||||
@ -1417,7 +1417,7 @@ fn run_once(mut app: App) -> AppExit {
|
||||
/// This type is roughly meant to map to a standard definition of a process exit code (0 means success, not 0 means error). Due to portability concerns
|
||||
/// (see [`ExitCode`](https://doc.rust-lang.org/std/process/struct.ExitCode.html) and [`process::exit`](https://doc.rust-lang.org/std/process/fn.exit.html#))
|
||||
/// we only allow error codes between 1 and [255](u8::MAX).
|
||||
#[derive(Event, Debug, Clone, Default, PartialEq, Eq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub enum AppExit {
|
||||
/// [`App`] exited without any problems.
|
||||
#[default]
|
||||
@ -1485,7 +1485,7 @@ mod tests {
|
||||
change_detection::{DetectChanges, ResMut},
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
event::{Event, EventWriter, Events},
|
||||
event::{BufferedEvent, Event, EventWriter, Events},
|
||||
lifecycle::RemovedComponents,
|
||||
query::With,
|
||||
resource::Resource,
|
||||
@ -1851,7 +1851,7 @@ mod tests {
|
||||
}
|
||||
#[test]
|
||||
fn events_should_be_updated_once_per_update() {
|
||||
#[derive(Event, Clone)]
|
||||
#[derive(Event, BufferedEvent, Clone)]
|
||||
struct TestEvent;
|
||||
|
||||
let mut app = App::new();
|
||||
|
||||
@ -338,7 +338,7 @@ impl SubApp {
|
||||
/// See [`App::add_event`].
|
||||
pub fn add_event<T>(&mut self) -> &mut Self
|
||||
where
|
||||
T: Event,
|
||||
T: BufferedEvent,
|
||||
{
|
||||
if !self.world.contains_resource::<Events<T>>() {
|
||||
EventRegistry::register_event::<T>(self.world_mut());
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
use crate::{Asset, AssetId, AssetLoadError, AssetPath, UntypedAssetId};
|
||||
use bevy_ecs::event::Event;
|
||||
use bevy_ecs::event::{BufferedEvent, Event};
|
||||
use bevy_reflect::Reflect;
|
||||
use core::fmt::Debug;
|
||||
|
||||
/// An event emitted when a specific [`Asset`] fails to load.
|
||||
/// A [`BufferedEvent`] emitted when a specific [`Asset`] fails to load.
|
||||
///
|
||||
/// For an untyped equivalent, see [`UntypedAssetLoadFailedEvent`].
|
||||
#[derive(Event, Clone, Debug)]
|
||||
#[derive(Event, BufferedEvent, Clone, Debug)]
|
||||
pub struct AssetLoadFailedEvent<A: Asset> {
|
||||
/// The stable identifier of the asset that failed to load.
|
||||
pub id: AssetId<A>,
|
||||
@ -24,7 +24,7 @@ impl<A: Asset> AssetLoadFailedEvent<A> {
|
||||
}
|
||||
|
||||
/// An untyped version of [`AssetLoadFailedEvent`].
|
||||
#[derive(Event, Clone, Debug)]
|
||||
#[derive(Event, BufferedEvent, Clone, Debug)]
|
||||
pub struct UntypedAssetLoadFailedEvent {
|
||||
/// The stable identifier of the asset that failed to load.
|
||||
pub id: UntypedAssetId,
|
||||
@ -44,9 +44,9 @@ impl<A: Asset> From<&AssetLoadFailedEvent<A>> for UntypedAssetLoadFailedEvent {
|
||||
}
|
||||
}
|
||||
|
||||
/// Events that occur for a specific loaded [`Asset`], such as "value changed" events and "dependency" events.
|
||||
/// [`BufferedEvent`]s that occur for a specific loaded [`Asset`], such as "value changed" events and "dependency" events.
|
||||
#[expect(missing_docs, reason = "Documenting the id fields is unhelpful.")]
|
||||
#[derive(Event, Reflect)]
|
||||
#[derive(Event, BufferedEvent, Reflect)]
|
||||
pub enum AssetEvent<A: Asset> {
|
||||
/// Emitted whenever an [`Asset`] is added.
|
||||
Added { id: AssetId<A> },
|
||||
|
||||
@ -32,7 +32,7 @@ fn button_on_key_event(
|
||||
q_state: Query<(&CoreButton, Has<InteractionDisabled>)>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
if let Ok((bstate, disabled)) = q_state.get(trigger.target().unwrap()) {
|
||||
if let Ok((bstate, disabled)) = q_state.get(trigger.target()) {
|
||||
if !disabled {
|
||||
let event = &trigger.event().input;
|
||||
if !event.repeat
|
||||
@ -52,7 +52,7 @@ fn button_on_pointer_click(
|
||||
mut q_state: Query<(&CoreButton, Has<Pressed>, Has<InteractionDisabled>)>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
if let Ok((bstate, pressed, disabled)) = q_state.get_mut(trigger.target().unwrap()) {
|
||||
if let Ok((bstate, pressed, disabled)) = q_state.get_mut(trigger.target()) {
|
||||
trigger.propagate(false);
|
||||
if pressed && !disabled {
|
||||
if let Some(on_click) = bstate.on_click {
|
||||
@ -69,7 +69,7 @@ fn button_on_pointer_down(
|
||||
focus_visible: Option<ResMut<InputFocusVisible>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
if let Ok((button, disabled, pressed)) = q_state.get_mut(trigger.target().unwrap()) {
|
||||
if let Ok((button, disabled, pressed)) = q_state.get_mut(trigger.target()) {
|
||||
trigger.propagate(false);
|
||||
if !disabled {
|
||||
if !pressed {
|
||||
@ -78,7 +78,7 @@ fn button_on_pointer_down(
|
||||
// Clicking on a button makes it the focused input,
|
||||
// and hides the focus ring if it was visible.
|
||||
if let Some(mut focus) = focus {
|
||||
focus.0 = trigger.target();
|
||||
focus.0 = (trigger.target() != Entity::PLACEHOLDER).then_some(trigger.target());
|
||||
}
|
||||
if let Some(mut focus_visible) = focus_visible {
|
||||
focus_visible.0 = false;
|
||||
@ -92,7 +92,7 @@ fn button_on_pointer_up(
|
||||
mut q_state: Query<(Entity, Has<InteractionDisabled>, Has<Pressed>), With<CoreButton>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
if let Ok((button, disabled, pressed)) = q_state.get_mut(trigger.target().unwrap()) {
|
||||
if let Ok((button, disabled, pressed)) = q_state.get_mut(trigger.target()) {
|
||||
trigger.propagate(false);
|
||||
if !disabled && pressed {
|
||||
commands.entity(button).remove::<Pressed>();
|
||||
@ -105,7 +105,7 @@ fn button_on_pointer_drag_end(
|
||||
mut q_state: Query<(Entity, Has<InteractionDisabled>, Has<Pressed>), With<CoreButton>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
if let Ok((button, disabled, pressed)) = q_state.get_mut(trigger.target().unwrap()) {
|
||||
if let Ok((button, disabled, pressed)) = q_state.get_mut(trigger.target()) {
|
||||
trigger.propagate(false);
|
||||
if !disabled && pressed {
|
||||
commands.entity(button).remove::<Pressed>();
|
||||
@ -118,7 +118,7 @@ fn button_on_pointer_cancel(
|
||||
mut q_state: Query<(Entity, Has<InteractionDisabled>, Has<Pressed>), With<CoreButton>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
if let Ok((button, disabled, pressed)) = q_state.get_mut(trigger.target().unwrap()) {
|
||||
if let Ok((button, disabled, pressed)) = q_state.get_mut(trigger.target()) {
|
||||
trigger.propagate(false);
|
||||
if !disabled && pressed {
|
||||
commands.entity(button).remove::<Pressed>();
|
||||
|
||||
@ -3,7 +3,8 @@ use core::ops::RangeInclusive;
|
||||
use accesskit::{Orientation, Role};
|
||||
use bevy_a11y::AccessibilityNode;
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_ecs::event::Event;
|
||||
use bevy_ecs::entity::Entity;
|
||||
use bevy_ecs::event::{EntityEvent, Event};
|
||||
use bevy_ecs::hierarchy::{ChildOf, Children};
|
||||
use bevy_ecs::lifecycle::Insert;
|
||||
use bevy_ecs::query::Has;
|
||||
@ -211,13 +212,13 @@ pub(crate) fn slider_on_pointer_down(
|
||||
focus_visible: Option<ResMut<InputFocusVisible>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
if q_thumb.contains(trigger.target().unwrap()) {
|
||||
if q_thumb.contains(trigger.target()) {
|
||||
// Thumb click, stop propagation to prevent track click.
|
||||
trigger.propagate(false);
|
||||
|
||||
// Find the slider entity that's an ancestor of the thumb
|
||||
if let Some(slider_entity) = q_parents
|
||||
.iter_ancestors(trigger.target().unwrap())
|
||||
.iter_ancestors(trigger.target())
|
||||
.find(|entity| q_slider.contains(*entity))
|
||||
{
|
||||
// Set focus to slider and hide focus ring
|
||||
@ -229,14 +230,14 @@ pub(crate) fn slider_on_pointer_down(
|
||||
}
|
||||
}
|
||||
} else if let Ok((slider, value, range, step, node, node_target, transform, disabled)) =
|
||||
q_slider.get(trigger.target().unwrap())
|
||||
q_slider.get(trigger.target())
|
||||
{
|
||||
// Track click
|
||||
trigger.propagate(false);
|
||||
|
||||
// Set focus to slider and hide focus ring
|
||||
if let Some(mut focus) = focus {
|
||||
focus.0 = trigger.target();
|
||||
focus.0 = (trigger.target() != Entity::PLACEHOLDER).then_some(trigger.target());
|
||||
}
|
||||
if let Some(mut focus_visible) = focus_visible {
|
||||
focus_visible.0 = false;
|
||||
@ -248,7 +249,7 @@ pub(crate) fn slider_on_pointer_down(
|
||||
|
||||
// Find thumb size by searching descendants for the first entity with CoreSliderThumb
|
||||
let thumb_size = q_children
|
||||
.iter_descendants(trigger.target().unwrap())
|
||||
.iter_descendants(trigger.target())
|
||||
.find_map(|child_id| q_thumb.get(child_id).ok().map(|thumb| thumb.size().x))
|
||||
.unwrap_or(0.0);
|
||||
|
||||
@ -283,7 +284,7 @@ pub(crate) fn slider_on_pointer_down(
|
||||
commands.run_system_with(on_change, new_value);
|
||||
} else {
|
||||
commands
|
||||
.entity(trigger.target().unwrap())
|
||||
.entity(trigger.target())
|
||||
.insert(SliderValue(new_value));
|
||||
}
|
||||
}
|
||||
@ -300,7 +301,7 @@ pub(crate) fn slider_on_drag_start(
|
||||
With<CoreSlider>,
|
||||
>,
|
||||
) {
|
||||
if let Ok((value, mut drag, disabled)) = q_slider.get_mut(trigger.target().unwrap()) {
|
||||
if let Ok((value, mut drag, disabled)) = q_slider.get_mut(trigger.target()) {
|
||||
trigger.propagate(false);
|
||||
if !disabled {
|
||||
drag.dragging = true;
|
||||
@ -324,8 +325,7 @@ pub(crate) fn slider_on_drag(
|
||||
mut commands: Commands,
|
||||
ui_scale: Res<UiScale>,
|
||||
) {
|
||||
if let Ok((node, slider, range, transform, drag, disabled)) =
|
||||
q_slider.get_mut(trigger.target().unwrap())
|
||||
if let Ok((node, slider, range, transform, drag, disabled)) = q_slider.get_mut(trigger.target())
|
||||
{
|
||||
trigger.propagate(false);
|
||||
if drag.dragging && !disabled {
|
||||
@ -334,7 +334,7 @@ pub(crate) fn slider_on_drag(
|
||||
let distance = transform.transform_vector2(distance);
|
||||
// Find thumb size by searching descendants for the first entity with CoreSliderThumb
|
||||
let thumb_size = q_children
|
||||
.iter_descendants(trigger.target().unwrap())
|
||||
.iter_descendants(trigger.target())
|
||||
.find_map(|child_id| q_thumb.get(child_id).ok().map(|thumb| thumb.size().x))
|
||||
.unwrap_or(0.0);
|
||||
let slider_width = ((node.size().x - thumb_size) * node.inverse_scale_factor).max(1.0);
|
||||
@ -349,7 +349,7 @@ pub(crate) fn slider_on_drag(
|
||||
commands.run_system_with(on_change, new_value);
|
||||
} else {
|
||||
commands
|
||||
.entity(trigger.target().unwrap())
|
||||
.entity(trigger.target())
|
||||
.insert(SliderValue(new_value));
|
||||
}
|
||||
}
|
||||
@ -360,7 +360,7 @@ pub(crate) fn slider_on_drag_end(
|
||||
mut trigger: On<Pointer<DragEnd>>,
|
||||
mut q_slider: Query<(&CoreSlider, &mut CoreSliderDragState)>,
|
||||
) {
|
||||
if let Ok((_slider, mut drag)) = q_slider.get_mut(trigger.target().unwrap()) {
|
||||
if let Ok((_slider, mut drag)) = q_slider.get_mut(trigger.target()) {
|
||||
trigger.propagate(false);
|
||||
if drag.dragging {
|
||||
drag.dragging = false;
|
||||
@ -379,7 +379,7 @@ fn slider_on_key_input(
|
||||
)>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
if let Ok((slider, value, range, step, disabled)) = q_slider.get(trigger.target().unwrap()) {
|
||||
if let Ok((slider, value, range, step, disabled)) = q_slider.get(trigger.target()) {
|
||||
let event = &trigger.event().input;
|
||||
if !disabled && event.state == ButtonState::Pressed {
|
||||
let new_value = match event.key_code {
|
||||
@ -400,14 +400,14 @@ fn slider_on_key_input(
|
||||
}
|
||||
|
||||
pub(crate) fn slider_on_insert(trigger: On<Insert, CoreSlider>, mut world: DeferredWorld) {
|
||||
let mut entity = world.entity_mut(trigger.target().unwrap());
|
||||
let mut entity = world.entity_mut(trigger.target());
|
||||
if let Some(mut accessibility) = entity.get_mut::<AccessibilityNode>() {
|
||||
accessibility.set_orientation(Orientation::Horizontal);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn slider_on_insert_value(trigger: On<Insert, SliderValue>, mut world: DeferredWorld) {
|
||||
let mut entity = world.entity_mut(trigger.target().unwrap());
|
||||
let mut entity = world.entity_mut(trigger.target());
|
||||
let value = entity.get::<SliderValue>().unwrap().0;
|
||||
if let Some(mut accessibility) = entity.get_mut::<AccessibilityNode>() {
|
||||
accessibility.set_numeric_value(value.into());
|
||||
@ -415,7 +415,7 @@ pub(crate) fn slider_on_insert_value(trigger: On<Insert, SliderValue>, mut world
|
||||
}
|
||||
|
||||
pub(crate) fn slider_on_insert_range(trigger: On<Insert, SliderRange>, mut world: DeferredWorld) {
|
||||
let mut entity = world.entity_mut(trigger.target().unwrap());
|
||||
let mut entity = world.entity_mut(trigger.target());
|
||||
let range = *entity.get::<SliderRange>().unwrap();
|
||||
if let Some(mut accessibility) = entity.get_mut::<AccessibilityNode>() {
|
||||
accessibility.set_min_numeric_value(range.start().into());
|
||||
@ -424,14 +424,14 @@ pub(crate) fn slider_on_insert_range(trigger: On<Insert, SliderRange>, mut world
|
||||
}
|
||||
|
||||
pub(crate) fn slider_on_insert_step(trigger: On<Insert, SliderStep>, mut world: DeferredWorld) {
|
||||
let mut entity = world.entity_mut(trigger.target().unwrap());
|
||||
let mut entity = world.entity_mut(trigger.target());
|
||||
let step = entity.get::<SliderStep>().unwrap().0;
|
||||
if let Some(mut accessibility) = entity.get_mut::<AccessibilityNode>() {
|
||||
accessibility.set_numeric_value_step(step.into());
|
||||
}
|
||||
}
|
||||
|
||||
/// Event which can be triggered on a slider to modify the value (using the `on_change` callback).
|
||||
/// An [`EntityEvent`] that can be triggered on a slider to modify its value (using the `on_change` callback).
|
||||
/// This can be used to control the slider via gamepad buttons or other inputs. The value will be
|
||||
/// clamped when the event is processed.
|
||||
///
|
||||
@ -456,7 +456,7 @@ pub(crate) fn slider_on_insert_step(trigger: On<Insert, SliderStep>, mut world:
|
||||
/// commands.trigger_targets(SetSliderValue::Relative(-0.25), slider);
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Event)]
|
||||
#[derive(Event, EntityEvent)]
|
||||
pub enum SetSliderValue {
|
||||
/// Set the slider value to a specific value.
|
||||
Absolute(f32),
|
||||
@ -471,7 +471,7 @@ fn slider_on_set_value(
|
||||
q_slider: Query<(&CoreSlider, &SliderValue, &SliderRange, Option<&SliderStep>)>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
if let Ok((slider, value, range, step)) = q_slider.get(trigger.target().unwrap()) {
|
||||
if let Ok((slider, value, range, step)) = q_slider.get(trigger.target()) {
|
||||
trigger.propagate(false);
|
||||
let new_value = match trigger.event() {
|
||||
SetSliderValue::Absolute(new_value) => range.clamp(*new_value),
|
||||
@ -484,7 +484,7 @@ fn slider_on_set_value(
|
||||
commands.run_system_with(on_change, new_value);
|
||||
} else {
|
||||
commands
|
||||
.entity(trigger.target().unwrap())
|
||||
.entity(trigger.target())
|
||||
.insert(SliderValue(new_value));
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,7 +49,7 @@ pub enum CiTestingEvent {
|
||||
}
|
||||
|
||||
/// A custom event that can be configured from a configuration file for CI testing.
|
||||
#[derive(Event)]
|
||||
#[derive(Event, BufferedEvent)]
|
||||
pub struct CiTestingCustomEvent(pub String);
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@ -5,9 +5,9 @@ use bevy_color::prelude::*;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_picking::backend::HitData;
|
||||
use bevy_picking::hover::HoverMap;
|
||||
use bevy_picking::pointer::{Location, PointerId, PointerPress};
|
||||
use bevy_picking::pointer::{Location, PointerId, PointerInput, PointerLocation, PointerPress};
|
||||
use bevy_picking::prelude::*;
|
||||
use bevy_picking::{pointer, PickingSystems};
|
||||
use bevy_picking::PickingSystems;
|
||||
use bevy_reflect::prelude::*;
|
||||
use bevy_render::prelude::*;
|
||||
use bevy_text::prelude::*;
|
||||
@ -91,7 +91,7 @@ impl Plugin for DebugPickingPlugin {
|
||||
(
|
||||
// This leaves room to easily change the log-level associated
|
||||
// with different events, should that be desired.
|
||||
log_event_debug::<pointer::PointerInput>.run_if(DebugPickingMode::is_noisy),
|
||||
log_event_debug::<PointerInput>.run_if(DebugPickingMode::is_noisy),
|
||||
log_pointer_event_debug::<Over>,
|
||||
log_pointer_event_debug::<Out>,
|
||||
log_pointer_event_debug::<Press>,
|
||||
@ -121,7 +121,7 @@ impl Plugin for DebugPickingPlugin {
|
||||
}
|
||||
|
||||
/// Listen for any event and logs it at the debug level
|
||||
pub fn log_event_debug<E: Event + Debug>(mut events: EventReader<pointer::PointerInput>) {
|
||||
pub fn log_event_debug<E: BufferedEvent + Debug>(mut events: EventReader<PointerInput>) {
|
||||
for event in events.read() {
|
||||
debug!("{event:?}");
|
||||
}
|
||||
@ -214,7 +214,7 @@ pub fn update_debug_data(
|
||||
entity_names: Query<NameOrEntity>,
|
||||
mut pointers: Query<(
|
||||
&PointerId,
|
||||
&pointer::PointerLocation,
|
||||
&PointerLocation,
|
||||
&PointerPress,
|
||||
&mut PointerDebug,
|
||||
)>,
|
||||
|
||||
@ -277,26 +277,24 @@ world.spawn(PlayerBundle {
|
||||
});
|
||||
```
|
||||
|
||||
### Events
|
||||
### Buffered Events
|
||||
|
||||
Events offer a communication channel between one or more systems. Events can be sent using the system parameter `EventWriter` and received with `EventReader`.
|
||||
Buffered events offer a communication channel between one or more systems.
|
||||
They can be sent using the `EventWriter` system parameter and received with `EventReader`.
|
||||
|
||||
```rust
|
||||
use bevy_ecs::prelude::*;
|
||||
|
||||
#[derive(Event)]
|
||||
struct MyEvent {
|
||||
message: String,
|
||||
#[derive(Event, BufferedEvent)]
|
||||
struct Message(String);
|
||||
|
||||
fn writer(mut writer: EventWriter<Message>) {
|
||||
writer.write(Message("Hello!".to_string()));
|
||||
}
|
||||
|
||||
fn writer(mut writer: EventWriter<MyEvent>) {
|
||||
writer.write(MyEvent {
|
||||
message: "hello!".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
fn reader(mut reader: EventReader<MyEvent>) {
|
||||
for event in reader.read() {
|
||||
fn reader(mut reader: EventReader<Message>) {
|
||||
for Message(message) in reader.read() {
|
||||
println!("{}", message);
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -309,39 +307,41 @@ Observers are systems that listen for a "trigger" of a specific `Event`:
|
||||
use bevy_ecs::prelude::*;
|
||||
|
||||
#[derive(Event)]
|
||||
struct MyEvent {
|
||||
struct Speak {
|
||||
message: String
|
||||
}
|
||||
|
||||
let mut world = World::new();
|
||||
|
||||
world.add_observer(|trigger: On<MyEvent>| {
|
||||
println!("{}", trigger.event().message);
|
||||
world.add_observer(|trigger: On<Speak>| {
|
||||
println!("{}", trigger.message);
|
||||
});
|
||||
|
||||
world.flush();
|
||||
|
||||
world.trigger(MyEvent {
|
||||
message: "hello!".to_string(),
|
||||
world.trigger(Speak {
|
||||
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!
|
||||
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:
|
||||
If the event is an `EntityEvent`, it can also be triggered to target specific entities:
|
||||
|
||||
```rust
|
||||
use bevy_ecs::prelude::*;
|
||||
|
||||
#[derive(Event)]
|
||||
#[derive(Event, EntityEvent)]
|
||||
struct Explode;
|
||||
|
||||
let mut world = World::new();
|
||||
let entity = world.spawn_empty().id();
|
||||
|
||||
world.add_observer(|trigger: On<Explode>, mut commands: Commands| {
|
||||
println!("Entity {} goes BOOM!", trigger.target().unwrap());
|
||||
commands.entity(trigger.target().unwrap()).despawn();
|
||||
println!("Entity {} goes BOOM!", trigger.target());
|
||||
commands.entity(trigger.target()).despawn();
|
||||
});
|
||||
|
||||
world.flush();
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
//! In this example a system sends a custom event with a 50/50 chance during any frame.
|
||||
//! In this example a system sends a custom buffered event with a 50/50 chance during any frame.
|
||||
//! If an event was sent, it will be printed by the console in a receiving system.
|
||||
|
||||
#![expect(clippy::print_stdout, reason = "Allowed in examples.")]
|
||||
@ -15,7 +15,7 @@ fn main() {
|
||||
// Create a schedule to store our systems
|
||||
let mut schedule = Schedule::default();
|
||||
|
||||
// Events need to be updated in every frame in order to clear our buffers.
|
||||
// Buffered events need to be updated every frame in order to clear our buffers.
|
||||
// This update should happen before we use the events.
|
||||
// Here, we use system sets to control the ordering.
|
||||
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
@ -37,7 +37,7 @@ fn main() {
|
||||
}
|
||||
|
||||
// This is our event that we will send and receive in systems
|
||||
#[derive(Event)]
|
||||
#[derive(Event, BufferedEvent)]
|
||||
struct MyEvent {
|
||||
pub message: String,
|
||||
pub random_value: f32,
|
||||
|
||||
@ -13,11 +13,28 @@ use syn::{
|
||||
LitStr, Member, Path, Result, Token, Type, Visibility,
|
||||
};
|
||||
|
||||
pub const EVENT: &str = "event";
|
||||
pub const EVENT: &str = "entity_event";
|
||||
pub const AUTO_PROPAGATE: &str = "auto_propagate";
|
||||
pub const TRAVERSAL: &str = "traversal";
|
||||
|
||||
pub fn derive_event(input: TokenStream) -> TokenStream {
|
||||
let mut ast = parse_macro_input!(input as DeriveInput);
|
||||
let bevy_ecs_path: Path = crate::bevy_ecs_path();
|
||||
|
||||
ast.generics
|
||||
.make_where_clause()
|
||||
.predicates
|
||||
.push(parse_quote! { Self: Send + Sync + 'static });
|
||||
|
||||
let struct_name = &ast.ident;
|
||||
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
|
||||
|
||||
TokenStream::from(quote! {
|
||||
impl #impl_generics #bevy_ecs_path::event::Event for #struct_name #type_generics #where_clause {}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn derive_entity_event(input: TokenStream) -> TokenStream {
|
||||
let mut ast = parse_macro_input!(input as DeriveInput);
|
||||
let mut auto_propagate = false;
|
||||
let mut traversal: Type = parse_quote!(());
|
||||
@ -49,13 +66,30 @@ pub fn derive_event(input: TokenStream) -> TokenStream {
|
||||
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
|
||||
|
||||
TokenStream::from(quote! {
|
||||
impl #impl_generics #bevy_ecs_path::event::Event for #struct_name #type_generics #where_clause {
|
||||
impl #impl_generics #bevy_ecs_path::event::EntityEvent for #struct_name #type_generics #where_clause {
|
||||
type Traversal = #traversal;
|
||||
const AUTO_PROPAGATE: bool = #auto_propagate;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn derive_buffered_event(input: TokenStream) -> TokenStream {
|
||||
let mut ast = parse_macro_input!(input as DeriveInput);
|
||||
let bevy_ecs_path: Path = crate::bevy_ecs_path();
|
||||
|
||||
ast.generics
|
||||
.make_where_clause()
|
||||
.predicates
|
||||
.push(parse_quote! { Self: Send + Sync + 'static });
|
||||
|
||||
let struct_name = &ast.ident;
|
||||
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
|
||||
|
||||
TokenStream::from(quote! {
|
||||
impl #impl_generics #bevy_ecs_path::event::BufferedEvent for #struct_name #type_generics #where_clause {}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn derive_resource(input: TokenStream) -> TokenStream {
|
||||
let mut ast = parse_macro_input!(input as DeriveInput);
|
||||
let bevy_ecs_path: Path = crate::bevy_ecs_path();
|
||||
|
||||
@ -573,11 +573,23 @@ pub(crate) fn bevy_ecs_path() -> syn::Path {
|
||||
}
|
||||
|
||||
/// Implement the `Event` trait.
|
||||
#[proc_macro_derive(Event, attributes(event))]
|
||||
#[proc_macro_derive(Event)]
|
||||
pub fn derive_event(input: TokenStream) -> TokenStream {
|
||||
component::derive_event(input)
|
||||
}
|
||||
|
||||
/// Implement the `EntityEvent` trait.
|
||||
#[proc_macro_derive(EntityEvent, attributes(entity_event))]
|
||||
pub fn derive_entity_event(input: TokenStream) -> TokenStream {
|
||||
component::derive_entity_event(input)
|
||||
}
|
||||
|
||||
/// Implement the `BufferedEvent` trait.
|
||||
#[proc_macro_derive(BufferedEvent)]
|
||||
pub fn derive_buffered_event(input: TokenStream) -> TokenStream {
|
||||
component::derive_buffered_event(input)
|
||||
}
|
||||
|
||||
/// Implement the `Resource` trait.
|
||||
#[proc_macro_derive(Resource)]
|
||||
pub fn derive_resource(input: TokenStream) -> TokenStream {
|
||||
|
||||
@ -230,7 +230,7 @@ pub trait DetectChangesMut: DetectChanges {
|
||||
/// #[derive(Resource, PartialEq, Eq)]
|
||||
/// pub struct Score(u32);
|
||||
///
|
||||
/// #[derive(Event, PartialEq, Eq)]
|
||||
/// #[derive(Event, BufferedEvent, PartialEq, Eq)]
|
||||
/// pub struct ScoreChanged {
|
||||
/// current: u32,
|
||||
/// previous: u32,
|
||||
|
||||
@ -11,33 +11,81 @@ use core::{
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
||||
/// Something that "happens" and might be read / observed by app logic.
|
||||
/// Something that "happens" and can be processed 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 be triggered on a [`World`] using a method like [`trigger`](World::trigger),
|
||||
/// causing any global [`Observer`] watching that event to run. This allows for push-based
|
||||
/// event handling where observers are immediately notified of events as they happen.
|
||||
///
|
||||
/// Events can also be "triggered" on a [`World`], which will then cause any [`Observer`] of that trigger to run.
|
||||
/// Additional event handling behavior can be enabled by implementing the [`EntityEvent`]
|
||||
/// and [`BufferedEvent`] traits:
|
||||
///
|
||||
/// - [`EntityEvent`]s support targeting specific entities, triggering any observers watching those targets.
|
||||
/// They are useful for entity-specific event handlers and can even be propagated from one entity to another.
|
||||
/// - [`BufferedEvent`]s support a pull-based event handling system where events are written using an [`EventWriter`]
|
||||
/// and read later using an [`EventReader`]. This is an alternative to observers that allows efficient batch processing
|
||||
/// of events at fixed points in a schedule.
|
||||
///
|
||||
/// Events must be thread-safe.
|
||||
///
|
||||
/// ## Derive
|
||||
/// This trait can be derived.
|
||||
/// Adding `auto_propagate` sets [`Self::AUTO_PROPAGATE`] to true.
|
||||
/// Adding `traversal = "X"` sets [`Self::Traversal`] to be of type "X".
|
||||
/// # Usage
|
||||
///
|
||||
/// The [`Event`] trait can be derived:
|
||||
///
|
||||
/// ```
|
||||
/// use bevy_ecs::prelude::*;
|
||||
///
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #
|
||||
/// #[derive(Event)]
|
||||
/// #[event(auto_propagate)]
|
||||
/// struct MyEvent;
|
||||
/// struct Speak {
|
||||
/// message: String,
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// An [`Observer`] can then be added to listen for this event type:
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #
|
||||
/// # #[derive(Event)]
|
||||
/// # struct Speak {
|
||||
/// # message: String,
|
||||
/// # }
|
||||
/// #
|
||||
/// # let mut world = World::new();
|
||||
/// #
|
||||
/// world.add_observer(|trigger: On<Speak>| {
|
||||
/// println!("{}", trigger.message);
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// The event can be triggered on the [`World`] using the [`trigger`](World::trigger) method:
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #
|
||||
/// # #[derive(Event)]
|
||||
/// # struct Speak {
|
||||
/// # message: String,
|
||||
/// # }
|
||||
/// #
|
||||
/// # let mut world = World::new();
|
||||
/// #
|
||||
/// # world.add_observer(|trigger: On<Speak>| {
|
||||
/// # println!("{}", trigger.message);
|
||||
/// # });
|
||||
/// #
|
||||
/// # world.flush();
|
||||
/// #
|
||||
/// world.trigger(Speak {
|
||||
/// message: "Hello!".to_string(),
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// For events that additionally need entity targeting or buffering, consider also deriving
|
||||
/// [`EntityEvent`] or [`BufferedEvent`], respectively.
|
||||
///
|
||||
/// [`World`]: crate::world::World
|
||||
/// [`ComponentId`]: crate::component::ComponentId
|
||||
/// [`Observer`]: crate::observer::Observer
|
||||
/// [`Events<E>`]: super::Events
|
||||
/// [`EventReader`]: super::EventReader
|
||||
/// [`EventWriter`]: super::EventWriter
|
||||
#[diagnostic::on_unimplemented(
|
||||
@ -46,19 +94,6 @@ use core::{
|
||||
note = "consider annotating `{Self}` with `#[derive(Event)]`"
|
||||
)]
|
||||
pub trait Event: Send + Sync + 'static {
|
||||
/// The component that describes which [`Entity`] to propagate this event to next, when [propagation] is enabled.
|
||||
///
|
||||
/// [`Entity`]: crate::entity::Entity
|
||||
/// [propagation]: crate::observer::On::propagate
|
||||
type Traversal: Traversal<Self>;
|
||||
|
||||
/// When true, this event will always attempt to propagate when [triggered], without requiring a call
|
||||
/// to [`On::propagate`].
|
||||
///
|
||||
/// [triggered]: crate::system::Commands::trigger_targets
|
||||
/// [`On::propagate`]: crate::observer::On::propagate
|
||||
const AUTO_PROPAGATE: bool = false;
|
||||
|
||||
/// Generates the [`ComponentId`] for this event type.
|
||||
///
|
||||
/// If this type has already been registered,
|
||||
@ -90,6 +125,209 @@ pub trait Event: Send + Sync + 'static {
|
||||
}
|
||||
}
|
||||
|
||||
/// An [`Event`] that can be targeted at specific entities.
|
||||
///
|
||||
/// Entity events can be triggered on a [`World`] with specific entity targets using a method
|
||||
/// like [`trigger_targets`](World::trigger_targets), causing any [`Observer`] watching the event
|
||||
/// for those entities to run.
|
||||
///
|
||||
/// Unlike basic [`Event`]s, entity events can optionally be propagated from one entity target to another
|
||||
/// based on the [`EntityEvent::Traversal`] type associated with the event. This enables use cases
|
||||
/// such as bubbling events to parent entities for UI purposes.
|
||||
///
|
||||
/// Entity events must be thread-safe.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// The [`EntityEvent`] trait can be derived. The `event` attribute can be used to further configure
|
||||
/// the propagation behavior: adding `auto_propagate` sets [`EntityEvent::AUTO_PROPAGATE`] to `true`,
|
||||
/// while adding `traversal = X` sets [`EntityEvent::Traversal`] to be of type `X`.
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #
|
||||
/// // When the `Damage` event is triggered on an entity, bubble the event up to ancestors.
|
||||
/// #[derive(Event, EntityEvent)]
|
||||
/// #[entity_event(traversal = &'static ChildOf, auto_propagate)]
|
||||
/// struct Damage {
|
||||
/// amount: f32,
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// An [`Observer`] can then be added to listen for this event type for the desired entity:
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #
|
||||
/// # #[derive(Event, EntityEvent)]
|
||||
/// # #[entity_event(traversal = &'static ChildOf, auto_propagate)]
|
||||
/// # struct Damage {
|
||||
/// # amount: f32,
|
||||
/// # }
|
||||
/// #
|
||||
/// # #[derive(Component)]
|
||||
/// # struct Health(f32);
|
||||
/// #
|
||||
/// # #[derive(Component)]
|
||||
/// # struct Enemy;
|
||||
/// #
|
||||
/// # #[derive(Component)]
|
||||
/// # struct ArmorPiece;
|
||||
/// #
|
||||
/// # let mut world = World::new();
|
||||
/// #
|
||||
/// // Spawn an enemy entity.
|
||||
/// let enemy = world.spawn((Enemy, Health(100.0))).id();
|
||||
///
|
||||
/// // Spawn some armor as a child of the enemy entity.
|
||||
/// // When the armor takes damage, it will bubble the event up to the enemy,
|
||||
/// // which can then handle the event with its own observer.
|
||||
/// let armor_piece = world
|
||||
/// .spawn((ArmorPiece, Health(25.0), ChildOf(enemy)))
|
||||
/// .observe(|trigger: On<Damage>, mut query: Query<&mut Health>| {
|
||||
/// // Note: `On::target` only exists because this is an `EntityEvent`.
|
||||
/// let mut health = query.get_mut(trigger.target()).unwrap();
|
||||
/// health.0 -= trigger.amount;
|
||||
/// })
|
||||
/// .id();
|
||||
/// ```
|
||||
///
|
||||
/// The event can be triggered on the [`World`] using the [`trigger_targets`](World::trigger_targets) method,
|
||||
/// providing the desired entity target(s):
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #
|
||||
/// # #[derive(Event, EntityEvent)]
|
||||
/// # #[entity_event(traversal = &'static ChildOf, auto_propagate)]
|
||||
/// # struct Damage {
|
||||
/// # amount: f32,
|
||||
/// # }
|
||||
/// #
|
||||
/// # #[derive(Component)]
|
||||
/// # struct Health(f32);
|
||||
/// #
|
||||
/// # #[derive(Component)]
|
||||
/// # struct Enemy;
|
||||
/// #
|
||||
/// # #[derive(Component)]
|
||||
/// # struct ArmorPiece;
|
||||
/// #
|
||||
/// # let mut world = World::new();
|
||||
/// #
|
||||
/// # let enemy = world.spawn((Enemy, Health(100.0))).id();
|
||||
/// # let armor_piece = world
|
||||
/// # .spawn((ArmorPiece, Health(25.0), ChildOf(enemy)))
|
||||
/// # .observe(|trigger: On<Damage>, mut query: Query<&mut Health>| {
|
||||
/// # // Note: `On::target` only exists because this is an `EntityEvent`.
|
||||
/// # let mut health = query.get_mut(trigger.target()).unwrap();
|
||||
/// # health.0 -= trigger.amount;
|
||||
/// # })
|
||||
/// # .id();
|
||||
/// #
|
||||
/// # world.flush();
|
||||
/// #
|
||||
/// world.trigger_targets(Damage { amount: 10.0 }, armor_piece);
|
||||
/// ```
|
||||
///
|
||||
/// [`World`]: crate::world::World
|
||||
/// [`TriggerTargets`]: crate::observer::TriggerTargets
|
||||
/// [`Observer`]: crate::observer::Observer
|
||||
/// [`Events<E>`]: super::Events
|
||||
/// [`EventReader`]: super::EventReader
|
||||
/// [`EventWriter`]: super::EventWriter
|
||||
#[diagnostic::on_unimplemented(
|
||||
message = "`{Self}` is not an `EntityEvent`",
|
||||
label = "invalid `EntityEvent`",
|
||||
note = "consider annotating `{Self}` with `#[derive(Event, EntityEvent)]`"
|
||||
)]
|
||||
pub trait EntityEvent: Event {
|
||||
/// The component that describes which [`Entity`] to propagate this event to next, when [propagation] is enabled.
|
||||
///
|
||||
/// [`Entity`]: crate::entity::Entity
|
||||
/// [propagation]: crate::observer::On::propagate
|
||||
type Traversal: Traversal<Self>;
|
||||
|
||||
/// When true, this event will always attempt to propagate when [triggered], without requiring a call
|
||||
/// to [`On::propagate`].
|
||||
///
|
||||
/// [triggered]: crate::system::Commands::trigger_targets
|
||||
/// [`On::propagate`]: crate::observer::On::propagate
|
||||
const AUTO_PROPAGATE: bool = false;
|
||||
}
|
||||
|
||||
/// A buffered [`Event`] for pull-based event handling.
|
||||
///
|
||||
/// Buffered events can be written with [`EventWriter`] and read using the [`EventReader`] system parameter.
|
||||
/// These events are stored in the [`Events<E>`] resource, and require periodically polling the world for new events,
|
||||
/// typically in a system that runs as part of a schedule.
|
||||
///
|
||||
/// While the polling imposes a small overhead, buffered events are useful for efficiently batch processing
|
||||
/// a large number of events at once. This can make them more efficient than [`Event`]s used by [`Observer`]s
|
||||
/// for events that happen at a high frequency or in large quantities.
|
||||
///
|
||||
/// Unlike [`Event`]s triggered for observers, buffered events are evaluated at fixed points in the schedule
|
||||
/// rather than immediately when they are sent. This allows for more predictable scheduling and deferring
|
||||
/// event processing to a later point in time.
|
||||
///
|
||||
/// Buffered events do *not* trigger observers automatically when they are written via an [`EventWriter`].
|
||||
/// However, they can still also be triggered on a [`World`] in case you want both buffered and immediate
|
||||
/// event handling for the same event.
|
||||
///
|
||||
/// Buffered events must be thread-safe.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// The [`BufferedEvent`] trait can be derived:
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #
|
||||
/// #[derive(Event, BufferedEvent)]
|
||||
/// struct Message(String);
|
||||
/// ```
|
||||
///
|
||||
/// The event can then be written to the event buffer using an [`EventWriter`]:
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #
|
||||
/// # #[derive(Event, BufferedEvent)]
|
||||
/// # struct Message(String);
|
||||
/// #
|
||||
/// fn write_hello(mut writer: EventWriter<Message>) {
|
||||
/// writer.write(Message("Hello!".to_string()));
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Buffered events can be efficiently read using an [`EventReader`]:
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #
|
||||
/// # #[derive(Event, BufferedEvent)]
|
||||
/// # struct Message(String);
|
||||
/// #
|
||||
/// fn read_messages(mut reader: EventReader<Message>) {
|
||||
/// // Process all buffered events of type `Message`.
|
||||
/// for Message(message) in reader.read() {
|
||||
/// println!("{message}");
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`World`]: crate::world::World
|
||||
/// [`Observer`]: crate::observer::Observer
|
||||
/// [`Events<E>`]: super::Events
|
||||
/// [`EventReader`]: super::EventReader
|
||||
/// [`EventWriter`]: super::EventWriter
|
||||
#[diagnostic::on_unimplemented(
|
||||
message = "`{Self}` is not an `BufferedEvent`",
|
||||
label = "invalid `BufferedEvent`",
|
||||
note = "consider annotating `{Self}` with `#[derive(Event, BufferedEvent)]`"
|
||||
)]
|
||||
pub trait BufferedEvent: Event {}
|
||||
|
||||
/// An internal type that implements [`Component`] for a given [`Event`] type.
|
||||
///
|
||||
/// This exists so we can easily get access to a unique [`ComponentId`] for each [`Event`] type,
|
||||
@ -116,7 +354,7 @@ struct EventWrapperComponent<E: Event + ?Sized>(PhantomData<E>);
|
||||
derive(Reflect),
|
||||
reflect(Clone, Debug, PartialEq, Hash)
|
||||
)]
|
||||
pub struct EventId<E: Event> {
|
||||
pub struct EventId<E: BufferedEvent> {
|
||||
/// Uniquely identifies the event associated with this ID.
|
||||
// This value corresponds to the order in which each event was added to the world.
|
||||
pub id: usize,
|
||||
@ -126,21 +364,21 @@ pub struct EventId<E: Event> {
|
||||
pub(super) _marker: PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<E: Event> Copy for EventId<E> {}
|
||||
impl<E: BufferedEvent> Copy for EventId<E> {}
|
||||
|
||||
impl<E: Event> Clone for EventId<E> {
|
||||
impl<E: BufferedEvent> Clone for EventId<E> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Event> fmt::Display for EventId<E> {
|
||||
impl<E: BufferedEvent> fmt::Display for EventId<E> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
<Self as fmt::Debug>::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Event> fmt::Debug for EventId<E> {
|
||||
impl<E: BufferedEvent> fmt::Debug for EventId<E> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
@ -151,27 +389,27 @@ impl<E: Event> fmt::Debug for EventId<E> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Event> PartialEq for EventId<E> {
|
||||
impl<E: BufferedEvent> PartialEq for EventId<E> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Event> Eq for EventId<E> {}
|
||||
impl<E: BufferedEvent> Eq for EventId<E> {}
|
||||
|
||||
impl<E: Event> PartialOrd for EventId<E> {
|
||||
impl<E: BufferedEvent> PartialOrd for EventId<E> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Event> Ord for EventId<E> {
|
||||
impl<E: BufferedEvent> Ord for EventId<E> {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.id.cmp(&other.id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Event> Hash for EventId<E> {
|
||||
impl<E: BufferedEvent> Hash for EventId<E> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
Hash::hash(&self.id, state);
|
||||
}
|
||||
@ -179,7 +417,7 @@ impl<E: Event> Hash for EventId<E> {
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
pub(crate) struct EventInstance<E: Event> {
|
||||
pub(crate) struct EventInstance<E: BufferedEvent> {
|
||||
pub event_id: EventId<E>,
|
||||
pub event: E,
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use alloc::vec::Vec;
|
||||
use bevy_ecs::{
|
||||
change_detection::MaybeLocation,
|
||||
event::{Event, EventCursor, EventId, EventInstance},
|
||||
event::{BufferedEvent, EventCursor, EventId, EventInstance},
|
||||
resource::Resource,
|
||||
};
|
||||
use core::{
|
||||
@ -38,10 +38,11 @@ use {
|
||||
/// dropped silently.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use bevy_ecs::event::{Event, Events};
|
||||
///
|
||||
/// #[derive(Event)]
|
||||
/// ```
|
||||
/// use bevy_ecs::event::{BufferedEvent, Event, Events};
|
||||
///
|
||||
/// #[derive(Event, BufferedEvent)]
|
||||
/// struct MyEvent {
|
||||
/// value: usize
|
||||
/// }
|
||||
@ -91,7 +92,7 @@ use {
|
||||
/// [`event_update_system`]: super::event_update_system
|
||||
#[derive(Debug, Resource)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Resource, Default))]
|
||||
pub struct Events<E: Event> {
|
||||
pub struct Events<E: BufferedEvent> {
|
||||
/// Holds the oldest still active events.
|
||||
/// Note that `a.start_event_count + a.len()` should always be equal to `events_b.start_event_count`.
|
||||
pub(crate) events_a: EventSequence<E>,
|
||||
@ -101,7 +102,7 @@ pub struct Events<E: Event> {
|
||||
}
|
||||
|
||||
// Derived Default impl would incorrectly require E: Default
|
||||
impl<E: Event> Default for Events<E> {
|
||||
impl<E: BufferedEvent> Default for Events<E> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
events_a: Default::default(),
|
||||
@ -111,7 +112,7 @@ impl<E: Event> Default for Events<E> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Event> Events<E> {
|
||||
impl<E: BufferedEvent> Events<E> {
|
||||
/// Returns the index of the oldest event stored in the event buffer.
|
||||
pub fn oldest_event_count(&self) -> usize {
|
||||
self.events_a.start_event_count
|
||||
@ -286,7 +287,7 @@ impl<E: Event> Events<E> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Event> Extend<E> for Events<E> {
|
||||
impl<E: BufferedEvent> Extend<E> for Events<E> {
|
||||
#[track_caller]
|
||||
fn extend<I>(&mut self, iter: I)
|
||||
where
|
||||
@ -321,13 +322,13 @@ impl<E: Event> Extend<E> for Events<E> {
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Default))]
|
||||
pub(crate) struct EventSequence<E: Event> {
|
||||
pub(crate) struct EventSequence<E: BufferedEvent> {
|
||||
pub(crate) events: Vec<EventInstance<E>>,
|
||||
pub(crate) start_event_count: usize,
|
||||
}
|
||||
|
||||
// Derived Default impl would incorrectly require E: Default
|
||||
impl<E: Event> Default for EventSequence<E> {
|
||||
impl<E: BufferedEvent> Default for EventSequence<E> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
events: Default::default(),
|
||||
@ -336,7 +337,7 @@ impl<E: Event> Default for EventSequence<E> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Event> Deref for EventSequence<E> {
|
||||
impl<E: BufferedEvent> Deref for EventSequence<E> {
|
||||
type Target = Vec<EventInstance<E>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@ -344,7 +345,7 @@ impl<E: Event> Deref for EventSequence<E> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Event> DerefMut for EventSequence<E> {
|
||||
impl<E: BufferedEvent> DerefMut for EventSequence<E> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.events
|
||||
}
|
||||
@ -357,7 +358,7 @@ pub struct SendBatchIds<E> {
|
||||
_marker: PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<E: Event> Iterator for SendBatchIds<E> {
|
||||
impl<E: BufferedEvent> Iterator for SendBatchIds<E> {
|
||||
type Item = EventId<E>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
@ -377,7 +378,7 @@ impl<E: Event> Iterator for SendBatchIds<E> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Event> ExactSizeIterator for SendBatchIds<E> {
|
||||
impl<E: BufferedEvent> ExactSizeIterator for SendBatchIds<E> {
|
||||
fn len(&self) -> usize {
|
||||
self.event_count.saturating_sub(self.last_count)
|
||||
}
|
||||
@ -385,12 +386,11 @@ impl<E: Event> ExactSizeIterator for SendBatchIds<E> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::event::Events;
|
||||
use bevy_ecs_macros::Event;
|
||||
use crate::event::{BufferedEvent, Event, Events};
|
||||
|
||||
#[test]
|
||||
fn iter_current_update_events_iterates_over_current_events() {
|
||||
#[derive(Event, Clone)]
|
||||
#[derive(Event, BufferedEvent, Clone)]
|
||||
struct TestEvent;
|
||||
|
||||
let mut test_events = Events::<TestEvent>::default();
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
use bevy_ecs::event::{
|
||||
Event, EventIterator, EventIteratorWithId, EventMutIterator, EventMutIteratorWithId, Events,
|
||||
BufferedEvent, EventIterator, EventIteratorWithId, EventMutIterator, EventMutIteratorWithId,
|
||||
Events,
|
||||
};
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
use bevy_ecs::event::{EventMutParIter, EventParIter};
|
||||
@ -19,9 +20,9 @@ use core::marker::PhantomData;
|
||||
///
|
||||
/// ```
|
||||
/// use bevy_ecs::prelude::*;
|
||||
/// use bevy_ecs::event::{Event, Events, EventCursor};
|
||||
/// use bevy_ecs::event::{BufferedEvent, Events, EventCursor};
|
||||
///
|
||||
/// #[derive(Event, Clone, Debug)]
|
||||
/// #[derive(Event, BufferedEvent, Clone, Debug)]
|
||||
/// struct MyEvent;
|
||||
///
|
||||
/// /// A system that both sends and receives events using a [`Local`] [`EventCursor`].
|
||||
@ -50,12 +51,12 @@ use core::marker::PhantomData;
|
||||
/// [`EventReader`]: super::EventReader
|
||||
/// [`EventMutator`]: super::EventMutator
|
||||
#[derive(Debug)]
|
||||
pub struct EventCursor<E: Event> {
|
||||
pub struct EventCursor<E: BufferedEvent> {
|
||||
pub(super) last_event_count: usize,
|
||||
pub(super) _marker: PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<E: Event> Default for EventCursor<E> {
|
||||
impl<E: BufferedEvent> Default for EventCursor<E> {
|
||||
fn default() -> Self {
|
||||
EventCursor {
|
||||
last_event_count: 0,
|
||||
@ -64,7 +65,7 @@ impl<E: Event> Default for EventCursor<E> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Event> Clone for EventCursor<E> {
|
||||
impl<E: BufferedEvent> Clone for EventCursor<E> {
|
||||
fn clone(&self) -> Self {
|
||||
EventCursor {
|
||||
last_event_count: self.last_event_count,
|
||||
@ -73,7 +74,7 @@ impl<E: Event> Clone for EventCursor<E> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Event> EventCursor<E> {
|
||||
impl<E: BufferedEvent> EventCursor<E> {
|
||||
/// See [`EventReader::read`](super::EventReader::read)
|
||||
pub fn read<'a>(&'a mut self, events: &'a Events<E>) -> EventIterator<'a, E> {
|
||||
self.read_with_id(events).without_id()
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
use bevy_ecs::batching::BatchingStrategy;
|
||||
use bevy_ecs::event::{Event, EventCursor, EventId, EventInstance, Events};
|
||||
use bevy_ecs::event::{BufferedEvent, EventCursor, EventId, EventInstance, Events};
|
||||
use core::{iter::Chain, slice::Iter};
|
||||
|
||||
/// An iterator that yields any unread events from an [`EventReader`](super::EventReader) or [`EventCursor`].
|
||||
#[derive(Debug)]
|
||||
pub struct EventIterator<'a, E: Event> {
|
||||
pub struct EventIterator<'a, E: BufferedEvent> {
|
||||
iter: EventIteratorWithId<'a, E>,
|
||||
}
|
||||
|
||||
impl<'a, E: Event> Iterator for EventIterator<'a, E> {
|
||||
impl<'a, E: BufferedEvent> Iterator for EventIterator<'a, E> {
|
||||
type Item = &'a E;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.iter.next().map(|(event, _)| event)
|
||||
@ -35,7 +35,7 @@ impl<'a, E: Event> Iterator for EventIterator<'a, E> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, E: Event> ExactSizeIterator for EventIterator<'a, E> {
|
||||
impl<'a, E: BufferedEvent> ExactSizeIterator for EventIterator<'a, E> {
|
||||
fn len(&self) -> usize {
|
||||
self.iter.len()
|
||||
}
|
||||
@ -43,13 +43,13 @@ impl<'a, E: Event> ExactSizeIterator for EventIterator<'a, E> {
|
||||
|
||||
/// An iterator that yields any unread events (and their IDs) from an [`EventReader`](super::EventReader) or [`EventCursor`].
|
||||
#[derive(Debug)]
|
||||
pub struct EventIteratorWithId<'a, E: Event> {
|
||||
pub struct EventIteratorWithId<'a, E: BufferedEvent> {
|
||||
reader: &'a mut EventCursor<E>,
|
||||
chain: Chain<Iter<'a, EventInstance<E>>, Iter<'a, EventInstance<E>>>,
|
||||
unread: usize,
|
||||
}
|
||||
|
||||
impl<'a, E: Event> EventIteratorWithId<'a, E> {
|
||||
impl<'a, E: BufferedEvent> EventIteratorWithId<'a, E> {
|
||||
/// Creates a new iterator that yields any `events` that have not yet been seen by `reader`.
|
||||
pub fn new(reader: &'a mut EventCursor<E>, events: &'a Events<E>) -> Self {
|
||||
let a_index = reader
|
||||
@ -81,7 +81,7 @@ impl<'a, E: Event> EventIteratorWithId<'a, E> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, E: Event> Iterator for EventIteratorWithId<'a, E> {
|
||||
impl<'a, E: BufferedEvent> Iterator for EventIteratorWithId<'a, E> {
|
||||
type Item = (&'a E, EventId<E>);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self
|
||||
@ -131,16 +131,16 @@ impl<'a, E: Event> Iterator for EventIteratorWithId<'a, E> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, E: Event> ExactSizeIterator for EventIteratorWithId<'a, E> {
|
||||
impl<'a, E: BufferedEvent> ExactSizeIterator for EventIteratorWithId<'a, E> {
|
||||
fn len(&self) -> usize {
|
||||
self.unread
|
||||
}
|
||||
}
|
||||
|
||||
/// A parallel iterator over `Event`s.
|
||||
/// A parallel iterator over `BufferedEvent`s.
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
#[derive(Debug)]
|
||||
pub struct EventParIter<'a, E: Event> {
|
||||
pub struct EventParIter<'a, E: BufferedEvent> {
|
||||
reader: &'a mut EventCursor<E>,
|
||||
slices: [&'a [EventInstance<E>]; 2],
|
||||
batching_strategy: BatchingStrategy,
|
||||
@ -149,7 +149,7 @@ pub struct EventParIter<'a, E: Event> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
impl<'a, E: Event> EventParIter<'a, E> {
|
||||
impl<'a, E: BufferedEvent> EventParIter<'a, E> {
|
||||
/// Creates a new parallel iterator over `events` that have not yet been seen by `reader`.
|
||||
pub fn new(reader: &'a mut EventCursor<E>, events: &'a Events<E>) -> Self {
|
||||
let a_index = reader
|
||||
@ -248,7 +248,7 @@ impl<'a, E: Event> EventParIter<'a, E> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of [`Event`]s to be iterated.
|
||||
/// Returns the number of [`BufferedEvent`]s to be iterated.
|
||||
pub fn len(&self) -> usize {
|
||||
self.slices.iter().map(|s| s.len()).sum()
|
||||
}
|
||||
@ -260,7 +260,7 @@ impl<'a, E: Event> EventParIter<'a, E> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
impl<'a, E: Event> IntoIterator for EventParIter<'a, E> {
|
||||
impl<'a, E: BufferedEvent> IntoIterator for EventParIter<'a, E> {
|
||||
type IntoIter = EventIteratorWithId<'a, E>;
|
||||
type Item = <Self::IntoIter as Iterator>::Item;
|
||||
|
||||
|
||||
@ -11,8 +11,8 @@ mod update;
|
||||
mod writer;
|
||||
|
||||
pub(crate) use base::EventInstance;
|
||||
pub use base::{Event, EventId};
|
||||
pub use bevy_ecs_macros::Event;
|
||||
pub use base::{BufferedEvent, EntityEvent, Event, EventId};
|
||||
pub use bevy_ecs_macros::{BufferedEvent, EntityEvent, Event};
|
||||
pub use collections::{Events, SendBatchIds};
|
||||
pub use event_cursor::EventCursor;
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
@ -38,17 +38,20 @@ pub use writer::EventWriter;
|
||||
mod tests {
|
||||
use alloc::{vec, vec::Vec};
|
||||
use bevy_ecs::{event::*, system::assert_is_read_only_system};
|
||||
use bevy_ecs_macros::Event;
|
||||
use bevy_ecs_macros::BufferedEvent;
|
||||
|
||||
#[derive(Event, Copy, Clone, PartialEq, Eq, Debug)]
|
||||
#[derive(Event, BufferedEvent, Copy, Clone, PartialEq, Eq, Debug)]
|
||||
struct TestEvent {
|
||||
i: usize,
|
||||
}
|
||||
|
||||
#[derive(Event, Clone, PartialEq, Debug, Default)]
|
||||
#[derive(Event, BufferedEvent, Clone, PartialEq, Debug, Default)]
|
||||
struct EmptyTestEvent;
|
||||
|
||||
fn get_events<E: Event + Clone>(events: &Events<E>, cursor: &mut EventCursor<E>) -> Vec<E> {
|
||||
fn get_events<E: BufferedEvent + Clone>(
|
||||
events: &Events<E>,
|
||||
cursor: &mut EventCursor<E>,
|
||||
) -> Vec<E> {
|
||||
cursor.read(events).cloned().collect::<Vec<E>>()
|
||||
}
|
||||
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
use bevy_ecs::batching::BatchingStrategy;
|
||||
use bevy_ecs::event::{Event, EventCursor, EventId, EventInstance, Events};
|
||||
use bevy_ecs::event::{BufferedEvent, EventCursor, EventId, EventInstance, Events};
|
||||
use core::{iter::Chain, slice::IterMut};
|
||||
|
||||
/// An iterator that yields any unread events from an [`EventMutator`] or [`EventCursor`].
|
||||
///
|
||||
/// [`EventMutator`]: super::EventMutator
|
||||
#[derive(Debug)]
|
||||
pub struct EventMutIterator<'a, E: Event> {
|
||||
pub struct EventMutIterator<'a, E: BufferedEvent> {
|
||||
iter: EventMutIteratorWithId<'a, E>,
|
||||
}
|
||||
|
||||
impl<'a, E: Event> Iterator for EventMutIterator<'a, E> {
|
||||
impl<'a, E: BufferedEvent> Iterator for EventMutIterator<'a, E> {
|
||||
type Item = &'a mut E;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.iter.next().map(|(event, _)| event)
|
||||
@ -37,7 +37,7 @@ impl<'a, E: Event> Iterator for EventMutIterator<'a, E> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, E: Event> ExactSizeIterator for EventMutIterator<'a, E> {
|
||||
impl<'a, E: BufferedEvent> ExactSizeIterator for EventMutIterator<'a, E> {
|
||||
fn len(&self) -> usize {
|
||||
self.iter.len()
|
||||
}
|
||||
@ -47,13 +47,13 @@ impl<'a, E: Event> ExactSizeIterator for EventMutIterator<'a, E> {
|
||||
///
|
||||
/// [`EventMutator`]: super::EventMutator
|
||||
#[derive(Debug)]
|
||||
pub struct EventMutIteratorWithId<'a, E: Event> {
|
||||
pub struct EventMutIteratorWithId<'a, E: BufferedEvent> {
|
||||
mutator: &'a mut EventCursor<E>,
|
||||
chain: Chain<IterMut<'a, EventInstance<E>>, IterMut<'a, EventInstance<E>>>,
|
||||
unread: usize,
|
||||
}
|
||||
|
||||
impl<'a, E: Event> EventMutIteratorWithId<'a, E> {
|
||||
impl<'a, E: BufferedEvent> EventMutIteratorWithId<'a, E> {
|
||||
/// Creates a new iterator that yields any `events` that have not yet been seen by `mutator`.
|
||||
pub fn new(mutator: &'a mut EventCursor<E>, events: &'a mut Events<E>) -> Self {
|
||||
let a_index = mutator
|
||||
@ -84,7 +84,7 @@ impl<'a, E: Event> EventMutIteratorWithId<'a, E> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, E: Event> Iterator for EventMutIteratorWithId<'a, E> {
|
||||
impl<'a, E: BufferedEvent> Iterator for EventMutIteratorWithId<'a, E> {
|
||||
type Item = (&'a mut E, EventId<E>);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self
|
||||
@ -134,16 +134,16 @@ impl<'a, E: Event> Iterator for EventMutIteratorWithId<'a, E> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, E: Event> ExactSizeIterator for EventMutIteratorWithId<'a, E> {
|
||||
impl<'a, E: BufferedEvent> ExactSizeIterator for EventMutIteratorWithId<'a, E> {
|
||||
fn len(&self) -> usize {
|
||||
self.unread
|
||||
}
|
||||
}
|
||||
|
||||
/// A parallel iterator over `Event`s.
|
||||
/// A parallel iterator over `BufferedEvent`s.
|
||||
#[derive(Debug)]
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
pub struct EventMutParIter<'a, E: Event> {
|
||||
pub struct EventMutParIter<'a, E: BufferedEvent> {
|
||||
mutator: &'a mut EventCursor<E>,
|
||||
slices: [&'a mut [EventInstance<E>]; 2],
|
||||
batching_strategy: BatchingStrategy,
|
||||
@ -152,7 +152,7 @@ pub struct EventMutParIter<'a, E: Event> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
impl<'a, E: Event> EventMutParIter<'a, E> {
|
||||
impl<'a, E: BufferedEvent> EventMutParIter<'a, E> {
|
||||
/// Creates a new parallel iterator over `events` that have not yet been seen by `mutator`.
|
||||
pub fn new(mutator: &'a mut EventCursor<E>, events: &'a mut Events<E>) -> Self {
|
||||
let a_index = mutator
|
||||
@ -251,7 +251,7 @@ impl<'a, E: Event> EventMutParIter<'a, E> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of [`Event`]s to be iterated.
|
||||
/// Returns the number of [`BufferedEvent`]s to be iterated.
|
||||
pub fn len(&self) -> usize {
|
||||
self.slices.iter().map(|s| s.len()).sum()
|
||||
}
|
||||
@ -263,7 +263,7 @@ impl<'a, E: Event> EventMutParIter<'a, E> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
impl<'a, E: Event> IntoIterator for EventMutParIter<'a, E> {
|
||||
impl<'a, E: BufferedEvent> IntoIterator for EventMutParIter<'a, E> {
|
||||
type IntoIter = EventMutIteratorWithId<'a, E>;
|
||||
type Item = <Self::IntoIter as Iterator>::Item;
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
use bevy_ecs::event::EventMutParIter;
|
||||
use bevy_ecs::{
|
||||
event::{Event, EventCursor, EventMutIterator, EventMutIteratorWithId, Events},
|
||||
event::{BufferedEvent, EventCursor, EventMutIterator, EventMutIteratorWithId, Events},
|
||||
system::{Local, ResMut, SystemParam},
|
||||
};
|
||||
|
||||
@ -15,7 +15,7 @@ use bevy_ecs::{
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
///
|
||||
/// #[derive(Event, Debug)]
|
||||
/// #[derive(Event, BufferedEvent, Debug)]
|
||||
/// pub struct MyEvent(pub u32); // Custom event type.
|
||||
/// fn my_system(mut reader: EventMutator<MyEvent>) {
|
||||
/// for event in reader.read() {
|
||||
@ -42,13 +42,13 @@ use bevy_ecs::{
|
||||
/// [`EventReader`]: super::EventReader
|
||||
/// [`EventWriter`]: super::EventWriter
|
||||
#[derive(SystemParam, Debug)]
|
||||
pub struct EventMutator<'w, 's, E: Event> {
|
||||
pub struct EventMutator<'w, 's, E: BufferedEvent> {
|
||||
pub(super) reader: Local<'s, EventCursor<E>>,
|
||||
#[system_param(validation_message = "Event not initialized")]
|
||||
#[system_param(validation_message = "BufferedEvent not initialized")]
|
||||
events: ResMut<'w, Events<E>>,
|
||||
}
|
||||
|
||||
impl<'w, 's, E: Event> EventMutator<'w, 's, E> {
|
||||
impl<'w, 's, E: BufferedEvent> EventMutator<'w, 's, E> {
|
||||
/// Iterates over the events this [`EventMutator`] has not seen yet. This updates the
|
||||
/// [`EventMutator`]'s event counter, which means subsequent event reads will not include events
|
||||
/// that happened before now.
|
||||
@ -69,7 +69,7 @@ impl<'w, 's, E: Event> EventMutator<'w, 's, E> {
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
///
|
||||
/// #[derive(Event)]
|
||||
/// #[derive(Event, BufferedEvent)]
|
||||
/// struct MyEvent {
|
||||
/// value: usize,
|
||||
/// }
|
||||
@ -116,7 +116,7 @@ impl<'w, 's, E: Event> EventMutator<'w, 's, E> {
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #
|
||||
/// #[derive(Event)]
|
||||
/// #[derive(Event, BufferedEvent)]
|
||||
/// struct CollisionEvent;
|
||||
///
|
||||
/// fn play_collision_sound(mut events: EventMutator<CollisionEvent>) {
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
use bevy_ecs::event::EventParIter;
|
||||
use bevy_ecs::{
|
||||
event::{Event, EventCursor, EventIterator, EventIteratorWithId, Events},
|
||||
event::{BufferedEvent, EventCursor, EventIterator, EventIteratorWithId, Events},
|
||||
system::{Local, Res, SystemParam},
|
||||
};
|
||||
|
||||
/// Reads events of type `T` in order and tracks which events have already been read.
|
||||
/// Reads [`BufferedEvent`]s of type `T` in order and tracks which events have already been read.
|
||||
///
|
||||
/// # Concurrency
|
||||
///
|
||||
@ -14,13 +14,13 @@ use bevy_ecs::{
|
||||
///
|
||||
/// [`EventWriter<T>`]: super::EventWriter
|
||||
#[derive(SystemParam, Debug)]
|
||||
pub struct EventReader<'w, 's, E: Event> {
|
||||
pub struct EventReader<'w, 's, E: BufferedEvent> {
|
||||
pub(super) reader: Local<'s, EventCursor<E>>,
|
||||
#[system_param(validation_message = "Event not initialized")]
|
||||
#[system_param(validation_message = "BufferedEvent not initialized")]
|
||||
events: Res<'w, Events<E>>,
|
||||
}
|
||||
|
||||
impl<'w, 's, E: Event> EventReader<'w, 's, E> {
|
||||
impl<'w, 's, E: BufferedEvent> EventReader<'w, 's, E> {
|
||||
/// Iterates over the events this [`EventReader`] has not seen yet. This updates the
|
||||
/// [`EventReader`]'s event counter, which means subsequent event reads will not include events
|
||||
/// that happened before now.
|
||||
@ -41,7 +41,7 @@ impl<'w, 's, E: Event> EventReader<'w, 's, E> {
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
///
|
||||
/// #[derive(Event)]
|
||||
/// #[derive(Event, BufferedEvent)]
|
||||
/// struct MyEvent {
|
||||
/// value: usize,
|
||||
/// }
|
||||
@ -88,7 +88,7 @@ impl<'w, 's, E: Event> EventReader<'w, 's, E> {
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #
|
||||
/// #[derive(Event)]
|
||||
/// #[derive(Event, BufferedEvent)]
|
||||
/// struct CollisionEvent;
|
||||
///
|
||||
/// fn play_collision_sound(mut events: EventReader<CollisionEvent>) {
|
||||
|
||||
@ -2,7 +2,7 @@ use alloc::vec::Vec;
|
||||
use bevy_ecs::{
|
||||
change_detection::{DetectChangesMut, MutUntyped},
|
||||
component::{ComponentId, Tick},
|
||||
event::{Event, Events},
|
||||
event::{BufferedEvent, Events},
|
||||
resource::Resource,
|
||||
world::World,
|
||||
};
|
||||
@ -45,7 +45,7 @@ impl EventRegistry {
|
||||
///
|
||||
/// If no instance of the [`EventRegistry`] exists in the world, this will add one - otherwise it will use
|
||||
/// the existing instance.
|
||||
pub fn register_event<T: Event>(world: &mut World) {
|
||||
pub fn register_event<T: BufferedEvent>(world: &mut World) {
|
||||
// By initializing the resource here, we can be sure that it is present,
|
||||
// and receive the correct, up-to-date `ComponentId` even if it was previously removed.
|
||||
let component_id = world.init_resource::<Events<T>>();
|
||||
@ -82,7 +82,7 @@ impl EventRegistry {
|
||||
}
|
||||
|
||||
/// Removes an event from the world and its associated [`EventRegistry`].
|
||||
pub fn deregister_events<T: Event>(world: &mut World) {
|
||||
pub fn deregister_events<T: BufferedEvent>(world: &mut World) {
|
||||
let component_id = world.init_resource::<Events<T>>();
|
||||
let mut registry = world.get_resource_or_init::<Self>();
|
||||
registry
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
use bevy_ecs::{
|
||||
event::{Event, EventId, Events, SendBatchIds},
|
||||
event::{BufferedEvent, EventId, Events, SendBatchIds},
|
||||
system::{ResMut, SystemParam},
|
||||
};
|
||||
|
||||
/// Sends events of type `T`.
|
||||
/// Sends [`BufferedEvent`]s of type `T`.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
@ -11,7 +11,7 @@ use bevy_ecs::{
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
///
|
||||
/// #[derive(Event)]
|
||||
/// #[derive(Event, BufferedEvent)]
|
||||
/// pub struct MyEvent; // Custom event type.
|
||||
/// fn my_system(mut writer: EventWriter<MyEvent>) {
|
||||
/// writer.write(MyEvent);
|
||||
@ -21,8 +21,8 @@ use bevy_ecs::{
|
||||
/// ```
|
||||
/// # Observers
|
||||
///
|
||||
/// "Buffered" Events, such as those sent directly in [`Events`] or written using [`EventWriter`], do _not_ automatically
|
||||
/// trigger any [`Observer`]s watching for that event, as each [`Event`] has different requirements regarding _if_ it will
|
||||
/// "Buffered" events, such as those sent directly in [`Events`] or written using [`EventWriter`], do _not_ automatically
|
||||
/// trigger any [`Observer`]s watching for that event, as each [`BufferedEvent`] has different requirements regarding _if_ it will
|
||||
/// be triggered, and if so, _when_ it will be triggered in the schedule.
|
||||
///
|
||||
/// # Concurrency
|
||||
@ -38,7 +38,7 @@ use bevy_ecs::{
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::{prelude::*, event::Events};
|
||||
/// # #[derive(Event)]
|
||||
/// # #[derive(Event, BufferedEvent)]
|
||||
/// # pub struct MyEvent;
|
||||
/// fn send_untyped(mut commands: Commands) {
|
||||
/// // Send an event of a specific type without having to declare that
|
||||
@ -59,12 +59,12 @@ use bevy_ecs::{
|
||||
///
|
||||
/// [`Observer`]: crate::observer::Observer
|
||||
#[derive(SystemParam)]
|
||||
pub struct EventWriter<'w, E: Event> {
|
||||
#[system_param(validation_message = "Event not initialized")]
|
||||
pub struct EventWriter<'w, E: BufferedEvent> {
|
||||
#[system_param(validation_message = "BufferedEvent not initialized")]
|
||||
events: ResMut<'w, Events<E>>,
|
||||
}
|
||||
|
||||
impl<'w, E: Event> EventWriter<'w, E> {
|
||||
impl<'w, E: BufferedEvent> EventWriter<'w, E> {
|
||||
/// Writes an `event`, which can later be read by [`EventReader`](super::EventReader)s.
|
||||
/// This method returns the [ID](`EventId`) of the written `event`.
|
||||
///
|
||||
|
||||
@ -60,7 +60,7 @@ pub mod world;
|
||||
pub use bevy_ptr as ptr;
|
||||
|
||||
#[cfg(feature = "hotpatching")]
|
||||
use event::Event;
|
||||
use event::{BufferedEvent, Event};
|
||||
|
||||
/// The ECS prelude.
|
||||
///
|
||||
@ -78,7 +78,9 @@ pub mod prelude {
|
||||
component::Component,
|
||||
entity::{ContainsEntity, Entity, EntityMapper},
|
||||
error::{BevyError, Result},
|
||||
event::{Event, EventMutator, EventReader, EventWriter, Events},
|
||||
event::{
|
||||
BufferedEvent, EntityEvent, Event, EventMutator, EventReader, EventWriter, Events,
|
||||
},
|
||||
hierarchy::{ChildOf, ChildSpawner, ChildSpawnerCommands, Children},
|
||||
lifecycle::{
|
||||
Add, Despawn, Insert, OnAdd, OnDespawn, OnInsert, OnRemove, OnReplace, Remove,
|
||||
@ -137,7 +139,7 @@ pub mod __macro_exports {
|
||||
///
|
||||
/// Systems should refresh their inner pointers.
|
||||
#[cfg(feature = "hotpatching")]
|
||||
#[derive(Event, Default)]
|
||||
#[derive(Event, BufferedEvent, Default)]
|
||||
pub struct HotPatched;
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@ -53,7 +53,10 @@ use crate::{
|
||||
change_detection::MaybeLocation,
|
||||
component::{Component, ComponentId, ComponentIdFor, Tick},
|
||||
entity::Entity,
|
||||
event::{Event, EventCursor, EventId, EventIterator, EventIteratorWithId, Events},
|
||||
event::{
|
||||
BufferedEvent, EntityEvent, Event, EventCursor, EventId, EventIterator,
|
||||
EventIteratorWithId, Events,
|
||||
},
|
||||
query::FilteredAccessSet,
|
||||
relationship::RelationshipHookMode,
|
||||
storage::SparseSet,
|
||||
@ -325,7 +328,7 @@ pub const DESPAWN: ComponentId = ComponentId::new(4);
|
||||
/// Trigger emitted when a component is inserted onto an entity that does not already have that
|
||||
/// component. Runs before `Insert`.
|
||||
/// See [`crate::lifecycle::ComponentHooks::on_add`] for more information.
|
||||
#[derive(Event, Debug)]
|
||||
#[derive(Event, EntityEvent, Debug)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
|
||||
#[doc(alias = "OnAdd")]
|
||||
@ -334,7 +337,7 @@ pub struct Add;
|
||||
/// Trigger emitted when a component is inserted, regardless of whether or not the entity already
|
||||
/// had that component. Runs after `Add`, if it ran.
|
||||
/// See [`crate::lifecycle::ComponentHooks::on_insert`] for more information.
|
||||
#[derive(Event, Debug)]
|
||||
#[derive(Event, EntityEvent, Debug)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
|
||||
#[doc(alias = "OnInsert")]
|
||||
@ -345,7 +348,7 @@ pub struct Insert;
|
||||
///
|
||||
/// Runs before the value is replaced, so you can still access the original component data.
|
||||
/// See [`crate::lifecycle::ComponentHooks::on_replace`] for more information.
|
||||
#[derive(Event, Debug)]
|
||||
#[derive(Event, EntityEvent, Debug)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
|
||||
#[doc(alias = "OnReplace")]
|
||||
@ -354,7 +357,7 @@ pub struct Replace;
|
||||
/// Trigger emitted when a component is removed from an entity, and runs before the component is
|
||||
/// removed, so you can still access the component data.
|
||||
/// See [`crate::lifecycle::ComponentHooks::on_remove`] for more information.
|
||||
#[derive(Event, Debug)]
|
||||
#[derive(Event, EntityEvent, Debug)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
|
||||
#[doc(alias = "OnRemove")]
|
||||
@ -362,7 +365,7 @@ pub struct Remove;
|
||||
|
||||
/// Trigger emitted for each component on an entity when it is despawned.
|
||||
/// See [`crate::lifecycle::ComponentHooks::on_despawn`] for more information.
|
||||
#[derive(Event, Debug)]
|
||||
#[derive(Event, EntityEvent, Debug)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
|
||||
#[doc(alias = "OnDespawn")]
|
||||
@ -390,7 +393,7 @@ pub type OnDespawn = Despawn;
|
||||
|
||||
/// Wrapper around [`Entity`] for [`RemovedComponents`].
|
||||
/// Internally, `RemovedComponents` uses these as an `Events<RemovedComponentEntity>`.
|
||||
#[derive(Event, Debug, Clone, Into)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Into)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(Debug, Clone))]
|
||||
pub struct RemovedComponentEntity(Entity);
|
||||
|
||||
@ -121,14 +121,18 @@ fn component_clone_observed_by(_source: &SourceComponent, ctx: &mut ComponentClo
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
entity::EntityCloner, event::Event, observer::On, resource::Resource, system::ResMut,
|
||||
entity::EntityCloner,
|
||||
event::{EntityEvent, Event},
|
||||
observer::On,
|
||||
resource::Resource,
|
||||
system::ResMut,
|
||||
world::World,
|
||||
};
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
struct Num(usize);
|
||||
|
||||
#[derive(Event)]
|
||||
#[derive(Event, EntityEvent)]
|
||||
struct E;
|
||||
|
||||
#[test]
|
||||
|
||||
@ -54,7 +54,7 @@
|
||||
//! ## Triggering observers
|
||||
//!
|
||||
//! Observers are most commonly triggered by [`Commands`],
|
||||
//! via [`Commands::trigger`] (for untargeted events) or [`Commands::trigger_targets`] (for targeted events).
|
||||
//! via [`Commands::trigger`] (for untargeted [`Event`]s) or [`Commands::trigger_targets`] (for targeted [`EntityEvent`]s).
|
||||
//! Like usual, equivalent methods are available on [`World`], allowing you to reduce overhead when working with exclusive world access.
|
||||
//!
|
||||
//! If your observer is configured to watch for a specific component or set of components instead,
|
||||
@ -64,15 +64,14 @@
|
||||
//!
|
||||
//! ## Observer bubbling
|
||||
//!
|
||||
//! When events are targeted at an entity, they can optionally bubble to other targets,
|
||||
//! When using an [`EntityEvent`] targeted at an entity, the event can optionally be propagated to other targets,
|
||||
//! typically up to parents in an entity hierarchy.
|
||||
//!
|
||||
//! This behavior is controlled via [`Event::Traversal`] and [`Event::AUTO_PROPAGATE`],
|
||||
//! This behavior is controlled via [`EntityEvent::Traversal`] and [`EntityEvent::AUTO_PROPAGATE`],
|
||||
//! with the details of the propagation path specified by the [`Traversal`](crate::traversal::Traversal) trait.
|
||||
//!
|
||||
//! When auto-propagation is enabled, propagaion must be manually stopped to prevent the event from
|
||||
//! continuing to other targets.
|
||||
//! This can be done using the [`On::propagate`] method inside of your observer.
|
||||
//! When auto-propagation is enabled, propagation must be manually stopped to prevent the event from
|
||||
//! continuing to other targets. This can be done using the [`On::propagate`] method inside of your observer.
|
||||
//!
|
||||
//! ## Observer timing
|
||||
//!
|
||||
@ -212,12 +211,6 @@ impl<'w, E, B: Bundle> On<'w, E, B> {
|
||||
Ptr::from(&self.event)
|
||||
}
|
||||
|
||||
/// Returns the [`Entity`] that was targeted by the `event` that triggered this observer. It may
|
||||
/// be [`None`] if the trigger is not for a particular entity.
|
||||
pub fn target(&self) -> Option<Entity> {
|
||||
self.trigger.target
|
||||
}
|
||||
|
||||
/// Returns the components that triggered the observer, out of the
|
||||
/// components defined in `B`. Does not necessarily include all of them as
|
||||
/// `B` acts like an `OR` filter rather than an `AND` filter.
|
||||
@ -251,14 +244,28 @@ impl<'w, E, B: Bundle> On<'w, E, B> {
|
||||
self.trigger.observer
|
||||
}
|
||||
|
||||
/// Returns the source code location that triggered this observer.
|
||||
pub fn caller(&self) -> MaybeLocation {
|
||||
self.trigger.caller
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w, E: EntityEvent, B: Bundle> On<'w, E, B> {
|
||||
/// Returns the [`Entity`] that was targeted by the `event` that triggered this observer.
|
||||
///
|
||||
/// If the event was not targeted at a specific entity, this will return [`Entity::PLACEHOLDER`].
|
||||
pub fn target(&self) -> Entity {
|
||||
self.trigger.target.unwrap_or(Entity::PLACEHOLDER)
|
||||
}
|
||||
|
||||
/// Enables or disables event propagation, allowing the same event to trigger observers on a chain of different entities.
|
||||
///
|
||||
/// The path an event will propagate along is specified by its associated [`Traversal`] component. By default, events
|
||||
/// use `()` which ends the path immediately and prevents propagation.
|
||||
///
|
||||
/// To enable propagation, you must:
|
||||
/// + Set [`Event::Traversal`] to the component you want to propagate along.
|
||||
/// + Either call `propagate(true)` in the first observer or set [`Event::AUTO_PROPAGATE`] to `true`.
|
||||
/// + Set [`EntityEvent::Traversal`] to the component you want to propagate along.
|
||||
/// + Either call `propagate(true)` in the first observer or set [`EntityEvent::AUTO_PROPAGATE`] to `true`.
|
||||
///
|
||||
/// You can prevent an event from propagating further using `propagate(false)`.
|
||||
///
|
||||
@ -273,11 +280,6 @@ impl<'w, E, B: Bundle> On<'w, E, B> {
|
||||
pub fn get_propagate(&self) -> bool {
|
||||
*self.propagate
|
||||
}
|
||||
|
||||
/// Returns the source code location that triggered this observer.
|
||||
pub fn caller(&self) -> MaybeLocation {
|
||||
self.trigger.caller
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w, E: Debug, B: Bundle> Debug for On<'w, E, B> {
|
||||
@ -723,7 +725,7 @@ impl World {
|
||||
let event_id = E::register_component_id(self);
|
||||
// SAFETY: We just registered `event_id` with the type of `event`
|
||||
unsafe {
|
||||
self.trigger_targets_dynamic_ref_with_caller(event_id, &mut event, (), caller);
|
||||
self.trigger_dynamic_ref_with_caller(event_id, &mut event, caller);
|
||||
}
|
||||
}
|
||||
|
||||
@ -735,20 +737,40 @@ impl World {
|
||||
pub fn trigger_ref<E: Event>(&mut self, event: &mut E) {
|
||||
let event_id = E::register_component_id(self);
|
||||
// SAFETY: We just registered `event_id` with the type of `event`
|
||||
unsafe { self.trigger_targets_dynamic_ref(event_id, event, ()) };
|
||||
unsafe { self.trigger_dynamic_ref_with_caller(event_id, event, MaybeLocation::caller()) };
|
||||
}
|
||||
|
||||
/// Triggers the given [`Event`] for the given `targets`, which will run any [`Observer`]s watching for it.
|
||||
unsafe fn trigger_dynamic_ref_with_caller<E: Event>(
|
||||
&mut self,
|
||||
event_id: ComponentId,
|
||||
event_data: &mut E,
|
||||
caller: MaybeLocation,
|
||||
) {
|
||||
let mut world = DeferredWorld::from(self);
|
||||
// SAFETY: `event_data` is accessible as the type represented by `event_id`
|
||||
unsafe {
|
||||
world.trigger_observers_with_data::<_, ()>(
|
||||
event_id,
|
||||
None,
|
||||
core::iter::empty::<ComponentId>(),
|
||||
event_data,
|
||||
false,
|
||||
caller,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/// Triggers the given [`EntityEvent`] for the given `targets`, which will run any [`Observer`]s watching for it.
|
||||
///
|
||||
/// While event types commonly implement [`Copy`],
|
||||
/// those that don't will be consumed and will no longer be accessible.
|
||||
/// If you need to use the event after triggering it, use [`World::trigger_targets_ref`] instead.
|
||||
#[track_caller]
|
||||
pub fn trigger_targets<E: Event>(&mut self, event: E, targets: impl TriggerTargets) {
|
||||
pub fn trigger_targets<E: EntityEvent>(&mut self, event: E, targets: impl TriggerTargets) {
|
||||
self.trigger_targets_with_caller(event, targets, MaybeLocation::caller());
|
||||
}
|
||||
|
||||
pub(crate) fn trigger_targets_with_caller<E: Event>(
|
||||
pub(crate) fn trigger_targets_with_caller<E: EntityEvent>(
|
||||
&mut self,
|
||||
mut event: E,
|
||||
targets: impl TriggerTargets,
|
||||
@ -761,19 +783,23 @@ impl World {
|
||||
}
|
||||
}
|
||||
|
||||
/// Triggers the given [`Event`] as a mutable reference for the given `targets`,
|
||||
/// Triggers the given [`EntityEvent`] as a mutable reference for the given `targets`,
|
||||
/// which will run any [`Observer`]s watching for it.
|
||||
///
|
||||
/// Compared to [`World::trigger_targets`], this method is most useful when it's necessary to check
|
||||
/// or use the event after it has been modified by observers.
|
||||
#[track_caller]
|
||||
pub fn trigger_targets_ref<E: Event>(&mut self, event: &mut E, targets: impl TriggerTargets) {
|
||||
pub fn trigger_targets_ref<E: EntityEvent>(
|
||||
&mut self,
|
||||
event: &mut E,
|
||||
targets: impl TriggerTargets,
|
||||
) {
|
||||
let event_id = E::register_component_id(self);
|
||||
// SAFETY: We just registered `event_id` with the type of `event`
|
||||
unsafe { self.trigger_targets_dynamic_ref(event_id, event, targets) };
|
||||
}
|
||||
|
||||
/// Triggers the given [`Event`] for the given `targets`, which will run any [`Observer`]s watching for it.
|
||||
/// Triggers the given [`EntityEvent`] for the given `targets`, which will run any [`Observer`]s watching for it.
|
||||
///
|
||||
/// While event types commonly implement [`Copy`],
|
||||
/// those that don't will be consumed and will no longer be accessible.
|
||||
@ -783,7 +809,7 @@ impl World {
|
||||
///
|
||||
/// Caller must ensure that `event_data` is accessible as the type represented by `event_id`.
|
||||
#[track_caller]
|
||||
pub unsafe fn trigger_targets_dynamic<E: Event, Targets: TriggerTargets>(
|
||||
pub unsafe fn trigger_targets_dynamic<E: EntityEvent, Targets: TriggerTargets>(
|
||||
&mut self,
|
||||
event_id: ComponentId,
|
||||
mut event_data: E,
|
||||
@ -795,7 +821,7 @@ impl World {
|
||||
};
|
||||
}
|
||||
|
||||
/// Triggers the given [`Event`] as a mutable reference for the given `targets`,
|
||||
/// Triggers the given [`EntityEvent`] as a mutable reference for the given `targets`,
|
||||
/// which will run any [`Observer`]s watching for it.
|
||||
///
|
||||
/// Compared to [`World::trigger_targets_dynamic`], this method is most useful when it's necessary to check
|
||||
@ -805,7 +831,7 @@ impl World {
|
||||
///
|
||||
/// Caller must ensure that `event_data` is accessible as the type represented by `event_id`.
|
||||
#[track_caller]
|
||||
pub unsafe fn trigger_targets_dynamic_ref<E: Event, Targets: TriggerTargets>(
|
||||
pub unsafe fn trigger_targets_dynamic_ref<E: EntityEvent, Targets: TriggerTargets>(
|
||||
&mut self,
|
||||
event_id: ComponentId,
|
||||
event_data: &mut E,
|
||||
@ -822,7 +848,7 @@ impl World {
|
||||
/// # Safety
|
||||
///
|
||||
/// See `trigger_targets_dynamic_ref`
|
||||
unsafe fn trigger_targets_dynamic_ref_with_caller<E: Event, Targets: TriggerTargets>(
|
||||
unsafe fn trigger_targets_dynamic_ref_with_caller<E: EntityEvent, Targets: TriggerTargets>(
|
||||
&mut self,
|
||||
event_id: ComponentId,
|
||||
event_data: &mut E,
|
||||
@ -1006,10 +1032,10 @@ mod tests {
|
||||
#[component(storage = "SparseSet")]
|
||||
struct S;
|
||||
|
||||
#[derive(Event)]
|
||||
#[derive(Event, EntityEvent)]
|
||||
struct EventA;
|
||||
|
||||
#[derive(Event)]
|
||||
#[derive(Event, EntityEvent)]
|
||||
struct EventWithData {
|
||||
counter: usize,
|
||||
}
|
||||
@ -1033,8 +1059,8 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Event)]
|
||||
#[event(traversal = &'static ChildOf, auto_propagate)]
|
||||
#[derive(Component, Event, EntityEvent)]
|
||||
#[entity_event(traversal = &'static ChildOf, auto_propagate)]
|
||||
struct EventPropagating;
|
||||
|
||||
#[test]
|
||||
@ -1132,20 +1158,20 @@ mod tests {
|
||||
world.add_observer(
|
||||
|obs: On<Add, A>, mut res: ResMut<Order>, mut commands: Commands| {
|
||||
res.observed("add_a");
|
||||
commands.entity(obs.target().unwrap()).insert(B);
|
||||
commands.entity(obs.target()).insert(B);
|
||||
},
|
||||
);
|
||||
world.add_observer(
|
||||
|obs: On<Remove, A>, mut res: ResMut<Order>, mut commands: Commands| {
|
||||
res.observed("remove_a");
|
||||
commands.entity(obs.target().unwrap()).remove::<B>();
|
||||
commands.entity(obs.target()).remove::<B>();
|
||||
},
|
||||
);
|
||||
|
||||
world.add_observer(
|
||||
|obs: On<Add, B>, mut res: ResMut<Order>, mut commands: Commands| {
|
||||
res.observed("add_b");
|
||||
commands.entity(obs.target().unwrap()).remove::<A>();
|
||||
commands.entity(obs.target()).remove::<A>();
|
||||
},
|
||||
);
|
||||
world.add_observer(|_: On<Remove, B>, mut res: ResMut<Order>| {
|
||||
@ -1314,7 +1340,7 @@ mod tests {
|
||||
};
|
||||
world.spawn_empty().observe(system);
|
||||
world.add_observer(move |obs: On<EventA>, mut res: ResMut<Order>| {
|
||||
assert_eq!(obs.target(), None);
|
||||
assert_eq!(obs.target(), Entity::PLACEHOLDER);
|
||||
res.observed("event_a");
|
||||
});
|
||||
|
||||
@ -1341,7 +1367,7 @@ mod tests {
|
||||
.observe(|_: On<EventA>, mut res: ResMut<Order>| res.observed("a_1"))
|
||||
.id();
|
||||
world.add_observer(move |obs: On<EventA>, mut res: ResMut<Order>| {
|
||||
assert_eq!(obs.target().unwrap(), entity);
|
||||
assert_eq!(obs.target(), entity);
|
||||
res.observed("a_2");
|
||||
});
|
||||
|
||||
@ -1761,7 +1787,7 @@ mod tests {
|
||||
|
||||
world.add_observer(
|
||||
|trigger: On<EventPropagating>, query: Query<&A>, mut res: ResMut<Order>| {
|
||||
if query.get(trigger.target().unwrap()).is_ok() {
|
||||
if query.get(trigger.target()).is_ok() {
|
||||
res.observed("event");
|
||||
}
|
||||
},
|
||||
@ -1784,7 +1810,7 @@ mod tests {
|
||||
fn observer_modifies_relationship() {
|
||||
fn on_add(trigger: On<Add, A>, mut commands: Commands) {
|
||||
commands
|
||||
.entity(trigger.target().unwrap())
|
||||
.entity(trigger.target())
|
||||
.with_related_entities::<crate::hierarchy::ChildOf>(|rsc| {
|
||||
rsc.spawn_empty();
|
||||
});
|
||||
|
||||
@ -21,10 +21,12 @@ pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate:
|
||||
|
||||
/// 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`].
|
||||
/// Observers listen for a "trigger" of a specific [`Event`]. An event can be triggered on the [`World`]
|
||||
/// by calling [`World::trigger`], or if the event is an [`EntityEvent`], it can also be triggered for specific
|
||||
/// entity targets using [`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.
|
||||
/// Note that [`BufferedEvent`]s sent using [`EventReader`] and [`EventWriter`] are _not_ automatically triggered.
|
||||
/// They must be triggered at a specific point in the schedule.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
@ -113,18 +115,19 @@ pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate:
|
||||
/// 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`]:
|
||||
/// If the event is an [`EntityEvent`], it can be triggered for specific 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)]
|
||||
/// #[derive(Event, EntityEvent)]
|
||||
/// struct Explode;
|
||||
///
|
||||
/// world.add_observer(|trigger: On<Explode>, mut commands: Commands| {
|
||||
/// println!("Entity {} goes BOOM!", trigger.target().unwrap());
|
||||
/// commands.entity(trigger.target().unwrap()).despawn();
|
||||
/// println!("Entity {} goes BOOM!", trigger.target());
|
||||
/// commands.entity(trigger.target()).despawn();
|
||||
/// });
|
||||
///
|
||||
/// world.flush();
|
||||
@ -139,7 +142,7 @@ pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate:
|
||||
/// # let mut world = World::default();
|
||||
/// # let e1 = world.spawn_empty().id();
|
||||
/// # let e2 = world.spawn_empty().id();
|
||||
/// # #[derive(Event)]
|
||||
/// # #[derive(Event, EntityEvent)]
|
||||
/// # struct Explode;
|
||||
/// world.trigger_targets(Explode, [e1, e2]);
|
||||
/// ```
|
||||
@ -153,11 +156,11 @@ pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate:
|
||||
/// # let mut world = World::default();
|
||||
/// # let e1 = world.spawn_empty().id();
|
||||
/// # let e2 = world.spawn_empty().id();
|
||||
/// # #[derive(Event)]
|
||||
/// # #[derive(Event, EntityEvent)]
|
||||
/// # struct Explode;
|
||||
/// world.entity_mut(e1).observe(|trigger: On<Explode>, mut commands: Commands| {
|
||||
/// println!("Boom!");
|
||||
/// commands.entity(trigger.target().unwrap()).despawn();
|
||||
/// commands.entity(trigger.target()).despawn();
|
||||
/// });
|
||||
///
|
||||
/// world.entity_mut(e2).observe(|trigger: On<Explode>, mut commands: Commands| {
|
||||
@ -175,7 +178,7 @@ pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate:
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # let mut world = World::default();
|
||||
/// # let entity = world.spawn_empty().id();
|
||||
/// # #[derive(Event)]
|
||||
/// # #[derive(Event, EntityEvent)]
|
||||
/// # struct Explode;
|
||||
/// let mut observer = Observer::new(|trigger: On<Explode>| {});
|
||||
/// observer.watch_entity(entity);
|
||||
|
||||
@ -466,7 +466,7 @@ pub mod common_conditions {
|
||||
use super::{NotSystem, SystemCondition};
|
||||
use crate::{
|
||||
change_detection::DetectChanges,
|
||||
event::{Event, EventReader},
|
||||
event::{BufferedEvent, EventReader},
|
||||
lifecycle::RemovedComponents,
|
||||
prelude::{Component, Query, With},
|
||||
query::QueryFilter,
|
||||
@ -928,7 +928,7 @@ pub mod common_conditions {
|
||||
/// my_system.run_if(on_event::<MyEvent>),
|
||||
/// );
|
||||
///
|
||||
/// #[derive(Event)]
|
||||
/// #[derive(Event, BufferedEvent)]
|
||||
/// struct MyEvent;
|
||||
///
|
||||
/// fn my_system(mut counter: ResMut<Counter>) {
|
||||
@ -945,7 +945,7 @@ pub mod common_conditions {
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 1);
|
||||
/// ```
|
||||
pub fn on_event<T: Event>(mut reader: EventReader<T>) -> bool {
|
||||
pub fn on_event<T: BufferedEvent>(mut reader: EventReader<T>) -> bool {
|
||||
// The events need to be consumed, so that there are no false positives on subsequent
|
||||
// calls of the run condition. Simply checking `is_empty` would not be enough.
|
||||
// PERF: note that `count` is efficient (not actually looping/iterating),
|
||||
@ -1328,6 +1328,7 @@ where
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{common_conditions::*, SystemCondition};
|
||||
use crate::event::{BufferedEvent, Event};
|
||||
use crate::query::With;
|
||||
use crate::{
|
||||
change_detection::ResMut,
|
||||
@ -1336,7 +1337,7 @@ mod tests {
|
||||
system::Local,
|
||||
world::World,
|
||||
};
|
||||
use bevy_ecs_macros::{Event, Resource};
|
||||
use bevy_ecs_macros::Resource;
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
struct Counter(usize);
|
||||
@ -1447,7 +1448,7 @@ mod tests {
|
||||
#[derive(Component)]
|
||||
struct TestComponent;
|
||||
|
||||
#[derive(Event)]
|
||||
#[derive(Event, BufferedEvent)]
|
||||
struct TestEvent;
|
||||
|
||||
#[derive(Resource)]
|
||||
|
||||
@ -784,8 +784,7 @@ mod tests {
|
||||
#[derive(Component)]
|
||||
struct B;
|
||||
|
||||
// An event type
|
||||
#[derive(Event)]
|
||||
#[derive(Event, BufferedEvent)]
|
||||
struct E;
|
||||
|
||||
#[derive(Resource, Component)]
|
||||
|
||||
@ -9,7 +9,7 @@ use crate::{
|
||||
change_detection::MaybeLocation,
|
||||
entity::Entity,
|
||||
error::Result,
|
||||
event::{Event, Events},
|
||||
event::{BufferedEvent, EntityEvent, Event, Events},
|
||||
observer::TriggerTargets,
|
||||
resource::Resource,
|
||||
schedule::ScheduleLabel,
|
||||
@ -208,9 +208,7 @@ pub fn run_schedule(label: impl ScheduleLabel) -> impl Command<Result> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Command`] that sends a global [observer] [`Event`] without any targets.
|
||||
///
|
||||
/// [observer]: crate::observer::Observer
|
||||
/// A [`Command`] that sends a global [`Event`] without any targets.
|
||||
#[track_caller]
|
||||
pub fn trigger(event: impl Event) -> impl Command {
|
||||
let caller = MaybeLocation::caller();
|
||||
@ -219,11 +217,9 @@ pub fn trigger(event: impl Event) -> impl Command {
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Command`] that sends an [observer] [`Event`] for the given targets.
|
||||
///
|
||||
/// [observer]: crate::observer::Observer
|
||||
/// A [`Command`] that sends an [`EntityEvent`] for the given targets.
|
||||
pub fn trigger_targets(
|
||||
event: impl Event,
|
||||
event: impl EntityEvent,
|
||||
targets: impl TriggerTargets + Send + Sync + 'static,
|
||||
) -> impl Command {
|
||||
let caller = MaybeLocation::caller();
|
||||
@ -232,9 +228,9 @@ pub fn trigger_targets(
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Command`] that sends an arbitrary [`Event`].
|
||||
/// A [`Command`] that sends an arbitrary [`BufferedEvent`].
|
||||
#[track_caller]
|
||||
pub fn send_event<E: Event>(event: E) -> impl Command {
|
||||
pub fn send_event<E: BufferedEvent>(event: E) -> impl Command {
|
||||
let caller = MaybeLocation::caller();
|
||||
move |world: &mut World| {
|
||||
let mut events = world.resource_mut::<Events<E>>();
|
||||
|
||||
@ -12,7 +12,7 @@ use crate::{
|
||||
change_detection::MaybeLocation,
|
||||
component::{Component, ComponentId, ComponentInfo},
|
||||
entity::{Entity, EntityClonerBuilder},
|
||||
event::Event,
|
||||
event::EntityEvent,
|
||||
relationship::RelationshipHookMode,
|
||||
system::IntoObserverSystem,
|
||||
world::{error::EntityMutableFetchError, EntityWorldMut, FromWorld},
|
||||
@ -218,7 +218,7 @@ pub fn despawn() -> impl EntityCommand {
|
||||
/// An [`EntityCommand`] that creates an [`Observer`](crate::observer::Observer)
|
||||
/// listening for events of type `E` targeting an entity
|
||||
#[track_caller]
|
||||
pub fn observe<E: Event, B: Bundle, M>(
|
||||
pub fn observe<E: EntityEvent, B: Bundle, M>(
|
||||
observer: impl IntoObserverSystem<E, B, M>,
|
||||
) -> impl EntityCommand {
|
||||
let caller = MaybeLocation::caller();
|
||||
@ -227,11 +227,11 @@ pub fn observe<E: Event, B: Bundle, M>(
|
||||
}
|
||||
}
|
||||
|
||||
/// An [`EntityCommand`] that sends an [observer](crate::observer::Observer) [`Event`] targeting an entity.
|
||||
/// An [`EntityCommand`] that sends an [`EntityEvent`] targeting an entity.
|
||||
///
|
||||
/// This will run any [`Observer`](crate::observer::Observer) of the given [`Event`] watching the entity.
|
||||
/// This will run any [`Observer`](crate::observer::Observer) of the given [`EntityEvent`] watching the entity.
|
||||
#[track_caller]
|
||||
pub fn trigger(event: impl Event) -> impl EntityCommand {
|
||||
pub fn trigger(event: impl EntityEvent) -> impl EntityCommand {
|
||||
let caller = MaybeLocation::caller();
|
||||
move |mut entity: EntityWorldMut| {
|
||||
let id = entity.id();
|
||||
|
||||
@ -20,7 +20,7 @@ use crate::{
|
||||
component::{Component, ComponentId, Mutable},
|
||||
entity::{Entities, Entity, EntityClonerBuilder, EntityDoesNotExistError},
|
||||
error::{ignore, warn, BevyError, CommandWithEntity, ErrorContext, HandleError},
|
||||
event::Event,
|
||||
event::{BufferedEvent, EntityEvent, Event},
|
||||
observer::{Observer, TriggerTargets},
|
||||
resource::Resource,
|
||||
schedule::ScheduleLabel,
|
||||
@ -1078,7 +1078,7 @@ impl<'w, 's> Commands<'w, 's> {
|
||||
self.queue(command::run_system_cached_with(system, input).handle_error_with(warn));
|
||||
}
|
||||
|
||||
/// Sends a global [observer](Observer) [`Event`] without any targets.
|
||||
/// Sends a global [`Event`] without any targets.
|
||||
///
|
||||
/// This will run any [`Observer`] of the given [`Event`] that isn't scoped to specific targets.
|
||||
#[track_caller]
|
||||
@ -1086,13 +1086,13 @@ impl<'w, 's> Commands<'w, 's> {
|
||||
self.queue(command::trigger(event));
|
||||
}
|
||||
|
||||
/// Sends an [observer](Observer) [`Event`] for the given targets.
|
||||
/// Sends an [`EntityEvent`] for the given targets.
|
||||
///
|
||||
/// This will run any [`Observer`] of the given [`Event`] watching those targets.
|
||||
/// This will run any [`Observer`] of the given [`EntityEvent`] watching those targets.
|
||||
#[track_caller]
|
||||
pub fn trigger_targets(
|
||||
&mut self,
|
||||
event: impl Event,
|
||||
event: impl EntityEvent,
|
||||
targets: impl TriggerTargets + Send + Sync + 'static,
|
||||
) {
|
||||
self.queue(command::trigger_targets(event, targets));
|
||||
@ -1119,7 +1119,7 @@ impl<'w, 's> Commands<'w, 's> {
|
||||
self.spawn(Observer::new(observer))
|
||||
}
|
||||
|
||||
/// Sends an arbitrary [`Event`].
|
||||
/// Sends an arbitrary [`BufferedEvent`].
|
||||
///
|
||||
/// This is a convenience method for sending events
|
||||
/// without requiring an [`EventWriter`](crate::event::EventWriter).
|
||||
@ -1132,7 +1132,7 @@ impl<'w, 's> Commands<'w, 's> {
|
||||
/// If these events are performance-critical or very frequently sent,
|
||||
/// consider using a typed [`EventWriter`](crate::event::EventWriter) instead.
|
||||
#[track_caller]
|
||||
pub fn send_event<E: Event>(&mut self, event: E) -> &mut Self {
|
||||
pub fn send_event<E: BufferedEvent>(&mut self, event: E) -> &mut Self {
|
||||
self.queue(command::send_event(event));
|
||||
self
|
||||
}
|
||||
@ -1957,16 +1957,16 @@ impl<'a> EntityCommands<'a> {
|
||||
&mut self.commands
|
||||
}
|
||||
|
||||
/// Sends an [observer](Observer) [`Event`] targeting the entity.
|
||||
/// Sends an [`EntityEvent`] targeting the entity.
|
||||
///
|
||||
/// This will run any [`Observer`] of the given [`Event`] watching this entity.
|
||||
/// This will run any [`Observer`] of the given [`EntityEvent`] watching this entity.
|
||||
#[track_caller]
|
||||
pub fn trigger(&mut self, event: impl Event) -> &mut Self {
|
||||
pub fn trigger(&mut self, event: impl EntityEvent) -> &mut Self {
|
||||
self.queue(entity_command::trigger(event))
|
||||
}
|
||||
|
||||
/// Creates an [`Observer`] listening for events of type `E` targeting this entity.
|
||||
pub fn observe<E: Event, B: Bundle, M>(
|
||||
pub fn observe<E: EntityEvent, B: Bundle, M>(
|
||||
&mut self,
|
||||
observer: impl IntoObserverSystem<E, B, M>,
|
||||
) -> &mut Self {
|
||||
|
||||
@ -131,7 +131,7 @@ impl SystemMeta {
|
||||
/// # use bevy_ecs::system::SystemState;
|
||||
/// # use bevy_ecs::event::Events;
|
||||
/// #
|
||||
/// # #[derive(Event)]
|
||||
/// # #[derive(Event, BufferedEvent)]
|
||||
/// # struct MyEvent;
|
||||
/// # #[derive(Resource)]
|
||||
/// # struct MyResource(u32);
|
||||
@ -164,7 +164,7 @@ impl SystemMeta {
|
||||
/// # use bevy_ecs::system::SystemState;
|
||||
/// # use bevy_ecs::event::Events;
|
||||
/// #
|
||||
/// # #[derive(Event)]
|
||||
/// # #[derive(Event, BufferedEvent)]
|
||||
/// # struct MyEvent;
|
||||
/// #[derive(Resource)]
|
||||
/// struct CachedSystemState {
|
||||
|
||||
@ -57,7 +57,7 @@ use variadics_please::{all_tuples, all_tuples_enumerated};
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # #[derive(Resource)]
|
||||
/// # struct SomeResource;
|
||||
/// # #[derive(Event)]
|
||||
/// # #[derive(Event, BufferedEvent)]
|
||||
/// # struct SomeEvent;
|
||||
/// # #[derive(Resource)]
|
||||
/// # struct SomeOtherResource;
|
||||
@ -598,7 +598,7 @@ unsafe impl<'w, 's, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> Re
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #
|
||||
/// # #[derive(Event)]
|
||||
/// # #[derive(Event, BufferedEvent)]
|
||||
/// # struct MyEvent;
|
||||
/// # impl MyEvent {
|
||||
/// # pub fn new() -> Self { Self }
|
||||
@ -2841,7 +2841,7 @@ impl Display for SystemParamValidationError {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::system::assert_is_system;
|
||||
use crate::{event::Event, system::assert_is_system};
|
||||
use core::cell::RefCell;
|
||||
|
||||
// Compile test for https://github.com/bevyengine/bevy/pull/2838.
|
||||
@ -3087,11 +3087,11 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "Encountered an error in system `bevy_ecs::system::system_param::tests::missing_event_error::event_system`: Parameter `EventReader<MissingEvent>::events` failed validation: Event not initialized"]
|
||||
#[should_panic = "Encountered an error in system `bevy_ecs::system::system_param::tests::missing_event_error::event_system`: Parameter `EventReader<MissingEvent>::events` failed validation: BufferedEvent not initialized"]
|
||||
fn missing_event_error() {
|
||||
use crate::prelude::{Event, EventReader};
|
||||
use crate::prelude::{BufferedEvent, EventReader};
|
||||
|
||||
#[derive(Event)]
|
||||
#[derive(Event, BufferedEvent)]
|
||||
pub struct MissingEvent;
|
||||
|
||||
let mut schedule = crate::schedule::Schedule::default();
|
||||
|
||||
@ -17,7 +17,7 @@ use crate::{entity::Entity, query::ReadOnlyQueryData, relationship::Relationship
|
||||
/// parameter `D` is the event type given in `On<E>`. This allows traversal to differ depending on event
|
||||
/// data.
|
||||
///
|
||||
/// [specify the direction]: crate::event::Event::Traversal
|
||||
/// [specify the direction]: crate::event::EntityEvent::Traversal
|
||||
/// [event propagation]: crate::observer::On::propagate
|
||||
/// [observers]: crate::observer::Observer
|
||||
pub trait Traversal<D: ?Sized>: ReadOnlyQueryData {
|
||||
|
||||
@ -5,7 +5,7 @@ use crate::{
|
||||
change_detection::{MaybeLocation, MutUntyped},
|
||||
component::{ComponentId, Mutable},
|
||||
entity::Entity,
|
||||
event::{Event, EventId, Events, SendBatchIds},
|
||||
event::{BufferedEvent, EntityEvent, Event, EventId, Events, SendBatchIds},
|
||||
lifecycle::{HookContext, INSERT, REPLACE},
|
||||
observer::{Observers, TriggerTargets},
|
||||
prelude::{Component, QueryState},
|
||||
@ -496,27 +496,27 @@ impl<'w> DeferredWorld<'w> {
|
||||
unsafe { self.world.get_non_send_resource_mut() }
|
||||
}
|
||||
|
||||
/// Sends an [`Event`].
|
||||
/// Sends a [`BufferedEvent`].
|
||||
/// This method returns the [ID](`EventId`) of the sent `event`,
|
||||
/// or [`None`] if the `event` could not be sent.
|
||||
#[inline]
|
||||
pub fn send_event<E: Event>(&mut self, event: E) -> Option<EventId<E>> {
|
||||
pub fn send_event<E: BufferedEvent>(&mut self, event: E) -> Option<EventId<E>> {
|
||||
self.send_event_batch(core::iter::once(event))?.next()
|
||||
}
|
||||
|
||||
/// Sends the default value of the [`Event`] of type `E`.
|
||||
/// Sends the default value of the [`BufferedEvent`] of type `E`.
|
||||
/// This method returns the [ID](`EventId`) of the sent `event`,
|
||||
/// or [`None`] if the `event` could not be sent.
|
||||
#[inline]
|
||||
pub fn send_event_default<E: Event + Default>(&mut self) -> Option<EventId<E>> {
|
||||
pub fn send_event_default<E: BufferedEvent + Default>(&mut self) -> Option<EventId<E>> {
|
||||
self.send_event(E::default())
|
||||
}
|
||||
|
||||
/// Sends a batch of [`Event`]s from an iterator.
|
||||
/// Sends a batch of [`BufferedEvent`]s from an iterator.
|
||||
/// This method returns the [IDs](`EventId`) of the sent `events`,
|
||||
/// or [`None`] if the `event` could not be sent.
|
||||
#[inline]
|
||||
pub fn send_event_batch<E: Event>(
|
||||
pub fn send_event_batch<E: BufferedEvent>(
|
||||
&mut self,
|
||||
events: impl IntoIterator<Item = E>,
|
||||
) -> Option<SendBatchIds<E>> {
|
||||
@ -807,15 +807,23 @@ impl<'w> DeferredWorld<'w> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends a "global" [observer](crate::observer::Observer) [`Event`] without any targets.
|
||||
/// Sends a global [`Event`] without any targets.
|
||||
///
|
||||
/// This will run any [`Observer`] of the given [`Event`] that isn't scoped to specific targets.
|
||||
///
|
||||
/// [`Observer`]: crate::observer::Observer
|
||||
pub fn trigger(&mut self, trigger: impl Event) {
|
||||
self.commands().trigger(trigger);
|
||||
}
|
||||
|
||||
/// Sends an [observer](crate::observer::Observer) [`Event`] with the given `targets`.
|
||||
/// Sends an [`EntityEvent`] with the given `targets`
|
||||
///
|
||||
/// This will run any [`Observer`] of the given [`EntityEvent`] watching those targets.
|
||||
///
|
||||
/// [`Observer`]: crate::observer::Observer
|
||||
pub fn trigger_targets(
|
||||
&mut self,
|
||||
trigger: impl Event,
|
||||
trigger: impl EntityEvent,
|
||||
targets: impl TriggerTargets + Send + Sync + 'static,
|
||||
) {
|
||||
self.commands().trigger_targets(trigger, targets);
|
||||
|
||||
@ -13,7 +13,7 @@ use crate::{
|
||||
ContainsEntity, Entity, EntityCloner, EntityClonerBuilder, EntityEquivalent,
|
||||
EntityIdLocation, EntityLocation,
|
||||
},
|
||||
event::Event,
|
||||
event::EntityEvent,
|
||||
lifecycle::{DESPAWN, REMOVE, REPLACE},
|
||||
observer::Observer,
|
||||
query::{Access, DebugCheckedUnwrap, ReadOnlyQueryData},
|
||||
@ -2626,7 +2626,7 @@ impl<'w> EntityWorldMut<'w> {
|
||||
/// # Panics
|
||||
///
|
||||
/// If the entity has been despawned while this `EntityWorldMut` is still alive.
|
||||
pub fn trigger(&mut self, event: impl Event) -> &mut Self {
|
||||
pub fn trigger(&mut self, event: impl EntityEvent) -> &mut Self {
|
||||
self.assert_not_despawned();
|
||||
self.world.trigger_targets(event, self.entity);
|
||||
self.world.flush();
|
||||
@ -2643,14 +2643,14 @@ impl<'w> EntityWorldMut<'w> {
|
||||
///
|
||||
/// Panics if the given system is an exclusive system.
|
||||
#[track_caller]
|
||||
pub fn observe<E: Event, B: Bundle, M>(
|
||||
pub fn observe<E: EntityEvent, B: Bundle, M>(
|
||||
&mut self,
|
||||
observer: impl IntoObserverSystem<E, B, M>,
|
||||
) -> &mut Self {
|
||||
self.observe_with_caller(observer, MaybeLocation::caller())
|
||||
}
|
||||
|
||||
pub(crate) fn observe_with_caller<E: Event, B: Bundle, M>(
|
||||
pub(crate) fn observe_with_caller<E: EntityEvent, B: Bundle, M>(
|
||||
&mut self,
|
||||
observer: impl IntoObserverSystem<E, B, M>,
|
||||
caller: MaybeLocation,
|
||||
@ -5739,7 +5739,7 @@ mod tests {
|
||||
assert_eq!((&mut X(8), &mut Y(9)), (x_component, y_component));
|
||||
}
|
||||
|
||||
#[derive(Event)]
|
||||
#[derive(Event, EntityEvent)]
|
||||
struct TestEvent;
|
||||
|
||||
#[test]
|
||||
@ -5748,9 +5748,7 @@ mod tests {
|
||||
let entity = world
|
||||
.spawn_empty()
|
||||
.observe(|trigger: On<TestEvent>, mut commands: Commands| {
|
||||
commands
|
||||
.entity(trigger.target().unwrap())
|
||||
.insert(TestComponent(0));
|
||||
commands.entity(trigger.target()).insert(TestComponent(0));
|
||||
})
|
||||
.id();
|
||||
|
||||
@ -5769,7 +5767,7 @@ mod tests {
|
||||
fn location_on_despawned_entity_panics() {
|
||||
let mut world = World::new();
|
||||
world.add_observer(|trigger: On<Add, TestComponent>, mut commands: Commands| {
|
||||
commands.entity(trigger.target().unwrap()).despawn();
|
||||
commands.entity(trigger.target()).despawn();
|
||||
});
|
||||
let entity = world.spawn_empty().id();
|
||||
let mut a = world.entity_mut(entity);
|
||||
|
||||
@ -19,6 +19,7 @@ pub use crate::{
|
||||
};
|
||||
use crate::{
|
||||
error::{DefaultErrorHandler, ErrorHandler},
|
||||
event::BufferedEvent,
|
||||
lifecycle::{ComponentHooks, ADD, DESPAWN, INSERT, REMOVE, REPLACE},
|
||||
prelude::{Add, Despawn, Insert, Remove, Replace},
|
||||
};
|
||||
@ -2598,27 +2599,27 @@ impl World {
|
||||
Some(result)
|
||||
}
|
||||
|
||||
/// Sends an [`Event`].
|
||||
/// Sends a [`BufferedEvent`].
|
||||
/// This method returns the [ID](`EventId`) of the sent `event`,
|
||||
/// or [`None`] if the `event` could not be sent.
|
||||
#[inline]
|
||||
pub fn send_event<E: Event>(&mut self, event: E) -> Option<EventId<E>> {
|
||||
pub fn send_event<E: BufferedEvent>(&mut self, event: E) -> Option<EventId<E>> {
|
||||
self.send_event_batch(core::iter::once(event))?.next()
|
||||
}
|
||||
|
||||
/// Sends the default value of the [`Event`] of type `E`.
|
||||
/// Sends the default value of the [`BufferedEvent`] of type `E`.
|
||||
/// This method returns the [ID](`EventId`) of the sent `event`,
|
||||
/// or [`None`] if the `event` could not be sent.
|
||||
#[inline]
|
||||
pub fn send_event_default<E: Event + Default>(&mut self) -> Option<EventId<E>> {
|
||||
pub fn send_event_default<E: BufferedEvent + Default>(&mut self) -> Option<EventId<E>> {
|
||||
self.send_event(E::default())
|
||||
}
|
||||
|
||||
/// Sends a batch of [`Event`]s from an iterator.
|
||||
/// Sends a batch of [`BufferedEvent`]s from an iterator.
|
||||
/// This method returns the [IDs](`EventId`) of the sent `events`,
|
||||
/// or [`None`] if the `event` could not be sent.
|
||||
#[inline]
|
||||
pub fn send_event_batch<E: Event>(
|
||||
pub fn send_event_batch<E: BufferedEvent>(
|
||||
&mut self,
|
||||
events: impl IntoIterator<Item = E>,
|
||||
) -> Option<SendBatchIds<E>> {
|
||||
|
||||
@ -10,7 +10,7 @@ use bevy_ecs::{
|
||||
change_detection::DetectChangesMut,
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
event::{Event, EventReader, EventWriter},
|
||||
event::{BufferedEvent, Event, EventReader, EventWriter},
|
||||
name::Name,
|
||||
system::{Commands, Query},
|
||||
};
|
||||
@ -32,7 +32,7 @@ use thiserror::Error;
|
||||
/// the in-frame relative ordering of events is important.
|
||||
///
|
||||
/// This event is produced by `bevy_input`.
|
||||
#[derive(Event, Debug, Clone, PartialEq, From)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, From)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -59,7 +59,7 @@ pub enum GamepadEvent {
|
||||
/// the in-frame relative ordering of events is important.
|
||||
///
|
||||
/// This event type is used by `bevy_input` to feed its components.
|
||||
#[derive(Event, Debug, Clone, PartialEq, From)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, From)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -80,7 +80,7 @@ pub enum RawGamepadEvent {
|
||||
}
|
||||
|
||||
/// [`GamepadButton`] changed event unfiltered by [`GamepadSettings`].
|
||||
#[derive(Event, Debug, Copy, Clone, PartialEq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Copy, Clone, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -112,7 +112,7 @@ impl RawGamepadButtonChangedEvent {
|
||||
}
|
||||
|
||||
/// [`GamepadAxis`] changed event unfiltered by [`GamepadSettings`].
|
||||
#[derive(Event, Debug, Copy, Clone, PartialEq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Copy, Clone, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -143,9 +143,9 @@ impl RawGamepadAxisChangedEvent {
|
||||
}
|
||||
}
|
||||
|
||||
/// A Gamepad connection event. Created when a connection to a gamepad
|
||||
/// A [`Gamepad`] connection event. Created when a connection to a gamepad
|
||||
/// is established and when a gamepad is disconnected.
|
||||
#[derive(Event, Debug, Clone, PartialEq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -184,7 +184,7 @@ impl GamepadConnectionEvent {
|
||||
}
|
||||
|
||||
/// [`GamepadButton`] event triggered by a digital state change.
|
||||
#[derive(Event, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -216,7 +216,7 @@ impl GamepadButtonStateChangedEvent {
|
||||
}
|
||||
|
||||
/// [`GamepadButton`] event triggered by an analog state change.
|
||||
#[derive(Event, Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -251,7 +251,7 @@ impl GamepadButtonChangedEvent {
|
||||
}
|
||||
|
||||
/// [`GamepadAxis`] event triggered by an analog state change.
|
||||
#[derive(Event, Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
@ -1774,7 +1774,7 @@ impl GamepadRumbleIntensity {
|
||||
#[doc(alias = "force feedback")]
|
||||
#[doc(alias = "vibration")]
|
||||
#[doc(alias = "vibrate")]
|
||||
#[derive(Event, Clone)]
|
||||
#[derive(Event, BufferedEvent, Clone)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Clone))]
|
||||
pub enum GamepadRumbleRequest {
|
||||
/// Add a rumble to the given gamepad.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
//! Gestures functionality, from touchscreens and touchpads.
|
||||
|
||||
use bevy_ecs::event::Event;
|
||||
use bevy_ecs::event::{BufferedEvent, Event};
|
||||
use bevy_math::Vec2;
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
use bevy_reflect::Reflect;
|
||||
@ -17,7 +17,7 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
||||
///
|
||||
/// - Only available on **`macOS`** and **`iOS`**.
|
||||
/// - On **`iOS`**, must be enabled first
|
||||
#[derive(Event, Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -39,7 +39,7 @@ pub struct PinchGesture(pub f32);
|
||||
///
|
||||
/// - Only available on **`macOS`** and **`iOS`**.
|
||||
/// - On **`iOS`**, must be enabled first
|
||||
#[derive(Event, Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -58,7 +58,7 @@ pub struct RotationGesture(pub f32);
|
||||
///
|
||||
/// - Only available on **`macOS`** and **`iOS`**.
|
||||
/// - On **`iOS`**, must be enabled first
|
||||
#[derive(Event, Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -76,7 +76,7 @@ pub struct DoubleTapGesture;
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - On **`iOS`**, must be enabled first
|
||||
#[derive(Event, Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
|
||||
@ -69,7 +69,7 @@ use crate::{ButtonInput, ButtonState};
|
||||
use bevy_ecs::{
|
||||
change_detection::DetectChangesMut,
|
||||
entity::Entity,
|
||||
event::{Event, EventReader},
|
||||
event::{BufferedEvent, Event, EventReader},
|
||||
system::ResMut,
|
||||
};
|
||||
|
||||
@ -94,7 +94,7 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
||||
///
|
||||
/// The event is consumed inside of the [`keyboard_input_system`]
|
||||
/// to update the [`ButtonInput<KeyCode>`](ButtonInput<KeyCode>) resource.
|
||||
#[derive(Event, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -139,7 +139,7 @@ pub struct KeyboardInput {
|
||||
/// when, for example, switching between windows with 'Alt-Tab' or using any other
|
||||
/// OS specific key combination that leads to Bevy window losing focus and not receiving any
|
||||
/// input events
|
||||
#[derive(Event, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Clone, PartialEq))]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(
|
||||
|
||||
@ -4,7 +4,7 @@ use crate::{ButtonInput, ButtonState};
|
||||
use bevy_ecs::{
|
||||
change_detection::DetectChangesMut,
|
||||
entity::Entity,
|
||||
event::{Event, EventReader},
|
||||
event::{BufferedEvent, Event, EventReader},
|
||||
resource::Resource,
|
||||
system::ResMut,
|
||||
};
|
||||
@ -26,7 +26,7 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
||||
///
|
||||
/// The event is read inside of the [`mouse_button_input_system`]
|
||||
/// to update the [`ButtonInput<MouseButton>`] resource.
|
||||
#[derive(Event, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -91,7 +91,7 @@ pub enum MouseButton {
|
||||
/// However, the event data does not make it possible to distinguish which device it is referring to.
|
||||
///
|
||||
/// [`DeviceEvent::MouseMotion`]: https://docs.rs/winit/latest/winit/event/enum.DeviceEvent.html#variant.MouseMotion
|
||||
#[derive(Event, Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -140,7 +140,7 @@ pub enum MouseScrollUnit {
|
||||
/// A mouse wheel event.
|
||||
///
|
||||
/// This event is the translated version of the `WindowEvent::MouseWheel` from the `winit` crate.
|
||||
#[derive(Event, Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
use bevy_ecs::{
|
||||
entity::Entity,
|
||||
event::{Event, EventReader},
|
||||
event::{BufferedEvent, Event, EventReader},
|
||||
resource::Resource,
|
||||
system::ResMut,
|
||||
};
|
||||
@ -37,7 +37,7 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
||||
///
|
||||
/// This event is the translated version of the `WindowEvent::Touch` from the `winit` crate.
|
||||
/// It is available to the end user and can be used for game logic.
|
||||
#[derive(Event, Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
|
||||
@ -137,21 +137,16 @@ pub struct InputFocusVisible(pub bool);
|
||||
///
|
||||
/// To set up your own bubbling input event, add the [`dispatch_focused_input::<MyEvent>`](dispatch_focused_input) system to your app,
|
||||
/// in the [`InputFocusSystems::Dispatch`] system set during [`PreUpdate`].
|
||||
#[derive(Clone, Debug, Component)]
|
||||
#[derive(Event, EntityEvent, Clone, Debug, Component)]
|
||||
#[entity_event(traversal = WindowTraversal, auto_propagate)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component, Clone))]
|
||||
pub struct FocusedInput<E: Event + Clone> {
|
||||
pub struct FocusedInput<E: BufferedEvent + Clone> {
|
||||
/// The underlying input event.
|
||||
pub input: E,
|
||||
/// The primary window entity.
|
||||
window: Entity,
|
||||
}
|
||||
|
||||
impl<E: Event + Clone> Event for FocusedInput<E> {
|
||||
type Traversal = WindowTraversal;
|
||||
|
||||
const AUTO_PROPAGATE: bool = true;
|
||||
}
|
||||
|
||||
#[derive(QueryData)]
|
||||
/// These are for accessing components defined on the targeted entity
|
||||
pub struct WindowTraversal {
|
||||
@ -159,7 +154,7 @@ pub struct WindowTraversal {
|
||||
window: Option<&'static Window>,
|
||||
}
|
||||
|
||||
impl<E: Event + Clone> Traversal<FocusedInput<E>> for WindowTraversal {
|
||||
impl<E: BufferedEvent + Clone> Traversal<FocusedInput<E>> for WindowTraversal {
|
||||
fn traverse(item: Self::Item<'_>, event: &FocusedInput<E>) -> Option<Entity> {
|
||||
let WindowTraversalItem { child_of, window } = item;
|
||||
|
||||
@ -230,7 +225,7 @@ pub fn set_initial_focus(
|
||||
|
||||
/// System which dispatches bubbled input events to the focused entity, or to the primary window
|
||||
/// if no entity has focus.
|
||||
pub fn dispatch_focused_input<E: Event + Clone>(
|
||||
pub fn dispatch_focused_input<E: BufferedEvent + Clone>(
|
||||
mut key_events: EventReader<E>,
|
||||
focus: Res<InputFocus>,
|
||||
windows: Query<Entity, With<PrimaryWindow>>,
|
||||
@ -384,7 +379,7 @@ mod tests {
|
||||
trigger: On<FocusedInput<KeyboardInput>>,
|
||||
mut query: Query<&mut GatherKeyboardEvents>,
|
||||
) {
|
||||
if let Ok(mut gather) = query.get_mut(trigger.target().unwrap()) {
|
||||
if let Ok(mut gather) = query.get_mut(trigger.target()) {
|
||||
if let Key::Character(c) = &trigger.input.logical_key {
|
||||
gather.0.push_str(c.as_str());
|
||||
}
|
||||
|
||||
@ -551,7 +551,7 @@ pub(crate) fn add_light_view_entities(
|
||||
trigger: On<Add, (ExtractedDirectionalLight, ExtractedPointLight)>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
if let Ok(mut v) = commands.get_entity(trigger.target().unwrap()) {
|
||||
if let Ok(mut v) = commands.get_entity(trigger.target()) {
|
||||
v.insert(LightViewEntities::default());
|
||||
}
|
||||
}
|
||||
@ -561,7 +561,7 @@ pub(crate) fn extracted_light_removed(
|
||||
trigger: On<Remove, (ExtractedDirectionalLight, ExtractedPointLight)>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
if let Ok(mut v) = commands.get_entity(trigger.target().unwrap()) {
|
||||
if let Ok(mut v) = commands.get_entity(trigger.target()) {
|
||||
v.try_remove::<LightViewEntities>();
|
||||
}
|
||||
}
|
||||
@ -571,7 +571,7 @@ pub(crate) fn remove_light_view_entities(
|
||||
query: Query<&LightViewEntities>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
if let Ok(entities) = query.get(trigger.target().unwrap()) {
|
||||
if let Ok(entities) = query.get(trigger.target()) {
|
||||
for v in entities.0.values() {
|
||||
for e in v.iter().copied() {
|
||||
if let Ok(mut v) = commands.get_entity(e) {
|
||||
|
||||
@ -55,7 +55,7 @@ pub mod prelude {
|
||||
/// Note that systems reading these events in [`PreUpdate`](bevy_app::PreUpdate) will not report ordering
|
||||
/// ambiguities with picking backends. Take care to ensure such systems are explicitly ordered
|
||||
/// against [`PickingSystems::Backend`](crate::PickingSystems::Backend), or better, avoid reading `PointerHits` in `PreUpdate`.
|
||||
#[derive(Event, Debug, Clone, Reflect)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Reflect)]
|
||||
#[reflect(Debug, Clone)]
|
||||
pub struct PointerHits {
|
||||
/// The pointer associated with this hit test.
|
||||
|
||||
@ -59,7 +59,8 @@ use crate::{
|
||||
///
|
||||
/// The documentation for the [`pointer_events`] explains the events this module exposes and
|
||||
/// the order in which they fire.
|
||||
#[derive(Clone, PartialEq, Debug, Reflect, Component)]
|
||||
#[derive(Event, BufferedEvent, EntityEvent, Clone, PartialEq, Debug, Reflect, Component)]
|
||||
#[entity_event(traversal = PointerTraversal, auto_propagate)]
|
||||
#[reflect(Component, Debug, Clone)]
|
||||
pub struct Pointer<E: Debug + Clone + Reflect> {
|
||||
/// The original target of this picking event, before bubbling
|
||||
@ -106,15 +107,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Event for Pointer<E>
|
||||
where
|
||||
E: Debug + Clone + Reflect,
|
||||
{
|
||||
type Traversal = PointerTraversal;
|
||||
|
||||
const AUTO_PROPAGATE: bool = true;
|
||||
}
|
||||
|
||||
impl<E: Debug + Clone + Reflect> core::fmt::Display for Pointer<E> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.write_fmt(format_args!(
|
||||
|
||||
@ -48,20 +48,20 @@
|
||||
//! # use bevy_ecs::prelude::*;
|
||||
//! # use bevy_transform::prelude::*;
|
||||
//! # use bevy_picking::prelude::*;
|
||||
//! # #[derive(Event)]
|
||||
//! # #[derive(Event, BufferedEvent)]
|
||||
//! # struct Greeting;
|
||||
//! fn setup(mut commands: Commands) {
|
||||
//! commands.spawn(Transform::default())
|
||||
//! // Spawn your entity here, e.g. a `Mesh3d`.
|
||||
//! // When dragged, mutate the `Transform` component on the dragged target entity:
|
||||
//! .observe(|trigger: On<Pointer<Drag>>, mut transforms: Query<&mut Transform>| {
|
||||
//! let mut transform = transforms.get_mut(trigger.target().unwrap()).unwrap();
|
||||
//! let mut transform = transforms.get_mut(trigger.target()).unwrap();
|
||||
//! let drag = trigger.event();
|
||||
//! transform.rotate_local_y(drag.delta.x / 50.0);
|
||||
//! })
|
||||
//! .observe(|trigger: On<Pointer<Click>>, mut commands: Commands| {
|
||||
//! println!("Entity {} goes BOOM!", trigger.target().unwrap());
|
||||
//! commands.entity(trigger.target().unwrap()).despawn();
|
||||
//! println!("Entity {} goes BOOM!", trigger.target());
|
||||
//! commands.entity(trigger.target()).despawn();
|
||||
//! })
|
||||
//! .observe(|trigger: On<Pointer<Over>>, mut events: EventWriter<Greeting>| {
|
||||
//! events.write(Greeting);
|
||||
|
||||
@ -269,7 +269,7 @@ pub enum PointerAction {
|
||||
}
|
||||
|
||||
/// An input event effecting a pointer.
|
||||
#[derive(Event, Debug, Clone, Reflect)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Reflect)]
|
||||
#[reflect(Clone)]
|
||||
pub struct PointerInput {
|
||||
/// The id of the pointer.
|
||||
|
||||
@ -15,14 +15,14 @@ use async_channel::{Receiver, Sender};
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_asset::Handle;
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::schedule::IntoScheduleConfigs;
|
||||
use bevy_ecs::{
|
||||
change_detection::ResMut,
|
||||
entity::Entity,
|
||||
event::Event,
|
||||
event::EntityEvent,
|
||||
prelude::{Component, Resource, World},
|
||||
system::{Query, Res},
|
||||
};
|
||||
use bevy_ecs::{event::Event, schedule::IntoScheduleConfigs};
|
||||
use bevy_image::{Image, TextureFormatPixelInfo};
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_reflect::Reflect;
|
||||
@ -96,7 +96,7 @@ impl Readback {
|
||||
///
|
||||
/// The event contains the data as a `Vec<u8>`, which can be interpreted as the raw bytes of the
|
||||
/// requested buffer or texture.
|
||||
#[derive(Event, Deref, DerefMut, Reflect, Debug)]
|
||||
#[derive(Event, EntityEvent, Deref, DerefMut, Reflect, Debug)]
|
||||
#[reflect(Debug)]
|
||||
pub struct ReadbackComplete(pub Vec<u8>);
|
||||
|
||||
|
||||
@ -95,14 +95,14 @@ impl Plugin for SyncWorldPlugin {
|
||||
app.init_resource::<PendingSyncEntity>();
|
||||
app.add_observer(
|
||||
|trigger: On<Add, SyncToRenderWorld>, mut pending: ResMut<PendingSyncEntity>| {
|
||||
pending.push(EntityRecord::Added(trigger.target().unwrap()));
|
||||
pending.push(EntityRecord::Added(trigger.target()));
|
||||
},
|
||||
);
|
||||
app.add_observer(
|
||||
|trigger: On<Remove, SyncToRenderWorld>,
|
||||
mut pending: ResMut<PendingSyncEntity>,
|
||||
query: Query<&RenderEntity>| {
|
||||
if let Ok(e) = query.get(trigger.target().unwrap()) {
|
||||
if let Ok(e) = query.get(trigger.target()) {
|
||||
pending.push(EntityRecord::Removed(*e));
|
||||
};
|
||||
},
|
||||
@ -514,14 +514,14 @@ mod tests {
|
||||
|
||||
main_world.add_observer(
|
||||
|trigger: On<Add, SyncToRenderWorld>, mut pending: ResMut<PendingSyncEntity>| {
|
||||
pending.push(EntityRecord::Added(trigger.target().unwrap()));
|
||||
pending.push(EntityRecord::Added(trigger.target()));
|
||||
},
|
||||
);
|
||||
main_world.add_observer(
|
||||
|trigger: On<Remove, SyncToRenderWorld>,
|
||||
mut pending: ResMut<PendingSyncEntity>,
|
||||
query: Query<&RenderEntity>| {
|
||||
if let Ok(e) = query.get(trigger.target().unwrap()) {
|
||||
if let Ok(e) = query.get(trigger.target()) {
|
||||
pending.push(EntityRecord::Removed(*e));
|
||||
};
|
||||
},
|
||||
|
||||
@ -39,7 +39,7 @@ use std::{
|
||||
use tracing::{error, info, warn};
|
||||
use wgpu::{CommandEncoder, Extent3d, TextureFormat};
|
||||
|
||||
#[derive(Event, Deref, DerefMut, Reflect, Debug)]
|
||||
#[derive(Event, EntityEvent, Deref, DerefMut, Reflect, Debug)]
|
||||
#[reflect(Debug)]
|
||||
pub struct ScreenshotCaptured(pub Image);
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ use crate::{DynamicScene, Scene};
|
||||
use bevy_asset::{AssetEvent, AssetId, Assets, Handle};
|
||||
use bevy_ecs::{
|
||||
entity::{Entity, EntityHashMap},
|
||||
event::{Event, EventCursor, Events},
|
||||
event::{EntityEvent, Event, EventCursor, Events},
|
||||
hierarchy::ChildOf,
|
||||
reflect::AppTypeRegistry,
|
||||
resource::Resource,
|
||||
@ -25,7 +25,7 @@ use bevy_ecs::{
|
||||
/// See also [`On`], [`SceneSpawner::instance_is_ready`].
|
||||
///
|
||||
/// [`On`]: bevy_ecs::observer::On
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Event, Reflect)]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Event, EntityEvent, Reflect)]
|
||||
#[reflect(Debug, PartialEq, Clone)]
|
||||
pub struct SceneInstanceReady {
|
||||
/// Instance which has been spawned.
|
||||
@ -734,7 +734,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
trigger.target(),
|
||||
scene_entity,
|
||||
scene_entity.unwrap_or(Entity::PLACEHOLDER),
|
||||
"`SceneInstanceReady` triggered on the wrong parent entity"
|
||||
);
|
||||
assert!(
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use core::{marker::PhantomData, mem};
|
||||
|
||||
use bevy_ecs::{
|
||||
event::{Event, EventReader, EventWriter},
|
||||
event::{BufferedEvent, Event, EventReader, EventWriter},
|
||||
schedule::{IntoScheduleConfigs, Schedule, ScheduleLabel, Schedules, SystemSet},
|
||||
system::{Commands, In, ResMut},
|
||||
world::World,
|
||||
@ -55,11 +55,11 @@ pub struct OnTransition<S: States> {
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
|
||||
pub struct StateTransition;
|
||||
|
||||
/// Event sent when any state transition of `S` happens.
|
||||
/// A [`BufferedEvent`] sent when any state transition of `S` happens.
|
||||
/// This includes identity transitions, where `exited` and `entered` have the same value.
|
||||
///
|
||||
/// If you know exactly what state you want to respond to ahead of time, consider [`OnEnter`], [`OnTransition`], or [`OnExit`]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Event)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Event, BufferedEvent)]
|
||||
pub struct StateTransitionEvent<S: States> {
|
||||
/// The state being exited.
|
||||
pub exited: Option<S>,
|
||||
|
||||
@ -3,7 +3,7 @@ use core::marker::PhantomData;
|
||||
|
||||
use bevy_app::{App, SubApp};
|
||||
use bevy_ecs::{
|
||||
event::{Event, EventReader, Events},
|
||||
event::{BufferedEvent, EventReader, Events},
|
||||
resource::Resource,
|
||||
system::Commands,
|
||||
world::World,
|
||||
@ -12,7 +12,7 @@ use bevy_platform::collections::HashMap;
|
||||
|
||||
use crate::state::{OnEnter, OnExit, StateTransitionEvent, States};
|
||||
|
||||
fn clear_event_queue<E: Event>(w: &mut World) {
|
||||
fn clear_event_queue<E: BufferedEvent>(w: &mut World) {
|
||||
if let Some(mut queue) = w.get_resource_mut::<Events<E>>() {
|
||||
queue.clear();
|
||||
}
|
||||
@ -33,7 +33,7 @@ struct StateScopedEvents<S: States> {
|
||||
}
|
||||
|
||||
impl<S: States> StateScopedEvents<S> {
|
||||
fn add_event<E: Event>(&mut self, state: S, transition_type: TransitionType) {
|
||||
fn add_event<E: BufferedEvent>(&mut self, state: S, transition_type: TransitionType) {
|
||||
let map = match transition_type {
|
||||
TransitionType::OnExit => &mut self.on_exit,
|
||||
TransitionType::OnEnter => &mut self.on_enter,
|
||||
@ -106,7 +106,7 @@ fn clear_events_on_enter_state<S: States>(
|
||||
});
|
||||
}
|
||||
|
||||
fn clear_events_on_state_transition<E: Event, S: States>(
|
||||
fn clear_events_on_state_transition<E: BufferedEvent, S: States>(
|
||||
app: &mut SubApp,
|
||||
_p: PhantomData<E>,
|
||||
state: S,
|
||||
@ -128,7 +128,7 @@ fn clear_events_on_state_transition<E: Event, S: States>(
|
||||
|
||||
/// Extension trait for [`App`] adding methods for registering state scoped events.
|
||||
pub trait StateScopedEventsAppExt {
|
||||
/// Clears an [`Event`] when exiting the specified `state`.
|
||||
/// Clears an [`BufferedEvent`] when exiting the specified `state`.
|
||||
///
|
||||
/// Note that event cleanup is ambiguously ordered relative to
|
||||
/// [`DespawnOnExitState`](crate::prelude::DespawnOnExitState) entity cleanup,
|
||||
@ -136,9 +136,9 @@ pub trait StateScopedEventsAppExt {
|
||||
/// All of these (state scoped entities and events cleanup, and `OnExit`)
|
||||
/// occur within schedule [`StateTransition`](crate::prelude::StateTransition)
|
||||
/// and system set `StateTransitionSystems::ExitSchedules`.
|
||||
fn clear_events_on_exit_state<E: Event>(&mut self, state: impl States) -> &mut Self;
|
||||
fn clear_events_on_exit_state<E: BufferedEvent>(&mut self, state: impl States) -> &mut Self;
|
||||
|
||||
/// Clears an [`Event`] when entering the specified `state`.
|
||||
/// Clears an [`BufferedEvent`] when entering the specified `state`.
|
||||
///
|
||||
/// Note that event cleanup is ambiguously ordered relative to
|
||||
/// [`DespawnOnEnterState`](crate::prelude::DespawnOnEnterState) entity cleanup,
|
||||
@ -146,11 +146,11 @@ pub trait StateScopedEventsAppExt {
|
||||
/// All of these (state scoped entities and events cleanup, and `OnEnter`)
|
||||
/// occur within schedule [`StateTransition`](crate::prelude::StateTransition)
|
||||
/// and system set `StateTransitionSystems::EnterSchedules`.
|
||||
fn clear_events_on_enter_state<E: Event>(&mut self, state: impl States) -> &mut Self;
|
||||
fn clear_events_on_enter_state<E: BufferedEvent>(&mut self, state: impl States) -> &mut Self;
|
||||
}
|
||||
|
||||
impl StateScopedEventsAppExt for App {
|
||||
fn clear_events_on_exit_state<E: Event>(&mut self, state: impl States) -> &mut Self {
|
||||
fn clear_events_on_exit_state<E: BufferedEvent>(&mut self, state: impl States) -> &mut Self {
|
||||
clear_events_on_state_transition(
|
||||
self.main_mut(),
|
||||
PhantomData::<E>,
|
||||
@ -160,7 +160,7 @@ impl StateScopedEventsAppExt for App {
|
||||
self
|
||||
}
|
||||
|
||||
fn clear_events_on_enter_state<E: Event>(&mut self, state: impl States) -> &mut Self {
|
||||
fn clear_events_on_enter_state<E: BufferedEvent>(&mut self, state: impl States) -> &mut Self {
|
||||
clear_events_on_state_transition(
|
||||
self.main_mut(),
|
||||
PhantomData::<E>,
|
||||
@ -172,12 +172,12 @@ impl StateScopedEventsAppExt for App {
|
||||
}
|
||||
|
||||
impl StateScopedEventsAppExt for SubApp {
|
||||
fn clear_events_on_exit_state<E: Event>(&mut self, state: impl States) -> &mut Self {
|
||||
fn clear_events_on_exit_state<E: BufferedEvent>(&mut self, state: impl States) -> &mut Self {
|
||||
clear_events_on_state_transition(self, PhantomData::<E>, state, TransitionType::OnExit);
|
||||
self
|
||||
}
|
||||
|
||||
fn clear_events_on_enter_state<E: Event>(&mut self, state: impl States) -> &mut Self {
|
||||
fn clear_events_on_enter_state<E: BufferedEvent>(&mut self, state: impl States) -> &mut Self {
|
||||
clear_events_on_state_transition(self, PhantomData::<E>, state, TransitionType::OnEnter);
|
||||
self
|
||||
}
|
||||
@ -187,6 +187,7 @@ impl StateScopedEventsAppExt for SubApp {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::app::StatesPlugin;
|
||||
use bevy_ecs::event::{BufferedEvent, Event};
|
||||
use bevy_state::prelude::*;
|
||||
|
||||
#[derive(States, Default, Clone, Hash, Eq, PartialEq, Debug)]
|
||||
@ -196,10 +197,10 @@ mod tests {
|
||||
B,
|
||||
}
|
||||
|
||||
#[derive(Event, Debug)]
|
||||
#[derive(Event, BufferedEvent, Debug)]
|
||||
struct StandardEvent;
|
||||
|
||||
#[derive(Event, Debug)]
|
||||
#[derive(Event, BufferedEvent, Debug)]
|
||||
struct StateScopedEvent;
|
||||
|
||||
#[test]
|
||||
|
||||
@ -185,7 +185,10 @@ mod tests {
|
||||
use crate::{Fixed, Time, TimePlugin, TimeUpdateStrategy, Virtual};
|
||||
use bevy_app::{App, FixedUpdate, Startup, Update};
|
||||
use bevy_ecs::{
|
||||
event::{Event, EventReader, EventRegistry, EventWriter, Events, ShouldUpdateEvents},
|
||||
event::{
|
||||
BufferedEvent, Event, EventReader, EventRegistry, EventWriter, Events,
|
||||
ShouldUpdateEvents,
|
||||
},
|
||||
resource::Resource,
|
||||
system::{Local, Res, ResMut},
|
||||
};
|
||||
@ -193,7 +196,7 @@ mod tests {
|
||||
use core::time::Duration;
|
||||
use std::println;
|
||||
|
||||
#[derive(Event)]
|
||||
#[derive(Event, BufferedEvent)]
|
||||
struct TestEvent<T: Default> {
|
||||
sender: std::sync::mpsc::Sender<T>,
|
||||
}
|
||||
@ -206,7 +209,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Event)]
|
||||
#[derive(Event, BufferedEvent)]
|
||||
struct DummyEvent;
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
|
||||
@ -119,7 +119,7 @@ use {
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # use bevy_time::prelude::*;
|
||||
/// #
|
||||
/// #[derive(Event)]
|
||||
/// #[derive(Event, BufferedEvent)]
|
||||
/// struct PauseEvent(bool);
|
||||
///
|
||||
/// fn pause_system(mut time: ResMut<Time<Virtual>>, mut events: EventReader<PauseEvent>) {
|
||||
|
||||
@ -19,7 +19,7 @@ use bevy_ecs::{
|
||||
pub struct InteractionDisabled;
|
||||
|
||||
pub(crate) fn on_add_disabled(trigger: On<Add, InteractionDisabled>, mut world: DeferredWorld) {
|
||||
let mut entity = world.entity_mut(trigger.target().unwrap());
|
||||
let mut entity = world.entity_mut(trigger.target());
|
||||
if let Some(mut accessibility) = entity.get_mut::<AccessibilityNode>() {
|
||||
accessibility.set_disabled();
|
||||
}
|
||||
@ -29,7 +29,7 @@ pub(crate) fn on_remove_disabled(
|
||||
trigger: On<Remove, InteractionDisabled>,
|
||||
mut world: DeferredWorld,
|
||||
) {
|
||||
let mut entity = world.entity_mut(trigger.target().unwrap());
|
||||
let mut entity = world.entity_mut(trigger.target());
|
||||
if let Some(mut accessibility) = entity.get_mut::<AccessibilityNode>() {
|
||||
accessibility.clear_disabled();
|
||||
}
|
||||
@ -53,7 +53,7 @@ impl Checked {
|
||||
}
|
||||
|
||||
pub(crate) fn on_insert_is_checked(trigger: On<Insert, Checked>, mut world: DeferredWorld) {
|
||||
let mut entity = world.entity_mut(trigger.target().unwrap());
|
||||
let mut entity = world.entity_mut(trigger.target());
|
||||
let checked = entity.get::<Checked>().unwrap().get();
|
||||
if let Some(mut accessibility) = entity.get_mut::<AccessibilityNode>() {
|
||||
accessibility.set_toggled(match checked {
|
||||
@ -64,7 +64,7 @@ pub(crate) fn on_insert_is_checked(trigger: On<Insert, Checked>, mut world: Defe
|
||||
}
|
||||
|
||||
pub(crate) fn on_remove_is_checked(trigger: On<Remove, Checked>, mut world: DeferredWorld) {
|
||||
let mut entity = world.entity_mut(trigger.target().unwrap());
|
||||
let mut entity = world.entity_mut(trigger.target());
|
||||
if let Some(mut accessibility) = entity.get_mut::<AccessibilityNode>() {
|
||||
accessibility.set_toggled(accesskit::Toggled::False);
|
||||
}
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
use alloc::string::String;
|
||||
use bevy_ecs::{entity::Entity, event::Event};
|
||||
use bevy_ecs::{
|
||||
entity::Entity,
|
||||
event::{BufferedEvent, Event},
|
||||
};
|
||||
use bevy_input::{
|
||||
gestures::*,
|
||||
keyboard::{KeyboardFocusLost, KeyboardInput},
|
||||
@ -23,7 +26,7 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
||||
use crate::WindowTheme;
|
||||
|
||||
/// A window event that is sent whenever a window's logical size has changed.
|
||||
#[derive(Event, Debug, Clone, PartialEq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -45,7 +48,7 @@ pub struct WindowResized {
|
||||
|
||||
/// An event that indicates all of the application's windows should be redrawn,
|
||||
/// even if their control flow is set to `Wait` and there have been no window events.
|
||||
#[derive(Event, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -61,7 +64,7 @@ pub struct RequestRedraw;
|
||||
/// An event that is sent whenever a new window is created.
|
||||
///
|
||||
/// To create a new window, spawn an entity with a [`crate::Window`] on it.
|
||||
#[derive(Event, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -87,7 +90,7 @@ pub struct WindowCreated {
|
||||
///
|
||||
/// [`WindowPlugin`]: crate::WindowPlugin
|
||||
/// [`Window`]: crate::Window
|
||||
#[derive(Event, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -105,7 +108,7 @@ pub struct WindowCloseRequested {
|
||||
|
||||
/// An event that is sent whenever a window is closed. This will be sent when
|
||||
/// the window entity loses its [`Window`](crate::window::Window) component or is despawned.
|
||||
#[derive(Event, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -126,7 +129,7 @@ pub struct WindowClosed {
|
||||
|
||||
/// An event that is sent whenever a window is closing. This will be sent when
|
||||
/// after a [`WindowCloseRequested`] event is received and the window is in the process of closing.
|
||||
#[derive(Event, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -146,7 +149,7 @@ pub struct WindowClosing {
|
||||
///
|
||||
/// Note that if your application only has a single window, this event may be your last chance to
|
||||
/// persist state before the application terminates.
|
||||
#[derive(Event, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -176,7 +179,7 @@ pub struct WindowDestroyed {
|
||||
/// you should not use it for non-cursor-like behavior such as 3D camera control. Please see `MouseMotion` instead.
|
||||
///
|
||||
/// [`WindowEvent::CursorMoved`]: https://docs.rs/winit/latest/winit/event/enum.WindowEvent.html#variant.CursorMoved
|
||||
#[derive(Event, Debug, Clone, PartialEq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -201,7 +204,7 @@ pub struct CursorMoved {
|
||||
}
|
||||
|
||||
/// An event that is sent whenever the user's cursor enters a window.
|
||||
#[derive(Event, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -218,7 +221,7 @@ pub struct CursorEntered {
|
||||
}
|
||||
|
||||
/// An event that is sent whenever the user's cursor leaves a window.
|
||||
#[derive(Event, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -239,7 +242,7 @@ pub struct CursorLeft {
|
||||
/// This event is the translated version of the `WindowEvent::Ime` from the `winit` crate.
|
||||
///
|
||||
/// It is only sent if IME was enabled on the window with [`Window::ime_enabled`](crate::window::Window::ime_enabled).
|
||||
#[derive(Event, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -284,7 +287,7 @@ pub enum Ime {
|
||||
}
|
||||
|
||||
/// An event that indicates a window has received or lost focus.
|
||||
#[derive(Event, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -311,7 +314,7 @@ pub struct WindowFocused {
|
||||
/// It is the translated version of [`WindowEvent::Occluded`] from the `winit` crate.
|
||||
///
|
||||
/// [`WindowEvent::Occluded`]: https://docs.rs/winit/latest/winit/event/enum.WindowEvent.html#variant.Occluded
|
||||
#[derive(Event, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -330,7 +333,7 @@ pub struct WindowOccluded {
|
||||
}
|
||||
|
||||
/// An event that indicates a window's scale factor has changed.
|
||||
#[derive(Event, Debug, Clone, PartialEq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -349,7 +352,7 @@ pub struct WindowScaleFactorChanged {
|
||||
}
|
||||
|
||||
/// An event that indicates a window's OS-reported scale factor has changed.
|
||||
#[derive(Event, Debug, Clone, PartialEq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -368,7 +371,7 @@ pub struct WindowBackendScaleFactorChanged {
|
||||
}
|
||||
|
||||
/// Events related to files being dragged and dropped on a window.
|
||||
#[derive(Event, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -404,7 +407,7 @@ pub enum FileDragAndDrop {
|
||||
}
|
||||
|
||||
/// An event that is sent when a window is repositioned in physical pixels.
|
||||
#[derive(Event, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -426,7 +429,7 @@ pub struct WindowMoved {
|
||||
///
|
||||
/// This event is only sent when the window is relying on the system theme to control its appearance.
|
||||
/// i.e. It is only sent when [`Window::window_theme`](crate::window::Window::window_theme) is `None` and the system theme changes.
|
||||
#[derive(Event, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -445,7 +448,7 @@ pub struct WindowThemeChanged {
|
||||
}
|
||||
|
||||
/// Application lifetime events
|
||||
#[derive(Event, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -488,7 +491,7 @@ impl AppLifecycle {
|
||||
/// access window events in the order they were received from the
|
||||
/// operating system. Otherwise, the event types are individually
|
||||
/// readable with `EventReader<E>` (e.g. `EventReader<KeyboardInput>`).
|
||||
#[derive(Event, Debug, Clone, PartialEq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
|
||||
@ -195,7 +195,7 @@ fn update_cursors(
|
||||
fn on_remove_cursor_icon(trigger: On<Remove, CursorIcon>, mut commands: Commands) {
|
||||
// Use `try_insert` to avoid panic if the window is being destroyed.
|
||||
commands
|
||||
.entity(trigger.target().unwrap())
|
||||
.entity(trigger.target())
|
||||
.try_insert(PendingCursor(Some(CursorSource::System(
|
||||
convert_system_cursor_icon(SystemCursorIcon::Default),
|
||||
))));
|
||||
|
||||
@ -71,9 +71,9 @@ thread_local! {
|
||||
/// in systems.
|
||||
///
|
||||
/// When using eg. `MinimalPlugins` you can add this using `WinitPlugin::<WakeUp>::default()`, where
|
||||
/// `WakeUp` is the default `Event` that bevy uses.
|
||||
/// `WakeUp` is the default event that bevy uses.
|
||||
#[derive(Default)]
|
||||
pub struct WinitPlugin<T: Event = WakeUp> {
|
||||
pub struct WinitPlugin<T: BufferedEvent = WakeUp> {
|
||||
/// Allows the window (and the event loop) to be created on any thread
|
||||
/// instead of only the main thread.
|
||||
///
|
||||
@ -87,7 +87,7 @@ pub struct WinitPlugin<T: Event = WakeUp> {
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Event> Plugin for WinitPlugin<T> {
|
||||
impl<T: BufferedEvent> Plugin for WinitPlugin<T> {
|
||||
fn name(&self) -> &str {
|
||||
"bevy_winit::WinitPlugin"
|
||||
}
|
||||
@ -155,7 +155,7 @@ impl<T: Event> Plugin for WinitPlugin<T> {
|
||||
|
||||
/// The default event that can be used to wake the window loop
|
||||
/// Wakes up the loop if in wait state
|
||||
#[derive(Debug, Default, Clone, Copy, Event, Reflect)]
|
||||
#[derive(Debug, Default, Clone, Copy, Event, BufferedEvent, Reflect)]
|
||||
#[reflect(Debug, Default, Clone)]
|
||||
pub struct WakeUp;
|
||||
|
||||
@ -166,7 +166,7 @@ pub struct WakeUp;
|
||||
///
|
||||
/// When you receive this event it has already been handled by Bevy's main loop.
|
||||
/// Sending these events will NOT cause them to be processed by Bevy.
|
||||
#[derive(Debug, Clone, Event)]
|
||||
#[derive(Debug, Clone, Event, BufferedEvent)]
|
||||
pub struct RawWinitWindowEvent {
|
||||
/// The window for which the event was fired.
|
||||
pub window_id: WindowId,
|
||||
|
||||
@ -58,7 +58,7 @@ use crate::{
|
||||
|
||||
/// Persistent state that is used to run the [`App`] according to the current
|
||||
/// [`UpdateMode`].
|
||||
struct WinitAppRunnerState<T: Event> {
|
||||
struct WinitAppRunnerState<T: BufferedEvent> {
|
||||
/// The running app.
|
||||
app: App,
|
||||
/// Exit value once the loop is finished.
|
||||
@ -106,7 +106,7 @@ struct WinitAppRunnerState<T: Event> {
|
||||
)>,
|
||||
}
|
||||
|
||||
impl<T: Event> WinitAppRunnerState<T> {
|
||||
impl<T: BufferedEvent> WinitAppRunnerState<T> {
|
||||
fn new(mut app: App) -> Self {
|
||||
app.add_event::<T>();
|
||||
#[cfg(feature = "custom_cursor")]
|
||||
@ -198,7 +198,7 @@ pub enum CursorSource {
|
||||
#[derive(Component, Debug)]
|
||||
pub struct PendingCursor(pub Option<CursorSource>);
|
||||
|
||||
impl<T: Event> ApplicationHandler<T> for WinitAppRunnerState<T> {
|
||||
impl<T: BufferedEvent> ApplicationHandler<T> for WinitAppRunnerState<T> {
|
||||
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
|
||||
if event_loop.exiting() {
|
||||
return;
|
||||
@ -549,7 +549,7 @@ impl<T: Event> ApplicationHandler<T> for WinitAppRunnerState<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Event> WinitAppRunnerState<T> {
|
||||
impl<T: BufferedEvent> WinitAppRunnerState<T> {
|
||||
fn redraw_requested(&mut self, event_loop: &ActiveEventLoop) {
|
||||
let mut redraw_event_reader = EventCursor::<RequestRedraw>::default();
|
||||
|
||||
@ -934,7 +934,7 @@ impl<T: Event> WinitAppRunnerState<T> {
|
||||
///
|
||||
/// Overriding the app's [runner](bevy_app::App::runner) while using `WinitPlugin` will bypass the
|
||||
/// `EventLoop`.
|
||||
pub fn winit_runner<T: Event>(mut app: App, event_loop: EventLoop<T>) -> AppExit {
|
||||
pub fn winit_runner<T: BufferedEvent>(mut app: App, event_loop: EventLoop<T>) -> AppExit {
|
||||
if app.plugins_state() == PluginsState::Ready {
|
||||
app.finish();
|
||||
app.cleanup();
|
||||
|
||||
@ -65,12 +65,12 @@ fn change_material(
|
||||
mut asset_materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
// Get the `ColorOverride` of the entity, if it does not have a color override, skip
|
||||
let Ok(color_override) = color_override.get(trigger.target().unwrap()) else {
|
||||
let Ok(color_override) = color_override.get(trigger.target()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Iterate over all children recursively
|
||||
for descendants in children.iter_descendants(trigger.target().unwrap()) {
|
||||
for descendants in children.iter_descendants(trigger.target()) {
|
||||
// Get the material of the descendant
|
||||
if let Some(material) = mesh_materials
|
||||
.get(descendants)
|
||||
|
||||
@ -72,7 +72,7 @@ enum LightingMode {
|
||||
/// An event that's fired whenever the user changes the lighting mode.
|
||||
///
|
||||
/// This is also fired when the scene loads for the first time.
|
||||
#[derive(Clone, Copy, Default, Event)]
|
||||
#[derive(Clone, Copy, Default, Event, BufferedEvent)]
|
||||
struct LightingModeChanged;
|
||||
|
||||
#[derive(Clone, Copy, Component, Debug)]
|
||||
|
||||
@ -74,7 +74,7 @@ fn add_raytracing_meshes_on_scene_load(
|
||||
}
|
||||
}
|
||||
|
||||
for descendant in children.iter_descendants(trigger.target().unwrap()) {
|
||||
for descendant in children.iter_descendants(trigger.target()) {
|
||||
if let Ok(mesh) = mesh.get(descendant) {
|
||||
commands
|
||||
.entity(descendant)
|
||||
|
||||
@ -70,12 +70,12 @@ fn play_animation_when_ready(
|
||||
) {
|
||||
// The entity we spawned in `setup_mesh_and_animation` is the trigger's target.
|
||||
// Start by finding the AnimationToPlay component we added to that entity.
|
||||
if let Ok(animation_to_play) = animations_to_play.get(trigger.target().unwrap()) {
|
||||
if let Ok(animation_to_play) = animations_to_play.get(trigger.target()) {
|
||||
// The SceneRoot component will have spawned the scene as a hierarchy
|
||||
// of entities parented to our entity. Since the asset contained a skinned
|
||||
// mesh and animations, it will also have spawned an animation player
|
||||
// component. Search our entity's descendants to find the animation player.
|
||||
for child in children.iter_descendants(trigger.target().unwrap()) {
|
||||
for child in children.iter_descendants(trigger.target()) {
|
||||
if let Ok(mut player) = players.get_mut(child) {
|
||||
// Tell the animation player to start the animation and keep
|
||||
// repeating it.
|
||||
|
||||
@ -37,7 +37,7 @@ struct Animations {
|
||||
graph_handle: Handle<AnimationGraph>,
|
||||
}
|
||||
|
||||
#[derive(Event, Reflect, Clone)]
|
||||
#[derive(Event, EntityEvent, Reflect, Clone)]
|
||||
struct OnStep;
|
||||
|
||||
fn observe_on_step(
|
||||
@ -47,10 +47,7 @@ fn observe_on_step(
|
||||
transforms: Query<&GlobalTransform>,
|
||||
mut seeded_rng: ResMut<SeededRng>,
|
||||
) {
|
||||
let translation = transforms
|
||||
.get(trigger.target().unwrap())
|
||||
.unwrap()
|
||||
.translation();
|
||||
let translation = transforms.get(trigger.target()).unwrap().translation();
|
||||
// Spawn a bunch of particles.
|
||||
for _ in 0..14 {
|
||||
let horizontal = seeded_rng.0.r#gen::<Dir2>() * seeded_rng.0.gen_range(8.0..12.0);
|
||||
|
||||
@ -9,7 +9,6 @@ use bevy::{
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_event::<MessageEvent>()
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, animate_text_opacity)
|
||||
.add_observer(edit_message)
|
||||
@ -19,7 +18,7 @@ fn main() {
|
||||
#[derive(Component)]
|
||||
struct MessageText;
|
||||
|
||||
#[derive(Event, Clone)]
|
||||
#[derive(Event, EntityEvent, Clone)]
|
||||
struct MessageEvent {
|
||||
value: String,
|
||||
color: Color,
|
||||
|
||||
@ -38,7 +38,7 @@ fn main() {
|
||||
}
|
||||
|
||||
/// A basic message. This is what we will be sending from the [`CaptureLayer`] to [`CapturedLogEvents`] non-send resource.
|
||||
#[derive(Debug, Event)]
|
||||
#[derive(Debug, Event, BufferedEvent)]
|
||||
struct LogEvent {
|
||||
message: String,
|
||||
level: Level,
|
||||
|
||||
@ -20,7 +20,7 @@ fn main() {
|
||||
#[derive(Resource, Deref)]
|
||||
struct StreamReceiver(Receiver<u32>);
|
||||
|
||||
#[derive(Event)]
|
||||
#[derive(Event, BufferedEvent)]
|
||||
struct StreamEvent(u32);
|
||||
|
||||
fn setup(mut commands: Commands) {
|
||||
|
||||
@ -12,7 +12,7 @@ fn main() {
|
||||
.run();
|
||||
}
|
||||
|
||||
#[derive(Event, Default)]
|
||||
#[derive(Event, BufferedEvent, Default)]
|
||||
struct PlayPitch;
|
||||
|
||||
#[derive(Resource)]
|
||||
|
||||
@ -45,7 +45,7 @@ impl Component for MyComponent {
|
||||
#[derive(Resource, Default, Debug, Deref, DerefMut)]
|
||||
struct MyComponentIndex(HashMap<KeyCode, Entity>);
|
||||
|
||||
#[derive(Event)]
|
||||
#[derive(Event, BufferedEvent)]
|
||||
struct MyEvent;
|
||||
|
||||
fn main() {
|
||||
|
||||
@ -40,7 +40,7 @@ fn disable_entities_on_click(
|
||||
valid_query: Query<&DisableOnClick>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
let clicked_entity = trigger.target().unwrap();
|
||||
let clicked_entity = trigger.target();
|
||||
// Windows and text are entities and can be clicked!
|
||||
// We definitely don't want to disable the window itself,
|
||||
// because that would cause the app to close!
|
||||
|
||||
@ -6,17 +6,17 @@ use bevy::prelude::*;
|
||||
|
||||
// In order to send or receive events first you must define them
|
||||
// This event should be sent when something attempts to deal damage to another entity.
|
||||
#[derive(Event, Debug)]
|
||||
#[derive(Event, BufferedEvent, Debug)]
|
||||
struct DealDamage {
|
||||
pub amount: i32,
|
||||
}
|
||||
|
||||
// This event should be sent when an entity receives damage.
|
||||
#[derive(Event, Debug, Default)]
|
||||
#[derive(Event, BufferedEvent, Debug, Default)]
|
||||
struct DamageReceived;
|
||||
|
||||
// This event should be sent when an entity blocks damage with armor.
|
||||
#[derive(Event, Debug, Default)]
|
||||
#[derive(Event, BufferedEvent, Debug, Default)]
|
||||
struct ArmorBlockedDamage;
|
||||
|
||||
// This resource represents a timer used to determine when to deal damage
|
||||
|
||||
@ -53,8 +53,8 @@ fn setup(mut commands: Commands) {
|
||||
// - **auto_propagate:**
|
||||
// We can also choose whether or not this event will propagate by default when triggered. If this is
|
||||
// false, it will only propagate following a call to `On::propagate(true)`.
|
||||
#[derive(Clone, Component, Event)]
|
||||
#[event(traversal = &'static ChildOf, auto_propagate)]
|
||||
#[derive(Clone, Component, Event, EntityEvent)]
|
||||
#[entity_event(traversal = &'static ChildOf, auto_propagate)]
|
||||
struct Attack {
|
||||
damage: u16,
|
||||
}
|
||||
@ -78,14 +78,14 @@ fn attack_armor(entities: Query<Entity, With<Armor>>, mut commands: Commands) {
|
||||
}
|
||||
|
||||
fn attack_hits(trigger: On<Attack>, name: Query<&Name>) {
|
||||
if let Ok(name) = name.get(trigger.target().unwrap()) {
|
||||
if let Ok(name) = name.get(trigger.target()) {
|
||||
info!("Attack hit {}", name);
|
||||
}
|
||||
}
|
||||
|
||||
/// A callback placed on [`Armor`], checking if it absorbed all the [`Attack`] damage.
|
||||
fn block_attack(mut trigger: On<Attack>, armor: Query<(&Armor, &Name)>) {
|
||||
let (armor, name) = armor.get(trigger.target().unwrap()).unwrap();
|
||||
let (armor, name) = armor.get(trigger.target()).unwrap();
|
||||
let attack = trigger.event_mut();
|
||||
let damage = attack.damage.saturating_sub(**armor);
|
||||
if damage > 0 {
|
||||
@ -110,14 +110,14 @@ fn take_damage(
|
||||
mut app_exit: EventWriter<AppExit>,
|
||||
) {
|
||||
let attack = trigger.event();
|
||||
let (mut hp, name) = hp.get_mut(trigger.target().unwrap()).unwrap();
|
||||
let (mut hp, name) = hp.get_mut(trigger.target()).unwrap();
|
||||
**hp = hp.saturating_sub(attack.damage);
|
||||
|
||||
if **hp > 0 {
|
||||
info!("{} has {:.1} HP", name, hp.0);
|
||||
} else {
|
||||
warn!("💀 {} has died a gruesome death", name);
|
||||
commands.entity(trigger.target().unwrap()).despawn();
|
||||
commands.entity(trigger.target()).despawn();
|
||||
app_exit.write(AppExit::Success);
|
||||
}
|
||||
|
||||
|
||||
@ -60,13 +60,13 @@ impl Mine {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Event)]
|
||||
#[derive(Event, EntityEvent)]
|
||||
struct ExplodeMines {
|
||||
pos: Vec2,
|
||||
radius: f32,
|
||||
}
|
||||
|
||||
#[derive(Event)]
|
||||
#[derive(Event, EntityEvent)]
|
||||
struct Explode;
|
||||
|
||||
fn setup(mut commands: Commands) {
|
||||
@ -113,33 +113,29 @@ fn setup(mut commands: Commands) {
|
||||
}
|
||||
|
||||
fn on_add_mine(trigger: On<Add, Mine>, query: Query<&Mine>, mut index: ResMut<SpatialIndex>) {
|
||||
let mine = query.get(trigger.target().unwrap()).unwrap();
|
||||
let mine = query.get(trigger.target()).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.target().unwrap());
|
||||
index.map.entry(tile).or_default().insert(trigger.target());
|
||||
}
|
||||
|
||||
// Remove despawned mines from our index
|
||||
fn on_remove_mine(trigger: On<Remove, Mine>, query: Query<&Mine>, mut index: ResMut<SpatialIndex>) {
|
||||
let mine = query.get(trigger.target().unwrap()).unwrap();
|
||||
let mine = query.get(trigger.target()).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.target().unwrap());
|
||||
set.remove(&trigger.target());
|
||||
});
|
||||
}
|
||||
|
||||
fn explode_mine(trigger: On<Explode>, query: Query<&Mine>, mut commands: Commands) {
|
||||
// If a triggered event is targeting a specific entity you can access it with `.target()`
|
||||
let id = trigger.target().unwrap();
|
||||
let id = trigger.target();
|
||||
let Ok(mut entity) = commands.get_entity(id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
@ -50,7 +50,7 @@ fn remove_component(
|
||||
|
||||
fn react_on_removal(trigger: On<Remove, MyComponent>, mut query: Query<&mut Sprite>) {
|
||||
// The `Remove` trigger was automatically called on the `Entity` that had its `MyComponent` removed.
|
||||
let entity = trigger.target().unwrap();
|
||||
let entity = trigger.target();
|
||||
if let Ok(mut sprite) = query.get_mut(entity) {
|
||||
sprite.color = Color::srgb(0.5, 1., 1.);
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
//! From time to time, you may find that you want to both send and receive an event of the same type in a single system.
|
||||
//!
|
||||
//! Of course, this results in an error: the borrows of [`EventWriter`] and [`EventReader`] overlap,
|
||||
//! if and only if the [`Event`] type is the same.
|
||||
//! if and only if the [`BufferedEvent`] type is the same.
|
||||
//! One system parameter borrows the [`Events`] resource mutably, and another system parameter borrows the [`Events`] resource immutably.
|
||||
//! If Bevy allowed this, this would violate Rust's rules against aliased mutability.
|
||||
//! In other words, this would be Undefined Behavior (UB)!
|
||||
@ -46,10 +46,10 @@ fn main() {
|
||||
app.update();
|
||||
}
|
||||
|
||||
#[derive(Event)]
|
||||
#[derive(Event, BufferedEvent)]
|
||||
struct A;
|
||||
|
||||
#[derive(Event)]
|
||||
#[derive(Event, BufferedEvent)]
|
||||
struct B;
|
||||
|
||||
// This works fine, because the types are different,
|
||||
@ -62,7 +62,7 @@ fn read_and_write_different_event_types(mut a: EventWriter<A>, mut b: EventReade
|
||||
}
|
||||
|
||||
/// A dummy event type.
|
||||
#[derive(Debug, Clone, Event)]
|
||||
#[derive(Debug, Clone, Event, BufferedEvent)]
|
||||
struct DebugEvent {
|
||||
resend_from_param_set: bool,
|
||||
resend_from_local_event_reader: bool,
|
||||
|
||||
@ -89,7 +89,7 @@ struct Ball;
|
||||
#[derive(Component, Deref, DerefMut)]
|
||||
struct Velocity(Vec2);
|
||||
|
||||
#[derive(Event, Default)]
|
||||
#[derive(Event, BufferedEvent, Default)]
|
||||
struct CollisionEvent;
|
||||
|
||||
#[derive(Component)]
|
||||
|
||||
@ -6,7 +6,7 @@ use bevy::{ecs::system::EntityCommands, prelude::*};
|
||||
|
||||
/// An event that's sent whenever the user changes one of the settings by
|
||||
/// clicking a radio button.
|
||||
#[derive(Clone, Event, Deref, DerefMut)]
|
||||
#[derive(Clone, Event, BufferedEvent, Deref, DerefMut)]
|
||||
pub struct WidgetClickEvent<T>(T);
|
||||
|
||||
/// A marker component that we place on all widgets that send
|
||||
|
||||
@ -106,7 +106,7 @@ struct DelayedComponentTimer(Timer);
|
||||
#[component(immutable)]
|
||||
struct DelayedComponent<B: Bundle>(B);
|
||||
|
||||
#[derive(Event)]
|
||||
#[derive(Event, EntityEvent)]
|
||||
struct Unwrap;
|
||||
|
||||
fn tick_timers(
|
||||
@ -127,10 +127,7 @@ fn tick_timers(
|
||||
}
|
||||
|
||||
fn unwrap<B: Bundle>(trigger: On<Unwrap>, world: &mut World) {
|
||||
if let Some(mut target) = trigger
|
||||
.target()
|
||||
.and_then(|target| world.get_entity_mut(target).ok())
|
||||
{
|
||||
if let Ok(mut target) = world.get_entity_mut(trigger.target()) {
|
||||
if let Some(DelayedComponent(bundle)) = target.take::<DelayedComponent<B>>() {
|
||||
target.insert(bundle);
|
||||
}
|
||||
|
||||
@ -48,13 +48,13 @@ fn setup_scene(
|
||||
.observe(on_click_spawn_cube)
|
||||
.observe(
|
||||
|out: On<Pointer<Out>>, mut texts: Query<&mut TextColor>| {
|
||||
let mut text_color = texts.get_mut(out.target().unwrap()).unwrap();
|
||||
let mut text_color = texts.get_mut(out.target()).unwrap();
|
||||
text_color.0 = Color::WHITE;
|
||||
},
|
||||
)
|
||||
.observe(
|
||||
|over: On<Pointer<Over>>, mut texts: Query<&mut TextColor>| {
|
||||
let mut color = texts.get_mut(over.target().unwrap()).unwrap();
|
||||
let mut color = texts.get_mut(over.target()).unwrap();
|
||||
color.0 = bevy::color::palettes::tailwind::CYAN_400.into();
|
||||
},
|
||||
);
|
||||
@ -102,7 +102,7 @@ fn on_click_spawn_cube(
|
||||
}
|
||||
|
||||
fn on_drag_rotate(drag: On<Pointer<Drag>>, mut transforms: Query<&mut Transform>) {
|
||||
if let Ok(mut transform) = transforms.get_mut(drag.target().unwrap()) {
|
||||
if let Ok(mut transform) = transforms.get_mut(drag.target()) {
|
||||
transform.rotate_y(drag.delta.x * 0.02);
|
||||
transform.rotate_x(drag.delta.y * 0.02);
|
||||
}
|
||||
|
||||
@ -157,14 +157,14 @@ fn setup_scene(
|
||||
}
|
||||
|
||||
/// Returns an observer that updates the entity's material to the one specified.
|
||||
fn update_material_on<E>(
|
||||
fn update_material_on<E: EntityEvent>(
|
||||
new_material: Handle<StandardMaterial>,
|
||||
) -> impl Fn(On<E>, Query<&mut MeshMaterial3d<StandardMaterial>>) {
|
||||
// An observer closure that captures `new_material`. We do this to avoid needing to write four
|
||||
// versions of this observer, each triggered by a different event and with a different hardcoded
|
||||
// material. Instead, the event type is a generic, and the material is passed in.
|
||||
move |trigger, mut query| {
|
||||
if let Ok(mut material) = query.get_mut(trigger.target().unwrap()) {
|
||||
if let Ok(mut material) = query.get_mut(trigger.target()) {
|
||||
material.0 = new_material.clone();
|
||||
}
|
||||
}
|
||||
@ -191,7 +191,7 @@ fn rotate(mut query: Query<&mut Transform, With<Shape>>, time: Res<Time>) {
|
||||
|
||||
/// An observer to rotate an entity when it is dragged
|
||||
fn rotate_on_drag(drag: On<Pointer<Drag>>, mut transforms: Query<&mut Transform>) {
|
||||
let mut transform = transforms.get_mut(drag.target().unwrap()).unwrap();
|
||||
let mut transform = transforms.get_mut(drag.target()).unwrap();
|
||||
transform.rotate_y(drag.delta.x * 0.02);
|
||||
transform.rotate_x(drag.delta.y * 0.02);
|
||||
}
|
||||
|
||||
@ -26,12 +26,12 @@ fn setup_scene(
|
||||
))
|
||||
.observe(on_click_spawn_cube)
|
||||
.observe(|out: On<Pointer<Out>>, mut texts: Query<&mut TextColor>| {
|
||||
let mut text_color = texts.get_mut(out.target().unwrap()).unwrap();
|
||||
let mut text_color = texts.get_mut(out.target()).unwrap();
|
||||
text_color.0 = Color::WHITE;
|
||||
})
|
||||
.observe(
|
||||
|over: On<Pointer<Over>>, mut texts: Query<&mut TextColor>| {
|
||||
let mut color = texts.get_mut(over.target().unwrap()).unwrap();
|
||||
let mut color = texts.get_mut(over.target()).unwrap();
|
||||
color.0 = bevy::color::palettes::tailwind::CYAN_400.into();
|
||||
},
|
||||
);
|
||||
@ -78,7 +78,7 @@ fn on_click_spawn_cube(
|
||||
}
|
||||
|
||||
fn on_drag_rotate(drag: On<Pointer<Drag>>, mut transforms: Query<&mut Transform>) {
|
||||
if let Ok(mut transform) = transforms.get_mut(drag.target().unwrap()) {
|
||||
if let Ok(mut transform) = transforms.get_mut(drag.target()) {
|
||||
transform.rotate_y(drag.delta.x * 0.02);
|
||||
transform.rotate_x(drag.delta.y * 0.02);
|
||||
}
|
||||
|
||||
@ -150,9 +150,11 @@ fn setup_atlas(
|
||||
}
|
||||
|
||||
// An observer that changes the target entity's color.
|
||||
fn recolor_on<E: Debug + Clone + Reflect>(color: Color) -> impl Fn(On<E>, Query<&mut Sprite>) {
|
||||
fn recolor_on<E: EntityEvent + Debug + Clone + Reflect>(
|
||||
color: Color,
|
||||
) -> impl Fn(On<E>, Query<&mut Sprite>) {
|
||||
move |ev, mut sprites| {
|
||||
let Ok(mut sprite) = sprites.get_mut(ev.target().unwrap()) else {
|
||||
let Ok(mut sprite) = sprites.get_mut(ev.target()) else {
|
||||
return;
|
||||
};
|
||||
sprite.color = color;
|
||||
|
||||
@ -281,7 +281,7 @@ mod animation {
|
||||
animation: Res<Animation>,
|
||||
mut players: Query<(Entity, &mut AnimationPlayer)>,
|
||||
) {
|
||||
for child in children.iter_descendants(trigger.target().unwrap()) {
|
||||
for child in children.iter_descendants(trigger.target()) {
|
||||
if let Ok((entity, mut player)) = players.get_mut(child) {
|
||||
let mut transitions = AnimationTransitions::new();
|
||||
transitions
|
||||
|
||||
@ -81,7 +81,7 @@ fn button_on_add_pressed(
|
||||
mut text_query: Query<&mut Text>,
|
||||
) {
|
||||
if let Ok((hovered, disabled, mut color, mut border_color, children)) =
|
||||
buttons.get_mut(trigger.target().unwrap())
|
||||
buttons.get_mut(trigger.target())
|
||||
{
|
||||
let mut text = text_query.get_mut(children[0]).unwrap();
|
||||
set_button_style(
|
||||
@ -110,7 +110,7 @@ fn button_on_remove_pressed(
|
||||
mut text_query: Query<&mut Text>,
|
||||
) {
|
||||
if let Ok((hovered, disabled, mut color, mut border_color, children)) =
|
||||
buttons.get_mut(trigger.target().unwrap())
|
||||
buttons.get_mut(trigger.target())
|
||||
{
|
||||
let mut text = text_query.get_mut(children[0]).unwrap();
|
||||
set_button_style(
|
||||
@ -139,7 +139,7 @@ fn button_on_add_disabled(
|
||||
mut text_query: Query<&mut Text>,
|
||||
) {
|
||||
if let Ok((pressed, hovered, mut color, mut border_color, children)) =
|
||||
buttons.get_mut(trigger.target().unwrap())
|
||||
buttons.get_mut(trigger.target())
|
||||
{
|
||||
let mut text = text_query.get_mut(children[0]).unwrap();
|
||||
set_button_style(
|
||||
@ -168,7 +168,7 @@ fn button_on_remove_disabled(
|
||||
mut text_query: Query<&mut Text>,
|
||||
) {
|
||||
if let Ok((pressed, hovered, mut color, mut border_color, children)) =
|
||||
buttons.get_mut(trigger.target().unwrap())
|
||||
buttons.get_mut(trigger.target())
|
||||
{
|
||||
let mut text = text_query.get_mut(children[0]).unwrap();
|
||||
set_button_style(
|
||||
@ -198,7 +198,7 @@ fn button_on_change_hover(
|
||||
mut text_query: Query<&mut Text>,
|
||||
) {
|
||||
if let Ok((pressed, hovered, disabled, mut color, mut border_color, children)) =
|
||||
buttons.get_mut(trigger.target().unwrap())
|
||||
buttons.get_mut(trigger.target())
|
||||
{
|
||||
if children.is_empty() {
|
||||
return;
|
||||
@ -262,7 +262,7 @@ fn slider_on_add_disabled(
|
||||
children: Query<&Children>,
|
||||
mut thumbs: Query<(&mut BackgroundColor, Has<DemoSliderThumb>), Without<DemoSlider>>,
|
||||
) {
|
||||
if let Ok((slider_ent, hovered)) = sliders.get(trigger.target().unwrap()) {
|
||||
if let Ok((slider_ent, hovered)) = sliders.get(trigger.target()) {
|
||||
for child in children.iter_descendants(slider_ent) {
|
||||
if let Ok((mut thumb_bg, is_thumb)) = thumbs.get_mut(child) {
|
||||
if is_thumb {
|
||||
@ -279,7 +279,7 @@ fn slider_on_remove_disabled(
|
||||
children: Query<&Children>,
|
||||
mut thumbs: Query<(&mut BackgroundColor, Has<DemoSliderThumb>), Without<DemoSlider>>,
|
||||
) {
|
||||
if let Ok((slider_ent, hovered)) = sliders.get(trigger.target().unwrap()) {
|
||||
if let Ok((slider_ent, hovered)) = sliders.get(trigger.target()) {
|
||||
for child in children.iter_descendants(slider_ent) {
|
||||
if let Ok((mut thumb_bg, is_thumb)) = thumbs.get_mut(child) {
|
||||
if is_thumb {
|
||||
@ -296,7 +296,7 @@ fn slider_on_change_hover(
|
||||
children: Query<&Children>,
|
||||
mut thumbs: Query<(&mut BackgroundColor, Has<DemoSliderThumb>), Without<DemoSlider>>,
|
||||
) {
|
||||
if let Ok((slider_ent, hovered, disabled)) = sliders.get(trigger.target().unwrap()) {
|
||||
if let Ok((slider_ent, hovered, disabled)) = sliders.get(trigger.target()) {
|
||||
for child in children.iter_descendants(slider_ent) {
|
||||
if let Ok((mut thumb_bg, is_thumb)) = thumbs.get_mut(child) {
|
||||
if is_thumb {
|
||||
@ -313,7 +313,7 @@ fn slider_on_change_value(
|
||||
children: Query<&Children>,
|
||||
mut thumbs: Query<(&mut Node, Has<DemoSliderThumb>), Without<DemoSlider>>,
|
||||
) {
|
||||
if let Ok((slider_ent, value, range)) = sliders.get(trigger.target().unwrap()) {
|
||||
if let Ok((slider_ent, value, range)) = sliders.get(trigger.target()) {
|
||||
for child in children.iter_descendants(slider_ent) {
|
||||
if let Ok((mut thumb_node, is_thumb)) = thumbs.get_mut(child) {
|
||||
if is_thumb {
|
||||
@ -330,7 +330,7 @@ fn slider_on_change_range(
|
||||
children: Query<&Children>,
|
||||
mut thumbs: Query<(&mut Node, Has<DemoSliderThumb>), Without<DemoSlider>>,
|
||||
) {
|
||||
if let Ok((slider_ent, value, range)) = sliders.get(trigger.target().unwrap()) {
|
||||
if let Ok((slider_ent, value, range)) = sliders.get(trigger.target()) {
|
||||
for child in children.iter_descendants(slider_ent) {
|
||||
if let Ok((mut thumb_node, is_thumb)) = thumbs.get_mut(child) {
|
||||
if is_thumb {
|
||||
|
||||
@ -70,7 +70,7 @@ fn universal_button_click_behavior(
|
||||
mut trigger: On<Pointer<Click>>,
|
||||
mut button_query: Query<(&mut BackgroundColor, &mut ResetTimer)>,
|
||||
) {
|
||||
let button_entity = trigger.target().unwrap();
|
||||
let button_entity = trigger.target();
|
||||
if let Ok((mut color, mut reset_timer)) = button_query.get_mut(button_entity) {
|
||||
// This would be a great place to play a little sound effect too!
|
||||
color.0 = PRESSED_BUTTON.into();
|
||||
|
||||
@ -96,9 +96,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
.observe(
|
||||
|trigger: On<Pointer<Press>>, mut commands: Commands| {
|
||||
if trigger.event().button == PointerButton::Primary {
|
||||
commands
|
||||
.entity(trigger.target().unwrap())
|
||||
.despawn();
|
||||
commands.entity(trigger.target()).despawn();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@ -35,7 +35,7 @@ enum Constraint {
|
||||
#[derive(Copy, Clone, Component)]
|
||||
struct ButtonValue(Val);
|
||||
|
||||
#[derive(Event)]
|
||||
#[derive(Event, BufferedEvent)]
|
||||
struct ButtonActivatedEvent(Entity);
|
||||
|
||||
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
|
||||
@ -142,7 +142,7 @@ fn setup(mut commands: Commands) {
|
||||
.observe(
|
||||
|mut trigger: On<Pointer<Click>>,
|
||||
mut focus: ResMut<InputFocus>| {
|
||||
focus.0 = Some(trigger.target().unwrap());
|
||||
focus.0 = Some(trigger.target());
|
||||
trigger.propagate(false);
|
||||
},
|
||||
);
|
||||
|
||||
@ -91,7 +91,7 @@ fn test(
|
||||
|
||||
fn on_drag_viewport(drag: On<Pointer<Drag>>, mut node_query: Query<&mut Node>) {
|
||||
if matches!(drag.button, PointerButton::Secondary) {
|
||||
let mut node = node_query.get_mut(drag.target().unwrap()).unwrap();
|
||||
let mut node = node_query.get_mut(drag.target()).unwrap();
|
||||
|
||||
if let (Val::Px(top), Val::Px(left)) = (node.top, node.left) {
|
||||
node.left = Val::Px(left + drag.delta.x);
|
||||
@ -102,7 +102,7 @@ fn on_drag_viewport(drag: On<Pointer<Drag>>, mut node_query: Query<&mut Node>) {
|
||||
|
||||
fn on_drag_cuboid(drag: On<Pointer<Drag>>, mut transform_query: Query<&mut Transform>) {
|
||||
if matches!(drag.button, PointerButton::Primary) {
|
||||
let mut transform = transform_query.get_mut(drag.target().unwrap()).unwrap();
|
||||
let mut transform = transform_query.get_mut(drag.target()).unwrap();
|
||||
transform.rotate_y(drag.delta.x * 0.02);
|
||||
transform.rotate_x(drag.delta.y * 0.02);
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ use bevy::{
|
||||
};
|
||||
use std::fmt::Formatter;
|
||||
|
||||
#[derive(Default, Debug, Event)]
|
||||
#[derive(Default, Debug, Event, BufferedEvent)]
|
||||
enum CustomEvent {
|
||||
#[default]
|
||||
WakeUp,
|
||||
|
||||
14
release-content/migration-guides/event_split.md
Normal file
14
release-content/migration-guides/event_split.md
Normal file
@ -0,0 +1,14 @@
|
||||
---
|
||||
title: Event Split
|
||||
pull_requests: [19647]
|
||||
---
|
||||
|
||||
The `Event` trait was previously used for all types of events: "observer events" with and without targets,
|
||||
and "buffered events" using `EventReader` and `EventWriter`.
|
||||
|
||||
Buffered events and targeted events have now been split into dedicated `BufferedEvent` and `EntityEvent` traits.
|
||||
An event with just the `Event` trait implemented only supports non-targeted APIs such as global observers and the `trigger` method.
|
||||
|
||||
If an event is used with `trigger_targets` or an entity observer, make sure you have derived `EntityEvent` for it.
|
||||
|
||||
If an event is used with `EventReader` or `EventWriter`, make sure you have derived `BufferedEvent` for it.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user