use crate::demos::{CriterionResult, DemoEngine, DemoMeta};
use serde::Serialize;
pub trait RenderableDemo {
fn title(&self) -> String;
fn status_line(&self) -> String;
fn metrics(&self) -> Vec<(String, String)>;
fn current_step(&self) -> u64;
fn is_running(&self) -> bool;
}
#[derive(Debug)]
pub struct DemoRenderer<E: DemoEngine> {
engine: E,
running: bool,
paused: bool,
step_count: u64,
}
impl<E: DemoEngine> DemoRenderer<E> {
#[must_use]
pub fn new(engine: E) -> Self {
Self {
engine,
running: true,
paused: false,
step_count: 0,
}
}
#[must_use]
pub fn engine(&self) -> &E {
&self.engine
}
pub fn engine_mut(&mut self) -> &mut E {
&mut self.engine
}
#[must_use]
pub fn is_running(&self) -> bool {
self.running && !self.engine.is_complete()
}
#[must_use]
pub fn is_paused(&self) -> bool {
self.paused
}
pub fn toggle_pause(&mut self) {
self.paused = !self.paused;
}
pub fn stop(&mut self) {
self.running = false;
}
pub fn reset(&mut self) {
self.engine.reset();
self.step_count = 0;
self.paused = false;
}
pub fn step(&mut self) -> E::StepResult {
let result = self.engine.step();
self.step_count += 1;
result
}
#[must_use]
pub fn meta(&self) -> &DemoMeta {
self.engine.meta()
}
#[must_use]
pub fn state(&self) -> E::State {
self.engine.state()
}
#[must_use]
pub fn evaluate_criteria(&self) -> Vec<CriterionResult> {
self.engine.evaluate_criteria()
}
#[must_use]
pub fn step_count(&self) -> u64 {
self.step_count
}
#[must_use]
pub fn seed(&self) -> u64 {
self.engine.seed()
}
}
#[derive(Debug, Clone, Serialize)]
pub struct RenderFrame {
pub title: String,
pub demo_type: String,
pub step: u64,
pub seed: u64,
pub paused: bool,
pub complete: bool,
pub metrics: Vec<(String, String)>,
pub criteria: Vec<CriterionResult>,
}
impl<E: DemoEngine> DemoRenderer<E>
where
E::State: Serialize,
{
#[must_use]
pub fn render_frame(&self) -> RenderFrame {
let meta = self.engine.meta();
RenderFrame {
title: format!("{} ({})", meta.id, meta.version),
demo_type: meta.demo_type.clone(),
step: self.step_count,
seed: self.engine.seed(),
paused: self.paused,
complete: self.engine.is_complete(),
metrics: Vec::new(), criteria: self.engine.evaluate_criteria(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::demos::OrbitalEngine;
const TEST_YAML: &str = r#"
simulation:
type: orbit
name: "Test Orbit"
meta:
id: "TEST-001"
version: "1.0.0"
demo_type: orbit
reproducibility:
seed: 42
scenario:
type: kepler
central_body:
name: "Sun"
mass_kg: 1.989e30
position: [0.0, 0.0, 0.0]
orbiter:
name: "Earth"
mass_kg: 5.972e24
semi_major_axis_m: 1.496e11
eccentricity: 0.0167
integrator:
type: stormer_verlet
dt_seconds: 3600.0
"#;
#[test]
fn test_renderer_creation() {
let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
let renderer = DemoRenderer::new(engine);
assert!(renderer.is_running());
assert!(!renderer.is_paused());
assert_eq!(renderer.step_count(), 0);
}
#[test]
fn test_renderer_step() {
let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
let mut renderer = DemoRenderer::new(engine);
renderer.step();
assert_eq!(renderer.step_count(), 1);
renderer.step();
assert_eq!(renderer.step_count(), 2);
}
#[test]
fn test_renderer_pause() {
let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
let mut renderer = DemoRenderer::new(engine);
assert!(!renderer.is_paused());
renderer.toggle_pause();
assert!(renderer.is_paused());
renderer.toggle_pause();
assert!(!renderer.is_paused());
}
#[test]
fn test_renderer_reset() {
let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
let mut renderer = DemoRenderer::new(engine);
renderer.step();
renderer.step();
assert_eq!(renderer.step_count(), 2);
renderer.reset();
assert_eq!(renderer.step_count(), 0);
}
#[test]
fn test_renderer_meta() {
let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
let renderer = DemoRenderer::new(engine);
let meta = renderer.meta();
assert_eq!(meta.id, "TEST-001");
assert_eq!(meta.demo_type, "orbit");
}
#[test]
fn test_render_frame() {
let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
let mut renderer = DemoRenderer::new(engine);
renderer.step();
let frame = renderer.render_frame();
assert!(frame.title.contains("TEST-001"));
assert_eq!(frame.demo_type, "orbit");
assert_eq!(frame.step, 1);
assert_eq!(frame.seed, 42);
assert!(!frame.paused);
}
#[test]
fn test_renderer_stop() {
let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
let mut renderer = DemoRenderer::new(engine);
assert!(renderer.is_running());
renderer.stop();
assert!(!renderer.is_running());
}
#[test]
fn test_renderer_criteria() {
let engine = OrbitalEngine::from_yaml(TEST_YAML).unwrap();
let renderer = DemoRenderer::new(engine);
let criteria = renderer.evaluate_criteria();
assert!(!criteria.is_empty());
}
}