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}