Skip to main content

pebble/ecs/
system_condition.rs

1use crate::ecs::{
2    resources::Resources,
3    system::{IntoSystem, System},
4    system_set::IntoSystemSet,
5};
6
7/// A predicate checked before a system runs. If `should_run` returns
8/// `false`, the wrapped system's body is skipped entirely for that tick —
9/// its `SystemParam`s are never fetched.
10///
11/// Re-checked every tick. This is deliberate: a resource that exists now
12/// is not guaranteed to exist forever (e.g. if something explicitly removes
13/// it later), so conditions should not assume "ready once" means "ready
14/// forever".
15pub trait RunCondition: 'static {
16    fn should_run(world: &hecs::World, resources: &Resources) -> bool;
17}
18
19/// Runs only while resource `T` exists.
20pub struct ResourceExists<T>(std::marker::PhantomData<T>);
21impl<T: 'static + Send + Sync> RunCondition for ResourceExists<T> {
22    fn should_run(world: &hecs::World, resources: &Resources) -> bool {
23        resources.has_resource::<T>(world)
24    }
25}
26
27/// Runs only while both `A` and `B` would run.
28pub struct And<A, B>(std::marker::PhantomData<(A, B)>);
29impl<A: RunCondition, B: RunCondition> RunCondition for And<A, B> {
30    fn should_run(world: &hecs::World, resources: &Resources) -> bool {
31        A::should_run(world, resources) && B::should_run(world, resources)
32    }
33}
34
35/// Runs while either `A` or `B` would run.
36pub struct Or<A, B>(std::marker::PhantomData<(A, B)>);
37impl<A: RunCondition, B: RunCondition> RunCondition for Or<A, B> {
38    fn should_run(world: &hecs::World, resources: &Resources) -> bool {
39        A::should_run(world, resources) || B::should_run(world, resources)
40    }
41}
42
43/// Wraps a [`System`], skipping it for a tick whenever `C::should_run`
44/// returns `false`.
45pub struct Conditional<S, C> {
46    inner: S,
47    _marker: std::marker::PhantomData<C>,
48}
49
50impl<S: System, C: RunCondition> System for Conditional<S, C> {
51    fn run(&mut self, world: &hecs::World, resources: &Resources) {
52        if C::should_run(world, resources) {
53            self.inner.run(world, resources);
54        }
55    }
56
57    fn requires(&self) -> Vec<crate::ecs::system::RequiredResource> {
58        self.inner.requires()
59    }
60}
61
62/// Adds [`.run_if`](RunIfExt::run_if) to anything convertible into a
63/// [`System`], gating it on a [`RunCondition`].
64pub trait RunIfExt<Marker>: IntoSystem<Marker> + Sized {
65    fn run_if<C: RunCondition>(self) -> RunIfSystem<Self, Marker, C> {
66        RunIfSystem {
67            inner: self,
68            _marker: std::marker::PhantomData,
69        }
70    }
71}
72
73impl<Marker, T: IntoSystem<Marker>> RunIfExt<Marker> for T {}
74
75pub struct RunIfSystem<T, Marker, C> {
76    inner: T,
77    _marker: std::marker::PhantomData<(Marker, C)>,
78}
79
80impl<T, Marker, C> IntoSystem<Marker> for RunIfSystem<T, Marker, C>
81where
82    T: IntoSystem<Marker>,
83    C: RunCondition,
84{
85    type System = Conditional<T::System, C>;
86
87    fn into_system(self) -> Self::System {
88        Conditional {
89            inner: self.inner.into_system(),
90            _marker: std::marker::PhantomData,
91        }
92    }
93}
94
95/// Adds [`.run_if`](SystemSetRunIfExt::run_if) to an entire
96/// [`IntoSystemSet`] tuple at once, applying the same condition to every
97/// system in it.
98pub trait SystemSetRunIfExt<M>: IntoSystemSet<M> + Sized {
99    fn run_if<C: RunCondition>(self) -> ConditionalSet<Self, M, C> {
100        ConditionalSet {
101            inner: self,
102            _marker: std::marker::PhantomData,
103        }
104    }
105}
106
107impl<M, T: IntoSystemSet<M>> SystemSetRunIfExt<M> for T {}
108
109pub struct ConditionalSet<T, M, C> {
110    inner: T,
111    _marker: std::marker::PhantomData<(M, C)>,
112}
113
114/// Wraps an already-boxed, type-erased [`System`] with a [`RunCondition`].
115/// Used internally by [`ConditionalSet`], since by the time a system set is
116/// flattened into `Vec<Box<dyn System>>`, individual marker types are gone.
117struct BoxedConditional<C> {
118    inner: Box<dyn System>,
119    _marker: std::marker::PhantomData<C>,
120}
121
122impl<C: RunCondition> System for BoxedConditional<C> {
123    fn run(&mut self, world: &hecs::World, resources: &Resources) {
124        if C::should_run(world, resources) {
125            self.inner.run(world, resources);
126        }
127    }
128
129    fn requires(&self) -> Vec<crate::ecs::system::RequiredResource> {
130        self.inner.requires()
131    }
132}
133
134impl<T, M, C> IntoSystemSet<M> for ConditionalSet<T, M, C>
135where
136    T: IntoSystemSet<M>,
137    C: RunCondition,
138{
139    fn into_system_set(self) -> Vec<Box<dyn System>> {
140        self.inner
141            .into_system_set()
142            .into_iter()
143            .map(|system| {
144                Box::new(BoxedConditional::<C> {
145                    inner: system,
146                    _marker: std::marker::PhantomData,
147                }) as Box<dyn System>
148            })
149            .collect()
150    }
151}