1use crate::animations::core::{Animatable, AnimationConfig};
8use crate::animations::spring::Spring;
9use std::collections::HashMap;
10
11use std::any::{Any, TypeId};
12use std::cell::RefCell;
13
14pub struct ConfigPool {
16 available: Vec<AnimationConfig>,
17 in_use: HashMap<usize, AnimationConfig>,
18 next_id: usize,
19}
20
21impl ConfigPool {
22 pub fn new() -> Self {
24 Self::with_capacity(16)
25 }
26
27 pub fn with_capacity(capacity: usize) -> Self {
29 Self {
30 available: Vec::with_capacity(capacity),
31 in_use: HashMap::with_capacity(capacity),
32 next_id: 0,
33 }
34 }
35
36 pub fn get_config(&mut self) -> ConfigHandle {
38 let config = self.available.pop().unwrap_or_default();
39 let id = self.next_id;
40 self.next_id += 1;
41 self.in_use.insert(id, config);
42
43 ConfigHandle { id, valid: true }
44 }
45
46 pub fn return_config(&mut self, handle: ConfigHandle) {
48 if let Some(mut config) = self.in_use.remove(&handle.id) {
49 config.reset_to_default();
51 self.available.push(config);
52 }
53 }
56
57 pub fn modify_config<F>(&mut self, handle: &ConfigHandle, f: F)
59 where
60 F: FnOnce(&mut AnimationConfig),
61 {
62 if let Some(config) = self.in_use.get_mut(&handle.id) {
63 f(config);
64 }
65 }
66
67 pub fn get_config_ref(&self, handle: &ConfigHandle) -> Option<&AnimationConfig> {
69 self.in_use.get(&handle.id)
70 }
71
72 pub fn in_use_count(&self) -> usize {
74 self.in_use.len()
75 }
76
77 pub fn available_count(&self) -> usize {
79 self.available.len()
80 }
81
82 pub fn clear(&mut self) {
84 self.available.clear();
85 self.in_use.clear();
86 self.next_id = 0;
87 }
88
89 pub fn trim_to_size(&mut self, target_size: usize) {
92 let current_available = self.available.len();
93 if current_available > target_size {
94 self.available.truncate(target_size);
96 }
97 }
98}
99
100impl Default for ConfigPool {
101 fn default() -> Self {
102 Self::new()
103 }
104}
105
106pub struct ConfigHandle {
108 id: usize,
109 valid: bool,
111}
112
113impl ConfigHandle {
114 pub fn id(&self) -> usize {
116 self.id
117 }
118
119 #[cfg(test)]
122 pub fn new_test(id: usize) -> Self {
123 Self { id, valid: true }
124 }
125}
126
127impl Drop for ConfigHandle {
128 fn drop(&mut self) {
129 }
133}
134
135impl Clone for ConfigHandle {
136 fn clone(&self) -> Self {
137 Self {
138 id: self.id,
139 valid: self.valid,
140 }
141 }
142}
143
144trait ConfigPoolable {
146 fn reset_to_default(&mut self);
147}
148
149impl ConfigPoolable for AnimationConfig {
150 fn reset_to_default(&mut self) {
151 *self = AnimationConfig::default();
152 }
153}
154
155#[allow(dead_code)]
157trait PoolStatsProvider {
158 fn stats(&self) -> (usize, usize);
159}
160
161impl<T: Animatable + Send> PoolStatsProvider for SpringIntegratorPool<T> {
162 fn stats(&self) -> (usize, usize) {
163 (self.in_use.len(), self.available.len())
164 }
165}
166
167thread_local! {
169 static CONFIG_POOL: RefCell<ConfigPool> = RefCell::new(ConfigPool::new());
170}
171
172pub mod global {
174 use super::*;
175
176 pub fn get_config() -> ConfigHandle {
178 CONFIG_POOL.with(|pool| pool.borrow_mut().get_config())
179 }
180
181 pub fn return_config(handle: ConfigHandle) {
183 CONFIG_POOL.with(|pool| {
184 pool.borrow_mut().return_config(handle);
185 });
186 }
187
188 pub fn modify_config<F>(handle: &ConfigHandle, f: F)
190 where
191 F: FnOnce(&mut AnimationConfig),
192 {
193 CONFIG_POOL.with(|pool| {
194 pool.borrow_mut().modify_config(handle, f);
195 });
196 }
197
198 pub fn get_config_ref(handle: &ConfigHandle) -> Option<AnimationConfig> {
200 CONFIG_POOL.with(|pool| pool.borrow().get_config_ref(handle).cloned())
201 }
202
203 pub fn pool_stats() -> (usize, usize) {
205 CONFIG_POOL.with(|pool| {
206 let pool = pool.borrow();
207 (pool.in_use_count(), pool.available_count())
208 })
209 }
210
211 #[cfg(test)]
213 pub fn clear_pool() {
214 CONFIG_POOL.with(|pool| {
215 pool.borrow_mut().clear();
216 });
217 }
218}
219
220pub struct SpringIntegrator<T: Animatable> {
223 k1_pos: T,
225 k1_vel: T,
226 k2_pos: T,
227 k2_vel: T,
228 k3_pos: T,
229 k3_vel: T,
230 k4_pos: T,
231 k4_vel: T,
232 temp_pos: T,
234 temp_vel: T,
235}
236
237impl<T: Animatable> SpringIntegrator<T> {
238 pub fn new() -> Self {
240 Self {
241 k1_pos: T::default(),
242 k1_vel: T::default(),
243 k2_pos: T::default(),
244 k2_vel: T::default(),
245 k3_pos: T::default(),
246 k3_vel: T::default(),
247 k4_pos: T::default(),
248 k4_vel: T::default(),
249 temp_pos: T::default(),
250 temp_vel: T::default(),
251 }
252 }
253
254 pub fn integrate_rk4(
257 &mut self,
258 current_pos: T,
259 current_vel: T,
260 target: T,
261 spring: &Spring,
262 dt: f32,
263 ) -> (T, T) {
264 let stiffness = spring.stiffness;
265 let damping = spring.damping;
266 let mass_inv = 1.0 / spring.mass;
267
268 let delta = target - current_pos;
270 let force = delta * stiffness;
271 let damping_force = current_vel * damping;
272 let acc = (force - damping_force) * mass_inv;
273 self.k1_pos = current_vel;
274 self.k1_vel = acc;
275
276 self.temp_pos = current_pos + self.k1_pos * (dt * 0.5);
278 self.temp_vel = current_vel + self.k1_vel * (dt * 0.5);
279 let delta = target - self.temp_pos;
280 let force = delta * stiffness;
281 let damping_force = self.temp_vel * damping;
282 let acc = (force - damping_force) * mass_inv;
283 self.k2_pos = self.temp_vel;
284 self.k2_vel = acc;
285
286 self.temp_pos = current_pos + self.k2_pos * (dt * 0.5);
288 self.temp_vel = current_vel + self.k2_vel * (dt * 0.5);
289 let delta = target - self.temp_pos;
290 let force = delta * stiffness;
291 let damping_force = self.temp_vel * damping;
292 let acc = (force - damping_force) * mass_inv;
293 self.k3_pos = self.temp_vel;
294 self.k3_vel = acc;
295
296 self.temp_pos = current_pos + self.k3_pos * dt;
298 self.temp_vel = current_vel + self.k3_vel * dt;
299 let delta = target - self.temp_pos;
300 let force = delta * stiffness;
301 let damping_force = self.temp_vel * damping;
302 let acc = (force - damping_force) * mass_inv;
303 self.k4_pos = self.temp_vel;
304 self.k4_vel = acc;
305
306 const SIXTH: f32 = 1.0 / 6.0;
308 let new_pos = current_pos
309 + (self.k1_pos + self.k2_pos * 2.0 + self.k3_pos * 2.0 + self.k4_pos) * (dt * SIXTH);
310 let new_vel = current_vel
311 + (self.k1_vel + self.k2_vel * 2.0 + self.k3_vel * 2.0 + self.k4_vel) * (dt * SIXTH);
312
313 (new_pos, new_vel)
314 }
315
316 pub fn reset(&mut self) {
318 self.k1_pos = T::default();
319 self.k1_vel = T::default();
320 self.k2_pos = T::default();
321 self.k2_vel = T::default();
322 self.k3_pos = T::default();
323 self.k3_vel = T::default();
324 self.k4_pos = T::default();
325 self.k4_vel = T::default();
326 self.temp_pos = T::default();
327 self.temp_vel = T::default();
328 }
329}
330
331impl<T: Animatable> Default for SpringIntegrator<T> {
332 fn default() -> Self {
333 Self::new()
334 }
335}
336
337pub struct SpringIntegratorPool<T: Animatable> {
339 available: Vec<SpringIntegrator<T>>,
340 in_use: HashMap<usize, SpringIntegrator<T>>,
341 next_id: usize,
342}
343
344impl<T: Animatable> SpringIntegratorPool<T> {
345 pub fn new() -> Self {
347 Self::with_capacity(8)
348 }
349
350 pub fn with_capacity(capacity: usize) -> Self {
352 Self {
353 available: Vec::with_capacity(capacity),
354 in_use: HashMap::with_capacity(capacity),
355 next_id: 0,
356 }
357 }
358
359 pub fn get_integrator(&mut self) -> SpringIntegratorHandle {
361 let mut integrator = self.available.pop().unwrap_or_default();
362 integrator.reset(); let id = self.next_id;
365 self.next_id += 1;
366 self.in_use.insert(id, integrator);
367
368 SpringIntegratorHandle { id }
369 }
370
371 pub fn return_integrator(&mut self, handle: SpringIntegratorHandle) {
373 if let Some(integrator) = self.in_use.remove(&handle.id) {
374 self.available.push(integrator);
375 }
376 }
377
378 pub fn get_integrator_mut(
380 &mut self,
381 handle: &SpringIntegratorHandle,
382 ) -> Option<&mut SpringIntegrator<T>> {
383 self.in_use.get_mut(&handle.id)
384 }
385
386 pub fn stats(&self) -> (usize, usize) {
388 (self.in_use.len(), self.available.len())
389 }
390
391 pub fn clear(&mut self) {
393 self.available.clear();
394 self.in_use.clear();
395 self.next_id = 0;
396 }
397}
398
399impl<T: Animatable> Default for SpringIntegratorPool<T> {
400 fn default() -> Self {
401 Self::new()
402 }
403}
404
405#[derive(Debug, Clone, Copy, PartialEq, Eq)]
407pub struct SpringIntegratorHandle {
408 id: usize,
409}
410
411impl SpringIntegratorHandle {
412 pub fn id(&self) -> usize {
414 self.id
415 }
416}
417
418pub struct GlobalIntegratorPools {
420 pools: HashMap<TypeId, Box<dyn Any + Send>>,
421 stats_tracker: HashMap<TypeId, (usize, usize)>,
423}
424
425impl Default for GlobalIntegratorPools {
426 fn default() -> Self {
427 Self::new()
428 }
429}
430
431impl GlobalIntegratorPools {
432 pub fn new() -> Self {
433 Self {
434 pools: HashMap::new(),
435 stats_tracker: HashMap::new(),
436 }
437 }
438
439 pub fn get_pool<T: Animatable + Send + 'static>(&mut self) -> &mut SpringIntegratorPool<T> {
441 let type_id = TypeId::of::<T>();
442
443 let pool = self
445 .pools
446 .entry(type_id)
447 .or_insert_with(|| Box::new(SpringIntegratorPool::<T>::new()))
448 .downcast_mut::<SpringIntegratorPool<T>>()
449 .expect("Type mismatch in integrator pool");
450
451 let stats = pool.stats();
453 self.stats_tracker.insert(type_id, stats);
454
455 pool
456 }
457
458 pub fn clear(&mut self) {
460 self.pools.clear();
461 self.stats_tracker.clear();
462 }
463
464 pub fn stats(&self) -> HashMap<TypeId, (usize, usize)> {
466 self.stats_tracker.clone()
467 }
468
469 pub fn update_stats<T: Animatable + Send + 'static>(&mut self) {
471 let type_id = TypeId::of::<T>();
472 if let Some(pool) = self.pools.get(&type_id) {
473 if let Some(pool) = pool.downcast_ref::<SpringIntegratorPool<T>>() {
474 let stats = pool.stats();
475 self.stats_tracker.insert(type_id, stats);
476 }
477 }
478 }
479}
480
481pub struct MotionResourcePools {
484 pub config_pool: ConfigPool,
486 pub integrator_pools: GlobalIntegratorPools,
488 #[cfg(feature = "web")]
490 pub closure_pool: crate::animations::closure_pool::WebClosurePool,
491 pub config: PoolConfig,
493}
494
495impl MotionResourcePools {
496 pub fn new() -> Self {
498 Self::with_config(PoolConfig::default())
499 }
500
501 pub fn with_config(config: PoolConfig) -> Self {
503 Self {
504 config_pool: ConfigPool::with_capacity(config.config_pool_capacity),
505 integrator_pools: GlobalIntegratorPools::new(),
506 #[cfg(feature = "web")]
507 closure_pool: crate::animations::closure_pool::WebClosurePool::new(),
508 config,
509 }
510 }
511
512 pub fn stats(&self) -> PoolStats {
514 let (config_in_use, config_available) = (
515 self.config_pool.in_use_count(),
516 self.config_pool.available_count(),
517 );
518
519 #[cfg(feature = "web")]
520 let (closure_in_use, closure_available) = (
521 self.closure_pool.in_use_count(),
522 self.closure_pool.available_count(),
523 );
524 #[cfg(not(feature = "web"))]
525 let (closure_in_use, closure_available) = (0, 0);
526
527 let integrator_stats = INTEGRATOR_POOLS.with(|pools| pools.borrow().stats());
529
530 PoolStats {
531 config_pool: (config_in_use, config_available),
532 closure_pool: (closure_in_use, closure_available),
533 integrator_pools: integrator_stats,
534 total_memory_saved_bytes: self.estimate_memory_savings(),
535 }
536 }
537
538 fn estimate_memory_savings(&self) -> usize {
540 const CONFIG_SIZE: usize = std::mem::size_of::<AnimationConfig>();
542 const INTEGRATOR_SIZE: usize = 256; const CLOSURE_SIZE: usize = 64; let config_savings = self.config_pool.available_count() * CONFIG_SIZE;
546 let closure_savings = {
547 #[cfg(feature = "web")]
548 {
549 self.closure_pool.available_count() * CLOSURE_SIZE
550 }
551 #[cfg(not(feature = "web"))]
552 {
553 0
554 }
555 };
556
557 let integrator_savings = 8 * INTEGRATOR_SIZE; config_savings + closure_savings + integrator_savings
562 }
563
564 pub fn clear(&mut self) {
566 self.config_pool.clear();
567 self.integrator_pools.clear();
568 #[cfg(feature = "web")]
569 self.closure_pool.clear();
570 }
571
572 pub fn maintain(&mut self) {
574 if self.config_pool.available_count() > self.config.max_config_pool_size {
576 self.config_pool
577 .trim_to_size(self.config.target_config_pool_size);
578 }
579
580 }
582}
583
584impl Default for MotionResourcePools {
585 fn default() -> Self {
586 Self::new()
587 }
588}
589
590#[derive(Debug, Clone)]
592pub struct PoolConfig {
593 pub config_pool_capacity: usize,
595 pub max_config_pool_size: usize,
597 pub target_config_pool_size: usize,
599 pub auto_maintain: bool,
601 pub maintenance_interval: u32,
603}
604
605impl Default for PoolConfig {
606 fn default() -> Self {
607 Self {
608 config_pool_capacity: 16,
609 max_config_pool_size: 64,
610 target_config_pool_size: 32,
611 auto_maintain: true,
612 maintenance_interval: 1000, }
614 }
615}
616
617#[derive(Debug, Clone)]
619pub struct PoolStats {
620 pub config_pool: (usize, usize),
622 pub closure_pool: (usize, usize),
624 pub integrator_pools: HashMap<TypeId, (usize, usize)>,
626 pub total_memory_saved_bytes: usize,
628}
629
630thread_local! {
632 static MOTION_RESOURCE_POOLS: RefCell<MotionResourcePools> = RefCell::new(MotionResourcePools::new());
633 static INTEGRATOR_POOLS: RefCell<GlobalIntegratorPools> = RefCell::new(GlobalIntegratorPools::new());
634}
635
636pub mod integrator {
638 use super::*;
639
640 pub fn get_integrator<T: Animatable + Send + 'static>() -> SpringIntegratorHandle {
642 INTEGRATOR_POOLS.with(|pools| pools.borrow_mut().get_pool::<T>().get_integrator())
643 }
644
645 pub fn return_integrator<T: Animatable + Send + 'static>(handle: SpringIntegratorHandle) {
647 INTEGRATOR_POOLS.with(|pools| {
648 let mut pools = pools.borrow_mut();
649 pools.get_pool::<T>().return_integrator(handle);
650 pools.update_stats::<T>();
651 });
652 }
653
654 pub fn integrate_rk4<T: Animatable + Send + 'static>(
656 handle: &SpringIntegratorHandle,
657 current_pos: T,
658 current_vel: T,
659 target: T,
660 spring: &Spring,
661 dt: f32,
662 ) -> (T, T) {
663 INTEGRATOR_POOLS.with(|pools| {
664 let mut pools = pools.borrow_mut();
665 let pool = pools.get_pool::<T>();
666 pool.get_integrator_mut(handle).map_or_else(
667 || {
668 let mut integrator = SpringIntegrator::new();
670 integrator.integrate_rk4(current_pos, current_vel, target, spring, dt)
671 },
672 |integrator| integrator.integrate_rk4(current_pos, current_vel, target, spring, dt),
673 )
674 })
675 }
676
677 pub fn pool_stats<T: Animatable + Send + 'static>() -> (usize, usize) {
679 INTEGRATOR_POOLS.with(|pools| pools.borrow_mut().get_pool::<T>().stats())
680 }
681
682 #[cfg(test)]
684 pub fn clear_pools() {
685 INTEGRATOR_POOLS.with(|pools| {
686 pools.borrow_mut().clear();
687 });
688 }
689}
690
691pub mod resource_pools {
693 use super::*;
694
695 pub fn stats() -> PoolStats {
697 MOTION_RESOURCE_POOLS.with(|pools| pools.borrow().stats())
698 }
699
700 pub fn configure(config: PoolConfig) {
703 MOTION_RESOURCE_POOLS.with(|pools| {
704 *pools.borrow_mut() = MotionResourcePools::with_config(config);
705 });
706 }
707
708 pub fn init_high_performance() {
711 configure(PoolConfig {
712 config_pool_capacity: 64,
713 max_config_pool_size: 256,
714 target_config_pool_size: 128,
715 auto_maintain: true,
716 maintenance_interval: 500, });
718 }
719
720 pub fn init_memory_conservative() {
723 configure(PoolConfig {
724 config_pool_capacity: 8,
725 max_config_pool_size: 32,
726 target_config_pool_size: 16,
727 auto_maintain: true,
728 maintenance_interval: 2000, });
730 }
731
732 pub fn maintain() {
734 MOTION_RESOURCE_POOLS.with(|pools| {
735 pools.borrow_mut().maintain();
736 });
737 }
738
739 #[cfg(test)]
741 pub fn clear_all() {
742 MOTION_RESOURCE_POOLS.with(|pools| {
743 pools.borrow_mut().clear();
744 });
745 }
746
747 pub fn get_config() -> PoolConfig {
749 MOTION_RESOURCE_POOLS.with(|pools| pools.borrow().config.clone())
750 }
751
752 pub fn memory_usage_bytes() -> usize {
754 MOTION_RESOURCE_POOLS.with(|pools| {
755 let pools = pools.borrow();
756 let stats = pools.stats();
757
758 const CONFIG_SIZE: usize = std::mem::size_of::<AnimationConfig>();
760 const INTEGRATOR_SIZE: usize = 256;
761 const CLOSURE_SIZE: usize = 64;
762
763 let config_memory = (stats.config_pool.0 + stats.config_pool.1) * CONFIG_SIZE;
764 let closure_memory = (stats.closure_pool.0 + stats.closure_pool.1) * CLOSURE_SIZE;
765 let integrator_memory = stats
766 .integrator_pools
767 .values()
768 .map(|(in_use, available)| (in_use + available) * INTEGRATOR_SIZE)
769 .sum::<usize>();
770
771 config_memory + closure_memory + integrator_memory
772 })
773 }
774}
775
776#[cfg(test)]
777mod tests {
778 #![allow(clippy::unwrap_used)]
779 use super::*;
780 use crate::animations::core::AnimationMode;
781 use crate::animations::spring::Spring;
782 use instant::Duration;
783
784 #[test]
785 fn test_config_pool_basic_operations() {
786 let mut pool = ConfigPool::new();
787
788 let handle1 = pool.get_config();
790 assert_eq!(pool.in_use_count(), 1);
791 assert_eq!(pool.available_count(), 0);
792
793 let handle2 = pool.get_config();
795 assert_eq!(pool.in_use_count(), 2);
796 assert_eq!(pool.available_count(), 0);
797
798 pool.return_config(handle1);
800 assert_eq!(pool.in_use_count(), 1);
801 assert_eq!(pool.available_count(), 1);
802
803 let handle3 = pool.get_config();
805 assert_eq!(pool.in_use_count(), 2);
806 assert_eq!(pool.available_count(), 0);
807
808 pool.return_config(handle2);
810 pool.return_config(handle3);
811 }
812
813 #[test]
814 fn test_config_pool_modification() {
815 let mut pool = ConfigPool::new();
816 let handle = pool.get_config();
817
818 pool.modify_config(&handle, |config| {
820 config.mode = AnimationMode::Spring(Spring::default());
821 config.delay = Duration::from_millis(100);
822 });
823
824 let config_ref = pool.get_config_ref(&handle).unwrap();
826 assert!(matches!(config_ref.mode, AnimationMode::Spring(_)));
827 assert_eq!(config_ref.delay, Duration::from_millis(100));
828
829 pool.return_config(handle);
830 }
831
832 #[test]
833 fn test_config_pool_reset_on_return() {
834 let mut pool = ConfigPool::new();
835 let handle = pool.get_config();
836
837 pool.modify_config(&handle, |config| {
839 config.mode = AnimationMode::Spring(Spring::default());
840 config.delay = Duration::from_millis(100);
841 });
842
843 pool.return_config(handle);
845
846 let new_handle = pool.get_config();
848 let config_ref = pool.get_config_ref(&new_handle).unwrap();
849 assert!(matches!(config_ref.mode, AnimationMode::Tween(_)));
850 assert_eq!(config_ref.delay, Duration::default());
851
852 pool.return_config(new_handle);
853 }
854
855 #[test]
856 fn test_config_pool_with_capacity() {
857 let pool = ConfigPool::with_capacity(32);
858 assert_eq!(pool.available_count(), 0);
859 assert_eq!(pool.in_use_count(), 0);
860 }
861
862 #[test]
863 fn test_config_pool_clear() {
864 let mut pool = ConfigPool::new();
865 let handle1 = pool.get_config();
866 let _handle2 = pool.get_config();
867
868 pool.return_config(handle1);
869 assert_eq!(pool.in_use_count(), 1);
870 assert_eq!(pool.available_count(), 1);
871
872 pool.clear();
873 assert_eq!(pool.in_use_count(), 0);
874 assert_eq!(pool.available_count(), 0);
875
876 }
878
879 #[test]
880 fn test_global_config_pool() {
881 global::clear_pool();
882
883 let handle1 = global::get_config();
884 let handle2 = global::get_config();
885
886 let (in_use, available) = global::pool_stats();
887 assert_eq!(in_use, 2);
888 assert_eq!(available, 0);
889
890 global::modify_config(&handle1, |config| {
891 config.delay = Duration::from_millis(50);
892 });
893
894 let config = global::get_config_ref(&handle1).unwrap();
895 assert_eq!(config.delay, Duration::from_millis(50));
896
897 global::return_config(handle1);
898 global::return_config(handle2);
899
900 let (in_use, available) = global::pool_stats();
901 assert_eq!(in_use, 0);
902 assert_eq!(available, 2);
903 }
904
905 #[test]
906 fn test_config_handle_clone() {
907 let handle1 = ConfigHandle::new_test(42);
908 let handle2 = handle1.clone();
909
910 assert_eq!(handle1.id(), handle2.id());
911 assert_eq!(handle1.id(), 42);
912 }
913
914 #[test]
915 fn test_config_handle_explicit_cleanup() {
916 global::clear_pool();
918
919 let handle = global::get_config();
921
922 let (in_use, available) = global::pool_stats();
924 assert_eq!(in_use, 1);
925 assert_eq!(available, 0);
926
927 global::return_config(handle);
929
930 let (in_use, available) = global::pool_stats();
932 assert_eq!(in_use, 0);
933 assert_eq!(available, 1);
934 }
935
936 #[test]
937 fn test_config_handle_double_drop_safety() {
938 global::clear_pool();
940
941 let handle = global::get_config();
943 let handle_id = handle.id();
944
945 global::return_config(ConfigHandle {
947 id: handle_id,
948 valid: false,
949 });
950
951 let (in_use, available) = global::pool_stats();
953 assert_eq!(in_use, 0);
954 assert_eq!(available, 1);
955
956 drop(handle);
958
959 let (in_use, available) = global::pool_stats();
961 assert_eq!(in_use, 0);
962 assert_eq!(available, 1);
963 }
964
965 #[test]
966 fn test_spring_integrator() {
967 let mut integrator = SpringIntegrator::<f32>::new();
968 let spring = Spring::default();
969
970 let current_pos = 0.0f32;
971 let current_vel = 0.0f32;
972 let target = 100.0f32;
973 let dt = 1.0 / 60.0; let (new_pos, new_vel) =
976 integrator.integrate_rk4(current_pos, current_vel, target, &spring, dt);
977
978 assert!(new_pos > current_pos);
980 assert!(new_pos < target);
981 assert!(new_vel > 0.0); integrator.reset();
985 }
987
988 #[test]
989 fn test_spring_integrator_pool() {
990 let mut pool = SpringIntegratorPool::<f32>::new();
991
992 let handle1 = pool.get_integrator();
994 let handle2 = pool.get_integrator();
995
996 let (in_use, available) = pool.stats();
997 assert_eq!(in_use, 2);
998 assert_eq!(available, 0);
999
1000 let spring = Spring::default();
1002 if let Some(integrator) = pool.get_integrator_mut(&handle1) {
1003 let (new_pos, new_vel) = integrator.integrate_rk4(0.0, 0.0, 100.0, &spring, 1.0 / 60.0);
1004 assert!(new_pos > 0.0);
1005 assert!(new_vel > 0.0);
1006 }
1007
1008 pool.return_integrator(handle1);
1010 let (in_use, available) = pool.stats();
1011 assert_eq!(in_use, 1);
1012 assert_eq!(available, 1);
1013
1014 let handle3 = pool.get_integrator();
1016 let (in_use, available) = pool.stats();
1017 assert_eq!(in_use, 2);
1018 assert_eq!(available, 0);
1019
1020 pool.return_integrator(handle2);
1022 pool.return_integrator(handle3);
1023 }
1024
1025 #[test]
1026 fn test_global_integrator_pool() {
1027 integrator::clear_pools();
1028
1029 let handle1 = integrator::get_integrator::<f32>();
1030 let handle2 = integrator::get_integrator::<f32>();
1031
1032 let (in_use, available) = integrator::pool_stats::<f32>();
1033 assert_eq!(in_use, 2);
1034 assert_eq!(available, 0);
1035
1036 let spring = Spring::default();
1038 let (new_pos, new_vel) =
1039 integrator::integrate_rk4(&handle1, 0.0, 0.0, 100.0, &spring, 1.0 / 60.0);
1040 assert!(new_pos > 0.0);
1041 assert!(new_vel > 0.0);
1042
1043 integrator::return_integrator::<f32>(handle1);
1045 integrator::return_integrator::<f32>(handle2);
1046
1047 let (in_use, available) = integrator::pool_stats::<f32>();
1048 assert_eq!(in_use, 0);
1049 assert_eq!(available, 2);
1050 }
1051
1052 #[test]
1053 fn test_integrator_pool_stats_tracking() {
1054 integrator::clear_pools();
1056 resource_pools::clear_all();
1057
1058 let handle_f32 = integrator::get_integrator::<f32>();
1060 let handle_vec2 = integrator::get_integrator::<crate::animations::transform::Transform>();
1061
1062 let f32_stats = integrator::pool_stats::<f32>();
1064 let transform_stats = integrator::pool_stats::<crate::animations::transform::Transform>();
1065
1066 assert_eq!(f32_stats.0, 1); assert_eq!(f32_stats.1, 0); assert_eq!(transform_stats.0, 1); assert_eq!(transform_stats.1, 0); integrator::return_integrator::<f32>(handle_f32);
1075 integrator::return_integrator::<crate::animations::transform::Transform>(handle_vec2);
1076
1077 let updated_f32_stats = integrator::pool_stats::<f32>();
1079 let updated_transform_stats =
1080 integrator::pool_stats::<crate::animations::transform::Transform>();
1081
1082 assert_eq!(updated_f32_stats.0, 0); assert_eq!(updated_f32_stats.1, 1); assert_eq!(updated_transform_stats.0, 0); assert_eq!(updated_transform_stats.1, 1); }
1089
1090 #[test]
1091 fn test_spring_integrator_accuracy() {
1092 let mut integrator = SpringIntegrator::<f32>::new();
1094 let spring = Spring::default();
1095
1096 let current_pos = 10.0f32;
1097 let current_vel = 5.0f32;
1098 let target = 50.0f32;
1099 let dt = 1.0 / 120.0; let (new_pos, new_vel) =
1102 integrator.integrate_rk4(current_pos, current_vel, target, &spring, dt);
1103
1104 assert!(new_pos > current_pos);
1107 let expected_force_direction = target - current_pos;
1109 assert!(expected_force_direction > 0.0); assert!(new_vel > current_vel);
1112 }
1113
1114 #[test]
1115 fn test_motion_resource_pools() {
1116 let pools = MotionResourcePools::new();
1117
1118 let stats = pools.stats();
1120 assert_eq!(stats.config_pool, (0, 0));
1121 assert_eq!(stats.closure_pool, (0, 0));
1122 assert!(stats.integrator_pools.is_empty());
1123 assert_eq!(stats.total_memory_saved_bytes, 8 * 256); }
1125
1126 #[test]
1127 fn test_motion_resource_pools_with_config() {
1128 let config = PoolConfig {
1129 config_pool_capacity: 32,
1130 max_config_pool_size: 128,
1131 target_config_pool_size: 64,
1132 auto_maintain: false,
1133 maintenance_interval: 500,
1134 };
1135
1136 let pools = MotionResourcePools::with_config(config.clone());
1137 assert_eq!(pools.config.config_pool_capacity, 32);
1138 assert_eq!(pools.config.max_config_pool_size, 128);
1139 assert!(!pools.config.auto_maintain);
1140 }
1141
1142 #[test]
1143 fn test_motion_resource_pools_clear() {
1144 let mut pools = MotionResourcePools::new();
1145
1146 let _handle = pools.config_pool.get_config();
1148
1149 pools.clear();
1150
1151 let stats = pools.stats();
1152 assert_eq!(stats.config_pool, (0, 0));
1153 assert_eq!(stats.closure_pool, (0, 0));
1154 }
1155
1156 #[test]
1157 fn test_resource_pools_global_functions() {
1158 resource_pools::clear_all();
1159
1160 let config = PoolConfig {
1162 config_pool_capacity: 24,
1163 max_config_pool_size: 96,
1164 target_config_pool_size: 48,
1165 auto_maintain: true,
1166 maintenance_interval: 750,
1167 };
1168
1169 resource_pools::configure(config.clone());
1170 let retrieved_config = resource_pools::get_config();
1171 assert_eq!(retrieved_config.config_pool_capacity, 24);
1172 assert_eq!(retrieved_config.max_config_pool_size, 96);
1173
1174 let stats = resource_pools::stats();
1176 assert_eq!(stats.config_pool, (0, 0));
1177
1178 let memory_usage = resource_pools::memory_usage_bytes();
1180 assert!(memory_usage < 1_000_000); resource_pools::maintain();
1185 }
1186
1187 #[test]
1188 fn test_pool_config_default() {
1189 let config = PoolConfig::default();
1190 assert_eq!(config.config_pool_capacity, 16);
1191 assert_eq!(config.max_config_pool_size, 64);
1192 assert_eq!(config.target_config_pool_size, 32);
1193 assert!(config.auto_maintain);
1194 assert_eq!(config.maintenance_interval, 1000);
1195 }
1196
1197 #[test]
1198 fn test_pool_stats_memory_estimation() {
1199 let pools = MotionResourcePools::new();
1200 let stats = pools.stats();
1201
1202 assert!(stats.total_memory_saved_bytes > 0);
1204 assert!(stats.total_memory_saved_bytes < 1_000_000); }
1206
1207 #[test]
1208 fn test_resource_pools_stats_returns_actual_data() {
1209 resource_pools::clear_all();
1211
1212 let handle1 = integrator::get_integrator::<f32>();
1214 let handle2 = integrator::get_integrator::<crate::animations::transform::Transform>();
1215
1216 let stats = resource_pools::stats();
1218
1219 assert!(
1222 !stats.integrator_pools.is_empty(),
1223 "Integrator pools stats should not be empty"
1224 );
1225
1226 let f32_type_id = std::any::TypeId::of::<f32>();
1228 let transform_type_id = std::any::TypeId::of::<crate::animations::transform::Transform>();
1229
1230 assert!(
1231 stats.integrator_pools.contains_key(&f32_type_id),
1232 "Should have f32 stats"
1233 );
1234 assert!(
1235 stats.integrator_pools.contains_key(&transform_type_id),
1236 "Should have Transform stats"
1237 );
1238
1239 integrator::return_integrator::<f32>(handle1);
1241 integrator::return_integrator::<crate::animations::transform::Transform>(handle2);
1242 }
1243
1244 #[test]
1245 fn test_motion_resource_pools_maintain() {
1246 let mut pools = MotionResourcePools::new();
1247
1248 pools.maintain();
1250
1251 pools.config.max_config_pool_size = 1;
1253 pools.config.target_config_pool_size = 0;
1254 pools.maintain(); }
1256
1257 #[test]
1258 fn test_config_pool_trimming() {
1259 let mut pool = ConfigPool::new();
1260
1261 for _ in 0..10 {
1263 pool.available.push(AnimationConfig::default());
1264 }
1265
1266 assert_eq!(pool.available_count(), 10);
1268 assert_eq!(pool.in_use_count(), 0);
1269
1270 pool.trim_to_size(5);
1272 assert_eq!(pool.available_count(), 5);
1273 assert_eq!(pool.in_use_count(), 0);
1274
1275 pool.trim_to_size(2);
1277 assert_eq!(pool.available_count(), 2);
1278 assert_eq!(pool.in_use_count(), 0);
1279
1280 pool.trim_to_size(10);
1282 assert_eq!(pool.available_count(), 2);
1283 assert_eq!(pool.in_use_count(), 0);
1284
1285 pool.trim_to_size(0);
1287 assert_eq!(pool.available_count(), 0);
1288 assert_eq!(pool.in_use_count(), 0);
1289 }
1290
1291 #[test]
1292 fn test_config_pool_trimming_with_in_use_configs() {
1293 let mut pool = ConfigPool::new();
1294
1295 for _ in 0..10 {
1297 pool.available.push(AnimationConfig::default());
1298 }
1299
1300 let handle1 = pool.get_config();
1302 let handle2 = pool.get_config();
1303
1304 assert_eq!(pool.available_count(), 8);
1306 assert_eq!(pool.in_use_count(), 2);
1307
1308 pool.trim_to_size(3);
1310 assert_eq!(pool.available_count(), 3);
1311 assert_eq!(pool.in_use_count(), 2);
1312
1313 pool.return_config(handle1);
1315 pool.return_config(handle2);
1316 assert_eq!(pool.available_count(), 5);
1317 assert_eq!(pool.in_use_count(), 0);
1318 }
1319}