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