use std::path::{Path, PathBuf};
use std::time::SystemTime;
#[derive(Debug, Clone)]
pub struct HotReloadConfig {
pub watch_dir: PathBuf,
pub watch_extensions: Vec<String>,
pub debounce_ms: u64,
pub preserve_state: bool,
}
impl Default for HotReloadConfig {
fn default() -> Self {
Self {
watch_dir: PathBuf::from("crates/aether-nodes/src"),
watch_extensions: vec![".rs".to_string()],
debounce_ms: 500,
preserve_state: true,
}
}
}
pub struct HotReloadManager {
config: HotReloadConfig,
last_modified: std::collections::HashMap<PathBuf, SystemTime>,
pending_reload: Option<SystemTime>,
}
impl HotReloadManager {
pub fn new(config: HotReloadConfig) -> Self {
Self {
config,
last_modified: std::collections::HashMap::new(),
pending_reload: None,
}
}
pub fn check_for_changes(&mut self) -> bool {
let mut changed = false;
if let Ok(entries) = std::fs::read_dir(&self.config.watch_dir) {
for entry in entries.flatten() {
if let Ok(metadata) = entry.metadata() {
if metadata.is_file() {
let path = entry.path();
if let Some(ext) = path.extension() {
let ext_str = format!(".{}", ext.to_string_lossy());
if !self.config.watch_extensions.contains(&ext_str) {
continue;
}
} else {
continue;
}
if let Ok(modified) = metadata.modified() {
if let Some(&last_mod) = self.last_modified.get(&path) {
if modified > last_mod {
changed = true;
self.last_modified.insert(path.clone(), modified);
}
} else {
self.last_modified.insert(path, modified);
}
}
}
}
}
}
if changed {
self.pending_reload = Some(SystemTime::now());
return false;
}
if let Some(pending_time) = self.pending_reload {
if let Ok(elapsed) = pending_time.elapsed() {
if elapsed.as_millis() >= self.config.debounce_ms as u128 {
self.pending_reload = None;
return true;
}
}
}
false
}
pub fn reload_node(&self, node_name: &str) -> Result<(), String> {
println!("Hot reload: Recompiling {}...", node_name);
Err("Hot reload not fully implemented (placeholder)".to_string())
}
pub fn watch_dir(&self) -> &Path {
&self.config.watch_dir
}
pub fn watched_file_count(&self) -> usize {
self.last_modified.len()
}
}
#[derive(Debug, Clone)]
pub struct NodeStateSnapshot {
pub node_type: String,
pub state_data: Vec<u8>,
pub param_values: Vec<f32>,
}
impl NodeStateSnapshot {
pub fn new(node_type: impl Into<String>) -> Self {
Self {
node_type: node_type.into(),
state_data: Vec::new(),
param_values: Vec::new(),
}
}
pub fn add_param(&mut self, value: f32) {
self.param_values.push(value);
}
pub fn set_state_data(&mut self, data: Vec<u8>) {
self.state_data = data;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hotreload_config_default() {
let config = HotReloadConfig::default();
assert_eq!(config.debounce_ms, 500);
assert!(config.preserve_state);
assert_eq!(config.watch_extensions.len(), 1);
}
#[test]
fn test_hotreload_manager_creation() {
let config = HotReloadConfig::default();
let manager = HotReloadManager::new(config);
assert_eq!(manager.watched_file_count(), 0);
}
#[test]
fn test_node_state_snapshot() {
let mut snapshot = NodeStateSnapshot::new("Oscillator");
snapshot.add_param(440.0);
snapshot.add_param(0.5);
snapshot.set_state_data(vec![1, 2, 3, 4]);
assert_eq!(snapshot.node_type, "Oscillator");
assert_eq!(snapshot.param_values.len(), 2);
assert_eq!(snapshot.state_data.len(), 4);
}
#[test]
fn test_hotreload_reload_node_placeholder() {
let config = HotReloadConfig::default();
let manager = HotReloadManager::new(config);
let result = manager.reload_node("Oscillator");
assert!(result.is_err());
}
}