mecha10_sim/services/
simulation.rs1#![allow(dead_code)]
2
3use crate::paths;
9use crate::sim::{EnvironmentSelector, RobotGenerator, RobotProfile};
10use anyhow::Result;
11use std::path::{Path, PathBuf};
12
13pub struct SimulationService {
35 base_path: PathBuf,
37}
38
39impl SimulationService {
40 pub fn new() -> Self {
42 Self {
43 base_path: PathBuf::from(paths::project::SIMULATION_OUTPUT_DIR),
44 }
45 }
46
47 pub fn with_base_path(base_path: impl Into<PathBuf>) -> Self {
53 Self {
54 base_path: base_path.into(),
55 }
56 }
57
58 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)?;
75
76 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 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 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 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 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)?; 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 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 if profile.platform.is_empty() {
169 errors.push("Platform not specified".to_string());
170 }
171
172 if profile.sensors.is_empty() {
174 warnings.push("No sensors configured".to_string());
175 }
176
177 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 pub fn is_setup(&self) -> bool {
196 self.base_path.exists() && self.base_path.join("robot.tscn").exists()
197 }
198
199 pub fn base_path(&self) -> &Path {
201 &self.base_path
202 }
203
204 pub fn resolve_model_path(&self, model_ref: &str) -> Result<PathBuf> {
222 Self::resolve_asset_path(
223 model_ref,
224 AssetPathSpec {
225 kind: "Model",
226 namespace_prefix: "@mecha10/simulation-models/",
227 project_dir: paths::project::SIMULATION_MODELS_DIR,
228 framework_dir: paths::framework::SIMULATION_MODELS_DIR,
229 bundled_subdir: "simulation/models",
230 cached_path: |assets| assets.models_path(),
231 },
232 )
233 }
234
235 pub fn resolve_environment_path(&self, env_ref: &str) -> Result<PathBuf> {
253 Self::resolve_asset_path(
254 env_ref,
255 AssetPathSpec {
256 kind: "Environment",
257 namespace_prefix: "@mecha10/simulation-environments/",
258 project_dir: paths::project::SIMULATION_ENVIRONMENTS_DIR,
259 framework_dir: paths::framework::SIMULATION_ENVIRONMENTS_DIR,
260 bundled_subdir: "simulation/environments",
261 cached_path: |assets| assets.environments_path(),
262 },
263 )
264 }
265
266 fn resolve_asset_path(asset_ref: &str, spec: AssetPathSpec) -> Result<PathBuf> {
272 let asset_name = asset_ref.strip_prefix(spec.namespace_prefix).unwrap_or(asset_ref);
274
275 let project_path = PathBuf::from(spec.project_dir).join(asset_name);
277 if project_path.exists() {
278 return Ok(project_path);
279 }
280
281 if let Ok(framework_path) = std::env::var("MECHA10_FRAMEWORK_PATH") {
283 let framework_asset_path = PathBuf::from(framework_path).join(spec.framework_dir).join(asset_name);
284 if framework_asset_path.exists() {
285 return Ok(framework_asset_path);
286 }
287 }
288
289 let cli_manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
291 if let Some(packages_dir) = cli_manifest_dir.parent() {
292 let bundled_asset_path = packages_dir.join(spec.bundled_subdir).join(asset_name);
293 if bundled_asset_path.exists() {
294 return Ok(bundled_asset_path);
295 }
296 }
297
298 let assets_service = crate::services::SimulationAssetsService::new();
300 if let Some(cached_dir) = (spec.cached_path)(&assets_service) {
301 let cached_asset_path = cached_dir.join(asset_name);
302 if cached_asset_path.exists() {
303 return Ok(cached_asset_path);
304 }
305 }
306
307 Err(anyhow::anyhow!(
308 "{} not found: {}\n\nChecked:\n • Project: {}/{}\n • Framework: packages/{}/{}\n • Cached: ~/.mecha10/simulation/current/{}/{}",
309 spec.kind,
310 asset_ref,
311 spec.project_dir,
312 asset_name,
313 spec.bundled_subdir,
314 asset_name,
315 spec.bundled_subdir.rsplit('/').next().unwrap_or(spec.bundled_subdir),
316 asset_name
317 ))
318 }
319}
320
321struct AssetPathSpec {
324 kind: &'static str,
325 namespace_prefix: &'static str,
326 project_dir: &'static str,
327 framework_dir: &'static str,
328 bundled_subdir: &'static str,
329 cached_path: fn(&crate::services::SimulationAssetsService) -> Option<PathBuf>,
330}
331
332impl Default for SimulationService {
333 fn default() -> Self {
334 Self::new()
335 }
336}
337
338#[derive(Debug)]
340pub struct GenerationResult {
341 pub robot_scene: PathBuf,
342 pub environments: Vec<String>,
343 pub available_count: usize,
344}
345
346#[derive(Debug, Clone)]
348pub struct EnvironmentInfo {
349 pub id: String,
350 pub name: String,
351 pub description: String,
352 pub score: i32,
353 pub available: bool,
354}
355
356#[derive(Debug)]
358pub struct ValidationResult {
359 pub is_valid: bool,
360 pub errors: Vec<String>,
361 pub warnings: Vec<String>,
362 pub platform: String,
363 pub sensor_count: usize,
364 pub node_count: usize,
365}