use std::ops::Sub; use std::time::{Duration, Instant}; use minifb::{Key, MouseButton, MouseMode, ScaleMode, Window, WindowOptions}; use crsn::asm::data::literal::{Value, is_negative}; use crsn::asm::error::CrsnError; use crsn::asm::instr::cond::Flag; 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; use std::mem; #[derive(Debug)] struct Opts { auto_blit: bool, frame_rate: Duration, upscale: Value, } #[derive(Debug)] struct Backend { width: Value, height: Value, log_width: Value, log_height: Value, buffer: Vec, window: Option, last_render: Instant, opts: Opts, } impl Default for Backend { fn default() -> Self { Self { width: 0, height: 0, log_width: 0, log_height: 0, buffer: vec![], window: None, last_render: Instant::now().sub(Duration::from_secs(1)), opts: Opts { auto_blit: true, upscale: 1, frame_rate: Duration::from_micros(16600), }, } } } impl Backend { pub fn draw_rect_log(&mut self, mut x: Value, mut y: Value, w: Value, h: Value, color: u32) { for yy in (y * self.opts.upscale)..((y + h) * self.opts.upscale).min(self.height) { for xx in (x * self.opts.upscale)..((x + w) * self.opts.upscale).min(self.width) { self.buffer[(yy * self.width + xx) as usize] = color as u32; } } } } pub const OPT_AUTO_BLIT: u64 = 1; pub const OPT_FRAME_RATE: u64 = 2; pub const OPT_UPSCALE: u64 = 3; // 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 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 => { if val == 0 { state.set_flag(Flag::Invalid, true); } else { backend.opts.frame_rate = Duration::from_micros(1_000_000 as u64 / val); debug!("Set frame rate to {:?}", backend.opts.frame_rate); } } OPT_UPSCALE => { if val == 0 { state.set_flag(Flag::Invalid, true); } else { backend.opts.upscale = val; backend.log_width = backend.width / val; backend.log_height = backend.height / val; debug!("Set upscale to {:?}", val); } } _other => { unreachable!() } } } 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() { debug!("Window is closed"); let _ = backend.window.take(); return Err(Fault::Halt); } w.update(); } None => { state.set_flag(Flag::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.log_width as u64 || y >= backend.log_height as u64 { state.set_flag(Flag::Overflow, true); return Ok(eres); } match &mut backend.window { Some(_w) => { backend.draw_rect_log(x, y, 1, 1, color as u32); if backend.opts.auto_blit { blit_maybe(backend)?; } } None => { state.set_flag(Flag::Invalid, true); } } } ScreenOp::GetPixel { color, x, y } => { state.clear_status(); let x = state.read(x)?; let y = state.read(y)?; let backend: &mut Backend = state.ext_mut(); if x >= backend.log_width as u64 || y >= backend.log_height as u64 { state.set_flag(Flag::Overflow, true); return Ok(eres); } match &mut backend.window { Some(_w) => { let index = y * backend.opts.upscale * backend.width as u64 + x * backend.opts.upscale; if index as usize > backend.buffer.len() { warn!("Screen set pixel out of bounds"); state.set_flag(Flag::Invalid, true); } else { let c = backend.buffer[index as usize]; trace!("index {}, c {}", index, c); state.write(color, c as Value)?; } } None => { state.set_flag(Flag::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(Flag::Overflow, true); } Some((xf, yf)) => { let mut xval = xf.round() as u64; let mut yval = yf.round() as u64; xval /= backend.opts.upscale; yval /= backend.opts.upscale; state.write(x, xval)?; state.write(y, yval)?; } } } None => { state.set_flag(Flag::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(Flag::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(Flag::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(Flag::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(Flag::Invalid, true); } } } ScreenOp::FillRect { x, y, w, h, color } => { let mut x = state.read(x)?; let mut y = state.read(y)?; let mut w = state.read(w)?; let mut h = state.read(h)?; let c = state.read(color)?; let backend: &mut Backend = state.ext_mut(); if is_negative(w) || is_negative(h) { state.set_flag(Flag::Invalid, true); return Ok(eres); } if w > backend.log_width { w = backend.log_width; } if h > backend.log_height { h = backend.log_height; } if is_negative(x) { let xi : i64 = unsafe { mem::transmute(x) }; let minus = xi.abs() as u64; w -= minus; x = 0; } if is_negative(y) { let yi : i64 = unsafe { mem::transmute(y) }; let minus = yi.abs() as u64; h -= minus; y = 0; } match &mut backend.window { Some(_w) => { backend.draw_rect_log(x, y, w, h, c as u32); } None => { state.set_flag(Flag::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-wr"), A(x), A(y), A(color)]), ScreenOp::GetPixel { color, x, y } => sexp::list(&[A("sc-rd"), A(color), A(x), A(y)]), 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)]), ScreenOp::FillRect { x, y, w, h, color } => { sexp::list(&[A("sc-rect"), A(x), A(y), A(w), A(h), 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; backend.height = height; backend.log_width = width; backend.log_height = height; backend.buffer = vec![0; (width * height) as usize]; backend.window = Some(window); blit_maybe(backend)?; Ok(()) } fn blit_maybe(backend: &mut Backend) -> Result<(), Fault> { if backend.last_render.elapsed() >= backend.opts.frame_rate { blit(backend) } else { Ok(()) } } fn blit(backend: &mut Backend) -> Result<(), Fault> { let w = backend.window.as_mut().unwrap(); if !w.is_open() { debug!("Window is closed"); let _ = backend.window.take(); return Err(Fault::Halt); } w.update_with_buffer(&backend.buffer, backend.width as usize, backend.height as usize) .expect("Update screen"); // TODO fault backend.last_render = Instant::now(); Ok(()) } 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 } }