use crate::animations::core::{Animatable, AnimationConfig};
use crate::animations::spring::Spring;
use std::collections::HashMap;
use std::any::{Any, TypeId};
use std::cell::RefCell;
pub struct ConfigPool {
available: Vec<AnimationConfig>,
in_use: HashMap<usize, AnimationConfig>,
next_id: usize,
}
impl ConfigPool {
pub fn new() -> Self {
Self::with_capacity(16)
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
available: Vec::with_capacity(capacity),
in_use: HashMap::with_capacity(capacity),
next_id: 0,
}
}
pub fn get_config(&mut self) -> ConfigHandle {
let config = self.available.pop().unwrap_or_default();
let id = self.next_id;
self.next_id += 1;
self.in_use.insert(id, config);
ConfigHandle { id, valid: true }
}
pub fn return_config(&mut self, handle: ConfigHandle) {
if let Some(mut config) = self.in_use.remove(&handle.id) {
config.reset_to_default();
self.available.push(config);
}
}
pub fn modify_config<F>(&mut self, handle: &ConfigHandle, f: F)
where
F: FnOnce(&mut AnimationConfig),
{
if let Some(config) = self.in_use.get_mut(&handle.id) {
f(config);
}
}
pub fn get_config_ref(&self, handle: &ConfigHandle) -> Option<&AnimationConfig> {
self.in_use.get(&handle.id)
}
pub fn in_use_count(&self) -> usize {
self.in_use.len()
}
pub fn available_count(&self) -> usize {
self.available.len()
}
pub fn clear(&mut self) {
self.available.clear();
self.in_use.clear();
self.next_id = 0;
}
pub fn trim_to_size(&mut self, target_size: usize) {
let current_available = self.available.len();
if current_available > target_size {
self.available.truncate(target_size);
}
}
}
impl Default for ConfigPool {
fn default() -> Self {
Self::new()
}
}
pub struct ConfigHandle {
id: usize,
valid: bool,
}
impl ConfigHandle {
pub fn id(&self) -> usize {
self.id
}
#[cfg(test)]
pub fn new_test(id: usize) -> Self {
Self { id, valid: true }
}
}
impl Drop for ConfigHandle {
fn drop(&mut self) {
}
}
impl Clone for ConfigHandle {
fn clone(&self) -> Self {
Self {
id: self.id,
valid: self.valid,
}
}
}
trait ConfigPoolable {
fn reset_to_default(&mut self);
}
impl ConfigPoolable for AnimationConfig {
fn reset_to_default(&mut self) {
*self = AnimationConfig::default();
}
}
#[allow(dead_code)]
trait PoolStatsProvider {
fn stats(&self) -> (usize, usize);
}
impl<T: Animatable + Send> PoolStatsProvider for SpringIntegratorPool<T> {
fn stats(&self) -> (usize, usize) {
(self.in_use.len(), self.available.len())
}
}
thread_local! {
static CONFIG_POOL: RefCell<ConfigPool> = RefCell::new(ConfigPool::new());
}
pub mod global {
use super::*;
pub fn get_config() -> ConfigHandle {
CONFIG_POOL.with(|pool| pool.borrow_mut().get_config())
}
pub fn return_config(handle: ConfigHandle) {
CONFIG_POOL.with(|pool| {
pool.borrow_mut().return_config(handle);
});
}
pub fn modify_config<F>(handle: &ConfigHandle, f: F)
where
F: FnOnce(&mut AnimationConfig),
{
CONFIG_POOL.with(|pool| {
pool.borrow_mut().modify_config(handle, f);
});
}
pub fn get_config_ref(handle: &ConfigHandle) -> Option<AnimationConfig> {
CONFIG_POOL.with(|pool| pool.borrow().get_config_ref(handle).cloned())
}
pub fn pool_stats() -> (usize, usize) {
CONFIG_POOL.with(|pool| {
let pool = pool.borrow();
(pool.in_use_count(), pool.available_count())
})
}
#[cfg(test)]
pub fn clear_pool() {
CONFIG_POOL.with(|pool| {
pool.borrow_mut().clear();
});
}
}
pub struct SpringIntegrator<T: Animatable> {
k1_pos: T,
k1_vel: T,
k2_pos: T,
k2_vel: T,
k3_pos: T,
k3_vel: T,
k4_pos: T,
k4_vel: T,
temp_pos: T,
temp_vel: T,
}
impl<T: Animatable> SpringIntegrator<T> {
pub fn new() -> Self {
Self {
k1_pos: T::default(),
k1_vel: T::default(),
k2_pos: T::default(),
k2_vel: T::default(),
k3_pos: T::default(),
k3_vel: T::default(),
k4_pos: T::default(),
k4_vel: T::default(),
temp_pos: T::default(),
temp_vel: T::default(),
}
}
pub fn integrate_rk4(
&mut self,
current_pos: T,
current_vel: T,
target: T,
spring: &Spring,
dt: f32,
) -> (T, T) {
let stiffness = spring.stiffness;
let damping = spring.damping;
let mass_inv = 1.0 / spring.mass;
let delta = target - current_pos;
let force = delta * stiffness;
let damping_force = current_vel * damping;
let acc = (force - damping_force) * mass_inv;
self.k1_pos = current_vel;
self.k1_vel = acc;
self.temp_pos = current_pos + self.k1_pos * (dt * 0.5);
self.temp_vel = current_vel + self.k1_vel * (dt * 0.5);
let delta = target - self.temp_pos;
let force = delta * stiffness;
let damping_force = self.temp_vel * damping;
let acc = (force - damping_force) * mass_inv;
self.k2_pos = self.temp_vel;
self.k2_vel = acc;
self.temp_pos = current_pos + self.k2_pos * (dt * 0.5);
self.temp_vel = current_vel + self.k2_vel * (dt * 0.5);
let delta = target - self.temp_pos;
let force = delta * stiffness;
let damping_force = self.temp_vel * damping;
let acc = (force - damping_force) * mass_inv;
self.k3_pos = self.temp_vel;
self.k3_vel = acc;
self.temp_pos = current_pos + self.k3_pos * dt;
self.temp_vel = current_vel + self.k3_vel * dt;
let delta = target - self.temp_pos;
let force = delta * stiffness;
let damping_force = self.temp_vel * damping;
let acc = (force - damping_force) * mass_inv;
self.k4_pos = self.temp_vel;
self.k4_vel = acc;
const SIXTH: f32 = 1.0 / 6.0;
let new_pos = current_pos
+ (self.k1_pos + self.k2_pos * 2.0 + self.k3_pos * 2.0 + self.k4_pos) * (dt * SIXTH);
let new_vel = current_vel
+ (self.k1_vel + self.k2_vel * 2.0 + self.k3_vel * 2.0 + self.k4_vel) * (dt * SIXTH);
(new_pos, new_vel)
}
pub fn reset(&mut self) {
self.k1_pos = T::default();
self.k1_vel = T::default();
self.k2_pos = T::default();
self.k2_vel = T::default();
self.k3_pos = T::default();
self.k3_vel = T::default();
self.k4_pos = T::default();
self.k4_vel = T::default();
self.temp_pos = T::default();
self.temp_vel = T::default();
}
}
impl<T: Animatable> Default for SpringIntegrator<T> {
fn default() -> Self {
Self::new()
}
}
pub struct SpringIntegratorPool<T: Animatable> {
available: Vec<SpringIntegrator<T>>,
in_use: HashMap<usize, SpringIntegrator<T>>,
next_id: usize,
}
impl<T: Animatable> SpringIntegratorPool<T> {
pub fn new() -> Self {
Self::with_capacity(8)
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
available: Vec::with_capacity(capacity),
in_use: HashMap::with_capacity(capacity),
next_id: 0,
}
}
pub fn get_integrator(&mut self) -> SpringIntegratorHandle {
let mut integrator = self.available.pop().unwrap_or_default();
integrator.reset();
let id = self.next_id;
self.next_id += 1;
self.in_use.insert(id, integrator);
SpringIntegratorHandle { id }
}
pub fn return_integrator(&mut self, handle: SpringIntegratorHandle) {
if let Some(integrator) = self.in_use.remove(&handle.id) {
self.available.push(integrator);
}
}
pub fn get_integrator_mut(
&mut self,
handle: &SpringIntegratorHandle,
) -> Option<&mut SpringIntegrator<T>> {
self.in_use.get_mut(&handle.id)
}
pub fn stats(&self) -> (usize, usize) {
(self.in_use.len(), self.available.len())
}
pub fn clear(&mut self) {
self.available.clear();
self.in_use.clear();
self.next_id = 0;
}
}
impl<T: Animatable> Default for SpringIntegratorPool<T> {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SpringIntegratorHandle {
id: usize,
}
pub struct GlobalIntegratorPools {
pools: HashMap<TypeId, Box<dyn Any + Send>>,
stats_tracker: HashMap<TypeId, (usize, usize)>,
}
impl Default for GlobalIntegratorPools {
fn default() -> Self {
Self::new()
}
}
impl GlobalIntegratorPools {
pub fn new() -> Self {
Self {
pools: HashMap::new(),
stats_tracker: HashMap::new(),
}
}
pub fn get_pool<T: Animatable + Send + 'static>(&mut self) -> &mut SpringIntegratorPool<T> {
let type_id = TypeId::of::<T>();
let pool = self
.pools
.entry(type_id)
.or_insert_with(|| Box::new(SpringIntegratorPool::<T>::new()))
.downcast_mut::<SpringIntegratorPool<T>>()
.expect("Type mismatch in integrator pool");
let stats = pool.stats();
self.stats_tracker.insert(type_id, stats);
pool
}
pub fn clear(&mut self) {
self.pools.clear();
self.stats_tracker.clear();
}
pub fn stats(&self) -> HashMap<TypeId, (usize, usize)> {
self.stats_tracker.clone()
}
pub fn update_stats<T: Animatable + Send + 'static>(&mut self) {
let type_id = TypeId::of::<T>();
if let Some(pool) = self.pools.get(&type_id)
&& let Some(pool) = pool.downcast_ref::<SpringIntegratorPool<T>>()
{
let stats = pool.stats();
self.stats_tracker.insert(type_id, stats);
}
}
}
pub struct MotionResourcePools {
pub config_pool: ConfigPool,
pub integrator_pools: GlobalIntegratorPools,
#[cfg(feature = "web")]
pub closure_pool: crate::animations::closure_pool::WebClosurePool,
pub config: PoolConfig,
}
impl MotionResourcePools {
pub fn new() -> Self {
Self::with_config(PoolConfig::default())
}
pub fn with_config(config: PoolConfig) -> Self {
Self {
config_pool: ConfigPool::with_capacity(config.config_pool_capacity),
integrator_pools: GlobalIntegratorPools::new(),
#[cfg(feature = "web")]
closure_pool: crate::animations::closure_pool::WebClosurePool::new(),
config,
}
}
pub fn stats(&self) -> PoolStats {
let (config_in_use, config_available) = (
self.config_pool.in_use_count(),
self.config_pool.available_count(),
);
#[cfg(feature = "web")]
let (closure_in_use, closure_available) = (
self.closure_pool.in_use_count(),
self.closure_pool.available_count(),
);
#[cfg(not(feature = "web"))]
let (closure_in_use, closure_available) = (0, 0);
let integrator_stats = INTEGRATOR_POOLS.with(|pools| pools.borrow().stats());
PoolStats {
config_pool: (config_in_use, config_available),
closure_pool: (closure_in_use, closure_available),
integrator_pools: integrator_stats,
total_memory_saved_bytes: self.estimate_memory_savings(),
}
}
fn estimate_memory_savings(&self) -> usize {
const CONFIG_SIZE: usize = std::mem::size_of::<AnimationConfig>();
const INTEGRATOR_SIZE: usize = 256; const CLOSURE_SIZE: usize = 64;
let config_savings = self.config_pool.available_count() * CONFIG_SIZE;
let closure_savings = {
#[cfg(feature = "web")]
{
self.closure_pool.available_count() * CLOSURE_SIZE
}
#[cfg(not(feature = "web"))]
{
0
}
};
let integrator_savings = 8 * INTEGRATOR_SIZE;
config_savings + closure_savings + integrator_savings
}
pub fn clear(&mut self) {
self.config_pool.clear();
self.integrator_pools.clear();
#[cfg(feature = "web")]
self.closure_pool.clear();
}
pub fn maintain(&mut self) {
if self.config_pool.available_count() > self.config.max_config_pool_size {
self.config_pool
.trim_to_size(self.config.target_config_pool_size);
}
}
}
impl Default for MotionResourcePools {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct PoolConfig {
pub config_pool_capacity: usize,
pub max_config_pool_size: usize,
pub target_config_pool_size: usize,
pub auto_maintain: bool,
pub maintenance_interval: u32,
}
impl Default for PoolConfig {
fn default() -> Self {
Self {
config_pool_capacity: 16,
max_config_pool_size: 64,
target_config_pool_size: 32,
auto_maintain: true,
maintenance_interval: 1000, }
}
}
#[derive(Debug, Clone)]
pub struct PoolStats {
pub config_pool: (usize, usize),
pub closure_pool: (usize, usize),
pub integrator_pools: HashMap<TypeId, (usize, usize)>,
pub total_memory_saved_bytes: usize,
}
thread_local! {
static MOTION_RESOURCE_POOLS: RefCell<MotionResourcePools> = RefCell::new(MotionResourcePools::new());
static INTEGRATOR_POOLS: RefCell<GlobalIntegratorPools> = RefCell::new(GlobalIntegratorPools::new());
}
pub mod integrator {
use super::*;
pub fn get_integrator<T: Animatable + Send + 'static>() -> SpringIntegratorHandle {
INTEGRATOR_POOLS.with(|pools| pools.borrow_mut().get_pool::<T>().get_integrator())
}
pub fn return_integrator<T: Animatable + Send + 'static>(handle: SpringIntegratorHandle) {
INTEGRATOR_POOLS.with(|pools| {
let mut pools = pools.borrow_mut();
pools.get_pool::<T>().return_integrator(handle);
pools.update_stats::<T>();
});
}
pub fn integrate_rk4<T: Animatable + Send + 'static>(
handle: &SpringIntegratorHandle,
current_pos: T,
current_vel: T,
target: T,
spring: &Spring,
dt: f32,
) -> (T, T) {
INTEGRATOR_POOLS.with(|pools| {
let mut pools = pools.borrow_mut();
let pool = pools.get_pool::<T>();
pool.get_integrator_mut(handle).map_or_else(
|| {
let mut integrator = SpringIntegrator::new();
integrator.integrate_rk4(current_pos, current_vel, target, spring, dt)
},
|integrator| integrator.integrate_rk4(current_pos, current_vel, target, spring, dt),
)
})
}
pub fn pool_stats<T: Animatable + Send + 'static>() -> (usize, usize) {
INTEGRATOR_POOLS.with(|pools| pools.borrow_mut().get_pool::<T>().stats())
}
#[cfg(test)]
pub fn clear_pools() {
INTEGRATOR_POOLS.with(|pools| {
pools.borrow_mut().clear();
});
}
}
pub mod resource_pools {
use super::*;
pub fn stats() -> PoolStats {
MOTION_RESOURCE_POOLS.with(|pools| pools.borrow().stats())
}
pub fn configure(config: PoolConfig) {
MOTION_RESOURCE_POOLS.with(|pools| {
*pools.borrow_mut() = MotionResourcePools::with_config(config);
});
}
pub fn init_high_performance() {
configure(PoolConfig {
config_pool_capacity: 64,
max_config_pool_size: 256,
target_config_pool_size: 128,
auto_maintain: true,
maintenance_interval: 500, });
}
pub fn init_memory_conservative() {
configure(PoolConfig {
config_pool_capacity: 8,
max_config_pool_size: 32,
target_config_pool_size: 16,
auto_maintain: true,
maintenance_interval: 2000, });
}
pub fn maintain() {
MOTION_RESOURCE_POOLS.with(|pools| {
pools.borrow_mut().maintain();
});
}
#[cfg(test)]
pub fn clear_all() {
MOTION_RESOURCE_POOLS.with(|pools| {
pools.borrow_mut().clear();
});
}
pub fn get_config() -> PoolConfig {
MOTION_RESOURCE_POOLS.with(|pools| pools.borrow().config.clone())
}
pub fn memory_usage_bytes() -> usize {
MOTION_RESOURCE_POOLS.with(|pools| {
let pools = pools.borrow();
let stats = pools.stats();
const CONFIG_SIZE: usize = std::mem::size_of::<AnimationConfig>();
const INTEGRATOR_SIZE: usize = 256;
const CLOSURE_SIZE: usize = 64;
let config_memory = (stats.config_pool.0 + stats.config_pool.1) * CONFIG_SIZE;
let closure_memory = (stats.closure_pool.0 + stats.closure_pool.1) * CLOSURE_SIZE;
let integrator_memory = stats
.integrator_pools
.values()
.map(|(in_use, available)| (in_use + available) * INTEGRATOR_SIZE)
.sum::<usize>();
config_memory + closure_memory + integrator_memory
})
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
use crate::animations::core::AnimationMode;
use crate::animations::spring::Spring;
use instant::Duration;
#[test]
fn test_config_pool_basic_operations() {
let mut pool = ConfigPool::new();
let handle1 = pool.get_config();
assert_eq!(pool.in_use_count(), 1);
assert_eq!(pool.available_count(), 0);
let handle2 = pool.get_config();
assert_eq!(pool.in_use_count(), 2);
assert_eq!(pool.available_count(), 0);
pool.return_config(handle1);
assert_eq!(pool.in_use_count(), 1);
assert_eq!(pool.available_count(), 1);
let handle3 = pool.get_config();
assert_eq!(pool.in_use_count(), 2);
assert_eq!(pool.available_count(), 0);
pool.return_config(handle2);
pool.return_config(handle3);
}
#[test]
fn test_config_pool_modification() {
let mut pool = ConfigPool::new();
let handle = pool.get_config();
pool.modify_config(&handle, |config| {
config.mode = AnimationMode::Spring(Spring::default());
config.delay = Duration::from_millis(100);
});
let config_ref = pool.get_config_ref(&handle).unwrap();
assert!(matches!(config_ref.mode, AnimationMode::Spring(_)));
assert_eq!(config_ref.delay, Duration::from_millis(100));
pool.return_config(handle);
}
#[test]
fn test_config_pool_reset_on_return() {
let mut pool = ConfigPool::new();
let handle = pool.get_config();
pool.modify_config(&handle, |config| {
config.mode = AnimationMode::Spring(Spring::default());
config.delay = Duration::from_millis(100);
});
pool.return_config(handle);
let new_handle = pool.get_config();
let config_ref = pool.get_config_ref(&new_handle).unwrap();
assert!(matches!(config_ref.mode, AnimationMode::Tween(_)));
assert_eq!(config_ref.delay, Duration::default());
pool.return_config(new_handle);
}
#[test]
fn test_config_pool_with_capacity() {
let pool = ConfigPool::with_capacity(32);
assert_eq!(pool.available_count(), 0);
assert_eq!(pool.in_use_count(), 0);
}
#[test]
fn test_config_pool_clear() {
let mut pool = ConfigPool::new();
let handle1 = pool.get_config();
let _handle2 = pool.get_config();
pool.return_config(handle1);
assert_eq!(pool.in_use_count(), 1);
assert_eq!(pool.available_count(), 1);
pool.clear();
assert_eq!(pool.in_use_count(), 0);
assert_eq!(pool.available_count(), 0);
}
#[test]
fn test_global_config_pool() {
global::clear_pool();
let handle1 = global::get_config();
let handle2 = global::get_config();
let (in_use, available) = global::pool_stats();
assert_eq!(in_use, 2);
assert_eq!(available, 0);
global::modify_config(&handle1, |config| {
config.delay = Duration::from_millis(50);
});
let config = global::get_config_ref(&handle1).unwrap();
assert_eq!(config.delay, Duration::from_millis(50));
global::return_config(handle1);
global::return_config(handle2);
let (in_use, available) = global::pool_stats();
assert_eq!(in_use, 0);
assert_eq!(available, 2);
}
#[test]
fn test_config_handle_clone() {
let handle1 = ConfigHandle::new_test(42);
let handle2 = handle1.clone();
assert_eq!(handle1.id(), handle2.id());
assert_eq!(handle1.id(), 42);
}
#[test]
fn test_config_handle_explicit_cleanup() {
global::clear_pool();
let handle = global::get_config();
let (in_use, available) = global::pool_stats();
assert_eq!(in_use, 1);
assert_eq!(available, 0);
global::return_config(handle);
let (in_use, available) = global::pool_stats();
assert_eq!(in_use, 0);
assert_eq!(available, 1);
}
#[test]
fn test_config_handle_double_drop_safety() {
global::clear_pool();
let handle = global::get_config();
let handle_id = handle.id();
global::return_config(ConfigHandle {
id: handle_id,
valid: false,
});
let (in_use, available) = global::pool_stats();
assert_eq!(in_use, 0);
assert_eq!(available, 1);
drop(handle);
let (in_use, available) = global::pool_stats();
assert_eq!(in_use, 0);
assert_eq!(available, 1);
}
#[test]
fn test_spring_integrator() {
let mut integrator = SpringIntegrator::<f32>::new();
let spring = Spring::default();
let current_pos = 0.0f32;
let current_vel = 0.0f32;
let target = 100.0f32;
let dt = 1.0 / 60.0;
let (new_pos, new_vel) =
integrator.integrate_rk4(current_pos, current_vel, target, &spring, dt);
assert!(new_pos > current_pos);
assert!(new_pos < target);
assert!(new_vel > 0.0);
integrator.reset();
}
#[test]
fn test_spring_integrator_pool() {
let mut pool = SpringIntegratorPool::<f32>::new();
let handle1 = pool.get_integrator();
let handle2 = pool.get_integrator();
let (in_use, available) = pool.stats();
assert_eq!(in_use, 2);
assert_eq!(available, 0);
let spring = Spring::default();
if let Some(integrator) = pool.get_integrator_mut(&handle1) {
let (new_pos, new_vel) = integrator.integrate_rk4(0.0, 0.0, 100.0, &spring, 1.0 / 60.0);
assert!(new_pos > 0.0);
assert!(new_vel > 0.0);
}
pool.return_integrator(handle1);
let (in_use, available) = pool.stats();
assert_eq!(in_use, 1);
assert_eq!(available, 1);
let handle3 = pool.get_integrator();
let (in_use, available) = pool.stats();
assert_eq!(in_use, 2);
assert_eq!(available, 0);
pool.return_integrator(handle2);
pool.return_integrator(handle3);
}
#[test]
fn test_global_integrator_pool() {
integrator::clear_pools();
let handle1 = integrator::get_integrator::<f32>();
let handle2 = integrator::get_integrator::<f32>();
let (in_use, available) = integrator::pool_stats::<f32>();
assert_eq!(in_use, 2);
assert_eq!(available, 0);
let spring = Spring::default();
let (new_pos, new_vel) =
integrator::integrate_rk4(&handle1, 0.0, 0.0, 100.0, &spring, 1.0 / 60.0);
assert!(new_pos > 0.0);
assert!(new_vel > 0.0);
integrator::return_integrator::<f32>(handle1);
integrator::return_integrator::<f32>(handle2);
let (in_use, available) = integrator::pool_stats::<f32>();
assert_eq!(in_use, 0);
assert_eq!(available, 2);
}
#[test]
fn test_integrator_pool_stats_tracking() {
integrator::clear_pools();
resource_pools::clear_all();
let handle_f32 = integrator::get_integrator::<f32>();
let handle_vec2 = integrator::get_integrator::<crate::animations::transform::Transform>();
let f32_stats = integrator::pool_stats::<f32>();
let transform_stats = integrator::pool_stats::<crate::animations::transform::Transform>();
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);
integrator::return_integrator::<crate::animations::transform::Transform>(handle_vec2);
let updated_f32_stats = integrator::pool_stats::<f32>();
let updated_transform_stats =
integrator::pool_stats::<crate::animations::transform::Transform>();
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); }
#[test]
fn test_spring_integrator_accuracy() {
let mut integrator = SpringIntegrator::<f32>::new();
let spring = Spring::default();
let current_pos = 10.0f32;
let current_vel = 5.0f32;
let target = 50.0f32;
let dt = 1.0 / 120.0;
let (new_pos, new_vel) =
integrator.integrate_rk4(current_pos, current_vel, target, &spring, dt);
assert!(new_pos > current_pos);
let expected_force_direction = target - current_pos;
assert!(expected_force_direction > 0.0); assert!(new_vel > current_vel);
}
#[test]
fn test_motion_resource_pools() {
let pools = MotionResourcePools::new();
let stats = pools.stats();
assert_eq!(stats.config_pool, (0, 0));
assert_eq!(stats.closure_pool, (0, 0));
assert!(stats.integrator_pools.is_empty());
assert_eq!(stats.total_memory_saved_bytes, 8 * 256); }
#[test]
fn test_motion_resource_pools_with_config() {
let config = PoolConfig {
config_pool_capacity: 32,
max_config_pool_size: 128,
target_config_pool_size: 64,
auto_maintain: false,
maintenance_interval: 500,
};
let pools = MotionResourcePools::with_config(config.clone());
assert_eq!(pools.config.config_pool_capacity, 32);
assert_eq!(pools.config.max_config_pool_size, 128);
assert!(!pools.config.auto_maintain);
}
#[test]
fn test_motion_resource_pools_clear() {
let mut pools = MotionResourcePools::new();
let _handle = pools.config_pool.get_config();
pools.clear();
let stats = pools.stats();
assert_eq!(stats.config_pool, (0, 0));
assert_eq!(stats.closure_pool, (0, 0));
}
#[test]
fn test_resource_pools_global_functions() {
resource_pools::clear_all();
let config = PoolConfig {
config_pool_capacity: 24,
max_config_pool_size: 96,
target_config_pool_size: 48,
auto_maintain: true,
maintenance_interval: 750,
};
resource_pools::configure(config.clone());
let retrieved_config = resource_pools::get_config();
assert_eq!(retrieved_config.config_pool_capacity, 24);
assert_eq!(retrieved_config.max_config_pool_size, 96);
let stats = resource_pools::stats();
assert_eq!(stats.config_pool, (0, 0));
let memory_usage = resource_pools::memory_usage_bytes();
assert!(memory_usage < 1_000_000);
resource_pools::maintain();
}
#[test]
fn test_pool_config_default() {
let config = PoolConfig::default();
assert_eq!(config.config_pool_capacity, 16);
assert_eq!(config.max_config_pool_size, 64);
assert_eq!(config.target_config_pool_size, 32);
assert!(config.auto_maintain);
assert_eq!(config.maintenance_interval, 1000);
}
#[test]
fn test_pool_stats_memory_estimation() {
let pools = MotionResourcePools::new();
let stats = pools.stats();
assert!(stats.total_memory_saved_bytes > 0);
assert!(stats.total_memory_saved_bytes < 1_000_000); }
#[test]
fn test_resource_pools_stats_returns_actual_data() {
resource_pools::clear_all();
let handle1 = integrator::get_integrator::<f32>();
let handle2 = integrator::get_integrator::<crate::animations::transform::Transform>();
let stats = resource_pools::stats();
assert!(
!stats.integrator_pools.is_empty(),
"Integrator pools stats should not be empty"
);
let f32_type_id = std::any::TypeId::of::<f32>();
let transform_type_id = std::any::TypeId::of::<crate::animations::transform::Transform>();
assert!(
stats.integrator_pools.contains_key(&f32_type_id),
"Should have f32 stats"
);
assert!(
stats.integrator_pools.contains_key(&transform_type_id),
"Should have Transform stats"
);
integrator::return_integrator::<f32>(handle1);
integrator::return_integrator::<crate::animations::transform::Transform>(handle2);
}
#[test]
fn test_motion_resource_pools_maintain() {
let mut pools = MotionResourcePools::new();
pools.maintain();
pools.config.max_config_pool_size = 1;
pools.config.target_config_pool_size = 0;
pools.maintain(); }
#[test]
fn test_config_pool_trimming() {
let mut pool = ConfigPool::new();
for _ in 0..10 {
pool.available.push(AnimationConfig::default());
}
assert_eq!(pool.available_count(), 10);
assert_eq!(pool.in_use_count(), 0);
pool.trim_to_size(5);
assert_eq!(pool.available_count(), 5);
assert_eq!(pool.in_use_count(), 0);
pool.trim_to_size(2);
assert_eq!(pool.available_count(), 2);
assert_eq!(pool.in_use_count(), 0);
pool.trim_to_size(10);
assert_eq!(pool.available_count(), 2);
assert_eq!(pool.in_use_count(), 0);
pool.trim_to_size(0);
assert_eq!(pool.available_count(), 0);
assert_eq!(pool.in_use_count(), 0);
}
#[test]
fn test_config_pool_trimming_with_in_use_configs() {
let mut pool = ConfigPool::new();
for _ in 0..10 {
pool.available.push(AnimationConfig::default());
}
let handle1 = pool.get_config();
let handle2 = pool.get_config();
assert_eq!(pool.available_count(), 8);
assert_eq!(pool.in_use_count(), 2);
pool.trim_to_size(3);
assert_eq!(pool.available_count(), 3);
assert_eq!(pool.in_use_count(), 2);
pool.return_config(handle1);
pool.return_config(handle2);
assert_eq!(pool.available_count(), 5);
assert_eq!(pool.in_use_count(), 0);
}
}