mecha10_cli/types/
simulation.rs1use serde::{Deserialize, Serialize};
7use std::path::{Path, PathBuf};
8
9#[derive(Debug, Clone, Deserialize, Serialize)]
11#[allow(dead_code)] pub struct GodotConfig {
13 #[serde(default = "default_godot_path")]
15 pub executable_path: String,
16
17 #[serde(default)]
19 pub headless: bool,
20
21 #[serde(default = "default_viewport_width")]
23 pub viewport_width: u32,
24
25 #[serde(default = "default_viewport_height")]
27 pub viewport_height: u32,
28}
29
30fn default_godot_path() -> String {
31 "godot".to_string()
32}
33
34fn default_viewport_width() -> u32 {
35 1280
36}
37
38fn default_viewport_height() -> u32 {
39 720
40}
41
42impl Default for GodotConfig {
43 fn default() -> Self {
44 Self {
45 executable_path: default_godot_path(),
46 headless: false,
47 viewport_width: default_viewport_width(),
48 viewport_height: default_viewport_height(),
49 }
50 }
51}
52
53#[derive(Debug, Clone, Deserialize, Serialize)]
55#[allow(dead_code)] pub struct CameraConfig {
57 #[serde(default = "default_camera_enabled")]
59 pub enabled: bool,
60
61 #[serde(default = "default_camera_width")]
63 pub width: u32,
64
65 #[serde(default = "default_camera_height")]
67 pub height: u32,
68
69 #[serde(default = "default_camera_fps")]
71 pub fps: u32,
72}
73
74fn default_camera_enabled() -> bool {
75 true
76}
77
78fn default_camera_width() -> u32 {
79 320
80}
81
82fn default_camera_height() -> u32 {
83 240
84}
85
86fn default_camera_fps() -> u32 {
87 20
88}
89
90impl Default for CameraConfig {
91 fn default() -> Self {
92 Self {
93 enabled: default_camera_enabled(),
94 width: default_camera_width(),
95 height: default_camera_height(),
96 fps: default_camera_fps(),
97 }
98 }
99}
100
101#[derive(Debug, Clone, Deserialize, Serialize)]
103#[allow(dead_code)] pub struct NetworkingConfig {
105 #[serde(default = "default_protocol_port")]
107 pub protocol_port: u16,
108
109 #[serde(default = "default_bind_address")]
111 pub protocol_bind: String,
112
113 #[serde(default = "default_camera_port")]
115 pub camera_port: u16,
116
117 #[serde(default = "default_bind_address")]
119 pub camera_bind: String,
120
121 #[serde(default = "default_signaling_port")]
123 pub signaling_port: u16,
124
125 #[serde(default = "default_bind_address")]
127 pub signaling_bind: String,
128}
129
130fn default_protocol_port() -> u16 {
131 11008
132}
133
134fn default_camera_port() -> u16 {
135 11009
136}
137
138fn default_signaling_port() -> u16 {
139 11010
140}
141
142fn default_bind_address() -> String {
143 "0.0.0.0".to_string()
144}
145
146impl Default for NetworkingConfig {
147 fn default() -> Self {
148 Self {
149 protocol_port: default_protocol_port(),
150 protocol_bind: default_bind_address(),
151 camera_port: default_camera_port(),
152 camera_bind: default_bind_address(),
153 signaling_port: default_signaling_port(),
154 signaling_bind: default_bind_address(),
155 }
156 }
157}
158
159#[derive(Debug, Clone, Deserialize, Serialize)]
164pub struct SimulationConfig {
165 pub model: String,
167
168 pub model_config: Option<String>,
170
171 pub environment: String,
173
174 pub environment_config: Option<String>,
176
177 #[serde(default)]
179 pub godot: GodotConfig,
180
181 #[serde(default)]
183 pub camera: CameraConfig,
184
185 #[serde(default)]
187 pub networking: NetworkingConfig,
188}
189
190impl SimulationConfig {
191 #[allow(dead_code)] pub fn load() -> anyhow::Result<Self> {
200 Self::load_with_profile_and_scenario(None, None)
201 }
202
203 pub fn load_with_profile_and_scenario(profile: Option<&str>, scenario: Option<&str>) -> anyhow::Result<Self> {
209 use std::env;
210
211 let profile = profile
213 .map(String::from)
214 .or_else(|| env::var("MECHA10_CONFIG_PROFILE").ok())
215 .or_else(|| env::var("MECHA10_ENVIRONMENT").ok())
216 .unwrap_or_else(|| "dev".to_string());
217
218 let scenario = scenario
220 .map(String::from)
221 .or_else(|| env::var("MECHA10_SIMULATION_SCENARIO").ok());
222
223 let base_paths = vec![
225 PathBuf::from("."), PathBuf::from("../.."), PathBuf::from("../../../.."), ];
229
230 for base_path in &base_paths {
232 let configs_dir = base_path.join("configs");
233
234 if let Some(scenario_name) = &scenario {
236 let scenario_path = configs_dir
237 .join(&profile)
238 .join("simulation")
239 .join(format!("{}.json", scenario_name));
240
241 if scenario_path.exists() {
242 let content = std::fs::read_to_string(&scenario_path)?;
243 let config: Self = serde_json::from_str(&content)?;
244 return Ok(config);
245 }
246 }
247
248 let profile_config_path = configs_dir.join(&profile).join("simulation").join("config.json");
250
251 if profile_config_path.exists() {
252 let content = std::fs::read_to_string(&profile_config_path)?;
253 let config: Self = serde_json::from_str(&content)?;
254 return Ok(config);
255 }
256
257 let common_config_path = configs_dir.join("common").join("simulation").join("config.json");
259
260 if common_config_path.exists() {
261 let content = std::fs::read_to_string(&common_config_path)?;
262 let config: Self = serde_json::from_str(&content)?;
263 return Ok(config);
264 }
265 }
266
267 if let Some(scenario_name) = &scenario {
268 Err(anyhow::anyhow!(
269 "No simulation config found.\nTried:\n - configs/{}/simulation/{}.json\n - configs/{}/simulation/config.json\n - configs/common/simulation/config.json",
270 profile,
271 scenario_name,
272 profile
273 ))
274 } else {
275 Err(anyhow::anyhow!(
276 "No simulation config found.\nTried:\n - configs/{}/simulation/config.json\n - configs/common/simulation/config.json",
277 profile
278 ))
279 }
280 }
281
282 #[allow(dead_code)] pub fn with_overrides(mut self, overrides: SimulationOverrides) -> Self {
287 if let Some(model) = overrides.model {
288 self.model = model;
289 }
290 if let Some(model_config) = overrides.model_config {
291 self.model_config = Some(model_config);
292 }
293 if let Some(environment) = overrides.environment {
294 self.environment = environment;
295 }
296 if let Some(environment_config) = overrides.environment_config {
297 self.environment_config = Some(environment_config);
298 }
299 if let Some(headless) = overrides.headless {
300 self.godot.headless = headless;
301 }
302 self
303 }
304
305 #[allow(dead_code)] pub fn resolve_model_path(&self, framework_path: &Path) -> PathBuf {
310 if self.model.starts_with("@mecha10/") {
311 let relative = self.model.strip_prefix("@mecha10/").unwrap();
312 framework_path.join("packages").join(relative)
313 } else {
314 PathBuf::from(&self.model)
315 }
316 }
317
318 #[allow(dead_code)] pub fn resolve_environment_path(&self, framework_path: &Path) -> PathBuf {
323 if self.environment.starts_with("@mecha10/") {
324 let relative = self.environment.strip_prefix("@mecha10/").unwrap();
325 framework_path.join("packages").join(relative)
326 } else {
327 PathBuf::from(&self.environment)
328 }
329 }
330}
331
332#[derive(Debug, Default, Clone)]
334#[allow(dead_code)] pub struct SimulationOverrides {
336 pub model: Option<String>,
337 pub model_config: Option<String>,
338 pub environment: Option<String>,
339 pub environment_config: Option<String>,
340 pub headless: Option<bool>,
341}