use super::Name;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ParamId(pub(crate) u16);
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ParamKind {
Bool,
Float,
Trigger {
ttl: f32,
},
}
#[derive(Clone, Debug, PartialEq)]
struct Slot {
name: Name,
kind: ParamKind,
value: f32,
}
const PENDING: f32 = 1e-6;
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Params {
slots: Vec<Slot>,
}
impl Params {
pub fn declare(&mut self, name: impl Into<Name>, kind: ParamKind) -> ParamId {
let name = name.into();
if let Some(id) = self.id(&name) {
return id;
}
self.slots.push(Slot {
name,
kind,
value: 0.0,
});
ParamId((self.slots.len() - 1) as u16)
}
pub fn id(&self, name: &str) -> Option<ParamId> {
self.slots
.iter()
.position(|s| s.name == name)
.map(|i| ParamId(i as u16))
}
pub fn name(&self, id: ParamId) -> &str {
&self.slots[id.0 as usize].name
}
pub fn kind(&self, id: ParamId) -> ParamKind {
self.slots[id.0 as usize].kind
}
pub fn set_bool(&mut self, id: ParamId, v: bool) {
self.slots[id.0 as usize].value = if v { 1.0 } else { 0.0 };
}
pub fn set_float(&mut self, id: ParamId, v: f32) {
self.slots[id.0 as usize].value = v;
}
pub fn fire(&mut self, id: ParamId) {
let slot = &mut self.slots[id.0 as usize];
if let ParamKind::Trigger { ttl } = slot.kind {
slot.value = ttl.max(PENDING);
}
}
pub fn get_bool(&self, id: ParamId) -> bool {
self.slots[id.0 as usize].value != 0.0
}
pub fn get_float(&self, id: ParamId) -> f32 {
self.slots[id.0 as usize].value
}
pub fn pending(&self, id: ParamId) -> bool {
self.slots[id.0 as usize].value > 0.0
}
pub(crate) fn reset_to(&mut self, defaults: &Params) {
for (i, s) in self.slots.iter_mut().enumerate() {
s.value = defaults.slots.get(i).map_or(0.0, |d| d.value);
}
}
pub(crate) fn consume(&mut self, id: ParamId) {
self.slots[id.0 as usize].value = 0.0;
}
pub(crate) fn decay(&mut self, dt: f32) {
for s in &mut self.slots {
if matches!(s.kind, ParamKind::Trigger { .. }) {
s.value = (s.value - dt).max(0.0);
if s.value < PENDING {
s.value = 0.0;
}
}
}
}
pub fn iter(&self) -> impl Iterator<Item = (ParamId, &str, ParamKind, f32)> {
self.slots
.iter()
.enumerate()
.map(|(i, s)| (ParamId(i as u16), s.name.as_ref(), s.kind, s.value))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn triggers_wait_their_ttl_then_expire() {
let mut p = Params::default();
let t = p.declare("jump", ParamKind::Trigger { ttl: 0.1 });
p.fire(t);
p.decay(0.05);
assert!(p.pending(t));
p.decay(0.06);
assert!(!p.pending(t));
}
#[test]
fn a_zero_ttl_trigger_lives_one_update() {
let mut p = Params::default();
let t = p.declare("land", ParamKind::Trigger { ttl: 0.0 });
p.fire(t);
assert!(p.pending(t));
p.decay(1.0 / 120.0);
assert!(!p.pending(t));
}
#[test]
fn redeclaring_returns_the_same_id() {
let mut p = Params::default();
let a = p.declare("x", ParamKind::Float);
assert_eq!(p.declare("x", ParamKind::Bool), a);
assert_eq!(p.kind(a), ParamKind::Float);
}
}