simular/renderers/
wasm.rs1use crate::demos::{CriterionResult, DemoEngine, DemoMeta};
17use serde::Serialize;
18
19#[derive(Debug)]
24pub struct WasmRunner<E: DemoEngine> {
25 engine: E,
26 running: bool,
27 paused: bool,
28 step_count: u64,
29}
30
31impl<E: DemoEngine> WasmRunner<E> {
32 #[must_use]
34 pub fn new(engine: E) -> Self {
35 Self {
36 engine,
37 running: true,
38 paused: false,
39 step_count: 0,
40 }
41 }
42
43 pub fn from_yaml(yaml: &str) -> Result<Self, crate::demos::DemoError> {
49 let engine = E::from_yaml(yaml)?;
50 Ok(Self::new(engine))
51 }
52
53 #[must_use]
55 pub fn engine(&self) -> &E {
56 &self.engine
57 }
58
59 pub fn engine_mut(&mut self) -> &mut E {
61 &mut self.engine
62 }
63
64 #[must_use]
66 pub fn is_running(&self) -> bool {
67 self.running && !self.engine.is_complete()
68 }
69
70 #[must_use]
72 pub fn is_paused(&self) -> bool {
73 self.paused
74 }
75
76 pub fn toggle_pause(&mut self) -> bool {
78 self.paused = !self.paused;
79 self.paused
80 }
81
82 pub fn set_paused(&mut self, paused: bool) {
84 self.paused = paused;
85 }
86
87 pub fn stop(&mut self) {
89 self.running = false;
90 }
91
92 pub fn resume(&mut self) {
94 self.paused = false;
95 }
96
97 pub fn reset(&mut self) {
99 self.engine.reset();
100 self.step_count = 0;
101 self.paused = false;
102 }
103
104 pub fn step(&mut self) -> bool {
108 if self.paused || self.engine.is_complete() {
109 return false;
110 }
111 self.engine.step();
112 self.step_count += 1;
113 true
114 }
115
116 pub fn run_steps(&mut self, num_steps: u32) -> u32 {
120 let mut completed = 0;
121 for _ in 0..num_steps {
122 if !self.step() {
123 break;
124 }
125 completed += 1;
126 }
127 completed
128 }
129
130 #[must_use]
132 pub fn meta(&self) -> &DemoMeta {
133 self.engine.meta()
134 }
135
136 #[must_use]
138 pub fn state(&self) -> E::State {
139 self.engine.state()
140 }
141
142 #[must_use]
144 pub fn evaluate_criteria(&self) -> Vec<CriterionResult> {
145 self.engine.evaluate_criteria()
146 }
147
148 #[must_use]
150 pub fn step_count(&self) -> u64 {
151 self.step_count
152 }
153
154 #[must_use]
156 pub fn seed(&self) -> u64 {
157 self.engine.seed()
158 }
159
160 #[must_use]
162 pub fn is_complete(&self) -> bool {
163 self.engine.is_complete()
164 }
165}
166
167#[derive(Debug, Clone, Serialize)]
171pub struct WasmState {
172 pub id: String,
174 pub demo_type: String,
176 pub step: u64,
178 pub seed: u64,
180 pub paused: bool,
182 pub complete: bool,
184 pub state_json: String,
186 pub criteria: Vec<CriterionResultJson>,
188}
189
190#[derive(Debug, Clone, Serialize)]
192pub struct CriterionResultJson {
193 pub id: String,
195 pub passed: bool,
197 pub actual: f64,
199 pub expected: f64,
201 pub message: String,
203 pub severity: String,
205}
206
207impl From<&CriterionResult> for CriterionResultJson {
208 fn from(result: &CriterionResult) -> Self {
209 Self {
210 id: result.id.clone(),
211 passed: result.passed,
212 actual: result.actual,
213 expected: result.expected,
214 message: result.message.clone(),
215 severity: format!("{:?}", result.severity),
216 }
217 }
218}
219
220impl<E: DemoEngine> WasmRunner<E>
221where
222 E::State: Serialize,
223{
224 #[must_use]
226 pub fn state_json(&self) -> String {
227 serde_json::to_string(&self.engine.state()).unwrap_or_else(|_| "{}".to_string())
228 }
229
230 #[must_use]
232 pub fn wasm_state(&self) -> WasmState {
233 let meta = self.engine.meta();
234 let criteria = self
235 .engine
236 .evaluate_criteria()
237 .iter()
238 .map(CriterionResultJson::from)
239 .collect();
240
241 WasmState {
242 id: meta.id.clone(),
243 demo_type: meta.demo_type.clone(),
244 step: self.step_count,
245 seed: self.engine.seed(),
246 paused: self.paused,
247 complete: self.engine.is_complete(),
248 state_json: self.state_json(),
249 criteria,
250 }
251 }
252
253 #[must_use]
255 pub fn wasm_state_json(&self) -> String {
256 serde_json::to_string(&self.wasm_state()).unwrap_or_else(|_| "{}".to_string())
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263 use crate::demos::OrbitalEngine;
264
265 const TEST_YAML: &str = r#"
266simulation:
267 type: orbit
268 name: "Test Orbit"
269
270meta:
271 id: "TEST-001"
272 version: "1.0.0"
273 demo_type: orbit
274
275reproducibility:
276 seed: 42
277
278scenario:
279 type: kepler
280 central_body:
281 name: "Sun"
282 mass_kg: 1.989e30
283 position: [0.0, 0.0, 0.0]
284 orbiter:
285 name: "Earth"
286 mass_kg: 5.972e24
287 semi_major_axis_m: 1.496e11
288 eccentricity: 0.0167
289
290integrator:
291 type: stormer_verlet
292 dt_seconds: 3600.0
293"#;
294
295 #[test]
296 fn test_wasm_runner_creation() {
297 let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
298 let runner = WasmRunner::new(engine);
299
300 assert!(runner.is_running());
301 assert!(!runner.is_paused());
302 assert_eq!(runner.step_count(), 0);
303 }
304
305 #[test]
306 fn test_wasm_runner_from_yaml() {
307 let runner = WasmRunner::<OrbitalEngine>::from_yaml(TEST_YAML).unwrap();
308 assert_eq!(runner.meta().id, "TEST-001");
309 }
310
311 #[test]
312 fn test_wasm_runner_step() {
313 let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
314 let mut runner = WasmRunner::new(engine);
315
316 assert!(runner.step());
317 assert_eq!(runner.step_count(), 1);
318
319 assert!(runner.step());
320 assert_eq!(runner.step_count(), 2);
321 }
322
323 #[test]
324 fn test_wasm_runner_run_steps() {
325 let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
326 let mut runner = WasmRunner::new(engine);
327
328 let completed = runner.run_steps(10);
329 assert_eq!(completed, 10);
330 assert_eq!(runner.step_count(), 10);
331 }
332
333 #[test]
334 fn test_wasm_runner_pause() {
335 let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
336 let mut runner = WasmRunner::new(engine);
337
338 assert!(!runner.is_paused());
339 runner.toggle_pause();
340 assert!(runner.is_paused());
341
342 assert!(!runner.step());
344 assert_eq!(runner.step_count(), 0);
345
346 runner.resume();
347 assert!(!runner.is_paused());
348 assert!(runner.step());
349 }
350
351 #[test]
352 fn test_wasm_runner_reset() {
353 let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
354 let mut runner = WasmRunner::new(engine);
355
356 runner.run_steps(5);
357 assert_eq!(runner.step_count(), 5);
358
359 runner.reset();
360 assert_eq!(runner.step_count(), 0);
361 assert!(!runner.is_paused());
362 }
363
364 #[test]
365 fn test_wasm_runner_state_json() {
366 let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
367 let runner = WasmRunner::new(engine);
368
369 let json = runner.state_json();
370 assert!(json.contains("position"));
371 assert!(json.contains("velocity"));
372 }
373
374 #[test]
375 fn test_wasm_runner_wasm_state() {
376 let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
377 let mut runner = WasmRunner::new(engine);
378
379 runner.step();
380 let state = runner.wasm_state();
381
382 assert_eq!(state.id, "TEST-001");
383 assert_eq!(state.demo_type, "orbit");
384 assert_eq!(state.step, 1);
385 assert_eq!(state.seed, 42);
386 assert!(!state.paused);
387 assert!(!state.complete);
388 assert!(!state.criteria.is_empty());
389 }
390
391 #[test]
392 fn test_wasm_runner_wasm_state_json() {
393 let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
394 let runner = WasmRunner::new(engine);
395
396 let json = runner.wasm_state_json();
397 assert!(json.contains("TEST-001"));
398 assert!(json.contains("orbit"));
399 }
400
401 #[test]
402 fn test_wasm_runner_stop() {
403 let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
404 let mut runner = WasmRunner::new(engine);
405
406 assert!(runner.is_running());
407 runner.stop();
408 assert!(!runner.is_running());
409 }
410
411 #[test]
412 fn test_wasm_runner_set_paused() {
413 let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
414 let mut runner = WasmRunner::new(engine);
415
416 runner.set_paused(true);
417 assert!(runner.is_paused());
418 runner.set_paused(false);
419 assert!(!runner.is_paused());
420 }
421
422 #[test]
423 fn test_wasm_runner_criteria() {
424 let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
425 let runner = WasmRunner::new(engine);
426
427 let criteria = runner.evaluate_criteria();
428 assert!(!criteria.is_empty());
429 }
430
431 #[test]
432 fn test_wasm_runner_seed() {
433 let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
434 let runner = WasmRunner::new(engine);
435
436 assert_eq!(runner.seed(), 42);
437 }
438
439 #[test]
440 fn test_wasm_runner_is_complete() {
441 let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
442 let runner = WasmRunner::new(engine);
443
444 assert!(!runner.is_complete());
445 }
446}