dioxus_motion/
pool.rs

1//! Memory pool management for Dioxus Motion optimizations
2//!
3//! This module provides pooling systems to reduce memory allocations in hot paths
4//! of the animation system, particularly for configuration objects and other
5//! frequently allocated structures.
6
7use 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
14/// A pool for reusing AnimationConfig instances to reduce allocations
15pub struct ConfigPool {
16    available: Vec<AnimationConfig>,
17    in_use: HashMap<usize, AnimationConfig>,
18    next_id: usize,
19}
20
21impl ConfigPool {
22    /// Creates a new config pool with default capacity
23    pub fn new() -> Self {
24        Self::with_capacity(16)
25    }
26
27    /// Creates a new config pool with specified initial capacity
28    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    /// Gets a config from the pool, creating a new one if none available
37    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    /// Returns a config to the pool for reuse
47    pub fn return_config(&mut self, handle: ConfigHandle) {
48        if let Some(mut config) = self.in_use.remove(&handle.id) {
49            // Reset config to default state before returning to pool
50            config.reset_to_default();
51            self.available.push(config);
52        }
53        // If the config wasn't found in in_use, it might have already been returned
54        // This is safe to ignore as it prevents double-return issues
55    }
56
57    /// Modifies a config in the pool safely
58    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    /// Gets a reference to a config in the pool
68    pub fn get_config_ref(&self, handle: &ConfigHandle) -> Option<&AnimationConfig> {
69        self.in_use.get(&handle.id)
70    }
71
72    /// Gets the number of configs currently in use
73    pub fn in_use_count(&self) -> usize {
74        self.in_use.len()
75    }
76
77    /// Gets the number of configs available in the pool
78    pub fn available_count(&self) -> usize {
79        self.available.len()
80    }
81
82    /// Clears all configs from the pool
83    pub fn clear(&mut self) {
84        self.available.clear();
85        self.in_use.clear();
86        self.next_id = 0;
87    }
88
89    /// Trims the available configs to the specified target size
90    /// This removes excess configs from the available pool while preserving in-use configs
91    pub fn trim_to_size(&mut self, target_size: usize) {
92        let current_available = self.available.len();
93        if current_available > target_size {
94            // Remove excess configs from the end of the available vector
95            self.available.truncate(target_size);
96        }
97    }
98}
99
100impl Default for ConfigPool {
101    fn default() -> Self {
102        Self::new()
103    }
104}
105
106/// A handle to a pooled AnimationConfig that automatically returns to pool when dropped
107pub struct ConfigHandle {
108    id: usize,
109    // Track if this handle is still valid (not yet dropped)
110    valid: bool,
111}
112
113impl ConfigHandle {
114    /// Gets the ID of this handle
115    pub fn id(&self) -> usize {
116        self.id
117    }
118
119    /// Creates a new handle with the given ID and pool reference
120    /// This is primarily for testing purposes
121    #[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        // Don't automatically return configs to pool on drop
130        // This prevents issues with cloned handles returning the same config multiple times
131        // Configs should be explicitly returned via Motion::drop or other cleanup mechanisms
132    }
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
144/// Extension trait for AnimationConfig to support pooling
145trait 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/// Trait for pools that can provide statistics
156#[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
167// Thread-local config pool for efficient access
168thread_local! {
169    static CONFIG_POOL: RefCell<ConfigPool> = RefCell::new(ConfigPool::new());
170}
171
172/// Global functions for accessing the thread-local config pool
173pub mod global {
174    use super::*;
175
176    /// Gets a config from the global thread-local pool
177    pub fn get_config() -> ConfigHandle {
178        CONFIG_POOL.with(|pool| pool.borrow_mut().get_config())
179    }
180
181    /// Returns a config to the global thread-local pool
182    pub fn return_config(handle: ConfigHandle) {
183        CONFIG_POOL.with(|pool| {
184            pool.borrow_mut().return_config(handle);
185        });
186    }
187
188    /// Modifies a config in the global thread-local pool
189    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    /// Gets a reference to a config in the global thread-local pool
199    pub fn get_config_ref(handle: &ConfigHandle) -> Option<AnimationConfig> {
200        CONFIG_POOL.with(|pool| pool.borrow().get_config_ref(handle).cloned())
201    }
202
203    /// Gets pool statistics
204    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    /// Clears the global pool (primarily for testing)
212    #[cfg(test)]
213    pub fn clear_pool() {
214        CONFIG_POOL.with(|pool| {
215            pool.borrow_mut().clear();
216        });
217    }
218}
219
220/// Spring integrator with pre-allocated buffers for RK4 integration
221/// Eliminates temporary State struct allocations in hot paths
222pub struct SpringIntegrator<T: Animatable> {
223    // Pre-allocated buffers for RK4 integration steps
224    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    // Temporary state for calculations
233    temp_pos: T,
234    temp_vel: T,
235}
236
237impl<T: Animatable> SpringIntegrator<T> {
238    /// Creates a new spring integrator with default-initialized buffers
239    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    /// Performs RK4 integration using pre-allocated buffers
255    /// Returns the new position and velocity
256    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        // K1 calculation
269        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        // K2 calculation
277        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        // K3 calculation
287        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        // K4 calculation
297        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        // Final integration
307        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    /// Resets all buffers to default values (for pool reuse)
317    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
337/// Pool for reusing SpringIntegrator instances
338pub 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    /// Creates a new integrator pool
346    pub fn new() -> Self {
347        Self::with_capacity(8)
348    }
349
350    /// Creates a new integrator pool with specified capacity
351    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    /// Gets an integrator from the pool
360    pub fn get_integrator(&mut self) -> SpringIntegratorHandle {
361        let mut integrator = self.available.pop().unwrap_or_default();
362        integrator.reset(); // Ensure clean state
363
364        let id = self.next_id;
365        self.next_id += 1;
366        self.in_use.insert(id, integrator);
367
368        SpringIntegratorHandle { id }
369    }
370
371    /// Returns an integrator to the pool
372    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    /// Gets a mutable reference to an integrator
379    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    /// Gets pool statistics
387    pub fn stats(&self) -> (usize, usize) {
388        (self.in_use.len(), self.available.len())
389    }
390
391    /// Clears the pool
392    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/// Handle to a pooled SpringIntegrator
406#[derive(Debug, Clone, Copy, PartialEq, Eq)]
407pub struct SpringIntegratorHandle {
408    id: usize,
409}
410
411impl SpringIntegratorHandle {
412    /// Gets the ID of this handle
413    pub fn id(&self) -> usize {
414        self.id
415    }
416}
417
418/// Global integrator pool management using type-erased storage
419pub struct GlobalIntegratorPools {
420    pools: HashMap<TypeId, Box<dyn Any + Send>>,
421    // Track stats separately since we can't easily downcast trait objects
422    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    /// Gets or creates a pool for type T
440    pub fn get_pool<T: Animatable + Send + 'static>(&mut self) -> &mut SpringIntegratorPool<T> {
441        let type_id = TypeId::of::<T>();
442
443        // Get or create the pool
444        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        // Update stats tracker
452        let stats = pool.stats();
453        self.stats_tracker.insert(type_id, stats);
454
455        pool
456    }
457
458    /// Clears all pools
459    pub fn clear(&mut self) {
460        self.pools.clear();
461        self.stats_tracker.clear();
462    }
463
464    /// Gets statistics for all pools
465    pub fn stats(&self) -> HashMap<TypeId, (usize, usize)> {
466        self.stats_tracker.clone()
467    }
468
469    /// Updates stats for a specific type (called when integrators are returned)
470    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
481/// Global resource pool management for Motion optimizations
482/// Manages all pooled resources including configs, integrators, and closures
483pub struct MotionResourcePools {
484    /// Configuration pool for reusing AnimationConfig instances
485    pub config_pool: ConfigPool,
486    /// Integrator pools for different animatable types
487    pub integrator_pools: GlobalIntegratorPools,
488    /// Web closure pool for JavaScript closure reuse (web only)
489    #[cfg(feature = "web")]
490    pub closure_pool: crate::animations::closure_pool::WebClosurePool,
491    /// Pool configuration settings
492    pub config: PoolConfig,
493}
494
495impl MotionResourcePools {
496    /// Creates new resource pools with default configuration
497    pub fn new() -> Self {
498        Self::with_config(PoolConfig::default())
499    }
500
501    /// Creates new resource pools with specified configuration
502    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    /// Gets statistics for all pools
513    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        // Get integrator stats from the global integrator pools
528        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    /// Estimates memory savings from pooling (rough calculation)
539    fn estimate_memory_savings(&self) -> usize {
540        // Rough estimates based on typical struct sizes
541        const CONFIG_SIZE: usize = std::mem::size_of::<AnimationConfig>();
542        const INTEGRATOR_SIZE: usize = 256; // Rough estimate for SpringIntegrator<f32>
543        const CLOSURE_SIZE: usize = 64; // Rough estimate for web closures
544
545        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        // Integrator savings would need type-specific calculation
558        // For now, just estimate based on common usage
559        let integrator_savings = 8 * INTEGRATOR_SIZE; // Assume ~8 pooled integrators on average
560
561        config_savings + closure_savings + integrator_savings
562    }
563
564    /// Clears all pools (primarily for testing and cleanup)
565    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    /// Performs maintenance on all pools (removes excess capacity, etc.)
573    pub fn maintain(&mut self) {
574        // Trim config pool if it's grown too large
575        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        // Similar maintenance for other pools could be added here
581    }
582}
583
584impl Default for MotionResourcePools {
585    fn default() -> Self {
586        Self::new()
587    }
588}
589
590/// Configuration for resource pools
591#[derive(Debug, Clone)]
592pub struct PoolConfig {
593    /// Initial capacity for config pool
594    pub config_pool_capacity: usize,
595    /// Maximum size for config pool before trimming
596    pub max_config_pool_size: usize,
597    /// Target size to trim config pool to
598    pub target_config_pool_size: usize,
599    /// Whether to enable automatic pool maintenance
600    pub auto_maintain: bool,
601    /// Interval for automatic maintenance (in animation frames)
602    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, // Every ~16 seconds at 60fps
613        }
614    }
615}
616
617/// Statistics about all resource pools
618#[derive(Debug, Clone)]
619pub struct PoolStats {
620    /// Config pool stats: (in_use, available)
621    pub config_pool: (usize, usize),
622    /// Closure pool stats: (in_use, available)
623    pub closure_pool: (usize, usize),
624    /// Integrator pool stats by type
625    pub integrator_pools: HashMap<TypeId, (usize, usize)>,
626    /// Estimated memory saved by pooling (in bytes)
627    pub total_memory_saved_bytes: usize,
628}
629
630// Thread-local resource pools
631thread_local! {
632    static MOTION_RESOURCE_POOLS: RefCell<MotionResourcePools> = RefCell::new(MotionResourcePools::new());
633    static INTEGRATOR_POOLS: RefCell<GlobalIntegratorPools> = RefCell::new(GlobalIntegratorPools::new());
634}
635
636/// Global functions for integrator pool management
637pub mod integrator {
638    use super::*;
639
640    /// Gets an integrator from the global thread-local pool
641    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    /// Returns an integrator to the global thread-local pool
646    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    /// Performs RK4 integration using a pooled integrator
655    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                    // Fallback to non-pooled integration if handle is invalid
669                    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    /// Gets pool statistics for type T
678    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    /// Clears all integrator pools (primarily for testing)
683    #[cfg(test)]
684    pub fn clear_pools() {
685        INTEGRATOR_POOLS.with(|pools| {
686            pools.borrow_mut().clear();
687        });
688    }
689}
690
691/// Global functions for managing Motion resource pools
692pub mod resource_pools {
693    use super::*;
694
695    /// Gets statistics for all resource pools
696    pub fn stats() -> PoolStats {
697        MOTION_RESOURCE_POOLS.with(|pools| pools.borrow().stats())
698    }
699
700    /// Configures the global resource pools
701    /// This should be called early in your application startup for optimal performance
702    pub fn configure(config: PoolConfig) {
703        MOTION_RESOURCE_POOLS.with(|pools| {
704            *pools.borrow_mut() = MotionResourcePools::with_config(config);
705        });
706    }
707
708    /// Initializes resource pools with high-performance defaults
709    /// Recommended for applications with many concurrent animations
710    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, // More frequent maintenance
717        });
718    }
719
720    /// Initializes resource pools with memory-conservative defaults
721    /// Recommended for memory-constrained environments
722    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, // Less frequent maintenance
729        });
730    }
731
732    /// Performs maintenance on all resource pools
733    pub fn maintain() {
734        MOTION_RESOURCE_POOLS.with(|pools| {
735            pools.borrow_mut().maintain();
736        });
737    }
738
739    /// Clears all resource pools (primarily for testing)
740    #[cfg(test)]
741    pub fn clear_all() {
742        MOTION_RESOURCE_POOLS.with(|pools| {
743            pools.borrow_mut().clear();
744        });
745    }
746
747    /// Gets the current pool configuration
748    pub fn get_config() -> PoolConfig {
749        MOTION_RESOURCE_POOLS.with(|pools| pools.borrow().config.clone())
750    }
751
752    /// Estimates total memory usage of all pools
753    pub fn memory_usage_bytes() -> usize {
754        MOTION_RESOURCE_POOLS.with(|pools| {
755            let pools = pools.borrow();
756            let stats = pools.stats();
757
758            // Rough calculation of memory usage
759            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        // Test getting a config
789        let handle1 = pool.get_config();
790        assert_eq!(pool.in_use_count(), 1);
791        assert_eq!(pool.available_count(), 0);
792
793        // Test getting another config
794        let handle2 = pool.get_config();
795        assert_eq!(pool.in_use_count(), 2);
796        assert_eq!(pool.available_count(), 0);
797
798        // Test returning a config
799        pool.return_config(handle1);
800        assert_eq!(pool.in_use_count(), 1);
801        assert_eq!(pool.available_count(), 1);
802
803        // Test reusing returned config
804        let handle3 = pool.get_config();
805        assert_eq!(pool.in_use_count(), 2);
806        assert_eq!(pool.available_count(), 0);
807
808        // Clean up
809        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        // Modify the config
819        pool.modify_config(&handle, |config| {
820            config.mode = AnimationMode::Spring(Spring::default());
821            config.delay = Duration::from_millis(100);
822        });
823
824        // Verify modification
825        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        // Modify the config
838        pool.modify_config(&handle, |config| {
839            config.mode = AnimationMode::Spring(Spring::default());
840            config.delay = Duration::from_millis(100);
841        });
842
843        // Return to pool (should reset)
844        pool.return_config(handle);
845
846        // Get a new config and verify it's reset
847        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        // _handle2 is now invalid, but we won't try to return it
877    }
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        // Clear the pool first
917        global::clear_pool();
918
919        // Get a config handle
920        let handle = global::get_config();
921
922        // Verify the config is in use
923        let (in_use, available) = global::pool_stats();
924        assert_eq!(in_use, 1);
925        assert_eq!(available, 0);
926
927        // Explicitly return the handle to pool
928        global::return_config(handle);
929
930        // Verify the config was returned to the pool
931        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        // Clear the pool first
939        global::clear_pool();
940
941        // Get a config handle
942        let handle = global::get_config();
943        let handle_id = handle.id();
944
945        // Manually return the config
946        global::return_config(ConfigHandle {
947            id: handle_id,
948            valid: false,
949        });
950
951        // Verify it was returned
952        let (in_use, available) = global::pool_stats();
953        assert_eq!(in_use, 0);
954        assert_eq!(available, 1);
955
956        // Now drop the original handle - should not cause issues
957        drop(handle);
958
959        // Should still have the same state
960        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; // 60 FPS
974
975        let (new_pos, new_vel) =
976            integrator.integrate_rk4(current_pos, current_vel, target, &spring, dt);
977
978        // After one integration step, position should have moved toward target
979        assert!(new_pos > current_pos);
980        assert!(new_pos < target);
981        assert!(new_vel > 0.0); // Should be moving toward target
982
983        // Test reset
984        integrator.reset();
985        // All buffers should be reset to default (can't easily test without exposing internals)
986    }
987
988    #[test]
989    fn test_spring_integrator_pool() {
990        let mut pool = SpringIntegratorPool::<f32>::new();
991
992        // Test getting integrator
993        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        // Test using integrator
1001        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        // Test returning integrator
1009        pool.return_integrator(handle1);
1010        let (in_use, available) = pool.stats();
1011        assert_eq!(in_use, 1);
1012        assert_eq!(available, 1);
1013
1014        // Test reusing returned integrator
1015        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        // Clean up
1021        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        // Test integration
1037        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        // Return integrators
1044        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        // Clear both pools to start fresh
1055        integrator::clear_pools();
1056        resource_pools::clear_all();
1057
1058        // Get integrators for different types using the integrator module
1059        let handle_f32 = integrator::get_integrator::<f32>();
1060        let handle_vec2 = integrator::get_integrator::<crate::animations::transform::Transform>();
1061
1062        // Get stats from the integrator module directly
1063        let f32_stats = integrator::pool_stats::<f32>();
1064        let transform_stats = integrator::pool_stats::<crate::animations::transform::Transform>();
1065
1066        // Check that stats are tracked correctly
1067        assert_eq!(f32_stats.0, 1); // in_use
1068        assert_eq!(f32_stats.1, 0); // available
1069
1070        assert_eq!(transform_stats.0, 1); // in_use
1071        assert_eq!(transform_stats.1, 0); // available
1072
1073        // Return integrators
1074        integrator::return_integrator::<f32>(handle_f32);
1075        integrator::return_integrator::<crate::animations::transform::Transform>(handle_vec2);
1076
1077        // Get updated stats
1078        let updated_f32_stats = integrator::pool_stats::<f32>();
1079        let updated_transform_stats =
1080            integrator::pool_stats::<crate::animations::transform::Transform>();
1081
1082        // Check that stats were updated after returning
1083        assert_eq!(updated_f32_stats.0, 0); // in_use
1084        assert_eq!(updated_f32_stats.1, 1); // available
1085
1086        assert_eq!(updated_transform_stats.0, 0); // in_use
1087        assert_eq!(updated_transform_stats.1, 1); // available
1088    }
1089
1090    #[test]
1091    fn test_spring_integrator_accuracy() {
1092        // Test that the pooled integrator produces the same results as the original
1093        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; // 120 FPS
1100
1101        let (new_pos, new_vel) =
1102            integrator.integrate_rk4(current_pos, current_vel, target, &spring, dt);
1103
1104        // The result should be mathematically consistent
1105        // Position should move in the direction of velocity
1106        assert!(new_pos > current_pos);
1107        // Velocity should be affected by spring force
1108        let expected_force_direction = target - current_pos;
1109        assert!(expected_force_direction > 0.0); // Force toward target
1110        // With default spring settings, velocity should increase toward target
1111        assert!(new_vel > current_vel);
1112    }
1113
1114    #[test]
1115    fn test_motion_resource_pools() {
1116        let pools = MotionResourcePools::new();
1117
1118        // Test initial state
1119        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); // Estimated integrator savings
1124    }
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        // Add some items to pools (simplified test)
1147        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        // Test configuration
1161        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        // Test stats
1175        let stats = resource_pools::stats();
1176        assert_eq!(stats.config_pool, (0, 0));
1177
1178        // Test memory usage (should be reasonable)
1179        let memory_usage = resource_pools::memory_usage_bytes();
1180        // Memory usage should be reasonable (at least 0, but not too large)
1181        assert!(memory_usage < 1_000_000); // Shouldn't be unreasonably large
1182
1183        // Test maintenance (should not panic)
1184        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        // Memory savings should be reasonable estimate
1203        assert!(stats.total_memory_saved_bytes > 0);
1204        assert!(stats.total_memory_saved_bytes < 1_000_000); // Shouldn't be unreasonably large
1205    }
1206
1207    #[test]
1208    fn test_resource_pools_stats_returns_actual_data() {
1209        // Clear pools to start fresh
1210        resource_pools::clear_all();
1211
1212        // Get some integrators to populate the pools
1213        let handle1 = integrator::get_integrator::<f32>();
1214        let handle2 = integrator::get_integrator::<crate::animations::transform::Transform>();
1215
1216        // Get stats from resource_pools
1217        let stats = resource_pools::stats();
1218
1219        // Verify that we get actual stats instead of empty data
1220        // The integrator_pools should contain stats for the types we used
1221        assert!(
1222            !stats.integrator_pools.is_empty(),
1223            "Integrator pools stats should not be empty"
1224        );
1225
1226        // Check that we have stats for the types we used
1227        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        // Return integrators
1240        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        // Test maintenance doesn't panic
1249        pools.maintain();
1250
1251        // Test with modified config
1252        pools.config.max_config_pool_size = 1;
1253        pools.config.target_config_pool_size = 0;
1254        pools.maintain(); // Should handle edge cases gracefully
1255    }
1256
1257    #[test]
1258    fn test_config_pool_trimming() {
1259        let mut pool = ConfigPool::new();
1260
1261        // Add some configs to the available pool
1262        for _ in 0..10 {
1263            pool.available.push(AnimationConfig::default());
1264        }
1265
1266        // Verify initial state
1267        assert_eq!(pool.available_count(), 10);
1268        assert_eq!(pool.in_use_count(), 0);
1269
1270        // Trim to target size
1271        pool.trim_to_size(5);
1272        assert_eq!(pool.available_count(), 5);
1273        assert_eq!(pool.in_use_count(), 0);
1274
1275        // Trim to smaller size
1276        pool.trim_to_size(2);
1277        assert_eq!(pool.available_count(), 2);
1278        assert_eq!(pool.in_use_count(), 0);
1279
1280        // Trim to larger size (should not add configs)
1281        pool.trim_to_size(10);
1282        assert_eq!(pool.available_count(), 2);
1283        assert_eq!(pool.in_use_count(), 0);
1284
1285        // Trim to zero
1286        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        // Add some configs to the available pool
1296        for _ in 0..10 {
1297            pool.available.push(AnimationConfig::default());
1298        }
1299
1300        // Get some configs (put them in use)
1301        let handle1 = pool.get_config();
1302        let handle2 = pool.get_config();
1303
1304        // Verify state before trimming
1305        assert_eq!(pool.available_count(), 8);
1306        assert_eq!(pool.in_use_count(), 2);
1307
1308        // Trim available configs (should not affect in-use configs)
1309        pool.trim_to_size(3);
1310        assert_eq!(pool.available_count(), 3);
1311        assert_eq!(pool.in_use_count(), 2);
1312
1313        // Return configs and verify they're still available
1314        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}