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    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}