bevy_ecs/observer/condition.rs
1//! Run conditions for observers.
2//!
3//! This module provides the types needed to add run conditions to observers,
4//! allowing them to conditionally execute based on world state.
5
6use alloc::{boxed::Box, vec::Vec};
7use core::marker::PhantomData;
8
9use crate::{
10 bundle::Bundle,
11 event::Event,
12 schedule::{BoxedCondition, SystemCondition},
13 system::{IntoObserverSystem, IntoSystem},
14 world::{unsafe_world_cell::UnsafeWorldCell, World},
15};
16
17/// Stores a boxed condition system for an observer.
18pub(crate) struct ObserverCondition {
19 condition: BoxedCondition,
20}
21
22impl ObserverCondition {
23 pub(crate) fn new<M>(condition: impl SystemCondition<M>) -> Self {
24 Self {
25 condition: Box::new(IntoSystem::into_system(condition)),
26 }
27 }
28
29 pub(crate) fn from_boxed(condition: BoxedCondition) -> Self {
30 Self { condition }
31 }
32
33 pub(crate) fn initialize(&mut self, world: &mut World) {
34 self.condition.initialize(world);
35 }
36
37 /// # Safety
38 /// - The condition must be initialized.
39 /// - The world cell must have valid access for the condition's read-only parameters.
40 pub(crate) unsafe fn check(&mut self, world: UnsafeWorldCell) -> bool {
41 // SAFETY: Caller ensures world is valid and condition is initialized.
42 // Conditions are read-only systems, so they won't cause aliasing issues.
43 unsafe { self.condition.run_unsafe((), world) }.unwrap_or(false)
44 }
45}
46
47#[doc(hidden)]
48pub struct ObserverWithConditionMarker;
49
50/// An observer system with run conditions that preserves event type information.
51///
52/// This type is returned by [`ObserverSystemExt::run_if`](super::ObserverSystemExt::run_if)
53/// and allows `entity.observe(system.run_if(cond))` to work with compile-time
54/// verification that the event implements [`EntityEvent`](crate::event::EntityEvent).
55pub struct ObserverWithCondition<E: Event, B: Bundle, M, S: IntoObserverSystem<E, B, M>> {
56 pub(crate) system: S,
57 pub(crate) conditions: Vec<BoxedCondition>,
58 pub(crate) _marker: PhantomData<fn() -> (E, B, M)>,
59}
60
61impl<E: Event, B: Bundle, M, S: IntoObserverSystem<E, B, M>> ObserverWithCondition<E, B, M, S> {
62 /// Adds another run condition to this observer.
63 ///
64 /// All conditions must return `true` for the observer to run (AND semantics).
65 ///
66 /// **Note:** Chained `.run_if()` calls do **not** short-circuit — all conditions
67 /// run every time to maintain correct change detection ticks. If you need
68 /// short-circuit behavior, use `.run_if(a.and(b))`, but be aware this may cause
69 /// stale `Changed<T>` detection if the second condition is frequently skipped.
70 ///
71 /// # Example
72 ///
73 /// ```
74 /// # use bevy_ecs::prelude::*;
75 /// # #[derive(Event)]
76 /// # struct MyEvent;
77 /// # #[derive(Resource)]
78 /// # struct CondA(bool);
79 /// # #[derive(Resource)]
80 /// # struct CondB(bool);
81 /// # fn on_event(_: On<MyEvent>) {}
82 /// # let mut world = World::new();
83 /// # world.insert_resource(CondA(true));
84 /// # world.insert_resource(CondB(true));
85 /// world.add_observer(
86 /// on_event
87 /// .run_if(|a: Res<CondA>| a.0)
88 /// .run_if(|b: Res<CondB>| b.0)
89 /// );
90 /// ```
91 pub fn run_if<C, CM>(mut self, condition: C) -> Self
92 where
93 C: SystemCondition<CM>,
94 {
95 self.conditions
96 .push(Box::new(IntoSystem::into_system(condition)));
97 self
98 }
99
100 pub(crate) fn take_conditions(self) -> (S, Vec<ObserverCondition>) {
101 let conditions = self
102 .conditions
103 .into_iter()
104 .map(ObserverCondition::from_boxed)
105 .collect();
106 (self.system, conditions)
107 }
108}