use std::ops::Sub; use std::time::{Duration, Instant}; use minifb::{ScaleMode, Window, WindowOptions}; use crsn::asm::data::literal::Value; use crsn::asm::instr::Cond; use crsn::module::{EvalRes, OpTrait}; use crsn::runtime::fault::Fault; use crsn::runtime::run_thread::{state::RunState, ThreadInfo}; use crate::defs::ScreenOp; #[derive(Debug)] struct Opts { auto_blit: bool, frame_rate: Duration, } #[derive(Debug)] struct Backend { width: usize, height: usize, buffer: Vec, window: Option, last_render: Instant, opts: Opts, } impl Default for Backend { fn default() -> Self { Self { width: 0, height: 0, buffer: vec![], window: None, last_render: Instant::now().sub(Duration::from_secs(1)), opts: Opts { auto_blit: true, frame_rate: Duration::from_micros(16600), }, } } } const OPT_AUTO_BLIT: u64 = 1; const OPT_FRAME_RATE: u64 = 2; // Hack for Window unsafe impl std::marker::Send for Backend {} impl OpTrait for ScreenOp { fn execute(&self, _info: &ThreadInfo, state: &mut RunState) -> Result { let eres = EvalRes::default(); match self { ScreenOp::ScreenInit { width, height } => { let w = state.read(*width)?; let h = state.read(*height)?; init(state, w, h)?; } ScreenOp::SetOpt { opt, val } => { let opt = state.read(*opt)?; let val = state.read(*val)?; let backend: &mut Backend = state.ext_mut(); match opt { OPT_AUTO_BLIT => { backend.opts.auto_blit = val != 0; debug!("Set auto blit to {:?}", backend.opts.auto_blit); } OPT_FRAME_RATE => { backend.opts.frame_rate = Duration::from_micros(1_000_000 as u64 / val); debug!("Set frame rate to {:?}", backend.opts.frame_rate); } other => { warn!("Bad screen opt: {}", other); state.set_flag(Cond::Invalid, true); } } } ScreenOp::Blit { force } => { let force = state.read(*force)?; let backend: &mut Backend = state.ext_mut(); if force != 0 { blit(backend) } else { blit_maybe(backend) } } ScreenOp::SetPixel { x, y, color } => { let x = state.read(*x)?; let y = state.read(*y)?; let color = state.read(*color)?; let backend: &mut Backend = state.ext_mut(); if x >= backend.width as u64 || y >= backend.height as u64 { state.set_flag(Cond::Overflow, true); return Ok(eres); } match &mut backend.window { Some(_w) => { let index = y * backend.width as u64 + x; if index as usize > backend.buffer.len() { warn!("Screen set pixel out of bounds"); state.set_flag(Cond::Invalid, true); } else { backend.buffer[index as usize] = color as u32; if backend.opts.auto_blit { blit_maybe(backend); } } } None => { state.set_flag(Cond::Invalid, true); } } } } Ok(eres) } // } fn init(state: &mut RunState, width: Value, height: Value) -> Result<(), Fault> { let window = Window::new( "Croissant", width as usize, height as usize, WindowOptions { resize: true, scale_mode: ScaleMode::UpperLeft, ..WindowOptions::default() }, ).expect("Unable to create window"); // TODO fault let backend: &mut Backend = state.ext_mut(); if backend.window.is_some() { return Err(Fault::NotAllowed("Screen already initialized".into())); } backend.width = width as usize; backend.height = height as usize; backend.buffer = vec![0; (width * height) as usize]; // window.limit_update_rate(Some(std::time::Duration::from_micros(16600))); // window.limit_update_rate(None); backend.window = Some(window); blit_maybe(backend); Ok(()) } fn blit_maybe(backend: &mut Backend) { if backend.last_render.elapsed() >= backend.opts.frame_rate { blit(backend); } } fn blit(backend: &mut Backend) { backend.window.as_mut().unwrap() .update_with_buffer(&backend.buffer, backend.width, backend.height) .expect("Update screen"); // TODO fault backend.last_render = Instant::now(); }