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; 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";
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...");
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(); let mut previous_time = start_time;
let mut paused = false;
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(); elapsed_time = current_time.duration_since(previous_time).as_nanos() as u64; previous_time = current_time;
lag += elapsed_time;
while lag >= update_interval {
if !paused {
self.cpu_frame();
}
lag -= update_interval;
}
self.vdp.borrow_mut().update_buffer();
self.vdp.borrow_mut().graphics_render(sys);
controls.update(&mut event_pump);
if controls.f12 == 1 {
break;
}
if controls.pause == 1 {
paused = !paused;
}
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,
) {
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,
) {
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,
) {
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 {
std::thread::sleep(Duration::from_nanos((1_000_000_000 / 60 - elapsed) as u64));
}
}
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;
}
}
}