Skip to main content

game_toolkit_platformer/anim/
params.rs

1//! [`Params`]: named controller inputs: bools, floats and triggers with a time to live.
2
3use super::Name;
4
5/// A parameter, by index into its [`Params`].
6#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
7pub struct ParamId(pub(crate) u16);
8
9/// What kind of value a parameter holds.
10#[derive(Clone, Copy, Debug, PartialEq)]
11pub enum ParamKind {
12    /// On or off, set every update by the driver.
13    Bool,
14    /// A number, set every update by the driver.
15    Float,
16    /// A one-shot flag: fired, it stays pending until a transition consumes it or `ttl`
17    /// seconds pass. A `ttl` of 0 lives for exactly one update.
18    Trigger {
19        /// Seconds a fired trigger waits to be consumed.
20        ttl: f32,
21    },
22}
23
24/// One declared parameter.
25#[derive(Clone, Debug, PartialEq)]
26struct Slot {
27    /// Its name.
28    name: Name,
29    /// Its kind.
30    kind: ParamKind,
31    /// Bool as 0/1, float as is, trigger as seconds left (> 0: pending).
32    value: f32,
33}
34
35/// The smallest time left that still counts as pending, so a `ttl` of 0 survives the one
36/// update it was fired in.
37const PENDING: f32 = 1e-6;
38
39/// A set of named parameters.
40#[derive(Clone, Debug, Default, PartialEq)]
41pub struct Params {
42    /// The parameters in declaration order; [`ParamId`] indexes this.
43    slots: Vec<Slot>,
44}
45
46impl Params {
47    /// Declare `name` as `kind` and return its id; declaring an existing name again
48    /// returns the existing id (the first kind wins).
49    pub fn declare(&mut self, name: impl Into<Name>, kind: ParamKind) -> ParamId {
50        let name = name.into();
51        if let Some(id) = self.id(&name) {
52            return id;
53        }
54        self.slots.push(Slot {
55            name,
56            kind,
57            value: 0.0,
58        });
59        ParamId((self.slots.len() - 1) as u16)
60    }
61
62    /// The id of `name`, if declared.
63    pub fn id(&self, name: &str) -> Option<ParamId> {
64        self.slots
65            .iter()
66            .position(|s| s.name == name)
67            .map(|i| ParamId(i as u16))
68    }
69
70    /// The name of `id`.
71    pub fn name(&self, id: ParamId) -> &str {
72        &self.slots[id.0 as usize].name
73    }
74
75    /// The kind of `id`.
76    pub fn kind(&self, id: ParamId) -> ParamKind {
77        self.slots[id.0 as usize].kind
78    }
79
80    /// Set a bool parameter.
81    pub fn set_bool(&mut self, id: ParamId, v: bool) {
82        self.slots[id.0 as usize].value = if v { 1.0 } else { 0.0 };
83    }
84
85    /// Set a float parameter.
86    pub fn set_float(&mut self, id: ParamId, v: f32) {
87        self.slots[id.0 as usize].value = v;
88    }
89
90    /// Fire a trigger: pending for its time to live (re-firing restarts it).
91    pub fn fire(&mut self, id: ParamId) {
92        let slot = &mut self.slots[id.0 as usize];
93        if let ParamKind::Trigger { ttl } = slot.kind {
94            slot.value = ttl.max(PENDING);
95        }
96    }
97
98    /// A bool parameter's value.
99    pub fn get_bool(&self, id: ParamId) -> bool {
100        self.slots[id.0 as usize].value != 0.0
101    }
102
103    /// A float parameter's value.
104    pub fn get_float(&self, id: ParamId) -> f32 {
105        self.slots[id.0 as usize].value
106    }
107
108    /// Whether a trigger is pending.
109    pub fn pending(&self, id: ParamId) -> bool {
110        self.slots[id.0 as usize].value > 0.0
111    }
112
113    /// Set every value back to `defaults`' (by position); parameters beyond those, which
114    /// a driver declared on its own, go back to 0.
115    pub(crate) fn reset_to(&mut self, defaults: &Params) {
116        for (i, s) in self.slots.iter_mut().enumerate() {
117            s.value = defaults.slots.get(i).map_or(0.0, |d| d.value);
118        }
119    }
120
121    /// Clear a pending trigger: a transition used it.
122    pub(crate) fn consume(&mut self, id: ParamId) {
123        self.slots[id.0 as usize].value = 0.0;
124    }
125
126    /// Age pending triggers by `dt`; expired ones are dropped.
127    pub(crate) fn decay(&mut self, dt: f32) {
128        for s in &mut self.slots {
129            if matches!(s.kind, ParamKind::Trigger { .. }) {
130                s.value = (s.value - dt).max(0.0);
131                if s.value < PENDING {
132                    s.value = 0.0;
133                }
134            }
135        }
136    }
137
138    /// Every parameter: id, name, kind and raw value (bools 0/1, triggers time left).
139    pub fn iter(&self) -> impl Iterator<Item = (ParamId, &str, ParamKind, f32)> {
140        self.slots
141            .iter()
142            .enumerate()
143            .map(|(i, s)| (ParamId(i as u16), s.name.as_ref(), s.kind, s.value))
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[test]
152    fn triggers_wait_their_ttl_then_expire() {
153        let mut p = Params::default();
154        let t = p.declare("jump", ParamKind::Trigger { ttl: 0.1 });
155        p.fire(t);
156        p.decay(0.05);
157        assert!(p.pending(t));
158        p.decay(0.06);
159        assert!(!p.pending(t));
160    }
161
162    #[test]
163    fn a_zero_ttl_trigger_lives_one_update() {
164        let mut p = Params::default();
165        let t = p.declare("land", ParamKind::Trigger { ttl: 0.0 });
166        p.fire(t);
167        assert!(p.pending(t));
168        p.decay(1.0 / 120.0);
169        assert!(!p.pending(t));
170    }
171
172    #[test]
173    fn redeclaring_returns_the_same_id() {
174        let mut p = Params::default();
175        let a = p.declare("x", ParamKind::Float);
176        assert_eq!(p.declare("x", ParamKind::Bool), a);
177        assert_eq!(p.kind(a), ParamKind::Float);
178    }
179}