Skip to main content

game_toolkit_platformer/anim/
cond.rs

1//! [`Cond`]: when a transition fires, as a tree of tests on parameters and state.
2
3use super::{ParamId, Params};
4
5/// A condition over parameters `P` (names in a [`GraphSpec`](super::GraphSpec), ids in a
6/// compiled [`Graph`](super::Graph)).
7#[derive(Clone, Debug, PartialEq)]
8pub enum Cond<P = ParamId> {
9    /// Always holds.
10    Always,
11    /// A bool parameter is on.
12    Is(P),
13    /// A trigger is pending. A transition that fires consumes the triggers it tested.
14    Trigger(P),
15    /// A float parameter is above the value.
16    Above(P, f32),
17    /// A float parameter is below the value.
18    Below(P, f32),
19    /// The state's clip has finished (only [`Playback::Once`](super::Playback) clips do).
20    Finished,
21    /// At least this many seconds in the state.
22    After(f32),
23    /// The inner condition does not hold. Triggers under a `Not` are never consumed.
24    Not(Box<Cond<P>>),
25    /// Every inner condition holds.
26    All(Vec<Cond<P>>),
27    /// At least one inner condition holds.
28    Any(Vec<Cond<P>>),
29}
30
31impl<P> Cond<P> {
32    /// The negation of `c`.
33    #[allow(clippy::should_implement_trait)]
34    pub fn not(c: Cond<P>) -> Self {
35        Cond::Not(Box::new(c))
36    }
37
38    /// Both `self` and `o`.
39    pub fn and(self, o: Cond<P>) -> Self {
40        match self {
41            Cond::All(mut v) => {
42                v.push(o);
43                Cond::All(v)
44            }
45            c => Cond::All(vec![c, o]),
46        }
47    }
48
49    /// The same condition over other parameter references, or the first error `f` gives.
50    pub fn resolve<Q, E>(&self, f: &mut dyn FnMut(&P, Use) -> Result<Q, E>) -> Result<Cond<Q>, E> {
51        Ok(match self {
52            Cond::Always => Cond::Always,
53            Cond::Is(p) => Cond::Is(f(p, Use::Bool)?),
54            Cond::Trigger(p) => Cond::Trigger(f(p, Use::Trigger)?),
55            Cond::Above(p, v) => Cond::Above(f(p, Use::Float)?, *v),
56            Cond::Below(p, v) => Cond::Below(f(p, Use::Float)?, *v),
57            Cond::Finished => Cond::Finished,
58            Cond::After(s) => Cond::After(*s),
59            Cond::Not(c) => Cond::Not(Box::new(c.resolve(f)?)),
60            Cond::All(v) => Cond::All(v.iter().map(|c| c.resolve(f)).collect::<Result<_, E>>()?),
61            Cond::Any(v) => Cond::Any(v.iter().map(|c| c.resolve(f)).collect::<Result<_, E>>()?),
62        })
63    }
64}
65
66/// How a condition uses a parameter, so compiling can check the parameter's kind.
67#[derive(Clone, Copy, Debug, PartialEq, Eq)]
68pub enum Use {
69    /// As a bool.
70    Bool,
71    /// As a float.
72    Float,
73    /// As a trigger.
74    Trigger,
75}
76
77impl Cond {
78    /// Whether the condition holds, given the parameters, whether the clip finished and
79    /// seconds in the state.
80    pub fn eval(&self, p: &Params, finished: bool, time: f32) -> bool {
81        match self {
82            Cond::Always => true,
83            Cond::Is(id) => p.get_bool(*id),
84            Cond::Trigger(id) => p.pending(*id),
85            Cond::Above(id, v) => p.get_float(*id) > *v,
86            Cond::Below(id, v) => p.get_float(*id) < *v,
87            Cond::Finished => finished,
88            Cond::After(s) => time >= *s,
89            Cond::Not(c) => !c.eval(p, finished, time),
90            Cond::All(v) => v.iter().all(|c| c.eval(p, finished, time)),
91            Cond::Any(v) => v.iter().any(|c| c.eval(p, finished, time)),
92        }
93    }
94
95    /// The pending triggers this condition tested positively, to consume when its
96    /// transition fires.
97    pub fn used_triggers(&self, p: &Params, out: &mut Vec<ParamId>) {
98        match self {
99            Cond::Trigger(id) if p.pending(*id) => out.push(*id),
100            Cond::All(v) | Cond::Any(v) => v.iter().for_each(|c| c.used_triggers(p, out)),
101            _ => {}
102        }
103    }
104}