terminal mode! i like this better, also functional cli now
This commit is contained in:
parent
229b8b450d
commit
001d3e434c
File diff suppressed because it is too large
Load Diff
14
Cargo.toml
14
Cargo.toml
|
@ -8,18 +8,8 @@ edition = "2021"
|
|||
[dependencies]
|
||||
anyhow = "1.0.81"
|
||||
bdf = "0.6.0"
|
||||
clap = { version = "4.5.4", features = ["derive"] }
|
||||
criterion = "0.4"
|
||||
crossterm = "0.27.0"
|
||||
minifb = "0.25.0"
|
||||
ratatui = "0.26.3"
|
||||
# minifb = "0.25.0"
|
||||
# ratatui = "0.26.3"
|
||||
serde = { version = "1.0.197", features = ["serde_derive", "derive"] }
|
||||
toml = "0.8.12"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = {version = "0.4", features = ["html_reports"]}
|
||||
|
||||
[[bench]]
|
||||
name = "benchmark"
|
||||
path = "src/benches/benchmark.rs"
|
||||
harness = false
|
||||
|
|
11
run.sh
11
run.sh
|
@ -1,5 +1,12 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
vasm6502_oldstyle ./src/roms/george.asm -dotdir -wdc02 -ldots -Fbin -o ./src/roms/george.rom;
|
||||
cargo run;
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "Provide the name of the rom/asm file to run"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -e
|
||||
|
||||
vasm6502_oldstyle ./src/roms/$1.asm -dotdir -wdc02 -ldots -Fbin -o ./src/roms/$1.rom;
|
||||
cargo run rom "./src/roms/$1.rom";
|
||||
# hexdump -C ./cpu_dump.bin;
|
||||
|
|
22
src/cpu.rs
22
src/cpu.rs
|
@ -2,8 +2,8 @@ use crate::instructions::{get_instruction, Instruction};
|
|||
use crate::memory::{MemHandle, MemoryReader, MemoryWriter};
|
||||
use crate::types::{Byte, Word};
|
||||
use std::fmt::Display;
|
||||
use std::sync::mpsc::{Receiver, Sender};
|
||||
use std::thread::sleep;
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
use std::thread::{sleep, LocalKey};
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
|
@ -124,6 +124,13 @@ impl Cpu {
|
|||
// state_tx,
|
||||
}
|
||||
}
|
||||
pub fn new_with_control(memory: MemHandle) -> (Self, CpuController) {
|
||||
let (tx, rx) = channel::<CpuControl>();
|
||||
let controller = CpuController(tx);
|
||||
let receiver = CpuReceiver(rx);
|
||||
let cpu = Cpu::new(memory, receiver);
|
||||
(cpu, controller)
|
||||
}
|
||||
pub fn reset(&mut self) -> Result<()> {
|
||||
let reset_vector_pointer = self.read_word(0xFFFC)?;
|
||||
self.pc = reset_vector_pointer;
|
||||
|
@ -203,7 +210,11 @@ impl Cpu {
|
|||
}
|
||||
|
||||
fn receive_control(&mut self) {
|
||||
let control = self.receiver.0.recv().unwrap();
|
||||
let control = match self.receiver.0.try_recv() {
|
||||
Ok(control) => control,
|
||||
Err(_error) => return, // most of the time we won't have any impending control
|
||||
// messages, just return if that's the case
|
||||
};
|
||||
match control {
|
||||
CpuControl::Nmi => self.nmi = true,
|
||||
CpuControl::Irq => self.irq = true,
|
||||
|
@ -226,13 +237,13 @@ impl Cpu {
|
|||
}
|
||||
|
||||
pub fn cycle(&mut self) {
|
||||
// self.receive_control();
|
||||
self.receive_control();
|
||||
if self.stopped {
|
||||
return;
|
||||
}
|
||||
while self.pending_cycles != 0 {
|
||||
// roughly cycle-accurate timing
|
||||
sleep(Duration::from_nanos(100));
|
||||
sleep(Duration::from_nanos(500));
|
||||
self.pending_cycles -= 1;
|
||||
}
|
||||
if !self.get_flag(StatusFlag::IrqDisable) && self.irq {
|
||||
|
@ -272,7 +283,6 @@ impl Cpu {
|
|||
},
|
||||
}
|
||||
// self.cycle_count += 1;
|
||||
sleep(Duration::from_nanos(100));
|
||||
}
|
||||
pub fn stop(&mut self) {
|
||||
self.stopped = true;
|
||||
|
|
182
src/keyboard.rs
182
src/keyboard.rs
|
@ -1,4 +1,5 @@
|
|||
use minifb::{InputCallback, Key};
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
|
||||
// use minifb::{InputCallback, Key};
|
||||
|
||||
use crate::memory::{MemHandle, MemoryWriter};
|
||||
|
||||
|
@ -10,71 +11,66 @@ impl Keyboard {
|
|||
pub fn new(memory: MemHandle) -> Self {
|
||||
Self { memory }
|
||||
}
|
||||
}
|
||||
|
||||
impl MemoryWriter for Keyboard {
|
||||
fn write(&self, address: u16, data: u8) {
|
||||
self.memory.write(address, data);
|
||||
}
|
||||
}
|
||||
|
||||
impl InputCallback for Keyboard {
|
||||
fn add_char(&mut self, _uni_char: u32) {}
|
||||
fn set_key_state(&mut self, key: Key, _state: bool) {
|
||||
pub fn read_keys(&self, key: KeyEvent) {
|
||||
let mut row0 = 0;
|
||||
let mut row1 = 0;
|
||||
let mut row2 = 0;
|
||||
let mut row3 = 0;
|
||||
let mut row4 = 0;
|
||||
let mut row5 = 0;
|
||||
|
||||
match key {
|
||||
Key::Escape => row0 ^= 0b1000_0000,
|
||||
Key::W => row0 ^= 0b0100_0000,
|
||||
Key::E => row0 ^= 0b0010_0000,
|
||||
Key::R => row0 ^= 0b0001_0000,
|
||||
Key::T => row0 ^= 0b0000_1000,
|
||||
Key::U => row0 ^= 0b0000_0100,
|
||||
Key::O => row0 ^= 0b0000_0010,
|
||||
Key::Backspace => row0 ^= 0b0000_0001,
|
||||
Key::Tab => row1 ^= 0b1000_0000,
|
||||
Key::Q => row1 ^= 0b0100_0000,
|
||||
Key::S => row1 ^= 0b0010_0000,
|
||||
Key::G => row1 ^= 0b0001_0000,
|
||||
Key::Y => row1 ^= 0b0000_1000,
|
||||
Key::I => row1 ^= 0b0000_0100,
|
||||
Key::P => row1 ^= 0b0000_0010,
|
||||
Key::Enter => row1 ^= 0b0000_0001,
|
||||
Key::LeftShift | Key::RightShift => row2 ^= 0b1000_0000,
|
||||
Key::D => row2 ^= 0b0100_0000,
|
||||
Key::V => row2 ^= 0b0010_0000,
|
||||
Key::H => row2 ^= 0b0001_0000,
|
||||
Key::K => row2 ^= 0b0000_1000,
|
||||
Key::Apostrophe => row2 ^= 0b0000_0100,
|
||||
Key::Slash => row2 ^= 0b0000_0010,
|
||||
Key::A => row2 ^= 0b0000_0001,
|
||||
Key::LeftCtrl | Key::RightCtrl => row3 ^= 0b1000_0000,
|
||||
Key::Z => row3 ^= 0b0100_0000,
|
||||
Key::F => row3 ^= 0b0010_0000,
|
||||
Key::B => row3 ^= 0b0001_0000,
|
||||
Key::J => row3 ^= 0b0000_1000,
|
||||
Key::L => row3 ^= 0b0000_0100,
|
||||
Key::Key2 => row3 ^= 0b0000_0010,
|
||||
Key::Key4 => row3 ^= 0b0000_0001,
|
||||
Key::LeftAlt | Key::RightAlt => row4 ^= 0b1000_0000,
|
||||
Key::X => row4 ^= 0b0100_0000,
|
||||
Key::C => row4 ^= 0b0010_0000,
|
||||
Key::N => row4 ^= 0b0001_0000,
|
||||
Key::M => row4 ^= 0b0000_1000,
|
||||
Key::Comma => row4 ^= 0b0000_0100,
|
||||
Key::Key1 => row4 ^= 0b0000_0010,
|
||||
Key::Key3 => row4 ^= 0b0000_0001,
|
||||
Key::LeftSuper => row5 ^= 0b1000_0000,
|
||||
Key::Space => row5 ^= 0b0100_0000,
|
||||
Key::RightSuper => row5 ^= 0b0010_0000,
|
||||
if key.kind == KeyEventKind::Press || key.kind == KeyEventKind::Repeat {
|
||||
match key.modifiers {
|
||||
KeyModifiers::SHIFT => row2 ^= 0b1000_0000,
|
||||
KeyModifiers::CONTROL => row3 ^= 0b1000_0000,
|
||||
KeyModifiers::ALT => row4 ^= 0b1000_0000,
|
||||
KeyModifiers::META => row5 ^= 0b1000_0000,
|
||||
KeyModifiers::SUPER => row5 ^= 0b0010_0000,
|
||||
_ => {}
|
||||
};
|
||||
|
||||
match key.code {
|
||||
KeyCode::Esc => row0 ^= 0b1000_0000,
|
||||
KeyCode::Char('w') => row0 ^= 0b0100_0000,
|
||||
KeyCode::Char('e') => row0 ^= 0b0010_0000,
|
||||
KeyCode::Char('r') => row0 ^= 0b0001_0000,
|
||||
KeyCode::Char('t') => row0 ^= 0b0000_1000,
|
||||
KeyCode::Char('u') => row0 ^= 0b0000_0100,
|
||||
KeyCode::Char('o') => row0 ^= 0b0000_0010,
|
||||
KeyCode::Backspace => row0 ^= 0b0000_0001,
|
||||
KeyCode::Tab => row1 ^= 0b1000_0000,
|
||||
KeyCode::Char('q') => row1 ^= 0b0100_0000,
|
||||
KeyCode::Char('s') => row1 ^= 0b0010_0000,
|
||||
KeyCode::Char('g') => row1 ^= 0b0001_0000,
|
||||
KeyCode::Char('y') => row1 ^= 0b0000_1000,
|
||||
KeyCode::Char('i') => row1 ^= 0b0000_0100,
|
||||
KeyCode::Char('p') => row1 ^= 0b0000_0010,
|
||||
KeyCode::Enter => row1 ^= 0b0000_0001,
|
||||
KeyCode::Char('d') => row2 ^= 0b0100_0000,
|
||||
KeyCode::Char('v') => row2 ^= 0b0010_0000,
|
||||
KeyCode::Char('h') => row2 ^= 0b0001_0000,
|
||||
KeyCode::Char('k') => row2 ^= 0b0000_1000,
|
||||
KeyCode::Char('\'') => row2 ^= 0b0000_0100,
|
||||
KeyCode::Char('/') => row2 ^= 0b0000_0010,
|
||||
KeyCode::Char('a') => row2 ^= 0b0000_0001,
|
||||
KeyCode::Char('z') => row3 ^= 0b0100_0000,
|
||||
KeyCode::Char('f') => row3 ^= 0b0010_0000,
|
||||
KeyCode::Char('b') => row3 ^= 0b0001_0000,
|
||||
KeyCode::Char('j') => row3 ^= 0b0000_1000,
|
||||
KeyCode::Char('l') => row3 ^= 0b0000_0100,
|
||||
KeyCode::Char('2') => row3 ^= 0b0000_0010,
|
||||
KeyCode::Char('4') => row3 ^= 0b0000_0001,
|
||||
KeyCode::Char('x') => row4 ^= 0b0100_0000,
|
||||
KeyCode::Char('c') => row4 ^= 0b0010_0000,
|
||||
KeyCode::Char('n') => row4 ^= 0b0001_0000,
|
||||
KeyCode::Char('m') => row4 ^= 0b0000_1000,
|
||||
KeyCode::Char(',') => row4 ^= 0b0000_0100,
|
||||
KeyCode::Char('1') => row4 ^= 0b0000_0010,
|
||||
KeyCode::Char('3') => row4 ^= 0b0000_0001,
|
||||
KeyCode::Char(' ') => row5 ^= 0b0100_0000,
|
||||
_ => {}
|
||||
}
|
||||
};
|
||||
|
||||
self.memory.write(0x4400, row0);
|
||||
self.memory.write(0x4401, row1);
|
||||
self.memory.write(0x4402, row2);
|
||||
|
@ -83,3 +79,75 @@ impl InputCallback for Keyboard {
|
|||
self.memory.write(0x4405, row5);
|
||||
}
|
||||
}
|
||||
|
||||
impl MemoryWriter for Keyboard {
|
||||
fn write(&self, address: u16, data: u8) {
|
||||
self.memory.write(address, data);
|
||||
}
|
||||
}
|
||||
|
||||
// impl InputCallback for Keyboard {
|
||||
// fn add_char(&mut self, _uni_char: u32) {}
|
||||
// fn set_key_state(&mut self, key: Key, _state: bool) {
|
||||
// let mut row0 = 0;
|
||||
// let mut row1 = 0;
|
||||
// let mut row2 = 0;
|
||||
// let mut row3 = 0;
|
||||
// let mut row4 = 0;
|
||||
// let mut row5 = 0;
|
||||
|
||||
// match key {
|
||||
// Key::Escape => row0 ^= 0b1000_0000,
|
||||
// Key::W => row0 ^= 0b0100_0000,
|
||||
// Key::E => row0 ^= 0b0010_0000,
|
||||
// Key::R => row0 ^= 0b0001_0000,
|
||||
// Key::T => row0 ^= 0b0000_1000,
|
||||
// Key::U => row0 ^= 0b0000_0100,
|
||||
// Key::O => row0 ^= 0b0000_0010,
|
||||
// Key::Backspace => row0 ^= 0b0000_0001,
|
||||
// Key::Tab => row1 ^= 0b1000_0000,
|
||||
// Key::Q => row1 ^= 0b0100_0000,
|
||||
// Key::S => row1 ^= 0b0010_0000,
|
||||
// Key::G => row1 ^= 0b0001_0000,
|
||||
// Key::Y => row1 ^= 0b0000_1000,
|
||||
// Key::I => row1 ^= 0b0000_0100,
|
||||
// Key::P => row1 ^= 0b0000_0010,
|
||||
// Key::Enter => row1 ^= 0b0000_0001,
|
||||
// Key::LeftShift | Key::RightShift => row2 ^= 0b1000_0000,
|
||||
// Key::D => row2 ^= 0b0100_0000,
|
||||
// Key::V => row2 ^= 0b0010_0000,
|
||||
// Key::H => row2 ^= 0b0001_0000,
|
||||
// Key::K => row2 ^= 0b0000_1000,
|
||||
// Key::Apostrophe => row2 ^= 0b0000_0100,
|
||||
// Key::Slash => row2 ^= 0b0000_0010,
|
||||
// Key::A => row2 ^= 0b0000_0001,
|
||||
// Key::LeftCtrl | Key::RightCtrl => row3 ^= 0b1000_0000,
|
||||
// Key::Z => row3 ^= 0b0100_0000,
|
||||
// Key::F => row3 ^= 0b0010_0000,
|
||||
// Key::B => row3 ^= 0b0001_0000,
|
||||
// Key::J => row3 ^= 0b0000_1000,
|
||||
// Key::L => row3 ^= 0b0000_0100,
|
||||
// Key::Key2 => row3 ^= 0b0000_0010,
|
||||
// Key::Key4 => row3 ^= 0b0000_0001,
|
||||
// Key::LeftAlt | Key::RightAlt => row4 ^= 0b1000_0000,
|
||||
// Key::X => row4 ^= 0b0100_0000,
|
||||
// Key::C => row4 ^= 0b0010_0000,
|
||||
// Key::N => row4 ^= 0b0001_0000,
|
||||
// Key::M => row4 ^= 0b0000_1000,
|
||||
// Key::Comma => row4 ^= 0b0000_0100,
|
||||
// Key::Key1 => row4 ^= 0b0000_0010,
|
||||
// Key::Key3 => row4 ^= 0b0000_0001,
|
||||
// Key::LeftSuper => row5 ^= 0b1000_0000,
|
||||
// Key::Space => row5 ^= 0b0100_0000,
|
||||
// Key::RightSuper => row5 ^= 0b0010_0000,
|
||||
// _ => {}
|
||||
// };
|
||||
|
||||
// self.memory.write(0x4400, row0);
|
||||
// self.memory.write(0x4401, row1);
|
||||
// self.memory.write(0x4402, row2);
|
||||
// self.memory.write(0x4403, row3);
|
||||
// self.memory.write(0x4404, row4);
|
||||
// self.memory.write(0x4405, row5);
|
||||
// }
|
||||
// }
|
||||
|
|
159
src/main.rs
159
src/main.rs
|
@ -5,7 +5,6 @@ mod error;
|
|||
mod instructions;
|
||||
mod keyboard;
|
||||
mod memory;
|
||||
mod tui;
|
||||
mod types;
|
||||
mod video;
|
||||
|
||||
|
@ -14,41 +13,31 @@ use crate::keyboard::Keyboard;
|
|||
use crate::memory::Mem;
|
||||
use crate::video::{Screen, TerminalRenderer};
|
||||
|
||||
use cpu::{CpuController, CpuReceiver};
|
||||
use crossterm::cursor::Hide;
|
||||
use crossterm::execute;
|
||||
use crossterm::terminal::{size, Clear, ClearType, SetSize};
|
||||
// use cpu::CpuController;
|
||||
use memory::MemHandle;
|
||||
// use clap::Parser;
|
||||
// use minifb::{Scale, ScaleMode, Window, WindowOptions};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use std::env;
|
||||
use std::io::ErrorKind;
|
||||
use std::process::exit;
|
||||
use std::thread::{self, sleep};
|
||||
use std::time::Duration;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{stdout, Read, Result},
|
||||
path::PathBuf,
|
||||
str::FromStr,
|
||||
sync::mpsc,
|
||||
thread,
|
||||
};
|
||||
|
||||
use crossterm::{
|
||||
cursor,
|
||||
event::{self, KeyCode, KeyEventKind},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
// ExecutableCommand,
|
||||
};
|
||||
|
||||
// use toml::Table;
|
||||
|
||||
//#[derive(Parser)]
|
||||
//struct Cli {
|
||||
// // Load a rom onto the system rom
|
||||
// #[arg(short, required = true)]
|
||||
// rom: Option<std::path::PathBuf>,
|
||||
//}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct Config {
|
||||
char_rom: Option<String>,
|
||||
|
@ -56,21 +45,26 @@ struct Config {
|
|||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let mut stdout = stdout();
|
||||
let (cols, rows) = size()?;
|
||||
execute!(stdout, SetSize(64, 29), cursor::Hide, EnterAlternateScreen)?;
|
||||
enable_raw_mode()?;
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
let config: Config = match File::open("./config.toml") {
|
||||
let mut memory = Mem::new();
|
||||
match args.len() {
|
||||
0 => {
|
||||
println!("george-emu must be run in the terminal, don't know what went wrong here!");
|
||||
exit(1)
|
||||
}
|
||||
1 => {
|
||||
let config: Config = match File::open("./george.toml") {
|
||||
Ok(mut file) => {
|
||||
let mut string = String::new();
|
||||
file.read_to_string(&mut string).unwrap();
|
||||
toml::from_str(string.as_str()).unwrap()
|
||||
}
|
||||
Err(_) => return Ok(()),
|
||||
Err(_) => {
|
||||
println!("couldn't find a `george.toml` in the current directory!");
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let mut memory = Mem::new();
|
||||
let rom = match std::fs::File::open(config.rom) {
|
||||
Ok(file) => file,
|
||||
Err(error) => panic!("Couldn't open main rom! {:?}", error),
|
||||
|
@ -78,6 +72,86 @@ fn main() -> Result<()> {
|
|||
if let Err(error) = memory.load_rom(rom) {
|
||||
println!("{:?}", error);
|
||||
};
|
||||
}
|
||||
2 => match &args[1] as &str {
|
||||
"help" => {
|
||||
println!("ʕ·ᴥ·ʔ- george-emu is an emulator for george:");
|
||||
println!("https://git.augustkline.com/august/george\n");
|
||||
println!("commands:");
|
||||
println!(" help: print this help screen");
|
||||
println!(" help <command>: print help info for any command");
|
||||
println!(" rom <path>: load a rom/binary from path\n");
|
||||
println!("configuration:");
|
||||
println!(" george-emu searches for a `george.toml` in the current directory. in `george.toml` you can specify a path for the character rom using the key `char_rom` and the main rom/binary with the key `rom`");
|
||||
exit(0);
|
||||
}
|
||||
_ => {
|
||||
println!(
|
||||
"{:?} isn't a valid command!\n\nuse `{} help` to see all valid commands",
|
||||
&args[1], &args[0]
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
},
|
||||
3 => match &args[1] as &str {
|
||||
"help" => match &args[2] as &str {
|
||||
"rom" => {
|
||||
println!("{:?} rom <path>\nload a rom/binary from path", &args[0]);
|
||||
}
|
||||
_ => {
|
||||
println!(
|
||||
"{:?} isn't a valid command!\n\nuse `{} help` to see all valid commands",
|
||||
&args[2], &args[0]
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
},
|
||||
"rom" => {
|
||||
let rom = match std::fs::File::open(&args[2]) {
|
||||
Ok(file) => file,
|
||||
Err(error) => {
|
||||
match error.kind() {
|
||||
ErrorKind::NotFound => {
|
||||
println!("couldn't find the rom at {:?}", &args[2]);
|
||||
}
|
||||
ErrorKind::PermissionDenied => {
|
||||
println!(
|
||||
"didn't have sufficient permissions to open the rom at {:?}",
|
||||
&args[2]
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
println!("something went wrong! try again in a moment? really not sure why you're getting this error");
|
||||
}
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
if let Err(error) = memory.load_rom(rom) {
|
||||
println!("oh no! this rom couldn't be loaded: {:?}", error);
|
||||
exit(1);
|
||||
};
|
||||
}
|
||||
_ => {
|
||||
println!(
|
||||
"{:?} isn't a valid command!\n\nuse `{} help` to see all valid commands",
|
||||
&args[1], &args[0]
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
println!(
|
||||
"too many arguments were provided!\n\nuse `{} help` to see all valid commands",
|
||||
&args[0]
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
let mut stdout = stdout();
|
||||
let (cols, rows) = size()?;
|
||||
execute!(stdout, SetSize(64, 29), cursor::Hide, EnterAlternateScreen)?;
|
||||
enable_raw_mode()?;
|
||||
|
||||
memory
|
||||
.dump(PathBuf::from_str("./coredump.bin").unwrap())
|
||||
|
@ -88,11 +162,8 @@ fn main() -> Result<()> {
|
|||
let cpu_memory = shared_memory.clone();
|
||||
let keyboard_memory = shared_memory.clone();
|
||||
|
||||
let (cpu_tx, cpu_rx) = mpsc::channel();
|
||||
// let (state_tx, state_rx) = mpsc::channel();
|
||||
let screen_cpu_tx = cpu_tx.clone();
|
||||
// let (window_tx, window_rx) = mpsc::channel();
|
||||
|
||||
// For when we want to leave the terminal again :sigh:
|
||||
//
|
||||
// thread::spawn(move || {
|
||||
// let mut screen = Crtc::new(
|
||||
// screen_memory,
|
||||
|
@ -120,40 +191,30 @@ fn main() -> Result<()> {
|
|||
// )
|
||||
// .unwrap();
|
||||
|
||||
// window.set_input_callback(Box::new(Keyboard::new(keyboard_memory)));
|
||||
let keyboard = Keyboard::new(keyboard_memory);
|
||||
|
||||
let mut cpu = Cpu::new(cpu_memory, CpuReceiver::new(cpu_rx));
|
||||
// let mut cpu = Cpu::new(cpu_memory);
|
||||
// let mut tui = tui::App::new(
|
||||
// CpuController::new(cpu_tx.clone()),
|
||||
// shared_memory.clone(),
|
||||
// state_rx,
|
||||
// );
|
||||
thread::spawn(move || {
|
||||
cpu.reset().unwrap();
|
||||
cpu.memory.write(0x4400, 0b0000_0100);
|
||||
cpu.execute()
|
||||
let (mut cpu, cpu_controller) = Cpu::new_with_control(cpu_memory);
|
||||
let _ = cpu.reset();
|
||||
|
||||
thread::spawn(move || loop {
|
||||
cpu.cycle();
|
||||
});
|
||||
|
||||
let stdout_lock = stdout.lock();
|
||||
let renderer = TerminalRenderer::new(screen_memory, stdout_lock);
|
||||
let mut screen = Screen::new(CpuController::new(cpu_tx.clone()), renderer);
|
||||
|
||||
// thread::spawn(move || {
|
||||
// screen.run();
|
||||
// });
|
||||
let mut screen = Screen::new(renderer, cpu_controller);
|
||||
|
||||
loop {
|
||||
sleep(Duration::from_millis(16));
|
||||
screen.draw();
|
||||
if event::poll(std::time::Duration::from_millis(16))? {
|
||||
if let event::Event::Key(key) = event::read()? {
|
||||
keyboard.read_keys(key);
|
||||
if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// let buffer = window_rx.recv().unwrap();
|
||||
screen.draw()
|
||||
// tui.update(&mut terminal)?;
|
||||
}
|
||||
|
||||
execute!(
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use crate::types::{Byte, Word};
|
||||
use anyhow::{bail, Result};
|
||||
use std::cell::RefCell;
|
||||
use std::io::{self, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
use std::u16;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{fs::File, io::Read};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -13,30 +13,25 @@ impl MemHandle {
|
|||
pub fn new(memory: Mem) -> Self {
|
||||
Self(Arc::new(Mutex::new(memory)))
|
||||
}
|
||||
pub fn read(&self, address: Word) -> Byte {
|
||||
let memory = self.lock().unwrap();
|
||||
pub fn read(&self, address: u16) -> u8 {
|
||||
let memory = self.0.lock().unwrap();
|
||||
memory.read(address)
|
||||
}
|
||||
pub fn write(&self, address: Word, data: Byte) {
|
||||
let mut memory = self.lock().unwrap();
|
||||
pub fn write(&self, address: u16, data: u8) {
|
||||
let mut memory = self.0.lock().unwrap();
|
||||
memory.write(address, data);
|
||||
}
|
||||
|
||||
pub fn dump(&self) {
|
||||
let memory = self.lock().unwrap();
|
||||
memory.dump(PathBuf::from_str("./cpu_dump.bin").unwrap());
|
||||
}
|
||||
|
||||
fn lock(&self) -> Result<MutexGuard<'_, Mem>> {
|
||||
match self.0.lock() {
|
||||
Ok(result) => Ok(result),
|
||||
Err(error) => bail!("{error}"),
|
||||
let memory = self.0.lock().unwrap();
|
||||
let _ = memory.dump(PathBuf::from_str("./cpu_dump.bin").unwrap());
|
||||
}
|
||||
pub fn poke(&self, address: u16) {
|
||||
let memory = self.0.lock().unwrap();
|
||||
memory.poke(address)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: impl Iterator for MemHandle so we can get references to the underlying data from other threads without cloning
|
||||
|
||||
pub trait MemoryReader {
|
||||
fn read(&self, address: u16) -> u8;
|
||||
}
|
||||
|
@ -45,19 +40,16 @@ pub trait MemoryWriter {
|
|||
fn write(&self, address: u16, data: u8);
|
||||
}
|
||||
|
||||
// pub trait MemoryAccess: MemoryReader + MemoryWriter {}
|
||||
// impl<T> MemoryAccess for T where T: MemoryReader + MemoryWriter {}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Mem {
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
// This always feels wrong instead of 0xFFFF,
|
||||
// but remember it's the number of elements,
|
||||
// not the maximum index. 0x0000 to 0xFFFF addresses
|
||||
// 0x10000 elements
|
||||
pub struct Mem([u8; 0x10000]);
|
||||
|
||||
impl Default for Mem {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
data: vec![0; u16::MAX as usize + 1],
|
||||
}
|
||||
Self([0; 0x10000])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,23 +59,24 @@ impl Mem {
|
|||
}
|
||||
pub fn dump(&self, path: PathBuf) -> io::Result<()> {
|
||||
let mut outfile = File::create(path)?;
|
||||
outfile.write_all(&self.data)?;
|
||||
outfile.write_all(&self.0)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read(&self, address: Word) -> Byte {
|
||||
self.data[address as usize]
|
||||
pub fn read(&self, address: u16) -> u8 {
|
||||
self.0[address as usize]
|
||||
}
|
||||
|
||||
pub fn write(&mut self, address: Word, data: Byte) {
|
||||
self.data[address as usize] = data;
|
||||
pub fn write(&mut self, address: u16, data: u8) {
|
||||
self.0[address as usize] = data;
|
||||
}
|
||||
|
||||
pub fn load_rom(&mut self, rom: File) -> Result<()> {
|
||||
let bytes = rom.bytes();
|
||||
for (address, byte) in bytes.enumerate() {
|
||||
// println!("{address}");
|
||||
match byte {
|
||||
Ok(value) => self.write(address as Word + 0x8000, value),
|
||||
Ok(value) => self.write(address as u16 + 0x8000, value),
|
||||
Err(_) => {
|
||||
bail!("Loading rom: couldn't write byte {:#04x}", address);
|
||||
}
|
||||
|
@ -91,11 +84,15 @@ impl Mem {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn poke(&self, address: u16) {
|
||||
println!("{:02x}", self.read(address));
|
||||
}
|
||||
//pub fn read_from_bin(&mut self, f: File) -> Result<()> {
|
||||
// let bytes = f.bytes();
|
||||
// for (address, byte) in bytes.enumerate() {
|
||||
// match byte {
|
||||
// Ok(value) => self.write(address as Word, value),
|
||||
// Ok(value) => self.write(address as u16, value),
|
||||
// Err(_) => {
|
||||
// bail!("couldn't write byte {:#04x}", address);
|
||||
// }
|
||||
|
|
|
@ -37,7 +37,7 @@ cleardisplay:
|
|||
; cli
|
||||
|
||||
main:
|
||||
jsr keyboard
|
||||
jsr printtext
|
||||
; key_zero:
|
||||
; stz keyboard_cache, x
|
||||
; dex
|
||||
|
@ -45,164 +45,28 @@ main:
|
|||
; fim:
|
||||
; cli
|
||||
; bra fim
|
||||
; jsr kitty_keys
|
||||
jmp main
|
||||
|
||||
; ; copying @smal rn: https://github.com/smaldragon/KittyEMU/blob/main/roms/foxmon.asm
|
||||
; char_timer = $10
|
||||
; line_buffer = $0200
|
||||
; char_cur = $11
|
||||
; line_buffer_i = $12
|
||||
; line_buffer_l = $13
|
||||
printtext:
|
||||
ldx 0
|
||||
loop:
|
||||
lda text, x
|
||||
beq end
|
||||
sta $6000, x
|
||||
inx
|
||||
bra loop
|
||||
end:
|
||||
rts
|
||||
|
||||
; keyboard_cache = $14
|
||||
; line_cur = $20
|
||||
text:
|
||||
.asciiz "hi <3"
|
||||
|
||||
; irq:
|
||||
|
||||
; lda line_buffer_i
|
||||
; lda $e0
|
||||
; lda $6f
|
||||
; sei
|
||||
; stz char_cur
|
||||
; lda line_buffer_i
|
||||
; sta line_buffer_l
|
||||
; ldx #4
|
||||
|
||||
; kitty_keys: ; reads pressed key and writes keymap value to char_cur
|
||||
; phx
|
||||
; txa
|
||||
; asl
|
||||
; asl
|
||||
; asl
|
||||
; asl
|
||||
; asl ; i think this is supposed to be once for every keyboard row
|
||||
; tax
|
||||
; lda kb_row, x
|
||||
; plx
|
||||
|
||||
; pha
|
||||
; cmp keyboard_cache, x
|
||||
; bne change
|
||||
; jmp nochange
|
||||
; change:
|
||||
; bit7:
|
||||
; asl keyboard_cache, x
|
||||
; bcs bit6
|
||||
; bit #0b10000000
|
||||
; beq bit6
|
||||
; pha
|
||||
; lda keymap_7, x
|
||||
; sta char_cur
|
||||
; pla
|
||||
; bit6:
|
||||
; asl keyboard_cache, x
|
||||
; bcs bit5
|
||||
; bit #0b01000000
|
||||
; beq bit5
|
||||
; pha
|
||||
; lda keymap_6, x
|
||||
; sta char_cur
|
||||
; pla
|
||||
; bit5:
|
||||
; asl keyboard_cache, x
|
||||
; bcs bit4
|
||||
; bit #0b00100000
|
||||
; beq bit4
|
||||
; pha
|
||||
; lda keymap_5, x
|
||||
; sta char_cur
|
||||
; pla
|
||||
; bit4:
|
||||
; asl keyboard_cache, x
|
||||
; bcs bit3
|
||||
; bit #0b00010000
|
||||
; beq bit3
|
||||
; pha
|
||||
; lda keymap_4, x
|
||||
; sta char_cur
|
||||
; pla
|
||||
; bit3:
|
||||
; asl keyboard_cache, x
|
||||
; bcs bit2
|
||||
; bit #0b00001000
|
||||
; beq bit2
|
||||
; pha
|
||||
; lda keymap_3, x
|
||||
; sta char_cur
|
||||
; pla
|
||||
; bit2:
|
||||
; asl keyboard_cache, x
|
||||
; bcs bit1
|
||||
; bit #0b00000100
|
||||
; beq bit1
|
||||
; pha
|
||||
; lda keymap_2, x
|
||||
; sta char_cur
|
||||
; pla
|
||||
; bit1:
|
||||
; asl keyboard_cache, x
|
||||
; bcs bit0
|
||||
; bit #0b00000010
|
||||
; beq bit0
|
||||
; pha
|
||||
; lda keymap_1, x
|
||||
; sta char_cur
|
||||
; pla
|
||||
; bit0:
|
||||
; asl keyboard_cache, x
|
||||
; bcs bitend
|
||||
; bit #0b00000001
|
||||
; beq bitend
|
||||
; pha
|
||||
; lda keymap_0, x
|
||||
; sta char_cur
|
||||
; pla
|
||||
; bitend:
|
||||
; nochange:
|
||||
; pla
|
||||
; sta keyboard_cache, x
|
||||
; dex
|
||||
; bmi keyend
|
||||
; jmp kitty_keys
|
||||
; keyend:
|
||||
|
||||
; write:
|
||||
; lda char_cur
|
||||
; ldy cursor
|
||||
; sta $6000, y
|
||||
; inc cursor
|
||||
; rts
|
||||
|
||||
|
||||
; ; col = keyboard row, row = keyboard bit placement inverted
|
||||
; keymap_0:
|
||||
; .byte "??????"
|
||||
; keymap_1:
|
||||
; .byte "wqdzx "
|
||||
; keymap_2:
|
||||
; .byte "esvfc "
|
||||
; keymap_3:
|
||||
; .byte "rghbn?"
|
||||
; keymap_4:
|
||||
; .byte "tykjm?"
|
||||
; keymap_5:
|
||||
; .byte "ui?l??"
|
||||
; keymap_6:
|
||||
; .byte "op?21?"
|
||||
; keymap_7:
|
||||
; .byte "??a43?"
|
||||
;
|
||||
;
|
||||
keyboard:
|
||||
ldy #0
|
||||
x
|
||||
|
||||
not_keyboard:
|
||||
ldy #0
|
||||
.check_row: ; loop through each row
|
||||
lda kb_row, y
|
||||
beq .skip_row ; if row has no key pressed, skip checking which key
|
||||
; jmp key_down
|
||||
sta kb_row_cache, y ; if key pressed, cache it
|
||||
lda kb_row, y
|
||||
cmp kb_row_cache, y ; has key changed?
|
||||
|
|
Binary file not shown.
|
@ -1,26 +1,16 @@
|
|||
; .setcpu "65C02"
|
||||
.include "./macro.inc"
|
||||
|
||||
; okay so rn i wanna set up a very basic system init, and write a few subroutines to draw characters at x,y coordinates
|
||||
n = $01 ; temporary storage for data stack operations
|
||||
|
||||
key_row = $200 ; used for character lookup when key pressed
|
||||
key_col = $201
|
||||
cursor = $202
|
||||
|
||||
char_buffer = $300 ; 256 byte character buffer
|
||||
|
||||
kb_row = $4400 ; keyboard hardware register, there are 5 more but i can just increment from here
|
||||
kb_row_cache = $203 ; cache
|
||||
|
||||
.org $8000
|
||||
|
||||
n = $01 ; temporary storage for data stack operations
|
||||
|
||||
reset:
|
||||
sei
|
||||
ldx #0; initialize data stack pointer
|
||||
|
||||
initdisplay:
|
||||
lda #$20
|
||||
lda #0
|
||||
ldy #0
|
||||
|
||||
cleardisplay:
|
||||
|
@ -37,56 +27,13 @@ cleardisplay:
|
|||
cli
|
||||
|
||||
main:
|
||||
jsr draw
|
||||
; jmp draw
|
||||
jmp cleardisplay
|
||||
jmp main
|
||||
|
||||
keyboard:
|
||||
ldy #5
|
||||
.loop: ; loop through each row
|
||||
lda kb_row, y
|
||||
bne key_down; if row has key pressed, go to key_down subroutine
|
||||
dey
|
||||
bne .loop
|
||||
rts
|
||||
|
||||
key_down: ; a is loaded with the row byte
|
||||
sty key_row ; store character row
|
||||
inc cursor
|
||||
pha
|
||||
phy
|
||||
ldy #0
|
||||
.find_col: ; test each row bit, store column if key pressed
|
||||
lsr ; test bit 7
|
||||
bcs store_col ; if unset, don't go store character columnb
|
||||
.skip:
|
||||
iny
|
||||
cpy #8
|
||||
bne .find_col ; loop until we've checked each bit
|
||||
|
||||
do_something_w_key: ; we've stored the character position, now let's
|
||||
lda keymap, y
|
||||
ldy cursor
|
||||
sta $6000, y
|
||||
ply
|
||||
pla
|
||||
rts
|
||||
|
||||
keymap_0:
|
||||
.byte "?outrew?"
|
||||
keymap_1:
|
||||
.byte "?piygsq?"
|
||||
keymap_2:
|
||||
.byte "a??khvd?"
|
||||
keymap_3:
|
||||
.byte "42ljbfz?"
|
||||
keymap_4:
|
||||
.byte "31?mncx?"
|
||||
keymap_5:
|
||||
.byte "????? m"
|
||||
|
||||
draw:
|
||||
push_coords #0, #0
|
||||
push_char #$00
|
||||
push_char #$af
|
||||
jsr draw_char
|
||||
rts
|
||||
|
||||
|
@ -125,11 +72,16 @@ get_char_address: ; gets vram address for a character at (x, y),
|
|||
fill: ; fills an area from (x1, y1) to (x2, y2) will character c, (n1: c n2: x1 n3: y1 n4: x2 n5: y2 -- )
|
||||
jsr get_char_address
|
||||
|
||||
irq:
|
||||
|
||||
keyboard:
|
||||
rts
|
||||
|
||||
isr: ; interrupt service routine
|
||||
pha
|
||||
phx
|
||||
phy
|
||||
jsr keyboard
|
||||
jsr irq
|
||||
ply
|
||||
plx
|
||||
pla
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
; .setcpu "65C02"
|
||||
.include "./macro.inc"
|
||||
|
||||
.org $8000
|
||||
|
||||
n = $01 ; temporary storage for data stack operations
|
||||
|
||||
reset:
|
||||
sei
|
||||
ldx #0; initialize data stack pointer
|
||||
|
||||
initdisplay:
|
||||
lda #0
|
||||
ldy #0
|
||||
|
||||
cleardisplay:
|
||||
sta $6000,y
|
||||
sta $6100,y
|
||||
sta $6200,y
|
||||
sta $6300,y
|
||||
sta $6400,y
|
||||
sta $6500,y
|
||||
sta $6600,y
|
||||
sta $6700,y ; this goes slightly over but it's fine
|
||||
iny
|
||||
bne cleardisplay
|
||||
cli
|
||||
|
||||
main:
|
||||
jmp draw
|
||||
jmp main
|
||||
|
||||
draw:
|
||||
push_coords #0, #0
|
||||
push_char #$af
|
||||
jsr draw_char
|
||||
rts
|
||||
|
||||
draw_char: ; draw a character c at (x, y) (n1: x n2: y n3: c -- )
|
||||
lda 0, x ; load a with character to draw
|
||||
pop ; and pop it off the stack
|
||||
jsr get_char_address ; calculate where to put the character in memory
|
||||
sta (0, x) ; store a at the address pointed to on the stack
|
||||
rts
|
||||
|
||||
get_char_address: ; gets vram address for a character at (x, y),
|
||||
; (n1: x n2: y -- n: $6000 + x + (64 * y))
|
||||
;jsr push_lit ; push 64 onto stack, low byte first
|
||||
;.byte 64
|
||||
;.byte 0
|
||||
pha
|
||||
lda #64
|
||||
push ; doing this instead until `push_lit` is fixed
|
||||
sta 0, x
|
||||
stz 1, x
|
||||
jsr mult ; multiply 64 with y (n2)
|
||||
jsr plus ; add result with x (n1)
|
||||
|
||||
;jsr push_lit ; push vram address onto the stack
|
||||
;.byte $00
|
||||
;.byte $60
|
||||
lda #$60
|
||||
push
|
||||
sta 1, x
|
||||
stz 0, x
|
||||
jsr plus ; add vram start address to result
|
||||
|
||||
pla
|
||||
rts
|
||||
|
||||
fill: ; fills an area from (x1, y1) to (x2, y2) will character c, (n1: c n2: x1 n3: y1 n4: x2 n5: y2 -- )
|
||||
jsr get_char_address
|
||||
|
||||
isr: ; interrupt service routine
|
||||
pha
|
||||
phx
|
||||
phy
|
||||
; jsr irq
|
||||
ply
|
||||
plx
|
||||
pla
|
||||
rti
|
||||
|
||||
.include "math.inc"
|
||||
|
||||
.org $fffc
|
||||
.word reset
|
||||
.word isr
|
41
src/video.rs
41
src/video.rs
|
@ -1,21 +1,21 @@
|
|||
use crossterm::{
|
||||
cursor::{MoveTo, SavePosition},
|
||||
cursor::{MoveTo, RestorePosition, SavePosition},
|
||||
execute, queue,
|
||||
style::{Color, PrintStyledContent, Stylize},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
cpu::CpuController,
|
||||
memory::{self, MemHandle, MemoryReader},
|
||||
memory::{MemHandle, MemoryReader},
|
||||
types::{Byte, Word},
|
||||
};
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{Read, Stdout, StdoutLock, Write},
|
||||
io::{Read, StdoutLock, Write},
|
||||
path::Path,
|
||||
process::exit,
|
||||
sync::mpsc::Sender,
|
||||
thread::sleep,
|
||||
time::Duration,
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
const FG_COLOR: u32 = 0xFFCC00;
|
||||
|
@ -73,7 +73,7 @@ impl Renderer for WindowRenderer {
|
|||
let mut buffer = vec![0; 512 * 380];
|
||||
for char_row in 0..29 {
|
||||
for char_col in 0..64 {
|
||||
let ascii = self.memory.read(0x6000 + i);
|
||||
let ascii = self.read(0x6000 + i);
|
||||
i += 1;
|
||||
for row in 0..13 {
|
||||
let byte = self.char_rom[ascii as usize + (row * 0x100)];
|
||||
|
@ -118,7 +118,7 @@ impl MemoryReader for TerminalRenderer<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
static ASCII_LOOPUP: [&str; 256] = [
|
||||
const ASCII_LOOPUP: [&str; 256] = [
|
||||
" ", "░", "▒", "▓", "♡", "♥", "⭐", "✭", "", "✦", "✨", "♀", "♂", "⚢", "⚣", "⚥", "♩", "♪",
|
||||
"♫", "♬", "ﱝ", "", "", "", "奄", "奔", "婢", "ﱜ", "ﱛ", "", "", "", " ", "!", "\"", "#",
|
||||
"$", "%", "&", "\'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6",
|
||||
|
@ -137,11 +137,12 @@ static ASCII_LOOPUP: [&str; 256] = [
|
|||
|
||||
impl Renderer for TerminalRenderer<'_> {
|
||||
fn render(&mut self) {
|
||||
// let now = Instant::now();
|
||||
let _ = execute!(self.stdout, SavePosition);
|
||||
let mut i = 0;
|
||||
for char_row in 0..29 {
|
||||
for char_col in 0..64 {
|
||||
let ascii = self.memory.read(0x6000 + i);
|
||||
let ascii = self.read(0x6000 + i);
|
||||
i += 1;
|
||||
let char = ASCII_LOOPUP[ascii as usize];
|
||||
let _ = queue!(
|
||||
|
@ -165,32 +166,36 @@ impl Renderer for TerminalRenderer<'_> {
|
|||
}
|
||||
}
|
||||
let _ = self.stdout.flush();
|
||||
let _ = execute!(self.stdout, RestorePosition);
|
||||
// let elapsed = now.elapsed();
|
||||
// println!("{elapsed:?}");
|
||||
// exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Screen<'a> {
|
||||
cpu_controller: CpuController,
|
||||
// renderer: Box<dyn Renderer>,
|
||||
renderer: TerminalRenderer<'a>,
|
||||
controller: CpuController,
|
||||
}
|
||||
|
||||
impl<'a> Screen<'a> {
|
||||
// pub fn new(cpu_controller: CpuController, renderer: Box<dyn Renderer>) -> Self {
|
||||
pub fn new(cpu_controller: CpuController, renderer: TerminalRenderer<'a>) -> Self {
|
||||
pub fn new(renderer: TerminalRenderer<'a>, controller: CpuController) -> Self {
|
||||
Self {
|
||||
cpu_controller,
|
||||
renderer,
|
||||
controller,
|
||||
}
|
||||
}
|
||||
pub fn draw(&mut self) {
|
||||
self.controller.irq();
|
||||
self.renderer.render();
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
loop {
|
||||
self.cpu_controller.irq();
|
||||
sleep(Duration::from_millis(16));
|
||||
self.draw();
|
||||
}
|
||||
}
|
||||
// pub fn run(&mut self) {
|
||||
// loop {
|
||||
// // sleep(Duration::from_millis(16));
|
||||
// self.draw();
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue