librsmsx 0.4.6

a MSX emulator written in rust, a port from gomsx
Documentation
use std::fs;
use std::time::{Duration, Instant};
use std::{cell::RefCell, rc::Rc};

use sdl3::event::Event;
use sdl3::keyboard::Keycode;
use sdl3::pixels::Color;
use sdl3::render::FRect;
use sdl3::EventPump;
use serde::{Deserialize, Serialize};

use crate::libs::mouse_helper::MouseHelper;
use crate::libs::simple_ui::simple_ui_button;

use super::base_system::BaseSystem;
use super::memory::MemoryData;
use super::ppi::PPIData;
use super::vdp::{Vdp, VdpData};
use super::z80::z80_base::{Z80Data, Z80};

#[derive(Serialize, Deserialize, Debug)]
struct SaveData {
    z80: Z80Data,
    memory: MemoryData,
    ppi: PPIData,
    vdp: VdpData,
}

const CYCLES_PER_FRAME: u64 = 60000; // The z80 runs at 3.58 Mhz. Every 16msec 57280 cycles pass.
const NANO_SEC_PER_SEC: u32 = 1_000_000_000;
const MILLIS_PER_NANO_SEC: u32 = 1_000_000;
const SAVE_FILE_PATH: &str = "msx.save";

// fn nanoseconds() -> i64 {
//     (get_time() * NANO_SEC_PER_SEC as f64) as i64
// }

pub struct MSX<'a> {
    cpu_z80: Z80<'a>,
    vdp: Rc<RefCell<Vdp<'a>>>,
}

impl<'a> MSX<'a> {
    pub fn new(cpu_z80: Z80<'a>, vdp: Rc<RefCell<Vdp<'a>>>) -> Self {
        Self { cpu_z80, vdp }
    }
    pub fn main_loop(&mut self, frame_interval: isize, sys: &mut BaseSystem) -> f64 {
        log::info!("Beginning simulation...");
        // state_init();
        let mut event_pump = sys.create_event_pump();

        let mut controls = Controls::new();

        let mut current_time;
        let mut elapsed_time;
        let mut lag: u64 = 0;
        let mut n_frames: i64 = 0;
        let update_interval = (MILLIS_PER_NANO_SEC as u64) * (frame_interval as u64);

        let start_time = Instant::now(); //nanoseconds();
        let mut previous_time = start_time;
        let mut paused = false;

        // self.cpu_z80.debug = true;
        let scale = sys.get_scale();
        let canvas = sys.get_canvas();
        canvas.set_scale(scale, scale).unwrap();

        let mut mouse_helper = MouseHelper::new(&event_pump);
        'running: loop {
            for event in event_pump.poll_iter() {
                match event {
                    Event::Quit { .. }
                    | Event::KeyDown {
                        keycode: Some(Keycode::Escape),
                        ..
                    } => break 'running,
                    _ => {}
                }
            }
            self.cpu_z80.check_keycodes(&mut event_pump);
            current_time = Instant::now(); //nanoseconds();
            elapsed_time = current_time.duration_since(previous_time).as_nanos() as u64; //current_time - previous_time;
            previous_time = current_time;
            lag += elapsed_time;
            while lag >= update_interval {
                if !paused {
                    self.cpu_frame();
                }
                lag -= update_interval;
            }

            // clear_background(Color::RED);

            // draw_line(40.0, 40.0, 100.0, 200.0, 15.0, BLUE);
            // draw_rectangle(screen_width() / 2.0 - 60.0, 100.0, 120.0, 60.0, GREEN);
            // draw_text(format!("FPS: {}", get_fps()).as_str(), 0., 16., 32., WHITE);

            self.vdp.borrow_mut().update_buffer();
            self.vdp.borrow_mut().graphics_render(sys);

            // if !paused {
            // 	if n_frames%(60*5) == 0 {
            // 		state_save(msx);
            // 	}
            // }

            controls.update(&mut event_pump);
            if controls.f12 == 1 {
                break;
            }
            if controls.pause == 1 {
                paused = !paused;
            }
            // if controls.space == 1 {
            //     break;
            // }
            // if controls.f12 == 1 {
            // 	state_revert(msx);
            // 	paused = true;
            // }

            // if controls.space == 1 {
            // 	paused = false;
            // }

            let scale = sys.get_scale();
            let canvas = sys.get_canvas();

            mouse_helper.process(&event_pump);
            if simple_ui_button(
                &FRect {
                    x: 320.0,
                    y: 0.0,
                    w: 50.0,
                    h: 30.0,
                },
                Color::GREY,
                canvas,
                mouse_helper.get_mouse_pos(),
                mouse_helper.is_left_button_pressed(),
                scale,
                "SAVE",
            ) {
                println!("Save clicked");
                self.save();
            }
            if simple_ui_button(
                &FRect {
                    x: 320.0,
                    y: 60.0,
                    w: 50.0,
                    h: 30.0,
                },
                Color::GREEN,
                canvas,
                mouse_helper.get_mouse_pos(),
                mouse_helper.is_left_button_pressed(),
                scale,
                "LOAD",
            ) {
                println!("Load clicked");
                self.load();
            }
            if simple_ui_button(
                &FRect {
                    x: 320.0,
                    y: 120.0,
                    w: 50.0,
                    h: 30.0,
                },
                Color::RED,
                canvas,
                mouse_helper.get_mouse_pos(),
                mouse_helper.is_left_button_pressed(),
                scale,
                "RST",
            ) {
                println!("Reset clicked");
                self.cpu_z80.reboot();
            }

            canvas.present();
            n_frames += 1;
            let elapsed = Instant::now().duration_since(current_time).as_nanos();

            if elapsed < 1_000_000_000 / 60 {
                // 60fps
                std::thread::sleep(Duration::from_nanos((1_000_000_000 / 60 - elapsed) as u64));
            }
            // std::thread::sleep(Duration::from_millis(10));
            // next_frame().await;
        }
        let delta =
            Instant::now().duration_since(start_time).as_nanos() as f64 / (NANO_SEC_PER_SEC as f64);
        (n_frames as f64) / delta
    }

    pub fn cpu_frame(&mut self) {
        self.cpu_z80.run_one_frame(CYCLES_PER_FRAME);

        if self.vdp.borrow().has_enabled_interrupts() {
            self.vdp.borrow_mut().set_frame_flag();
            self.cpu_z80.interrupt();
        }
    }
    fn save(&mut self) {
        let sd = self.cpu_z80.get_save_data();
        let data = SaveData {
            z80: sd.0,
            memory: sd.1,
            ppi: sd.2,
            vdp: self.vdp.borrow().get_data(),
        };

        let serialized = serde_json::to_string(&data).unwrap();
        fs::write(SAVE_FILE_PATH, serialized).unwrap();
    }
    fn load(&mut self) {
        let contents = fs::read(SAVE_FILE_PATH).unwrap();
        let dd: SaveData = serde_json::from_slice(&contents).unwrap();
        self.cpu_z80.set_save_data(dd.z80, dd.memory, dd.ppi);
        self.vdp.borrow_mut().set_data(dd.vdp);
    }
}

pub struct Controls {
    pause: usize,
    f12: usize,
    space: usize,
}

impl Default for Controls {
    fn default() -> Self {
        Self::new()
    }
}

use sdl3::keyboard::Scancode;

fn is_key_pressed(e: &sdl3::EventPump, scancode: Scancode) -> bool {
    e.keyboard_state().is_scancode_pressed(scancode)
}

impl Controls {
    pub fn new() -> Self {
        Self {
            pause: 0,
            f12: 0,
            space: 0,
        }
    }
    pub fn update(&mut self, event_pump: &mut EventPump) {
        let is_pause_pressed = is_key_pressed(event_pump, Scancode::Pause);
        let is_f12_pressed = is_key_pressed(event_pump, Scancode::F12);
        let is_space_pressed = is_key_pressed(event_pump, Scancode::Space);

        if is_pause_pressed {
            if self.pause < 2 {
                self.pause += 1;
            }
        } else {
            self.pause = 0;
        }

        if is_f12_pressed {
            if self.f12 < 2 {
                self.f12 += 1;
            }
        } else {
            self.f12 = 0;
        }

        if is_space_pressed {
            if self.space < 2 {
                self.space += 1;
            }
        } else {
            self.space = 0;
        }
    }
}