1use crate::demos::{CriterionResult, DemoEngine, DemoMeta};
19use serde::Serialize;
20
21pub trait RenderableDemo {
26 fn title(&self) -> String;
28
29 fn status_line(&self) -> String;
31
32 fn metrics(&self) -> Vec<(String, String)>;
34
35 fn current_step(&self) -> u64;
37
38 fn is_running(&self) -> bool;
40}
41
42#[derive(Debug)]
46pub struct DemoRenderer<E: DemoEngine> {
47 engine: E,
48 running: bool,
49 paused: bool,
50 step_count: u64,
51}
52
53impl<E: DemoEngine> DemoRenderer<E> {
54 #[must_use]
56 pub fn new(engine: E) -> Self {
57 Self {
58 engine,
59 running: true,
60 paused: false,
61 step_count: 0,
62 }
63 }
64
65 #[must_use]
67 pub fn engine(&self) -> &E {
68 &self.engine
69 }
70
71 pub fn engine_mut(&mut self) -> &mut E {
73 &mut self.engine
74 }
75
76 #[must_use]
78 pub fn is_running(&self) -> bool {
79 self.running && !self.engine.is_complete()
80 }
81
82 #[must_use]
84 pub fn is_paused(&self) -> bool {
85 self.paused
86 }
87
88 pub fn toggle_pause(&mut self) {
90 self.paused = !self.paused;
91 }
92
93 pub fn stop(&mut self) {
95 self.running = false;
96 }
97
98 pub fn reset(&mut self) {
100 self.engine.reset();
101 self.step_count = 0;
102 self.paused = false;
103 }
104
105 pub fn step(&mut self) -> E::StepResult {
107 let result = self.engine.step();
108 self.step_count += 1;
109 result
110 }
111
112 #[must_use]
114 pub fn meta(&self) -> &DemoMeta {
115 self.engine.meta()
116 }
117
118 #[must_use]
120 pub fn state(&self) -> E::State {
121 self.engine.state()
122 }
123
124 #[must_use]
126 pub fn evaluate_criteria(&self) -> Vec<CriterionResult> {
127 self.engine.evaluate_criteria()
128 }
129
130 #[must_use]
132 pub fn step_count(&self) -> u64 {
133 self.step_count
134 }
135
136 #[must_use]
138 pub fn seed(&self) -> u64 {
139 self.engine.seed()
140 }
141}
142
143#[derive(Debug, Clone, Serialize)]
148pub struct RenderFrame {
149 pub title: String,
151 pub demo_type: String,
153 pub step: u64,
155 pub seed: u64,
157 pub paused: bool,
159 pub complete: bool,
161 pub metrics: Vec<(String, String)>,
163 pub criteria: Vec<CriterionResult>,
165}
166
167impl<E: DemoEngine> DemoRenderer<E>
168where
169 E::State: Serialize,
170{
171 #[must_use]
173 pub fn render_frame(&self) -> RenderFrame {
174 let meta = self.engine.meta();
175
176 RenderFrame {
177 title: format!("{} ({})", meta.id, meta.version),
178 demo_type: meta.demo_type.clone(),
179 step: self.step_count,
180 seed: self.engine.seed(),
181 paused: self.paused,
182 complete: self.engine.is_complete(),
183 metrics: Vec::new(), criteria: self.engine.evaluate_criteria(),
185 }
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192 use crate::demos::OrbitalEngine;
193
194 const TEST_YAML: &str = r#"
195simulation:
196 type: orbit
197 name: "Test Orbit"
198
199meta:
200 id: "TEST-001"
201 version: "1.0.0"
202 demo_type: orbit
203
204reproducibility:
205 seed: 42
206
207scenario:
208 type: kepler
209 central_body:
210 name: "Sun"
211 mass_kg: 1.989e30
212 position: [0.0, 0.0, 0.0]
213 orbiter:
214 name: "Earth"
215 mass_kg: 5.972e24
216 semi_major_axis_m: 1.496e11
217 eccentricity: 0.0167
218
219integrator:
220 type: stormer_verlet
221 dt_seconds: 3600.0
222"#;
223
224 #[test]
225 fn test_renderer_creation() {
226 let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
227 let renderer = DemoRenderer::new(engine);
228
229 assert!(renderer.is_running());
230 assert!(!renderer.is_paused());
231 assert_eq!(renderer.step_count(), 0);
232 }
233
234 #[test]
235 fn test_renderer_step() {
236 let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
237 let mut renderer = DemoRenderer::new(engine);
238
239 renderer.step();
240 assert_eq!(renderer.step_count(), 1);
241
242 renderer.step();
243 assert_eq!(renderer.step_count(), 2);
244 }
245
246 #[test]
247 fn test_renderer_pause() {
248 let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
249 let mut renderer = DemoRenderer::new(engine);
250
251 assert!(!renderer.is_paused());
252 renderer.toggle_pause();
253 assert!(renderer.is_paused());
254 renderer.toggle_pause();
255 assert!(!renderer.is_paused());
256 }
257
258 #[test]
259 fn test_renderer_reset() {
260 let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
261 let mut renderer = DemoRenderer::new(engine);
262
263 renderer.step();
264 renderer.step();
265 assert_eq!(renderer.step_count(), 2);
266
267 renderer.reset();
268 assert_eq!(renderer.step_count(), 0);
269 }
270
271 #[test]
272 fn test_renderer_meta() {
273 let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
274 let renderer = DemoRenderer::new(engine);
275
276 let meta = renderer.meta();
277 assert_eq!(meta.id, "TEST-001");
278 assert_eq!(meta.demo_type, "orbit");
279 }
280
281 #[test]
282 fn test_render_frame() {
283 let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
284 let mut renderer = DemoRenderer::new(engine);
285
286 renderer.step();
287 let frame = renderer.render_frame();
288
289 assert!(frame.title.contains("TEST-001"));
290 assert_eq!(frame.demo_type, "orbit");
291 assert_eq!(frame.step, 1);
292 assert_eq!(frame.seed, 42);
293 assert!(!frame.paused);
294 }
295
296 #[test]
297 fn test_renderer_stop() {
298 let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
299 let mut renderer = DemoRenderer::new(engine);
300
301 assert!(renderer.is_running());
302 renderer.stop();
303 assert!(!renderer.is_running());
304 }
305
306 #[test]
307 fn test_renderer_criteria() {
308 let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
309 let renderer = DemoRenderer::new(engine);
310
311 let criteria = renderer.evaluate_criteria();
312 assert!(!criteria.is_empty());
313 }
314}