Skip to main content

mecha10_sim/services/
simulation.rs

1#![allow(dead_code)]
2
3//! Simulation service for scene generation and management
4//!
5//! This service provides operations for generating and managing simulations,
6//! including robot scene generation, environment selection, and validation.
7
8use crate::paths;
9use crate::sim::{EnvironmentSelector, RobotGenerator, RobotProfile};
10use anyhow::Result;
11use std::path::{Path, PathBuf};
12
13/// Simulation service for managing simulations
14///
15/// # Examples
16///
17/// ```rust,ignore
18/// use mecha10_cli::services::SimulationService;
19/// use std::path::Path;
20///
21/// # async fn example() -> anyhow::Result<()> {
22/// let service = SimulationService::new();
23///
24/// // Generate simulation
25/// service.generate(
26///     Path::new("mecha10.json"),
27///     Path::new("simulation/output"),
28///     5, // max environments
29///     0  // min score
30/// )?;
31/// # Ok(())
32/// # }
33/// ```
34pub struct SimulationService {
35    /// Base path for simulation output
36    base_path: PathBuf,
37}
38
39impl SimulationService {
40    /// Create a new simulation service with default paths
41    pub fn new() -> Self {
42        Self {
43            base_path: PathBuf::from(paths::project::SIMULATION_OUTPUT_DIR),
44        }
45    }
46
47    /// Create a simulation service with custom base path
48    ///
49    /// # Arguments
50    ///
51    /// * `base_path` - Base directory for simulation output
52    pub fn with_base_path(base_path: impl Into<PathBuf>) -> Self {
53        Self {
54            base_path: base_path.into(),
55        }
56    }
57
58    /// Generate simulation scenes from project configuration
59    ///
60    /// # Arguments
61    ///
62    /// * `config_path` - Path to mecha10.json
63    /// * `output_path` - Output directory for simulation files
64    /// * `max_envs` - Maximum number of environments to generate
65    /// * `min_score` - Minimum compatibility score (0-100)
66    pub fn generate(
67        &self,
68        config_path: &Path,
69        output_path: &Path,
70        max_envs: usize,
71        min_score: i32,
72    ) -> Result<GenerationResult> {
73        // Load robot profile
74        let profile = RobotProfile::from_config_file(config_path)?;
75
76        // Generate robot scene
77        let robot_output = output_path.join("robot.tscn");
78        let robot_generator = RobotGenerator::from_config_file(config_path)?;
79        robot_generator.generate(&robot_output)?;
80
81        // Select environments
82        let selector = EnvironmentSelector::new()?;
83        let matches = selector.select_environments(&profile, max_envs)?;
84
85        let filtered_matches: Vec<_> = matches.into_iter().filter(|m| m.score >= min_score).collect();
86
87        if filtered_matches.is_empty() {
88            return Err(anyhow::anyhow!(
89                "No matching environments found (min score: {})",
90                min_score
91            ));
92        }
93
94        // Count available environments
95        let _catalog_path = PathBuf::from(paths::framework::ROBOT_TASKS_CATALOG);
96        let base_path = PathBuf::from(paths::framework::ROBOT_TASKS_DIR);
97        let mut available_count = 0;
98
99        for env_match in &filtered_matches {
100            let env_path = base_path.join(&env_match.environment.path);
101            if env_path.exists() {
102                available_count += 1;
103            }
104        }
105
106        Ok(GenerationResult {
107            robot_scene: robot_output,
108            environments: filtered_matches.iter().map(|m| m.environment.id.clone()).collect(),
109            available_count,
110        })
111    }
112
113    /// Generate only the robot scene
114    ///
115    /// # Arguments
116    ///
117    /// * `config_path` - Path to mecha10.json
118    /// * `output_path` - Output path for robot.tscn
119    pub fn generate_robot(&self, config_path: &Path, output_path: &Path) -> Result<()> {
120        let robot_generator = RobotGenerator::from_config_file(config_path)?;
121        robot_generator.generate(output_path)
122    }
123
124    /// List available environments
125    ///
126    /// # Arguments
127    ///
128    /// * `config_path` - Path to mecha10.json
129    /// * `verbose` - Show detailed information
130    pub fn list_environments(&self, config_path: &Path, verbose: bool) -> Result<Vec<EnvironmentInfo>> {
131        let profile = RobotProfile::from_config_file(config_path)?;
132        let selector = EnvironmentSelector::new()?;
133        let matches = selector.select_environments(&profile, 100)?; // Get all
134
135        let base_path = PathBuf::from(paths::framework::ROBOT_TASKS_DIR);
136        let mut environments = Vec::new();
137
138        for env_match in matches {
139            let env_path = base_path.join(&env_match.environment.path);
140            let available = env_path.exists();
141
142            if verbose || available {
143                environments.push(EnvironmentInfo {
144                    id: env_match.environment.id.clone(),
145                    name: env_match.environment.name.clone(),
146                    description: env_match.environment.description.clone(),
147                    score: env_match.score,
148                    available,
149                });
150            }
151        }
152
153        Ok(environments)
154    }
155
156    /// Validate project configuration for simulation
157    ///
158    /// # Arguments
159    ///
160    /// * `config_path` - Path to mecha10.json
161    pub fn validate_config(&self, config_path: &Path) -> Result<ValidationResult> {
162        let profile = RobotProfile::from_config_file(config_path)?;
163
164        let mut errors = Vec::new();
165        let mut warnings = Vec::new();
166
167        // Validate platform
168        if profile.platform.is_empty() {
169            errors.push("Platform not specified".to_string());
170        }
171
172        // Validate sensors
173        if profile.sensors.is_empty() {
174            warnings.push("No sensors configured".to_string());
175        }
176
177        // Validate task nodes
178        if profile.task_nodes.is_empty() {
179            warnings.push("No task nodes configured".to_string());
180        }
181
182        let is_valid = errors.is_empty();
183
184        Ok(ValidationResult {
185            is_valid,
186            errors,
187            warnings,
188            platform: profile.platform,
189            sensor_count: profile.sensors.len(),
190            node_count: profile.task_nodes.len(),
191        })
192    }
193
194    /// Check if simulation is properly set up
195    pub fn is_setup(&self) -> bool {
196        self.base_path.exists() && self.base_path.join("robot.tscn").exists()
197    }
198
199    /// Get the base path for simulations
200    pub fn base_path(&self) -> &Path {
201        &self.base_path
202    }
203}
204
205impl Default for SimulationService {
206    fn default() -> Self {
207        Self::new()
208    }
209}
210
211/// Result of simulation generation
212#[derive(Debug)]
213pub struct GenerationResult {
214    pub robot_scene: PathBuf,
215    pub environments: Vec<String>,
216    pub available_count: usize,
217}
218
219/// Information about an environment
220#[derive(Debug, Clone)]
221pub struct EnvironmentInfo {
222    pub id: String,
223    pub name: String,
224    pub description: String,
225    pub score: i32,
226    pub available: bool,
227}
228
229/// Result of configuration validation
230#[derive(Debug)]
231pub struct ValidationResult {
232    pub is_valid: bool,
233    pub errors: Vec<String>,
234    pub warnings: Vec<String>,
235    pub platform: String,
236    pub sensor_count: usize,
237    pub node_count: usize,
238}