Croissant Runtime
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
crsn/crsn_screen/src/exec.rs

423 lines
12 KiB

use std::ops::Sub;
use std::time::{Duration, Instant};
use minifb::{ScaleMode, Window, WindowOptions, MouseMode, Key, MouseButton};
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;
use crsn::sexp::Sexp;
use crsn::sexp;
use crsn::utils::A;
#[derive(Debug)]
struct Opts {
auto_blit: bool,
frame_rate: Duration,
}
#[derive(Debug)]
struct Backend {
width: usize,
height: usize,
buffer: Vec<u32>,
window: Option<Window>,
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<EvalRes, Fault> {
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) => {
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<Key> {
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
}
}