1use alloc::string::String;
13use alloc::vec::Vec;
14
15use crate::property::{PropError, PropKind, PropValue, PropertySpec};
16
17pub const CONTROL_CATEGORY: &str = "controller";
21
22#[derive(Debug, Clone, PartialEq)]
31pub enum ControlSource {
32 Step(Vec<(u64, f64)>),
35 Linear(Vec<(u64, f64)>),
38}
39
40impl ControlSource {
41 pub fn step(keys: impl IntoIterator<Item = (u64, f64)>) -> Self {
44 ControlSource::Step(sorted(keys))
45 }
46
47 pub fn linear(keys: impl IntoIterator<Item = (u64, f64)>) -> Self {
49 ControlSource::Linear(sorted(keys))
50 }
51
52 fn keys(&self) -> &[(u64, f64)] {
53 match self {
54 ControlSource::Step(k) | ControlSource::Linear(k) => k,
55 }
56 }
57
58 pub fn value_at(&self, t_ns: u64) -> f64 {
62 let keys = self.keys();
63 let (Some(&(first_t, first_v)), Some(&(last_t, last_v))) = (keys.first(), keys.last())
64 else {
65 return 0.0;
66 };
67 if t_ns <= first_t {
68 return first_v;
69 }
70 if t_ns >= last_t {
71 return last_v;
72 }
73 match self {
75 ControlSource::Step(_) => keys[keys.partition_point(|&(kt, _)| kt <= t_ns) - 1].1,
78 ControlSource::Linear(_) => {
79 let i = keys.partition_point(|&(kt, _)| kt < t_ns);
80 let (t1, v1) = keys[i];
81 let (t0, v0) = keys[i - 1];
82 let span = (t1 - t0) as f64;
84 let into = (t_ns - t0) as f64;
85 v0 + (v1 - v0) * (into / span)
86 }
87 }
88 }
89}
90
91fn sorted(keys: impl IntoIterator<Item = (u64, f64)>) -> Vec<(u64, f64)> {
92 let mut keys: Vec<(u64, f64)> = keys.into_iter().collect();
93 keys.sort_by_key(|&(t, _)| t);
94 keys
95}
96
97#[derive(Debug, Clone, Default, PartialEq)]
100pub struct ControlProgram {
101 bindings: Vec<(String, ControlSource)>,
102}
103
104impl ControlProgram {
105 pub fn new() -> Self {
106 Self::default()
107 }
108
109 pub fn bind(mut self, property: &str, source: ControlSource) -> Self {
112 match self.bindings.iter_mut().find(|(n, _)| n == property) {
113 Some(slot) => slot.1 = source,
114 None => self.bindings.push((String::from(property), source)),
115 }
116 self
117 }
118
119 pub fn is_empty(&self) -> bool {
120 self.bindings.is_empty()
121 }
122
123 pub fn resolve(self, specs: &[PropertySpec]) -> Result<ArmController, ControlFault> {
128 let mut bound = Vec::with_capacity(self.bindings.len());
129 for (property, source) in self.bindings {
130 let fault = |reason| ControlFault {
131 property: property.clone(),
132 reason,
133 };
134 if source.keys().is_empty() {
135 return Err(fault(ControlReason::NoKeyframes));
136 }
137 let spec = specs
138 .iter()
139 .find(|s| s.name == property)
140 .ok_or_else(|| fault(ControlReason::UnknownProperty))?;
141 if !animatable(spec.kind) {
142 return Err(fault(ControlReason::NotAnimatable(spec.kind)));
143 }
144 bound.push(Bound {
145 property,
146 kind: spec.kind,
147 source,
148 });
149 }
150 Ok(ArmController { bound })
151 }
152}
153
154fn animatable(kind: PropKind) -> bool {
158 matches!(
159 kind,
160 PropKind::Bool | PropKind::Int | PropKind::Uint | PropKind::Double
161 )
162}
163
164#[derive(Debug, Clone, PartialEq)]
165struct Bound {
166 property: String,
167 kind: PropKind,
168 source: ControlSource,
169}
170
171#[derive(Debug, Clone, PartialEq)]
175pub struct ArmController {
176 bound: Vec<Bound>,
177}
178
179impl ArmController {
180 pub fn apply<T: ControlTarget + ?Sized>(
184 &self,
185 target: &mut T,
186 pts_ns: u64,
187 ) -> Result<(), ControlFault> {
188 for b in &self.bound {
189 let value = convert(b.kind, b.source.value_at(pts_ns));
190 target
191 .set_control(&b.property, value)
192 .map_err(|e| ControlFault {
193 property: b.property.clone(),
194 reason: ControlReason::Rejected(e),
195 })?;
196 }
197 Ok(())
198 }
199}
200
201fn convert(kind: PropKind, sample: f64) -> PropValue {
205 match kind {
206 PropKind::Bool => PropValue::Bool(sample >= 0.5),
207 PropKind::Double => PropValue::Double(sample),
208 PropKind::Uint => PropValue::Uint(if sample <= 0.0 {
212 0
213 } else {
214 (sample + 0.5) as u64
215 }),
216 _ => PropValue::Int(if sample < 0.0 {
217 (sample - 0.5) as i64
218 } else {
219 (sample + 0.5) as i64
220 }),
221 }
222}
223
224#[derive(Debug, Clone, PartialEq)]
228pub struct ControlFault {
229 pub property: String,
230 pub reason: ControlReason,
231}
232
233#[derive(Debug, Clone, PartialEq)]
234#[non_exhaustive]
235pub enum ControlReason {
236 UnknownProperty,
238 NotAnimatable(PropKind),
240 NoKeyframes,
242 Rejected(PropError),
244}
245
246impl core::fmt::Display for ControlFault {
247 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
248 match &self.reason {
249 ControlReason::UnknownProperty => {
250 write!(f, "no property named `{}` on this element", self.property)
251 }
252 ControlReason::NotAnimatable(kind) => write!(
253 f,
254 "property `{}` is a {} and cannot be animated",
255 self.property,
256 kind.label()
257 ),
258 ControlReason::NoKeyframes => {
259 write!(f, "property `{}` is bound to an empty curve", self.property)
260 }
261 ControlReason::Rejected(e) => write!(
262 f,
263 "property `{}` rejected a sampled value ({e})",
264 self.property
265 ),
266 }
267 }
268}
269
270pub trait ControlTarget {
276 fn set_control(&mut self, name: &str, value: PropValue) -> Result<(), PropError>;
277}
278
279#[cfg(feature = "std")]
280impl ControlTarget for dyn crate::element::DynAsyncElement + '_ {
281 fn set_control(&mut self, name: &str, value: PropValue) -> Result<(), PropError> {
282 self.set_property(name, value)
283 }
284}
285
286#[cfg(feature = "std")]
287impl ControlTarget for dyn crate::runtime::DynMultiInputElement + '_ {
288 fn set_control(&mut self, name: &str, value: PropValue) -> Result<(), PropError> {
289 self.set_property(name, value)
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296
297 fn keys() -> [(u64, f64); 3] {
298 [(0, 0.0), (100, 10.0), (200, 20.0)]
299 }
300
301 #[test]
302 fn linear_interpolates_and_clamps_to_the_end_values() {
303 let s = ControlSource::linear(keys());
304 assert_eq!(s.value_at(0), 0.0);
305 assert_eq!(s.value_at(50), 5.0, "halfway between two keyframes");
306 assert_eq!(s.value_at(100), 10.0, "on a keyframe");
307 assert_eq!(s.value_at(150), 15.0);
308 assert_eq!(s.value_at(1_000), 20.0, "past the end holds the last value");
309 }
310
311 #[test]
312 fn step_holds_each_keyframe_until_the_next() {
313 let s = ControlSource::step(keys());
314 assert_eq!(s.value_at(0), 0.0);
315 assert_eq!(s.value_at(99), 0.0, "still the first value");
316 assert_eq!(s.value_at(100), 10.0);
317 assert_eq!(s.value_at(199), 10.0);
318 assert_eq!(s.value_at(500), 20.0);
319 }
320
321 #[test]
322 fn keyframes_are_sorted_at_construction() {
323 let s = ControlSource::linear([(200, 20.0), (0, 0.0), (100, 10.0)]);
324 assert_eq!(s.value_at(50), 5.0);
325 }
326
327 #[test]
328 fn conversion_rounds_clamps_and_thresholds() {
329 assert_eq!(convert(PropKind::Int, -2.4), PropValue::Int(-2));
330 assert_eq!(convert(PropKind::Int, 2.5), PropValue::Int(3));
331 assert_eq!(
332 convert(PropKind::Uint, -7.0),
333 PropValue::Uint(0),
334 "a negative sample clamps instead of wrapping"
335 );
336 assert_eq!(convert(PropKind::Bool, 0.49), PropValue::Bool(false));
337 assert_eq!(convert(PropKind::Bool, 0.5), PropValue::Bool(true));
338 assert_eq!(convert(PropKind::Double, 1.25), PropValue::Double(1.25));
339 }
340
341 #[test]
342 fn rebinding_a_property_replaces_its_curve() {
343 let p = ControlProgram::new()
344 .bind("x", ControlSource::step([(0, 1.0)]))
345 .bind("x", ControlSource::step([(0, 2.0)]));
346 assert_eq!(p.bindings.len(), 1);
347 assert_eq!(p.bindings[0].1.value_at(0), 2.0);
348 }
349}