use crate::Result;
use std::collections::HashMap;
use std::sync::{Arc, Mutex, OnceLock};
use std::time::Instant;
pub trait LazyModule: Send + Sync {
fn name(&self) -> &str;
fn size_estimate(&self) -> usize;
fn is_loaded(&self) -> bool;
}
pub struct AnimationLazyLoader {
loaded_modules: Arc<Mutex<HashMap<String, Box<dyn LazyModule>>>>,
load_times: Arc<Mutex<HashMap<String, Instant>>>,
module_sizes: Arc<Mutex<HashMap<String, usize>>>,
}
impl AnimationLazyLoader {
pub fn new() -> Self {
Self {
loaded_modules: Arc::new(Mutex::new(HashMap::new())),
load_times: Arc::new(Mutex::new(HashMap::new())),
module_sizes: Arc::new(Mutex::new(HashMap::new())),
}
}
pub fn load_module(&self, name: &str, module: Box<dyn LazyModule>) -> Result<()> {
let mut modules = self.loaded_modules.lock().unwrap();
let mut times = self.load_times.lock().unwrap();
let mut sizes = self.module_sizes.lock().unwrap();
if modules.contains_key(name) {
return Err(crate::AnimationError::EngineUnavailable(format!(
"Module {} already loaded",
name
)));
}
let size = module.size_estimate();
modules.insert(name.to_string(), module);
times.insert(name.to_string(), Instant::now());
sizes.insert(name.to_string(), size);
Ok(())
}
pub fn is_loaded(&self, name: &str) -> bool {
let modules = self.loaded_modules.lock().unwrap();
modules.contains_key(name)
}
pub fn get_module(&self, _name: &str) -> Option<Box<dyn LazyModule>> {
let _modules = self.loaded_modules.lock().unwrap();
None
}
pub fn total_loaded_size(&self) -> usize {
let sizes = self.module_sizes.lock().unwrap();
sizes.values().sum()
}
pub fn loaded_count(&self) -> usize {
let modules = self.loaded_modules.lock().unwrap();
modules.len()
}
pub fn get_load_time(&self, name: &str) -> Option<Instant> {
let times = self.load_times.lock().unwrap();
times.get(name).copied()
}
pub fn unload_module(&self, name: &str) -> Result<()> {
let mut modules = self.loaded_modules.lock().unwrap();
let mut times = self.load_times.lock().unwrap();
let mut sizes = self.module_sizes.lock().unwrap();
if !modules.contains_key(name) {
return Err(crate::AnimationError::EngineUnavailable(format!(
"Module {} not loaded",
name
)));
}
modules.remove(name);
times.remove(name);
sizes.remove(name);
Ok(())
}
pub fn clear_all(&self) {
let mut modules = self.loaded_modules.lock().unwrap();
let mut times = self.load_times.lock().unwrap();
let mut sizes = self.module_sizes.lock().unwrap();
modules.clear();
times.clear();
sizes.clear();
}
}
static LAZY_LOADER: OnceLock<AnimationLazyLoader> = OnceLock::new();
pub fn get_lazy_loader() -> &'static AnimationLazyLoader {
LAZY_LOADER.get_or_init(|| AnimationLazyLoader::new())
}
#[derive(Debug, Clone)]
pub struct LazyLoadingConfig {
pub max_loaded_modules: usize,
pub max_total_size: usize,
pub auto_cleanup: bool,
pub cleanup_threshold: f64,
}
impl Default for LazyLoadingConfig {
fn default() -> Self {
Self {
max_loaded_modules: 10,
max_total_size: 1024 * 1024, auto_cleanup: true,
cleanup_threshold: 0.8, }
}
}
pub struct FeatureModuleLoader {
config: LazyLoadingConfig,
loader: &'static AnimationLazyLoader,
}
impl FeatureModuleLoader {
pub fn new(config: LazyLoadingConfig) -> Self {
Self {
config,
loader: get_lazy_loader(),
}
}
pub fn load_features(&self, features: &[&str]) -> Result<()> {
for feature in features {
match *feature {
"gestures" => self.load_gesture_modules()?,
"layout" => self.load_layout_modules()?,
"scroll" => self.load_scroll_modules()?,
"performance" => self.load_performance_modules()?,
_ => {
continue;
}
}
}
Ok(())
}
fn load_gesture_modules(&self) -> Result<()> {
Ok(())
}
fn load_layout_modules(&self) -> Result<()> {
Ok(())
}
fn load_scroll_modules(&self) -> Result<()> {
Ok(())
}
fn load_performance_modules(&self) -> Result<()> {
Ok(())
}
pub fn needs_cleanup(&self) -> bool {
if !self.config.auto_cleanup {
return false;
}
let loaded_count = self.loader.loaded_count();
let total_size = self.loader.total_loaded_size();
let count_threshold =
(self.config.max_loaded_modules as f64 * self.config.cleanup_threshold) as usize;
let size_threshold =
(self.config.max_total_size as f64 * self.config.cleanup_threshold) as usize;
loaded_count > count_threshold || total_size > size_threshold
}
pub fn cleanup_if_needed(&self) -> Result<()> {
if self.needs_cleanup() {
self.loader.clear_all();
}
Ok(())
}
}
#[cfg(target_arch = "wasm32")]
pub mod dynamic_import {
use js_sys::Promise;
use std::future::Future;
use std::pin::Pin;
use wasm_bindgen::prelude::*;
pub async fn import_module(module_name: &str) -> Result<JsValue, JsValue> {
let promise = Promise::resolve(&JsValue::UNDEFINED);
let future = wasm_bindgen_futures::JsFuture::from(promise);
future.await
}
pub async fn load_on_interaction(interaction_type: &str) -> Result<JsValue, JsValue> {
match interaction_type {
"drag" => import_module("gesture_drag").await,
"hover" => import_module("gesture_hover").await,
"scroll" => import_module("scroll_handler").await,
_ => Err(JsValue::from_str("Unknown interaction type")),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
struct MockModule {
name: String,
size: usize,
loaded: bool,
}
impl LazyModule for MockModule {
fn name(&self) -> &str {
&self.name
}
fn size_estimate(&self) -> usize {
self.size
}
fn is_loaded(&self) -> bool {
self.loaded
}
}
#[test]
fn test_lazy_loader_creation() {
let loader = AnimationLazyLoader::new();
assert_eq!(loader.loaded_count(), 0);
assert_eq!(loader.total_loaded_size(), 0);
}
#[test]
fn test_lazy_loader_load_module() {
let loader = AnimationLazyLoader::new();
let module = Box::new(MockModule {
name: "test_module".to_string(),
size: 1024,
loaded: true,
});
let result = loader.load_module("test", module);
assert!(result.is_ok());
assert_eq!(loader.loaded_count(), 1);
assert_eq!(loader.total_loaded_size(), 1024);
assert!(loader.is_loaded("test"));
}
#[test]
fn test_lazy_loader_duplicate_load() {
let loader = AnimationLazyLoader::new();
let module1 = Box::new(MockModule {
name: "test_module1".to_string(),
size: 1024,
loaded: true,
});
let module2 = Box::new(MockModule {
name: "test_module2".to_string(),
size: 2048,
loaded: true,
});
loader.load_module("test", module1).unwrap();
let result = loader.load_module("test", module2);
assert!(result.is_err());
assert_eq!(loader.loaded_count(), 1);
}
#[test]
fn test_lazy_loader_unload() {
let loader = AnimationLazyLoader::new();
let module = Box::new(MockModule {
name: "test_module".to_string(),
size: 1024,
loaded: true,
});
loader.load_module("test", module).unwrap();
assert_eq!(loader.loaded_count(), 1);
let result = loader.unload_module("test");
assert!(result.is_ok());
assert_eq!(loader.loaded_count(), 0);
assert!(!loader.is_loaded("test"));
}
#[test]
fn test_feature_loader_config() {
let config = LazyLoadingConfig::default();
let loader = FeatureModuleLoader::new(config);
assert!(!loader.needs_cleanup());
let result = loader.cleanup_if_needed();
assert!(result.is_ok());
}
#[test]
fn test_feature_loader_load_features() {
let config = LazyLoadingConfig::default();
let loader = FeatureModuleLoader::new(config);
let features = ["gestures", "layout", "scroll"];
let result = loader.load_features(&features);
assert!(result.is_ok());
}
}