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("configs/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        let profile = RobotProfile::from_config_file(config_path)?;
74
75        let robot_output = output_path.join("robot.tscn");
76        let robot_generator = RobotGenerator::from_config_file(config_path)?;
77        robot_generator.generate(&robot_output)?;
78
79        let selector = EnvironmentSelector::new()?;
80        let matches = selector.select_environments(&profile, max_envs)?;
81
82        let filtered_matches: Vec<_> = matches.into_iter().filter(|m| m.score >= min_score).collect();
83
84        if filtered_matches.is_empty() {
85            return Err(anyhow::anyhow!(
86                "No matching environments found (min score: {})",
87                min_score
88            ));
89        }
90
91        let _catalog_path = PathBuf::from(paths::framework::ROBOT_TASKS_CATALOG);
92        let base_path = PathBuf::from(paths::framework::ROBOT_TASKS_DIR);
93        let mut available_count = 0;
94
95        for env_match in &filtered_matches {
96            let env_path = base_path.join(&env_match.environment.path);
97            if env_path.exists() {
98                available_count += 1;
99            }
100        }
101
102        Ok(GenerationResult {
103            robot_scene: robot_output,
104            environments: filtered_matches.iter().map(|m| m.environment.id.clone()).collect(),
105            available_count,
106        })
107    }
108
109    /// Generate only the robot scene
110    ///
111    /// # Arguments
112    ///
113    /// * `config_path` - Path to mecha10.json
114    /// * `output_path` - Output path for robot.tscn
115    pub fn generate_robot(&self, config_path: &Path, output_path: &Path) -> Result<()> {
116        let robot_generator = RobotGenerator::from_config_file(config_path)?;
117        robot_generator.generate(output_path)
118    }
119
120    /// List available environments
121    ///
122    /// # Arguments
123    ///
124    /// * `config_path` - Path to mecha10.json
125    /// * `verbose` - Show detailed information
126    pub fn list_environments(&self, config_path: &Path, verbose: bool) -> Result<Vec<EnvironmentInfo>> {
127        let profile = RobotProfile::from_config_file(config_path)?;
128        let selector = EnvironmentSelector::new()?;
129        let matches = selector.select_environments(&profile, 100)?;
130
131        let base_path = PathBuf::from(paths::framework::ROBOT_TASKS_DIR);
132        let mut environments = Vec::new();
133
134        for env_match in matches {
135            let env_path = base_path.join(&env_match.environment.path);
136            let available = env_path.exists();
137
138            if verbose || available {
139                environments.push(EnvironmentInfo {
140                    id: env_match.environment.id.clone(),
141                    name: env_match.environment.name.clone(),
142                    description: env_match.environment.description.clone(),
143                    score: env_match.score,
144                    available,
145                });
146            }
147        }
148
149        Ok(environments)
150    }
151
152    /// Validate project configuration for simulation
153    ///
154    /// # Arguments
155    ///
156    /// * `config_path` - Path to mecha10.json
157    pub fn validate_config(&self, config_path: &Path) -> Result<ValidationResult> {
158        let profile = RobotProfile::from_config_file(config_path)?;
159
160        let mut errors = Vec::new();
161        let mut warnings = Vec::new();
162
163        if profile.platform.is_empty() {
164            errors.push("Platform not specified".to_string());
165        }
166
167        if profile.sensors.is_empty() {
168            warnings.push("No sensors configured".to_string());
169        }
170
171        if profile.task_nodes.is_empty() {
172            warnings.push("No task nodes configured".to_string());
173        }
174
175        let is_valid = errors.is_empty();
176
177        Ok(ValidationResult {
178            is_valid,
179            errors,
180            warnings,
181            platform: profile.platform,
182            sensor_count: profile.sensors.len(),
183            node_count: profile.task_nodes.len(),
184        })
185    }
186
187    /// Check if simulation is properly set up
188    pub fn is_setup(&self) -> bool {
189        self.base_path.exists() && self.base_path.join("robot.tscn").exists()
190    }
191
192    /// Get the base path for simulations
193    pub fn base_path(&self) -> &Path {
194        &self.base_path
195    }
196}
197
198impl Default for SimulationService {
199    fn default() -> Self {
200        Self::new()
201    }
202}
203
204/// Result of simulation generation
205#[derive(Debug)]
206pub struct GenerationResult {
207    pub robot_scene: PathBuf,
208    pub environments: Vec<String>,
209    pub available_count: usize,
210}
211
212/// Information about an environment
213#[derive(Debug, Clone)]
214pub struct EnvironmentInfo {
215    pub id: String,
216    pub name: String,
217    pub description: String,
218    pub score: i32,
219    pub available: bool,
220}
221
222/// Result of configuration validation
223#[derive(Debug)]
224pub struct ValidationResult {
225    pub is_valid: bool,
226    pub errors: Vec<String>,
227    pub warnings: Vec<String>,
228    pub platform: String,
229    pub sensor_count: usize,
230    pub node_count: usize,
231}