1use crate::Duration;
2use crate::TimeProvider;
3use crate::animations::core::Animatable;
4use crate::animations::state_machine::AnimationState;
5use crate::keyframes::KeyframeAnimation;
6use crate::pool::{ConfigHandle, SpringIntegratorHandle, global};
7use crate::prelude::AnimationConfig;
8use crate::sequence::AnimationSequence;
9use std::sync::Arc;
10
11#[derive(Clone)]
12pub struct Motion<T: Animatable + Send + 'static> {
13 pub initial: T,
14 pub current: T,
15 pub target: T,
16 pub velocity: T,
17 pub running: bool,
18 pub elapsed: Duration,
19 pub delay_elapsed: Duration,
20 pub current_loop: u8,
21 pub reverse: bool,
22
23 pub animation_state: AnimationState<T>,
26 config_handle: ConfigHandle,
28 spring_integrator_handle: Option<SpringIntegratorHandle>,
30 pub sequence: Option<Arc<AnimationSequence<T>>>,
32 pub keyframe_animation: Option<Arc<KeyframeAnimation<T>>>,
34
35 value_cache: Option<(T, f32)>,
37}
38
39impl<T: Animatable + Send + 'static> Drop for Motion<T> {
40 fn drop(&mut self) {
41 global::return_config(self.config_handle.clone());
43
44 if let Some(handle) = self.spring_integrator_handle.take() {
46 self.try_return_spring_integrator(handle);
47 }
48 }
49}
50
51impl<T: Animatable + Send + 'static> Motion<T> {
52 pub fn new(initial: T) -> Self {
53 let config_handle = global::get_config();
54 global::modify_config(&config_handle, |config| {
55 *config = AnimationConfig::default();
56 });
57
58 Self {
59 initial,
60 current: initial,
61 target: initial,
62 velocity: T::default(),
63 running: false,
64 elapsed: Duration::default(),
65 delay_elapsed: Duration::default(),
66 current_loop: 0,
67 reverse: false,
68
69 animation_state: AnimationState::new_idle(),
71 config_handle,
72 spring_integrator_handle: None,
73 sequence: None,
74 keyframe_animation: None,
75
76 value_cache: None,
77 }
78 }
79
80 pub fn animate_to(&mut self, target: T, config: AnimationConfig) {
81 self.value_cache = None;
82 self.sequence = None;
83 self.initial = self.current;
84 self.target = target;
85 self.running = true;
86 self.elapsed = Duration::default();
87 self.delay_elapsed = Duration::default();
88 self.velocity = T::default();
89 self.current_loop = 0;
90
91 global::modify_config(&self.config_handle, |pooled_config| {
93 *pooled_config = config.clone();
94 });
95
96 if matches!(
98 config.mode,
99 crate::animations::core::AnimationMode::Spring(_)
100 ) && self.spring_integrator_handle.is_none()
101 {
102 self.spring_integrator_handle = self.try_get_spring_integrator();
103 }
104
105 self.animation_state = AnimationState::new_running(config.mode, self.config_handle.clone());
107 }
108
109 pub fn animate_sequence(&mut self, sequence: AnimationSequence<T>) {
110 self.value_cache = None;
111 if let Some(first_step) = sequence.steps().first() {
112 let first_config = (*first_step.config).clone();
113 self.animate_to(first_step.target, first_config);
114 let new_sequence = sequence.clone();
115 new_sequence.reset(); self.sequence = Some(Arc::new(new_sequence.clone()));
117
118 self.animation_state =
120 AnimationState::new_sequence(Arc::new(new_sequence), self.config_handle.clone());
121 }
122 }
123
124 pub fn animate_keyframes(&mut self, animation: KeyframeAnimation<T>) {
125 self.value_cache = None;
126 self.keyframe_animation = Some(Arc::new(animation.clone()));
127 self.running = true;
128 self.elapsed = Duration::default();
129 self.velocity = T::default();
130
131 self.animation_state =
133 AnimationState::new_keyframes(Arc::new(animation), self.config_handle.clone());
134 }
135
136 pub fn get_value(&self) -> T {
137 let now = crate::Time::now().elapsed().as_secs_f32();
139 if let Some((ref cached, cached_time)) = self.value_cache {
140 if (now - cached_time).abs() < 0.001 {
141 return *cached;
142 }
143 }
144 self.current
149 }
150
151 pub fn is_running(&self) -> bool {
152 self.animation_state.is_active()
153 }
154
155 pub fn reset(&mut self) {
156 self.value_cache = None;
157 self.stop();
158 self.current = self.initial;
159 self.elapsed = Duration::default();
160 }
161
162 pub fn stop(&mut self) {
163 self.value_cache = None;
164 self.running = false;
165 self.current_loop = 0;
166 self.velocity = T::default();
167 self.sequence = None;
168 self.keyframe_animation = None;
169 self.animation_state = AnimationState::new_idle();
170
171 if let Some(handle) = self.spring_integrator_handle.take() {
173 self.try_return_spring_integrator(handle);
174 }
175 }
176
177 pub fn delay(&mut self, duration: Duration) {
178 self.value_cache = None;
179
180 global::modify_config(&self.config_handle, |pooled_config| {
182 pooled_config.delay = duration;
183 });
184 }
185
186 pub fn get_epsilon(&self) -> f32 {
189 if let Some(config) = global::get_config_ref(&self.config_handle) {
190 config.epsilon.unwrap_or_else(T::epsilon)
191 } else {
192 T::epsilon()
193 }
194 }
195
196 pub fn update(&mut self, dt: f32) -> bool {
197 self.value_cache = None;
199
200 let mut state = std::mem::replace(&mut self.animation_state, AnimationState::new_idle());
203 let result = state.update(dt, self);
204 self.animation_state = state;
205 result
206 }
207
208 pub fn config_handle(&self) -> &ConfigHandle {
210 &self.config_handle
211 }
212
213 pub fn spring_integrator_handle(&self) -> Option<&SpringIntegratorHandle> {
215 self.spring_integrator_handle.as_ref()
216 }
217
218 pub fn optimization_stats(&self) -> MotionOptimizationStats {
220 MotionOptimizationStats {
221 has_config_handle: true, has_spring_integrator: self.spring_integrator_handle.is_some(),
223 state_machine_active: self.animation_state.is_active(),
224 value_cache_active: self.value_cache.is_some(),
225 }
226 }
227
228 fn try_get_spring_integrator(&self) -> Option<SpringIntegratorHandle> {
230 Some(crate::pool::integrator::get_integrator::<T>())
231 }
232
233 fn try_return_spring_integrator(&self, handle: SpringIntegratorHandle) {
235 crate::pool::integrator::return_integrator::<T>(handle);
236 }
237}
238
239#[derive(Debug, Clone, PartialEq)]
241pub struct MotionOptimizationStats {
242 pub has_config_handle: bool,
243 pub has_spring_integrator: bool,
244 pub state_machine_active: bool,
245 pub value_cache_active: bool,
246}
247
248#[cfg(test)]
249mod tests {
250 #![allow(clippy::unwrap_used)]
251 use super::*;
252 use crate::animations::core::AnimationMode;
253 use crate::animations::spring::Spring;
254 use crate::keyframes::KeyframeAnimation;
255 use crate::prelude::Tween;
256 use crate::sequence::{AnimationSequence, AnimationStep};
257
258 #[test]
259 fn test_motion_new_with_optimizations() {
260 crate::pool::global::clear_pool();
261
262 let motion = Motion::new(0.0f32);
263
264 assert_eq!(motion.initial, 0.0);
266 assert_eq!(motion.current, 0.0);
267 assert_eq!(motion.target, 0.0);
268 assert!(!motion.running);
269
270 let stats = motion.optimization_stats();
272 assert!(stats.has_config_handle);
273 assert!(!stats.has_spring_integrator); assert!(!stats.state_machine_active); assert!(!stats.value_cache_active);
276 }
277
278 #[test]
279 fn test_motion_animate_to_with_optimizations() {
280 crate::pool::global::clear_pool();
281
282 let mut motion = Motion::new(0.0f32);
283 let config = AnimationConfig::new(AnimationMode::Tween(Tween::default()));
284
285 motion.animate_to(100.0, config);
286
287 assert_eq!(motion.target, 100.0);
289 assert!(motion.running);
290
291 let stats = motion.optimization_stats();
293 assert!(stats.has_config_handle);
294 assert!(!stats.has_spring_integrator); assert!(stats.state_machine_active);
296
297 let handle = motion.config_handle();
299 let config = crate::pool::global::get_config_ref(handle).unwrap();
300 assert!(matches!(config.mode, AnimationMode::Tween(_)));
301 }
302
303 #[test]
304 fn test_motion_spring_animation_with_integrator() {
305 crate::pool::global::clear_pool();
306 crate::pool::integrator::clear_pools();
307
308 let mut motion = Motion::new(0.0f32);
309 let config = AnimationConfig::new(AnimationMode::Spring(Spring::default()));
310
311 motion.animate_to(100.0, config);
312
313 let stats = motion.optimization_stats();
315 assert!(stats.has_config_handle);
316 assert!(stats.has_spring_integrator);
317 assert!(stats.state_machine_active);
318
319 assert!(motion.spring_integrator_handle().is_some());
321
322 let (in_use, _) = crate::pool::integrator::pool_stats::<f32>();
324 assert_eq!(in_use, 1);
325 }
326
327 #[test]
328 fn test_motion_sequence_with_optimizations() {
329 crate::pool::global::clear_pool();
330
331 let mut motion = Motion::new(0.0f32);
332
333 let steps = vec![
334 AnimationStep {
335 target: 50.0,
336 config: Arc::new(AnimationConfig::default()),
337 predicted_next: None,
338 },
339 AnimationStep {
340 target: 100.0,
341 config: Arc::new(AnimationConfig::default()),
342 predicted_next: None,
343 },
344 ];
345
346 let sequence = AnimationSequence::from_steps(steps);
347 motion.animate_sequence(sequence);
348
349 assert!(motion.sequence.is_some());
351 assert_eq!(motion.target, 50.0); let stats = motion.optimization_stats();
355 assert!(stats.has_config_handle);
356 assert!(stats.state_machine_active);
357
358 assert!(matches!(
360 motion.animation_state,
361 AnimationState::Sequence { .. }
362 ));
363 }
364
365 #[test]
366 fn test_motion_keyframes_with_optimizations() {
367 crate::pool::global::clear_pool();
368
369 let mut motion = Motion::new(0.0f32);
370
371 let mut animation = KeyframeAnimation::new(Duration::from_secs(1));
372 animation = animation.add_keyframe(0.0, 0.0, None).unwrap();
373 animation = animation.add_keyframe(100.0, 1.0, None).unwrap();
374
375 motion.animate_keyframes(animation);
376
377 assert!(motion.keyframe_animation.is_some());
379 assert!(motion.running);
380
381 let stats = motion.optimization_stats();
383 assert!(stats.has_config_handle);
384 assert!(stats.state_machine_active);
385
386 assert!(matches!(
388 motion.animation_state,
389 AnimationState::Keyframes { .. }
390 ));
391 }
392
393 #[test]
394 fn test_motion_stop_cleanup() {
395 crate::pool::global::clear_pool();
396 crate::pool::integrator::clear_pools();
397
398 let mut motion = Motion::new(0.0f32);
399 let config = AnimationConfig::new(AnimationMode::Spring(Spring::default()));
400
401 motion.animate_to(100.0, config);
402
403 assert!(motion.spring_integrator_handle().is_some());
405 let (in_use_before, _) = crate::pool::integrator::pool_stats::<f32>();
406 assert_eq!(in_use_before, 1);
407
408 motion.stop();
409
410 assert!(motion.spring_integrator_handle().is_none());
412 assert!(!motion.running);
413 assert!(matches!(motion.animation_state, AnimationState::Idle));
414
415 let (in_use_after, available_after) = crate::pool::integrator::pool_stats::<f32>();
416 assert_eq!(in_use_after, 0);
417 assert_eq!(available_after, 1); }
419
420 #[test]
421 fn test_motion_drop_cleanup() {
422 crate::pool::global::clear_pool();
423 crate::pool::integrator::clear_pools();
424
425 {
426 let mut motion = Motion::new(0.0f32);
427 let config = AnimationConfig::new(AnimationMode::Spring(Spring::default()));
428 motion.animate_to(100.0, config);
429
430 let (in_use, _) = crate::pool::integrator::pool_stats::<f32>();
432 assert_eq!(in_use, 1); let (config_in_use, _) = crate::pool::global::pool_stats();
435 assert_eq!(config_in_use, 1);
436 } let (in_use_after, available_after) = crate::pool::integrator::pool_stats::<f32>();
440 assert_eq!(in_use_after, 0);
441 assert_eq!(available_after, 1); let (config_in_use_after, config_available_after) = crate::pool::global::pool_stats();
444 assert_eq!(config_in_use_after, 0);
445 assert_eq!(config_available_after, 1);
446 }
447
448 #[test]
449 fn test_motion_get_epsilon_optimization() {
450 crate::pool::global::clear_pool();
451
452 let motion = Motion::new(0.0f32);
453
454 assert_eq!(motion.get_epsilon(), f32::epsilon());
456
457 let handle = motion.config_handle();
459 crate::pool::global::modify_config(handle, |config| {
460 config.epsilon = Some(0.01);
461 });
462
463 assert_eq!(motion.get_epsilon(), 0.01);
464 }
465
466 #[test]
467 fn test_motion_delay_optimization() {
468 crate::pool::global::clear_pool();
469
470 let mut motion = Motion::new(0.0f32);
471 let delay = Duration::from_millis(100);
472
473 motion.delay(delay);
474
475 let handle = motion.config_handle();
478 let config = crate::pool::global::get_config_ref(handle).unwrap();
479 assert_eq!(config.delay, delay);
480 }
481
482 #[test]
483 fn test_motion_update_with_state_machine() {
484 crate::pool::global::clear_pool();
485
486 let mut motion = Motion::new(0.0f32);
487 let config = AnimationConfig::new(AnimationMode::Tween(Tween::default()));
488
489 motion.animate_to(100.0, config);
490
491 assert!(motion.animation_state.is_active());
493
494 let should_continue = motion.update(1.0 / 60.0);
496
497 assert!(should_continue);
499
500 assert!(motion.current > 0.0);
502 assert!(motion.current < 100.0);
503 }
504
505 #[test]
506 fn test_motion_optimization_stats() {
507 crate::pool::global::clear_pool();
508 crate::pool::integrator::clear_pools();
509
510 let mut motion = Motion::new(0.0f32);
511
512 let stats = motion.optimization_stats();
514 assert!(stats.has_config_handle);
515 assert!(!stats.has_spring_integrator);
516 assert!(!stats.state_machine_active);
517 assert!(!stats.value_cache_active);
518
519 let config = AnimationConfig::new(AnimationMode::Spring(Spring::default()));
521 motion.animate_to(100.0, config);
522
523 let stats = motion.optimization_stats();
524 assert!(stats.has_config_handle);
525 assert!(stats.has_spring_integrator); assert!(stats.state_machine_active);
527 assert!(!stats.value_cache_active);
528 }
529
530 #[test]
531 fn test_motion_backward_compatibility() {
532 crate::pool::global::clear_pool();
533
534 let mut motion = Motion::new(0.0f32);
535 let config = AnimationConfig::default();
536
537 motion.animate_to(100.0, config);
539
540 assert_eq!(motion.target, 100.0);
542 assert!(motion.running);
543
544 let stats = motion.optimization_stats();
546 assert!(stats.has_config_handle);
547 assert!(stats.state_machine_active);
548 }
549}