1use crate::{MotionValue, Property, Target, Transition};
3
4use std::{
5 cell::Cell,
6 fmt,
7 rc::{Rc, Weak},
8 sync::atomic::{AtomicU64, Ordering},
9 time::Duration,
10};
11
12static NEXT_GRAPH: AtomicU64 = AtomicU64::new(1);
13
14const START: u8 = 1;
15const CANCEL: u8 = 2;
16const CHANGE: u8 = 4;
17const COMPLETE: u8 = 8;
18const RESET_VELOCITY: u8 = 16;
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
22pub struct ValueId {
23 owner: u64,
24 index: usize,
25}
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
29pub enum GraphError {
30 Identity,
32 Derived,
34}
35
36impl fmt::Display for GraphError {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 write!(f, "invalid motion graph operation: {self:?}")
39 }
40}
41
42impl std::error::Error for GraphError {}
43
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
46pub enum MotionEventKind {
47 Start,
49 Cancel,
51 Change,
53 Complete,
55}
56
57#[derive(Clone, Copy, Debug)]
59pub struct MotionEvent {
60 pub id: ValueId,
62 pub kind: MotionEventKind,
64 pub value: f64,
66 pub velocity: f64,
68}
69
70#[derive(Debug)]
72pub struct GraphSubscription(Rc<Cell<bool>>);
73
74impl Drop for GraphSubscription {
75 fn drop(&mut self) {
76 self.0.set(false);
77 }
78}
79
80impl GraphSubscription {
81 pub fn cancel(&self) {
83 self.0.set(false);
84 }
85}
86
87struct Listener {
88 id: ValueId,
89 alive: Weak<Cell<bool>>,
90 callback: Box<dyn FnMut(MotionEvent)>,
91}
92
93type Mapper = Box<dyn Fn(&[f64]) -> f64>;
94
95enum Node {
96 Source(MotionValue),
97 Derived {
98 inputs: Vec<ValueId>,
99 values: Vec<f64>,
100 map: Mapper,
101 },
102}
103
104struct Value {
105 node: Node,
106 value: f64,
107 velocity: f64,
108 active: bool,
109 events: u8,
110}
111
112pub struct MotionGraph {
128 owner: u64,
129 values: Vec<Value>,
130 listeners: Vec<Listener>,
131 last: Duration,
132 active: bool,
133}
134
135impl fmt::Debug for MotionGraph {
136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 f.debug_struct("MotionGraph")
138 .field("values", &self.values.len())
139 .field("active", &self.active)
140 .finish()
141 }
142}
143
144impl Default for MotionGraph {
145 fn default() -> Self {
146 Self {
147 owner: NEXT_GRAPH.fetch_add(1, Ordering::Relaxed),
148 values: Vec::new(),
149 listeners: Vec::new(),
150 last: Duration::ZERO,
151 active: false,
152 }
153 }
154}
155
156impl MotionGraph {
157 pub fn source(&mut self, value: f64) -> ValueId {
159 let node = MotionValue::new(value);
160
161 let id = ValueId {
162 owner: self.owner,
163 index: self.values.len(),
164 };
165
166 self.values.push(Value {
167 node: Node::Source(node),
168 value,
169 velocity: 0.0,
170 active: false,
171 events: 0,
172 });
173
174 id
175 }
176
177 pub fn derive(
185 &mut self,
186 inputs: &[ValueId],
187 map: impl Fn(&[f64]) -> f64 + 'static,
188 ) -> Result<ValueId, GraphError> {
189 let values = inputs
190 .iter()
191 .map(|&id| self.get(id))
192 .collect::<Result<Vec<_>, _>>()?;
193
194 let value = map(&values);
195
196 assert!(
197 value.is_finite(),
198 "graph mapping must return a finite value"
199 );
200 let id = ValueId {
201 owner: self.owner,
202 index: self.values.len(),
203 };
204
205 self.values.push(Value {
206 node: Node::Derived {
207 inputs: inputs.to_vec(),
208 values,
209 map: Box::new(map),
210 },
211 value,
212 velocity: 0.0,
213 active: false,
214 events: 0,
215 });
216
217 Ok(id)
218 }
219
220 fn index(&self, id: ValueId) -> Result<usize, GraphError> {
221 if id.owner != self.owner || id.index >= self.values.len() {
222 return Err(GraphError::Identity);
223 }
224
225 Ok(id.index)
226 }
227
228 pub fn get(&self, id: ValueId) -> Result<f64, GraphError> {
230 Ok(self.values[self.index(id)?].value)
231 }
232
233 pub fn velocity(&self, id: ValueId) -> Result<f64, GraphError> {
235 Ok(self.values[self.index(id)?].velocity)
236 }
237
238 pub fn animate(
241 &mut self,
242 id: ValueId,
243 target: f64,
244 transition: Transition,
245 now: Duration,
246 ) -> Result<(), GraphError> {
247 let i = self.index(id)?;
248 let value = &mut self.values[i];
249
250 let Node::Source(source) = &mut value.node else {
251 return Err(GraphError::Derived);
252 };
253
254 let changed = !source.matches(target, transition);
255 let was_active = source.active();
256
257 source.animate(target, transition, now.max(self.last));
258
259 if changed && was_active {
260 value.events |= CANCEL;
261 }
262
263 if changed && source.active() {
264 value.events |= START;
265 }
266
267 Ok(())
268 }
269
270 pub fn set(&mut self, id: ValueId, value: f64, now: Duration) -> Result<(), GraphError> {
273 self.edit(id, value, now, false)
274 }
275
276 pub fn jump(&mut self, id: ValueId, value: f64, now: Duration) -> Result<(), GraphError> {
278 self.edit(id, value, now, true)
279 }
280
281 fn edit(
282 &mut self,
283 id: ValueId,
284 target: f64,
285 now: Duration,
286 jump: bool,
287 ) -> Result<(), GraphError> {
288 let i = self.index(id)?;
289 let value = &mut self.values[i];
290
291 let Node::Source(source) = &mut value.node else {
292 return Err(GraphError::Derived);
293 };
294
295 if source.active() {
296 value.events |= CANCEL;
297 }
298
299 if jump {
300 value.events = (value.events & !START) | RESET_VELOCITY;
301 source.jump(target, now.max(self.last));
302 } else {
303 source.set(target, now.max(self.last));
304 }
305
306 Ok(())
307 }
308
309 pub fn subscribe(
311 &mut self,
312 id: ValueId,
313 callback: impl FnMut(MotionEvent) + 'static,
314 ) -> Result<GraphSubscription, GraphError> {
315 self.index(id)?;
316 let alive = Rc::new(Cell::new(true));
317
318 self.listeners.push(Listener {
319 id,
320 alive: Rc::downgrade(&alive),
321 callback: Box::new(callback),
322 });
323
324 Ok(GraphSubscription(alive))
325 }
326
327 pub fn sample(&mut self, now: Duration, reduced_motion: bool) {
331 let now = now.max(self.last);
332 let dt = now.saturating_sub(self.last).as_secs_f64();
333
334 self.active = false;
335
336 for index in 0..self.values.len() {
337 let (before, rest) = self.values.split_at_mut(index);
338 let value = &mut rest[0];
339
340 let (next, velocity, active, completed) = match &mut value.node {
341 Node::Source(source) => {
342 let frame = source.sample(now, reduced_motion);
343
344 (
345 frame.value(),
346 frame.velocity(),
347 frame.is_animating(),
348 frame.just_completed(),
349 )
350 }
351 Node::Derived {
352 inputs,
353 values,
354 map,
355 } => {
356 let active = inputs.iter().any(|id| before[id.index].active);
357
358 let propagated = inputs.iter().fold(0, |events, id| {
359 events | (before[id.index].events & (START | CANCEL | RESET_VELOCITY))
360 });
361
362 value.events |= propagated;
363
364 for (id, slot) in inputs.iter().zip(values.iter_mut()) {
365 *slot = before[id.index].value;
366 }
367
368 let next = map(values);
369
370 assert!(next.is_finite(), "graph mapping must return a finite value");
371 let velocity = if propagated & RESET_VELOCITY != 0 {
372 0.0
373 } else if dt > 0.0 {
374 crate::value::finite((next - value.value) / dt)
375 } else {
376 value.velocity
377 };
378
379 (
380 next,
381 velocity,
382 active,
383 !active
384 && inputs
385 .iter()
386 .any(|id| before[id.index].events & COMPLETE != 0),
387 )
388 }
389 };
390
391 if !value.active && active {
392 value.events |= START;
393 }
394
395 if value.value != next {
396 value.events |= CHANGE;
397 }
398
399 if completed {
400 value.events |= COMPLETE;
401 }
402
403 value.value = next;
404 value.velocity = velocity;
405 value.active = active;
406 self.active |= active;
407 }
408
409 self.last = now;
410 self.listeners.retain_mut(|listener| {
411 let Some(alive) = listener.alive.upgrade().filter(|alive| alive.get()) else {
412 return false;
413 };
414
415 let value = &self.values[listener.id.index];
416
417 for (flag, kind) in [
418 (CANCEL, MotionEventKind::Cancel),
419 (START, MotionEventKind::Start),
420 (CHANGE, MotionEventKind::Change),
421 (COMPLETE, MotionEventKind::Complete),
422 ] {
423 if value.events & flag != 0 && alive.get() {
424 (listener.callback)(MotionEvent {
425 id: listener.id,
426 kind,
427 value: value.value,
428 velocity: value.velocity,
429 });
430 }
431 }
432
433 alive.get()
434 });
435
436 for value in &mut self.values {
437 value.events = 0;
438 }
439 }
440
441 pub fn is_animating(&self) -> bool {
443 self.active
444 }
445
446 pub fn target(
449 &self,
450 base: Target,
451 bindings: &[(Property, ValueId)],
452 ) -> Result<Target, GraphError> {
453 let mut values = base.values();
454 let mut mask = base.mask();
455
456 for &(property, id) in bindings {
457 let i = property as usize;
458 let value = self.get(id)?;
459
460 values[i] = if i == 2 {
461 value.clamp(0.0, 1.0)
462 } else if i >= 3 {
463 value.clamp(0.0, f32::MAX.into())
464 } else {
465 value.clamp(f32::MIN.into(), f32::MAX.into())
466 };
467 mask |= 1 << i;
468 }
469
470 Ok(Target::from_values(values, mask))
471 }
472}
473
474impl MotionGraph {
475 pub fn typed_source<T: crate::MotionData<N>, const N: usize>(
478 &mut self,
479 value: T,
480 ) -> [ValueId; N] {
481 let values = value.channels();
482
483 assert!(
484 N > 0 && values.iter().all(|v| v.is_finite()),
485 "invalid typed source"
486 );
487
488 std::array::from_fn(|i| self.source(values[i]))
489 }
490
491 pub fn typed_value<T: crate::MotionData<N>, const N: usize>(
493 &self,
494 template: T,
495 ids: [ValueId; N],
496 ) -> Result<T, GraphError> {
497 for id in ids {
498 self.index(id)?;
499 }
500
501 Ok(template.with_channels(std::array::from_fn(|i| self.values[ids[i].index].value)))
502 }
503
504 pub fn animate_typed<T: crate::MotionData<N>, const N: usize>(
507 &mut self,
508 ids: [ValueId; N],
509 target: T,
510 transition: Transition,
511 now: Duration,
512 ) -> Result<(), GraphError> {
513 let values = target.channels();
514
515 assert!(
516 values.iter().all(|v| v.is_finite()),
517 "invalid typed destination"
518 );
519
520 for id in ids {
521 if !matches!(self.values[self.index(id)?].node, Node::Source(_)) {
522 return Err(GraphError::Derived);
523 }
524 }
525
526 for i in 0..N {
527 self.animate(ids[i], values[i], transition, now)?;
528 }
529
530 Ok(())
531 }
532}