#![allow(dead_code)]
use crate::paths;
use crate::sim::{EnvironmentSelector, RobotGenerator, RobotProfile};
use anyhow::Result;
use std::path::{Path, PathBuf};
pub struct SimulationService {
base_path: PathBuf,
}
impl SimulationService {
pub fn new() -> Self {
Self {
base_path: PathBuf::from(paths::project::SIMULATION_OUTPUT_DIR),
}
}
pub fn with_base_path(base_path: impl Into<PathBuf>) -> Self {
Self {
base_path: base_path.into(),
}
}
pub fn generate(
&self,
config_path: &Path,
output_path: &Path,
max_envs: usize,
min_score: i32,
) -> Result<GenerationResult> {
let profile = RobotProfile::from_config_file(config_path)?;
let robot_output = output_path.join("robot.tscn");
let robot_generator = RobotGenerator::from_config_file(config_path)?;
robot_generator.generate(&robot_output)?;
let selector = EnvironmentSelector::new()?;
let matches = selector.select_environments(&profile, max_envs)?;
let filtered_matches: Vec<_> = matches.into_iter().filter(|m| m.score >= min_score).collect();
if filtered_matches.is_empty() {
return Err(anyhow::anyhow!(
"No matching environments found (min score: {})",
min_score
));
}
let _catalog_path = PathBuf::from(paths::framework::ROBOT_TASKS_CATALOG);
let base_path = PathBuf::from(paths::framework::ROBOT_TASKS_DIR);
let mut available_count = 0;
for env_match in &filtered_matches {
let env_path = base_path.join(&env_match.environment.path);
if env_path.exists() {
available_count += 1;
}
}
Ok(GenerationResult {
robot_scene: robot_output,
environments: filtered_matches.iter().map(|m| m.environment.id.clone()).collect(),
available_count,
})
}
pub fn generate_robot(&self, config_path: &Path, output_path: &Path) -> Result<()> {
let robot_generator = RobotGenerator::from_config_file(config_path)?;
robot_generator.generate(output_path)
}
pub fn list_environments(&self, config_path: &Path, verbose: bool) -> Result<Vec<EnvironmentInfo>> {
let profile = RobotProfile::from_config_file(config_path)?;
let selector = EnvironmentSelector::new()?;
let matches = selector.select_environments(&profile, 100)?;
let base_path = PathBuf::from(paths::framework::ROBOT_TASKS_DIR);
let mut environments = Vec::new();
for env_match in matches {
let env_path = base_path.join(&env_match.environment.path);
let available = env_path.exists();
if verbose || available {
environments.push(EnvironmentInfo {
id: env_match.environment.id.clone(),
name: env_match.environment.name.clone(),
description: env_match.environment.description.clone(),
score: env_match.score,
available,
});
}
}
Ok(environments)
}
pub fn validate_config(&self, config_path: &Path) -> Result<ValidationResult> {
let profile = RobotProfile::from_config_file(config_path)?;
let mut errors = Vec::new();
let mut warnings = Vec::new();
if profile.platform.is_empty() {
errors.push("Platform not specified".to_string());
}
if profile.sensors.is_empty() {
warnings.push("No sensors configured".to_string());
}
if profile.task_nodes.is_empty() {
warnings.push("No task nodes configured".to_string());
}
let is_valid = errors.is_empty();
Ok(ValidationResult {
is_valid,
errors,
warnings,
platform: profile.platform,
sensor_count: profile.sensors.len(),
node_count: profile.task_nodes.len(),
})
}
pub fn is_setup(&self) -> bool {
self.base_path.exists() && self.base_path.join("robot.tscn").exists()
}
pub fn base_path(&self) -> &Path {
&self.base_path
}
pub fn resolve_model_path(&self, model_ref: &str) -> Result<PathBuf> {
Self::resolve_asset_path(
model_ref,
AssetPathSpec {
kind: "Model",
namespace_prefix: "@mecha10/simulation-models/",
project_dir: paths::project::SIMULATION_MODELS_DIR,
framework_dir: paths::framework::SIMULATION_MODELS_DIR,
bundled_subdir: "simulation/models",
cached_path: |assets| assets.models_path(),
},
)
}
pub fn resolve_environment_path(&self, env_ref: &str) -> Result<PathBuf> {
Self::resolve_asset_path(
env_ref,
AssetPathSpec {
kind: "Environment",
namespace_prefix: "@mecha10/simulation-environments/",
project_dir: paths::project::SIMULATION_ENVIRONMENTS_DIR,
framework_dir: paths::framework::SIMULATION_ENVIRONMENTS_DIR,
bundled_subdir: "simulation/environments",
cached_path: |assets| assets.environments_path(),
},
)
}
fn resolve_asset_path(asset_ref: &str, spec: AssetPathSpec) -> Result<PathBuf> {
let asset_name = asset_ref.strip_prefix(spec.namespace_prefix).unwrap_or(asset_ref);
let project_path = PathBuf::from(spec.project_dir).join(asset_name);
if project_path.exists() {
return Ok(project_path);
}
if let Ok(framework_path) = std::env::var("MECHA10_FRAMEWORK_PATH") {
let framework_asset_path = PathBuf::from(framework_path).join(spec.framework_dir).join(asset_name);
if framework_asset_path.exists() {
return Ok(framework_asset_path);
}
}
let cli_manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
if let Some(packages_dir) = cli_manifest_dir.parent() {
let bundled_asset_path = packages_dir.join(spec.bundled_subdir).join(asset_name);
if bundled_asset_path.exists() {
return Ok(bundled_asset_path);
}
}
let assets_service = crate::services::SimulationAssetsService::new();
if let Some(cached_dir) = (spec.cached_path)(&assets_service) {
let cached_asset_path = cached_dir.join(asset_name);
if cached_asset_path.exists() {
return Ok(cached_asset_path);
}
}
Err(anyhow::anyhow!(
"{} not found: {}\n\nChecked:\n • Project: {}/{}\n • Framework: packages/{}/{}\n • Cached: ~/.mecha10/simulation/current/{}/{}",
spec.kind,
asset_ref,
spec.project_dir,
asset_name,
spec.bundled_subdir,
asset_name,
spec.bundled_subdir.rsplit('/').next().unwrap_or(spec.bundled_subdir),
asset_name
))
}
}
struct AssetPathSpec {
kind: &'static str,
namespace_prefix: &'static str,
project_dir: &'static str,
framework_dir: &'static str,
bundled_subdir: &'static str,
cached_path: fn(&crate::services::SimulationAssetsService) -> Option<PathBuf>,
}
impl Default for SimulationService {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct GenerationResult {
pub robot_scene: PathBuf,
pub environments: Vec<String>,
pub available_count: usize,
}
#[derive(Debug, Clone)]
pub struct EnvironmentInfo {
pub id: String,
pub name: String,
pub description: String,
pub score: i32,
pub available: bool,
}
#[derive(Debug)]
pub struct ValidationResult {
pub is_valid: bool,
pub errors: Vec<String>,
pub warnings: Vec<String>,
pub platform: String,
pub sensor_count: usize,
pub node_count: usize,
}