use std::ops::Sub; use std::time::{Duration, Instant}; use minifb::{Key, MouseButton, MouseMode, 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 crsn::sexp; use crsn::sexp::Sexp; use crsn::utils::A; 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::Erase { color } => { let color = (state.read(*color)? & 0xffffff) as u32; let backend: &mut Backend = state.ext_mut(); for n in 0..(backend.buffer.len()) { backend.buffer[n] = color; } } ScreenOp::SetOpt { opt, val } => { state.clear_status(); 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::Update => { let backend: &mut Backend = state.ext_mut(); match &mut backend.window { Some(w) => { if !w.is_open() { // TODO... std::process::exit(0); } w.update(); } None => { state.set_flag(Cond::Invalid, true); } } } ScreenOp::SetPixel { x, y, color } => { state.clear_status(); 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); } } } ScreenOp::GetMouse { x, y } => { state.clear_status(); let backend: &mut Backend = state.ext_mut(); match &mut backend.window { Some(w) => { let mp = w.get_mouse_pos(MouseMode::Discard); debug!("mp = {:?}", mp); match mp { None => { state.set_flag(Cond::Overflow, true); } Some((xf, yf)) => { let xval = xf.round() as u64; let yval = yf.round() as u64; state.write(*x, xval)?; state.write(*y, yval)?; } } } None => { state.set_flag(Cond::Invalid, true); } } } ScreenOp::TestKey { pressed, code } => { state.clear_status(); let num = num2key(state.read(*code)?); let backend: &mut Backend = state.ext_mut(); match &mut backend.window { Some(w) => { match num { None => { state.set_flag(Cond::Invalid, true); } Some(kn) => { let down = w.is_key_down(kn) as u64; state.write(*pressed, down)?; state.update_status(down); } } } None => { state.set_flag(Cond::Invalid, true); } } } ScreenOp::TestMouse { pressed, button } => { state.clear_status(); let omb = match state.read(*button)? { 0 => Some(MouseButton::Left), 1 => Some(MouseButton::Right), 2 => Some(MouseButton::Middle), _ => { state.set_flag(Cond::Invalid, true); None } }; let backend: &mut Backend = state.ext_mut(); match &mut backend.window { Some(w) => { if let Some(mb) = omb { let is_pressed = w.get_mouse_down(mb) as u64; state.write(*pressed, is_pressed)?; state.update_status(is_pressed); } } None => { state.set_flag(Cond::Invalid, true); } } } } Ok(eres) } fn to_sexp(&self) -> Sexp { match self { ScreenOp::SetOpt { opt, val } => sexp::list(&[A("sc-opt"), A(opt), A(val)]), ScreenOp::ScreenInit { width, height } => sexp::list(&[A("sc-init"), A(width), A(height)]), ScreenOp::SetPixel { x, y, color } => sexp::list(&[A("sc-px"), A(x), A(y), A(color)]), ScreenOp::Blit { force } => sexp::list(&[A("sc-blit"), A(force)]), ScreenOp::Update => sexp::list(&[A("sc-poll")]), ScreenOp::GetMouse { x, y } => sexp::list(&[A("sc-mouse"), A(x), A(y)]), ScreenOp::TestKey { pressed, code } => sexp::list(&[A("sc-key"), A(pressed), A(code)]), ScreenOp::TestMouse { pressed, button } => sexp::list(&[A("sc-mbtn"), A(pressed), A(button)]), ScreenOp::Erase { color } => sexp::list(&[A("sc-erase"), A(color)]), } } } 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]; 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) { let w = backend.window.as_mut().unwrap(); if !w.is_open() { // TODO... std::process::exit(0); } w.update_with_buffer(&backend.buffer, backend.width, backend.height) .expect("Update screen"); // TODO fault backend.last_render = Instant::now(); } fn num2key(num: Value) -> Option { let remap = [ Key::Key0, Key::Key1, Key::Key2, Key::Key3, Key::Key4, Key::Key5, Key::Key6, Key::Key7, Key::Key8, Key::Key9, Key::A, // 10 Key::B, Key::C, Key::D, Key::E, Key::F, Key::G, Key::H, Key::I, Key::J, Key::K, Key::L, Key::M, Key::N, Key::O, Key::P, Key::Q, Key::R, Key::S, Key::T, Key::U, Key::V, Key::W, Key::X, Key::Y, Key::Z, // 35 Key::F1, // 36 Key::F2, Key::F3, Key::F4, Key::F5, Key::F6, Key::F7, Key::F8, Key::F9, Key::F10, Key::F11, Key::F12, Key::F13, Key::F14, Key::F15, // 50 Key::Down, // 51 Key::Left, Key::Right, Key::Up, Key::Apostrophe, Key::Backquote, Key::Backslash, // 57 Key::Comma, Key::Equal, Key::LeftBracket, Key::Minus, Key::Period, Key::RightBracket, Key::Semicolon, Key::Slash, // 65 Key::Backspace, Key::Delete, Key::End, Key::Enter, Key::Escape, // 70 Key::Home, Key::Insert, Key::Menu, Key::PageDown, Key::PageUp, Key::Pause, // 76 Key::Space, Key::Tab, Key::NumLock, Key::CapsLock, Key::ScrollLock, Key::LeftShift, Key::RightShift, Key::LeftCtrl, Key::RightCtrl, Key::NumPad0, // 86 Key::NumPad1, Key::NumPad2, Key::NumPad3, Key::NumPad4, Key::NumPad5, Key::NumPad6, Key::NumPad7, Key::NumPad8, Key::NumPad9, Key::NumPadDot, Key::NumPadSlash, Key::NumPadAsterisk, Key::NumPadMinus, Key::NumPadPlus, Key::NumPadEnter, // 100 Key::LeftAlt, Key::RightAlt, Key::LeftSuper, Key::RightSuper, ]; if num < remap.len() as u64 { Some(remap[num as usize]) } else { None } }