311 lines
7.9 KiB
Rust
311 lines
7.9 KiB
Rust
#[global_allocator]
|
|
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
|
|
|
use canary_script::*;
|
|
use ludus::Console;
|
|
|
|
const FRAME_DURATION: f32 = 1.0 / 60.0;
|
|
const PIX_WIDTH: usize = 256;
|
|
const PIX_HEIGHT: usize = 240;
|
|
const PIX_NUM: usize = PIX_WIDTH * PIX_HEIGHT;
|
|
|
|
static ROM: &[u8] = include_bytes!("rom.nes");
|
|
|
|
export_abi!(NesPanel);
|
|
|
|
pub struct Config {
|
|
pub fb_pix_size: f32,
|
|
pub fb_origin_x: f32,
|
|
pub fb_origin_y: f32,
|
|
}
|
|
|
|
impl Default for Config {
|
|
fn default() -> Self {
|
|
Self {
|
|
fb_pix_size: 0.004,
|
|
fb_origin_x: -0.5,
|
|
fb_origin_y: -0.5,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct NesPanel {
|
|
panel: Panel,
|
|
fb: Framebuffer,
|
|
input: Input,
|
|
console: Console,
|
|
audio: DummyAudioDevice,
|
|
step: f32,
|
|
}
|
|
|
|
impl BindPanel for NesPanel {
|
|
fn bind(panel: Panel, message: Message) -> Box<dyn PanelImpl> {
|
|
let config = Config::default();
|
|
|
|
let cart = ludus::Cart::from_bytes(ROM).expect("Failed to decode rom");
|
|
|
|
let panel = Self {
|
|
panel,
|
|
fb: Framebuffer::new(&config),
|
|
input: Input::new(),
|
|
console: Console::new(cart, 8000),
|
|
audio: DummyAudioDevice,
|
|
step: 0.0,
|
|
};
|
|
|
|
Box::new(panel)
|
|
}
|
|
}
|
|
|
|
impl PanelImpl for NesPanel {
|
|
fn update(&mut self, dt: f32) {
|
|
self.step += dt;
|
|
|
|
self.input.update();
|
|
|
|
while self.step > FRAME_DURATION {
|
|
self.step -= FRAME_DURATION;
|
|
self.console.update_controller(self.input.get_state());
|
|
self.console.step_frame(&mut self.audio, &mut self.fb);
|
|
}
|
|
}
|
|
|
|
fn draw(&mut self) {
|
|
self.fb.draw(&self.panel);
|
|
self.input.draw(&self.panel);
|
|
}
|
|
|
|
fn on_cursor_event(&mut self, kind: CursorEventKind, at: Vec2) {
|
|
for button in self.input.buttons.iter_mut() {
|
|
button.on_cursor_event(kind, at);
|
|
}
|
|
}
|
|
|
|
fn on_message(&mut self, message: Message) {}
|
|
}
|
|
|
|
pub struct DummyAudioDevice;
|
|
|
|
impl ludus::ports::AudioDevice for DummyAudioDevice {
|
|
fn push_sample(&mut self, _sample: f32) {}
|
|
}
|
|
|
|
pub struct Framebuffer {
|
|
pub vertices: Vec<MeshVertex>,
|
|
pub indices: Vec<MeshIndex>,
|
|
pub double_buffer: Vec<u32>,
|
|
pub dirty: bool,
|
|
}
|
|
|
|
impl Framebuffer {
|
|
pub fn new(config: &Config) -> Self {
|
|
let mut vertices = Vec::with_capacity(PIX_NUM * 4);
|
|
let mut indices = Vec::with_capacity(PIX_NUM * 6);
|
|
|
|
let color = Color::BLACK;
|
|
|
|
for y in 0..PIX_HEIGHT {
|
|
for x in 0..PIX_WIDTH {
|
|
let index = vertices.len() as MeshIndex;
|
|
let y = PIX_HEIGHT - y;
|
|
let x = x as f32 * config.fb_pix_size + config.fb_origin_x;
|
|
let y = y as f32 * config.fb_pix_size + config.fb_origin_y;
|
|
|
|
vertices.push(MeshVertex {
|
|
position: Vec2 { x, y },
|
|
color,
|
|
});
|
|
|
|
vertices.push(MeshVertex {
|
|
position: Vec2 {
|
|
x: x + config.fb_pix_size,
|
|
y,
|
|
},
|
|
color,
|
|
});
|
|
|
|
vertices.push(MeshVertex {
|
|
position: Vec2 {
|
|
x,
|
|
y: y + config.fb_pix_size,
|
|
},
|
|
color,
|
|
});
|
|
|
|
vertices.push(MeshVertex {
|
|
position: Vec2 {
|
|
x: x + config.fb_pix_size,
|
|
y: y + config.fb_pix_size,
|
|
},
|
|
color,
|
|
});
|
|
|
|
indices.extend_from_slice(&[
|
|
index,
|
|
index + 1,
|
|
index + 2,
|
|
index + 1,
|
|
index + 2,
|
|
index + 3,
|
|
]);
|
|
}
|
|
}
|
|
|
|
Self {
|
|
vertices,
|
|
indices,
|
|
double_buffer: Vec::new(),
|
|
dirty: false,
|
|
}
|
|
}
|
|
|
|
pub fn draw(&mut self, panel: &Panel) {
|
|
if self.dirty {
|
|
self.dirty = false;
|
|
|
|
for (pixel, vertices) in self
|
|
.double_buffer
|
|
.iter()
|
|
.zip(self.vertices.chunks_exact_mut(4))
|
|
{
|
|
let r = ((pixel >> 16) & 0xff) as f32 / 255.0;
|
|
let g = ((pixel >> 8) & 0xff) as f32 / 255.0;
|
|
let b = (pixel & 0xff) as f32 / 255.0;
|
|
let color = Color::new(r, g, b, 1.0);
|
|
|
|
for vertex in vertices {
|
|
vertex.color = color;
|
|
}
|
|
}
|
|
}
|
|
|
|
panel.draw_indexed(&self.vertices, &self.indices);
|
|
}
|
|
}
|
|
|
|
impl ludus::ports::VideoDevice for Framebuffer {
|
|
fn blit_pixels(&mut self, pixels: &ludus::PixelBuffer) {
|
|
let pixels = pixels.as_ref();
|
|
self.double_buffer.resize(pixels.len(), 0);
|
|
self.double_buffer.copy_from_slice(pixels);
|
|
self.dirty = true;
|
|
}
|
|
}
|
|
|
|
pub struct Input {
|
|
pub buttons: [Button; 8],
|
|
pub state: ludus::ButtonState,
|
|
}
|
|
|
|
impl Input {
|
|
pub fn new() -> Self {
|
|
let button_size = Vec2 { x: 0.1, y: 0.1 };
|
|
|
|
let button_positions = [
|
|
Vec2 { x: -0.4, y: -0.3 },
|
|
Vec2 { x: -0.3, y: -0.4 },
|
|
Vec2 { x: -0.4, y: -0.5 },
|
|
Vec2 { x: -0.5, y: -0.4 },
|
|
Vec2 { x: -0.15, y: -0.5 },
|
|
Vec2 { x: 0.15, y: -0.5 },
|
|
Vec2 { x: 0.4, y: -0.4 },
|
|
Vec2 { x: 0.55, y: -0.4 },
|
|
];
|
|
|
|
Self {
|
|
buttons: button_positions.map(|pos| Button::new(pos, button_size)),
|
|
state: Default::default(),
|
|
}
|
|
}
|
|
|
|
pub fn get_state(&self) -> ludus::ButtonState {
|
|
ludus::ButtonState { ..self.state }
|
|
}
|
|
|
|
pub fn update(&mut self) {
|
|
self.state.up = self.buttons[0].pressed;
|
|
self.state.right = self.buttons[1].pressed;
|
|
self.state.down = self.buttons[2].pressed;
|
|
self.state.left = self.buttons[3].pressed;
|
|
self.state.select = self.buttons[4].pressed;
|
|
self.state.start = self.buttons[5].pressed;
|
|
self.state.b = self.buttons[6].pressed;
|
|
self.state.a = self.buttons[7].pressed;
|
|
}
|
|
|
|
pub fn draw(&self, panel: &Panel) {
|
|
for button in self.buttons.iter() {
|
|
button.draw(panel);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct Button {
|
|
pos: Vec2,
|
|
size: Vec2,
|
|
pressed: bool,
|
|
}
|
|
|
|
impl Button {
|
|
pub fn new(pos: Vec2, size: Vec2) -> Self {
|
|
Self {
|
|
pos,
|
|
size,
|
|
pressed: false,
|
|
}
|
|
}
|
|
|
|
pub fn draw(&self, panel: &Panel) {
|
|
let alpha = if self.pressed { 1.0 } else { 0.8 };
|
|
let color = Color::new(1.0, 1.0, 1.0, alpha);
|
|
|
|
let pos = self.pos;
|
|
let corner = Vec2 {
|
|
x: pos.x + self.size.x,
|
|
y: pos.y + self.size.y,
|
|
};
|
|
|
|
let vertices = &[
|
|
MeshVertex {
|
|
position: pos,
|
|
color,
|
|
},
|
|
MeshVertex {
|
|
position: Vec2 {
|
|
x: corner.x,
|
|
y: pos.y,
|
|
},
|
|
color,
|
|
},
|
|
MeshVertex {
|
|
position: Vec2 {
|
|
x: pos.x,
|
|
y: corner.y,
|
|
},
|
|
color,
|
|
},
|
|
MeshVertex {
|
|
position: corner,
|
|
color,
|
|
},
|
|
];
|
|
|
|
let indices = &[0, 1, 2, 1, 2, 3];
|
|
|
|
panel.draw_indexed(vertices.as_slice(), indices.as_slice());
|
|
}
|
|
|
|
pub fn on_cursor_event(&mut self, kind: CursorEventKind, at: Vec2) {
|
|
if kind == CursorEventKind::Deselect {
|
|
self.pressed = false;
|
|
} else if kind == CursorEventKind::Select
|
|
&& at.x > self.pos.x
|
|
&& at.y > self.pos.y
|
|
&& at.x < self.pos.x + self.size.x
|
|
&& at.y < self.pos.y + self.size.y
|
|
{
|
|
self.pressed = true;
|
|
}
|
|
}
|
|
}
|