Skip to main content

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