neser 1.2.0

NESER - Nintendo Emulation Systems Engine (Rust). Desktop and WebAssembly frontends.
Documentation
use neser::nes::cartridge::Cartridge;
use neser::nes::console::{Config, Nes};
use neser::platform::app_context::AppContext;
use std::cell::RefCell;
use std::rc::Rc;
use std::time::Instant;

fn main() {
    let rom_path = std::env::args()
        .nth(1)
        .expect("Usage: frame_bench <rom_path> [frames]");
    let num_frames: usize = std::env::args()
        .nth(2)
        .and_then(|s| s.parse().ok())
        .unwrap_or(600);

    let rom_data = std::fs::read(&rom_path).expect("Failed to read ROM file");
    let app_context = Rc::new(RefCell::new(AppContext::new_with_config(Config::default())));
    let mut nes = Nes::new(app_context.clone());
    let cart = Cartridge::load_from_file(&rom_data, &rom_path, Some(nes.rom_db()))
        .expect("Failed to load ROM");
    let rom_timing = cart.rom_timing_mode();
    app_context
        .borrow_mut()
        .config_mut()
        .apply_rom_timing_mode(rom_timing);
    nes = Nes::new(app_context.clone());
    nes.insert_cartridge(cart);
    nes.reset(false);

    for _ in 0..60 {
        run_one_frame(&mut nes);
    }

    let start = Instant::now();
    for _ in 0..num_frames {
        run_one_frame(&mut nes);
    }
    let elapsed = start.elapsed();

    let total_ms = elapsed.as_secs_f64() * 1000.0;
    let per_frame_ms = total_ms / num_frames as f64;
    let fps = num_frames as f64 / elapsed.as_secs_f64();

    println!("ROM: {rom_path}");
    println!("Frames: {num_frames}");
    println!("Total: {total_ms:.1}ms");
    println!("Per frame: {per_frame_ms:.3}ms");
    println!("FPS: {fps:.1}");

    println!("\nStability check (5 runs of {num_frames} frames):");
    for i in 0..5 {
        let start = Instant::now();
        for _ in 0..num_frames {
            run_one_frame(&mut nes);
        }
        let elapsed = start.elapsed();
        let per_frame = elapsed.as_secs_f64() * 1000.0 / num_frames as f64;
        println!("  Run {}: {per_frame:.3}ms/frame", i + 1);
    }
}

fn run_one_frame(nes: &mut Nes) {
    while !nes.is_ready_to_render() && !nes.cpu_ref().is_halted() {
        nes.run_cpu_tick();
        while nes.sample_ready() {
            nes.get_sample();
        }
    }
    nes.clear_ready_to_render();
}