use mecha10::prelude::*;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use std::time::Duration;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
struct RobotState {
position: (f64, f64, f64),
orientation: (f64, f64, f64, f64),
battery_level: f32,
mission_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct SensorCalibration {
camera_intrinsics: Vec<f64>,
imu_bias: (f64, f64, f64),
wheel_radius: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct TaskProgress {
total_tasks: u32,
completed_tasks: u32,
current_task: Option<String>,
start_time: u64,
}
pub async fn basic_save_load_example() -> Result<()> {
info!("Example 1: Basic state save and load");
let mgr = MemoryStateManager::new();
let state = RobotState {
position: (1.5, 2.3, 0.0),
orientation: (0.0, 0.0, 0.0, 1.0),
battery_level: 85.5,
mission_id: Some("mission-123".to_string()),
};
info!("Saving state: {:?}", state);
mgr.save("robot1", &state).await?;
let loaded: Option<RobotState> = mgr.load("robot1").await?;
info!("Loaded state: {:?}", loaded);
assert_eq!(loaded, Some(state));
Ok(())
}
pub async fn ttl_example() -> Result<()> {
info!("Example 2: State with TTL");
let mgr = MemoryStateManager::new();
let state = TaskProgress {
total_tasks: 10,
completed_tasks: 5,
current_task: Some("Processing data".to_string()),
start_time: now_micros(),
};
let options = StateOptions {
version: 1,
ttl: Some(Duration::from_secs(2)),
create_backup: false,
compress: false,
};
mgr.save_with_options("task_tracker", &state, options).await?;
info!("Saved task progress with 2s TTL");
let loaded: Option<TaskProgress> = mgr.load("task_tracker").await?;
info!("Loaded immediately: {:?}", loaded.is_some());
tokio::time::sleep(Duration::from_secs(3)).await;
let expired: Option<TaskProgress> = mgr.load("task_tracker").await?;
info!("Loaded after expiration: {:?}", expired.is_some());
Ok(())
}
pub async fn versioned_state_example() -> Result<()> {
info!("Example 3: Versioned state");
let mgr = MemoryStateManager::new();
let calibration = SensorCalibration {
camera_intrinsics: vec![640.0, 480.0, 320.0, 240.0],
imu_bias: (0.01, -0.02, 0.015),
wheel_radius: 0.05,
};
let options = StateOptions {
version: 1,
..Default::default()
};
mgr.save_with_options("calibration", &calibration, options).await?;
let versioned = mgr.load_versioned::<SensorCalibration>("calibration").await?;
if let Some(versioned) = versioned {
info!("Loaded calibration version: {}", versioned.version);
info!("Data age: {} seconds", versioned.age_seconds());
info!("Is expired: {}", versioned.is_expired());
info!("Calibration data: {:?}", versioned.data);
}
Ok(())
}
pub async fn snapshot_example() -> Result<()> {
info!("Example 4: Snapshots and restoration");
let mgr = MemoryStateManager::new();
let state_v1 = RobotState {
position: (0.0, 0.0, 0.0),
orientation: (0.0, 0.0, 0.0, 1.0),
battery_level: 100.0,
mission_id: None,
};
mgr.save("robot1", &state_v1).await?;
info!("Saved state v1: {:?}", state_v1);
let snapshot1 = mgr
.create_snapshot("robot1", Some("Initial position".to_string()))
.await?;
info!(
"Created snapshot: {} ({})",
snapshot1.id,
snapshot1.description.unwrap()
);
let state_v2 = RobotState {
position: (10.5, 5.2, 0.0),
orientation: (0.0, 0.0, 0.707, 0.707),
battery_level: 75.0,
mission_id: Some("explore".to_string()),
};
mgr.save("robot1", &state_v2).await?;
info!("Updated state v2: {:?}", state_v2);
let current: RobotState = mgr.load("robot1").await?.unwrap();
assert_eq!(current.position, (10.5, 5.2, 0.0));
mgr.restore_snapshot(&snapshot1.id).await?;
info!("Restored from snapshot");
let restored: RobotState = mgr.load("robot1").await?.unwrap();
info!("Restored state: {:?}", restored);
assert_eq!(restored.position, (0.0, 0.0, 0.0));
Ok(())
}
pub async fn compare_and_swap_example() -> Result<()> {
info!("Example 5: Compare-and-swap for atomic updates");
let mgr = MemoryStateManager::new();
let state = RobotState {
position: (0.0, 0.0, 0.0),
orientation: (0.0, 0.0, 0.0, 1.0),
battery_level: 100.0,
mission_id: None,
};
mgr.save("robot1", &state).await?;
let mut new_state = state.clone();
new_state.position = (1.0, 0.0, 0.0);
let success = mgr.compare_and_swap("robot1", &state, &new_state).await?;
info!("CAS with correct expected value: {}", success);
assert!(success);
let wrong_expected = state.clone();
let mut another_state = state.clone();
another_state.position = (2.0, 0.0, 0.0);
let success = mgr.compare_and_swap("robot1", &wrong_expected, &another_state).await?;
info!("CAS with wrong expected value: {}", success);
assert!(!success);
let current: RobotState = mgr.load("robot1").await?.unwrap();
assert_eq!(current.position, (1.0, 0.0, 0.0));
Ok(())
}
pub async fn filesystem_backend_example() -> Result<()> {
info!("Example 6: Filesystem backend");
let temp_dir = std::env::temp_dir().join("mecha10_state_test");
let mgr = FilesystemStateManager::new(&temp_dir).await?;
let state = RobotState {
position: (5.0, 3.0, 0.0),
orientation: (0.0, 0.0, 0.0, 1.0),
battery_level: 90.0,
mission_id: Some("patrol".to_string()),
};
info!("Saving state to filesystem: {:?}", temp_dir);
mgr.save("robot1", &state).await?;
let size = mgr.size("robot1").await?;
info!("State file size: {:?} bytes", size);
let snapshot = mgr
.create_snapshot("robot1", Some("Patrol checkpoint".to_string()))
.await?;
info!("Created snapshot: {}", snapshot.id);
let snapshots = mgr.list_snapshots("robot1").await?;
info!("Total snapshots for robot1: {}", snapshots.len());
for snap in &snapshots {
info!(" - {} ({:?})", snap.id, snap.description);
}
let loaded: Option<RobotState> = mgr.load("robot1").await?;
info!("Loaded from filesystem: {:?}", loaded);
std::fs::remove_dir_all(&temp_dir).ok();
Ok(())
}
pub async fn context_integration_example() -> Result<()> {
info!("Example 7: Context integration");
info!("In a real node, you would use:");
info!(" let state_mgr = ctx.state_manager().await?;");
info!(" state_mgr.save(ctx.node_id(), &state).await?;");
info!("");
info!("The context automatically provides:");
info!(" - Filesystem storage in ./state/ by default");
info!(" - Can be overridden with MECHA10_STATE_DIR env var");
info!(" - Custom backends via ctx.set_state_manager()");
Ok(())
}
pub async fn multi_node_example() -> Result<()> {
info!("Example 8: Multiple nodes with state");
let mgr = MemoryStateManager::new();
for i in 1..=5 {
let state = RobotState {
position: (i as f64, 0.0, 0.0),
orientation: (0.0, 0.0, 0.0, 1.0),
battery_level: 100.0 - (i as f32 * 5.0),
mission_id: Some(format!("mission-{}", i)),
};
let node_id = format!("robot{}", i);
mgr.save(&node_id, &state).await?;
}
let nodes = mgr.list_nodes().await?;
info!("Nodes with saved state: {:?}", nodes);
for node_id in &nodes {
let state: Option<RobotState> = mgr.load(node_id).await?;
if let Some(state) = state {
info!(
"{}: battery={}%, position={:?}",
node_id, state.battery_level, state.position
);
}
}
Ok(())
}
pub async fn cleanup_expired_example() -> Result<()> {
info!("Example 9: Cleaning expired states");
let mgr = MemoryStateManager::new();
for i in 1..=3 {
let state = RobotState {
position: (i as f64, 0.0, 0.0),
orientation: (0.0, 0.0, 0.0, 1.0),
battery_level: 100.0,
mission_id: None,
};
let options = StateOptions {
version: 1,
ttl: Some(Duration::from_secs(1)),
..Default::default()
};
mgr.save_with_options(&format!("temp_robot{}", i), &state, options)
.await?;
}
for i in 4..=6 {
let state = RobotState {
position: (i as f64, 0.0, 0.0),
orientation: (0.0, 0.0, 0.0, 1.0),
battery_level: 100.0,
mission_id: None,
};
mgr.save(&format!("persistent_robot{}", i), &state).await?;
}
let nodes_before = mgr.list_nodes().await?;
info!("Nodes before cleanup: {}", nodes_before.len());
tokio::time::sleep(Duration::from_secs(2)).await;
let removed = mgr.clear_expired().await?;
info!("Removed {} expired states", removed);
let nodes_after = mgr.list_nodes().await?;
info!("Nodes after cleanup: {}", nodes_after.len());
info!("Remaining nodes: {:?}", nodes_after);
Ok(())
}
pub async fn custom_backend_example() -> Result<()> {
info!("Example 10: Custom state backend");
info!("You can implement StateManager trait for custom backends:");
info!("");
info!("pub struct RedisStateManager {{");
info!(" redis: Arc<Redis>,");
info!("}}");
info!("");
info!("#[async_trait]");
info!("impl StateManager for RedisStateManager {{");
info!(" async fn save<T: Serialize + Send + Sync>(&self, node_id: &str, state: &T) -> Result<()> {{");
info!(" let key = format!(\"state:{{}}\", node_id);");
info!(" let json = serde_json::to_string(state)?;");
info!(" self.redis.set(&key, json).await?;");
info!(" Ok(())");
info!(" }}");
info!(" // ... implement other methods");
info!("}}");
info!("");
info!("Then use it:");
info!(" let redis_mgr = Arc::new(RedisStateManager::new());");
info!(" ctx.set_state_manager(redis_mgr).await;");
Ok(())
}
fn now_micros() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_micros() as u64
}
fn main() -> Result<()> {
tracing_subscriber::fmt().with_max_level(tracing::Level::INFO).init();
println!("Persistent State Storage Examples");
println!("==================================\n");
println!("StateManager provides persistent storage for node state across restarts.");
println!("Key features:");
println!(" • Multiple backends (memory, filesystem, Redis, custom)");
println!(" • Versioned state for migrations");
println!(" • Snapshots and restoration");
println!(" • TTL support for temporary state");
println!(" • Atomic compare-and-swap operations");
println!();
let runtime = tokio::runtime::Runtime::new().unwrap();
runtime.block_on(async {
basic_save_load_example().await?;
println!();
ttl_example().await?;
println!();
versioned_state_example().await?;
println!();
snapshot_example().await?;
println!();
compare_and_swap_example().await?;
println!();
filesystem_backend_example().await?;
println!();
context_integration_example().await?;
println!();
multi_node_example().await?;
println!();
cleanup_expired_example().await?;
println!();
custom_backend_example().await?;
println!();
Ok::<(), Mecha10Error>(())
})?;
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!("All examples completed!");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
println!("Common use cases:");
println!(" ✓ Robot pose and odometry persistence");
println!(" ✓ Sensor calibration data");
println!(" ✓ Mission progress tracking");
println!(" ✓ Learning/training checkpoints");
println!(" ✓ Configuration overrides");
println!(" ✓ Diagnostic logs and metrics");
println!();
println!("Best practices:");
println!(" • Use versioned state for schema evolution");
println!(" • Create snapshots before risky operations");
println!(" • Use TTL for temporary/cache-like state");
println!(" • Use CAS for concurrent updates");
println!(" • Choose backend based on durability needs");
Ok(())
}