rusting_engine 2.6.0

Vulkan 3D game engine with GPU-accelerated physics for massive physics-heavy scenes
//! Standard physics quality scenes, headless on the CPU solvers.
//!
//! ```sh
//! cargo run --release --example physics_suite
//! cargo run --release --example physics_suite -- target/suite.json
//! ```
//!
//! Prints one line per scene: its size, the mean step time, and the error a
//! correct solver keeps near zero (see `SuiteScene`). With a path, a missing
//! file becomes the baseline; an existing one is compared under the render
//! benchmark's rules (`SuiteReport::regressions`), and any regression exits
//! with 1.

use rusting_engine::runtime::{SuiteReport, SuiteScene};

fn main() {
    let mut scores = Vec::new();
    for (scene, size, steps) in [
        (SuiteScene::StackingStability, 20, 600),
        (SuiteScene::JointChain, 20, 600),
        (SuiteScene::CcdBullets, 400, 60),
        (SuiteScene::ClothHang, 40, 300),
        (SuiteScene::FluidDrop, 1000, 300),
        (SuiteScene::VehicleStress, 25, 600),
        (SuiteScene::FractureDrop, 50, 300),
        (SuiteScene::RagdollPile, 100, 300),
        (SuiteScene::ArticulatedChain, 20, 600),
        (SuiteScene::CharacterStairs, 10, 240),
        (SuiteScene::BuoyancySort, 16, 300),
        (SuiteScene::SheetCatch, 16, 300),
        (SuiteScene::JellyRaft, 16, 300),
    ] {
        let score = scene.run(size, steps);
        println!(
            "{:<20} size {:>4}  step {:>9.3?}  {} {:.4}",
            scene.name(),
            size,
            score.mean_step,
            scene.error_unit(),
            score.error
        );
        scores.push((score, false));
    }
    for (scene, size, steps) in [
        (SuiteScene::StackingStability, 20, 600),
        (SuiteScene::JointChain, 20, 600),
        (SuiteScene::CcdBullets, 400, 60),
    ] {
        let score = scene.run_2d(size, steps).expect("scene has a 2D form");
        println!(
            "{:<20} size {:>4}  step {:>9.3?}  {} {:.4}",
            format!("{} 2d", scene.name()),
            size,
            score.mean_step,
            scene.error_unit(),
            score.error
        );
        scores.push((score, true));
    }

    let Some(path) = std::env::args().nth(1) else {
        return;
    };
    let report = SuiteReport::new(&scores);
    let json = serde_json::to_string_pretty(&report).unwrap();
    match std::fs::read_to_string(&path) {
        Ok(stored) => {
            let stored: SuiteReport = serde_json::from_str(&stored)
                .unwrap_or_else(|error| panic!("{path}: {error}"));
            if report.machine != stored.machine {
                eprintln!(
                    "step times not compared: {} against baseline {}",
                    report.machine, stored.machine
                );
            }
            let regressions = report.regressions(&stored);
            if regressions.is_empty() {
                eprintln!("no regressions against {path}");
            } else {
                for regression in &regressions {
                    eprintln!("regression: {regression}");
                }
                std::process::exit(1);
            }
        }
        Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
            std::fs::write(&path, json + "\n")
                .unwrap_or_else(|error| panic!("{path}: {error}"));
            eprintln!("wrote baseline {path}");
        }
        Err(error) => panic!("{path}: {error}"),
    }
}