Added some comments and made some minor changes in preparation for extensions.

This commit is contained in:
JanNeuendorf 2025-02-20 10:58:18 +01:00
parent 920c9f02e6
commit fdea26ae7a
4 changed files with 38 additions and 9 deletions

2
Cargo.lock generated
View File

@ -548,7 +548,7 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "svc16" name = "svc16"
version = "1.0.0-beta2" version = "1.0.0-beta3"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "svc16" name = "svc16"
version = "1.0.0-beta2" version = "1.0.0-beta3"
edition = "2021" edition = "2021"
authors = ["Jan Neuendorf"] authors = ["Jan Neuendorf"]
description = "An emulator for a simple virtual computer" description = "An emulator for a simple virtual computer"

View File

@ -20,11 +20,14 @@ const BAND: u16 = 13;
const XOR: u16 = 14; const XOR: u16 = 14;
const SYNC: u16 = 15; const SYNC: u16 = 15;
// The goal is to eventually stabilize the api for the Engine so it can be easily reused in different emulators.
// This has to be postponed until the first expansions are implemented and tested.
pub struct Engine { pub struct Engine {
memory: Vec<u16>, memory: Vec<u16>,
screen_buffer: Vec<u16>, screen_buffer: Vec<u16>,
utility_buffer: Vec<u16>, utility_buffer: Vec<u16>,
instruction_pointer: u16, instruction_pointer: u16,
//These are the addreses that the input should be written to (as requested by Sync).
pos_code_dest: u16, pos_code_dest: u16,
key_code_dest: u16, key_code_dest: u16,
sync_called: bool, sync_called: bool,
@ -44,6 +47,8 @@ impl Engine {
pub fn new<T>(state: T) -> Self pub fn new<T>(state: T) -> Self
where where
T: IntoIterator<Item = u16>, T: IntoIterator<Item = u16>,
//The iterator can be shorter, in which case the rest of the memory is left as zeros.
//If it is longer, the end is never read.
{ {
let mut iter = state.into_iter(); let mut iter = state.into_iter();
let mut memory = vec![0; MEMSIZE]; let mut memory = vec![0; MEMSIZE];
@ -75,17 +80,30 @@ impl Engine {
self.set(self.pos_code_dest, pos_code); self.set(self.pos_code_dest, pos_code);
self.set(self.key_code_dest, key_code); self.set(self.key_code_dest, key_code);
} }
pub fn perform_sync(&mut self, pos_code: u16, key_code: u16, buffer: &mut Vec<u16>) -> () { pub fn perform_sync(
&mut self,
pos_code: u16,
key_code: u16,
screen_buffer_destination: &mut Vec<u16>,
) -> Option<Vec<u16>> {
self.sync_called = false; self.sync_called = false;
*buffer = self.screen_buffer.clone(); // The clone makes the API easier and doesn't seem to be to expensive in practice.
*screen_buffer_destination = self.screen_buffer.clone();
self.set_input(pos_code, key_code); self.set_input(pos_code, key_code);
// Even if no expansion is active, triggering the mechanism must still clear the utility buffer.
if self.expansion_triggered { if self.expansion_triggered {
self.expansion_triggered = false; self.expansion_triggered = false;
self.utility_buffer = vec![0; MEMSIZE]; return Some(std::mem::replace(
&mut self.utility_buffer,
vec![0; MEMSIZE],
));
} else {
return None;
} }
} }
} }
impl Engine { impl Engine {
// Public for debugging.
pub fn get(&self, index: u16) -> u16 { pub fn get(&self, index: u16) -> u16 {
return self.memory[index as usize]; return self.memory[index as usize];
} }

View File

@ -2,7 +2,7 @@ mod cli;
mod engine; mod engine;
mod ui; mod ui;
mod utils; mod utils;
#[allow(unused)] #[allow(unused)] // Usage depends on Gamepad feature
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use clap::Parser; use clap::Parser;
use cli::Cli; use cli::Cli;
@ -13,10 +13,12 @@ use macroquad::prelude::*;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use ui::Layout; use ui::Layout;
use utils::*; use utils::*;
const MAX_IPF: usize = 3000000; const MAX_IPF: usize = 3000000; // Maximum instruction can be changed here for easier testing.
const FRAMETIME: Duration = Duration::from_nanos((1000000000. / 30.) as u64); const FRAMETIME: Duration = Duration::from_nanos((1000000000. / 30.) as u64);
fn window_conf() -> Conf { fn window_conf() -> Conf {
// Both the scaling and the fullscreen options are only important for the initial launch of the window.
// You can still rescale or exit fullscreen mode.
let cli = Cli::parse(); let cli = Cli::parse();
if cli.fullscreen {} if cli.fullscreen {}
@ -34,15 +36,19 @@ async fn main() -> Result<()> {
let mut cli = Cli::parse(); let mut cli = Cli::parse();
print_keybinds(); print_keybinds();
// This is the raw image data.
let mut buffer = vec![Color::from_rgba(255, 255, 255, 255); 256 * 256]; let mut buffer = vec![Color::from_rgba(255, 255, 255, 255); 256 * 256];
let mut image = Image::gen_image_color(256, 256, Color::from_rgba(0, 0, 0, 255)); let mut image = Image::gen_image_color(256, 256, Color::from_rgba(0, 0, 0, 255));
let texture = Texture2D::from_image(&image); let texture = Texture2D::from_image(&image);
if cli.linear_filtering { if cli.linear_filtering {
texture.set_filter(FilterMode::Linear); texture.set_filter(FilterMode::Linear);
} else { } else {
texture.set_filter(FilterMode::Nearest); texture.set_filter(FilterMode::Nearest);
} }
// This is not the screen-buffer itself. It still needs to be synchronized.
let mut raw_buffer = vec![0 as u16; 256 * 256]; let mut raw_buffer = vec![0 as u16; 256 * 256];
let mut engine = Engine::new(read_u16s_from_file(&cli.program)?); let mut engine = Engine::new(read_u16s_from_file(&cli.program)?);
let mut paused = false; let mut paused = false;
@ -63,6 +69,7 @@ async fn main() -> Result<()> {
paused = !paused; paused = !paused;
} }
if is_key_pressed(KeyCode::R) { if is_key_pressed(KeyCode::R) {
// The current behavior is reloading the file and unpausing.
engine = Engine::new(read_u16s_from_file(&cli.program)?); engine = Engine::new(read_u16s_from_file(&cli.program)?);
paused = false; paused = false;
} }
@ -72,7 +79,9 @@ async fn main() -> Result<()> {
if is_key_pressed(KeyCode::C) { if is_key_pressed(KeyCode::C) {
cli.cursor = !cli.cursor; cli.cursor = !cli.cursor;
} }
// The size of the image in the window depends on the filtering.
// If it is linear, it is as big as it can be.
// If it is nearest, it is the largest possible integer scaling.
let layout = Layout::generate(cli.linear_filtering); let layout = Layout::generate(cli.linear_filtering);
if !paused { if !paused {
ipf = 0; ipf = 0;
@ -107,7 +116,7 @@ async fn main() -> Result<()> {
if layout.cursor_in_window() { if layout.cursor_in_window() {
show_mouse(cli.cursor); show_mouse(cli.cursor);
} else { } else {
show_mouse(true); show_mouse(true); //The cursor is always shown when it is not on the virtual screen.
} }
draw_texture_ex( draw_texture_ex(
@ -122,6 +131,7 @@ async fn main() -> Result<()> {
}, },
); );
if cli.verbose { if cli.verbose {
// Background of the performance metrics.
draw_rectangle( draw_rectangle(
layout.rect_x, layout.rect_x,
layout.rect_y, layout.rect_y,
@ -145,6 +155,7 @@ async fn main() -> Result<()> {
std::thread::sleep(FRAMETIME - elapsed); std::thread::sleep(FRAMETIME - elapsed);
} else { } else {
if cli.verbose { if cli.verbose {
// If you see this, the program is running too slow on your PC.
println!("Frame was not processed in time"); println!("Frame was not processed in time");
} }
} }