mirror of
https://github.com/JanNeuendorf/SVC16.git
synced 2025-06-06 03:25:28 +00:00
Compare commits
9 Commits
v1.0.0-bet
...
main
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a7c600a0b8 | ||
![]() |
7be70269e9 | ||
![]() |
b6086eed8c | ||
![]() |
aff6daa1e0 | ||
![]() |
fea7c69aff | ||
![]() |
fdea26ae7a | ||
![]() |
920c9f02e6 | ||
![]() |
32d83e93d2 | ||
![]() |
ad15d7b8d2 |
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -548,7 +548,7 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "svc16"
|
||||
version = "1.0.0-beta1"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "svc16"
|
||||
version = "1.0.0-beta1"
|
||||
version = "1.0.1"
|
||||
edition = "2021"
|
||||
authors = ["Jan Neuendorf"]
|
||||
description = "An emulator for a simple virtual computer"
|
||||
|
@ -40,7 +40,7 @@ The idea is that you have to build that yourself. You can play a game from the e
|
||||
|
||||
## Quick Overview
|
||||
> [!WARNING]
|
||||
> For a complete description of the system, please download the PDF from the _releases_ section.
|
||||
> For a complete description of the system, please download the PDF from the [releases](https://github.com/JanNeuendorf/SVC16/releases) section.
|
||||
|
||||
|
||||
### No Registers
|
||||
@ -108,7 +108,7 @@ The instructions have the form `opcode arg1 arg2 arg3`.
|
||||
In the following table, all instructions are listed. `@arg1` refers to the value at the memory address `arg1`.
|
||||
|
||||
> [!NOTE]
|
||||
> You can have data blobs in the binary that does not correspond with the opcodes.
|
||||
> You can have data blobs in the binary that do not correspond with the opcodes.
|
||||
> This is fine **until and unless** you explicitly try to run this blob of data as code.
|
||||
|
||||
|
||||
|
BIN
examples/fourinarow.svc16.gz
Normal file
BIN
examples/fourinarow.svc16.gz
Normal file
Binary file not shown.
@ -20,11 +20,14 @@ const BAND: u16 = 13;
|
||||
const XOR: u16 = 14;
|
||||
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 {
|
||||
memory: [u16; MEMSIZE],
|
||||
screen_buffer: [u16; MEMSIZE],
|
||||
utility_buffer: [u16; MEMSIZE],
|
||||
memory: Vec<u16>,
|
||||
screen_buffer: Vec<u16>,
|
||||
utility_buffer: Vec<u16>,
|
||||
instruction_pointer: u16,
|
||||
//These are the addreses that the input should be written to (as requested by Sync).
|
||||
pos_code_dest: u16,
|
||||
key_code_dest: u16,
|
||||
sync_called: bool,
|
||||
@ -44,9 +47,11 @@ impl Engine {
|
||||
pub fn new<T>(state: T) -> Self
|
||||
where
|
||||
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 memory = [0_u16; MEMSIZE];
|
||||
let mut memory = vec![0; MEMSIZE];
|
||||
for i in 0..MEMSIZE {
|
||||
match iter.next() {
|
||||
Some(val) => {
|
||||
@ -59,8 +64,8 @@ impl Engine {
|
||||
}
|
||||
Self {
|
||||
memory,
|
||||
screen_buffer: [0; MEMSIZE],
|
||||
utility_buffer: [0; MEMSIZE],
|
||||
screen_buffer: vec![0; MEMSIZE],
|
||||
utility_buffer: vec![0; MEMSIZE],
|
||||
instruction_pointer: 0,
|
||||
pos_code_dest: 0,
|
||||
key_code_dest: 0,
|
||||
@ -79,18 +84,28 @@ impl Engine {
|
||||
&mut self,
|
||||
pos_code: u16,
|
||||
key_code: u16,
|
||||
buffer: &mut [u16; MEMSIZE],
|
||||
) -> () {
|
||||
self.sync_called = false;
|
||||
*buffer = self.screen_buffer;
|
||||
self.set_input(pos_code, key_code);
|
||||
screen_buffer_destination: &mut Vec<u16>,
|
||||
) -> Option<Vec<u16>> {
|
||||
// The clone makes the API easier and doesn't seem to be to expensive in practice.
|
||||
*screen_buffer_destination = self.screen_buffer.clone();
|
||||
if self.sync_called {
|
||||
self.sync_called = false;
|
||||
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 {
|
||||
self.expansion_triggered = false;
|
||||
self.utility_buffer = [0; MEMSIZE];
|
||||
return Some(std::mem::replace(
|
||||
&mut self.utility_buffer,
|
||||
vec![0; MEMSIZE],
|
||||
));
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Engine {
|
||||
// Public for debugging.
|
||||
pub fn get(&self, index: u16) -> u16 {
|
||||
return self.memory[index as usize];
|
||||
}
|
||||
|
23
src/main.rs
23
src/main.rs
@ -2,7 +2,7 @@ mod cli;
|
||||
mod engine;
|
||||
mod ui;
|
||||
mod utils;
|
||||
#[allow(unused)]
|
||||
#[allow(unused)] // Usage depends on Gamepad feature
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use clap::Parser;
|
||||
use cli::Cli;
|
||||
@ -13,10 +13,12 @@ use macroquad::prelude::*;
|
||||
use std::time::{Duration, Instant};
|
||||
use ui::Layout;
|
||||
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);
|
||||
|
||||
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();
|
||||
if cli.fullscreen {}
|
||||
|
||||
@ -34,16 +36,20 @@ async fn main() -> Result<()> {
|
||||
let mut cli = Cli::parse();
|
||||
print_keybinds();
|
||||
|
||||
let mut buffer = [Color::from_rgba(255, 255, 255, 255); 256 * 256];
|
||||
// This is the raw image data.
|
||||
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 texture = Texture2D::from_image(&image);
|
||||
|
||||
if cli.linear_filtering {
|
||||
texture.set_filter(FilterMode::Linear);
|
||||
} else {
|
||||
texture.set_filter(FilterMode::Nearest);
|
||||
}
|
||||
|
||||
let mut raw_buffer = [0 as u16; 256 * 256];
|
||||
// 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 engine = Engine::new(read_u16s_from_file(&cli.program)?);
|
||||
let mut paused = false;
|
||||
let mut ipf = 0;
|
||||
@ -63,6 +69,7 @@ async fn main() -> Result<()> {
|
||||
paused = !paused;
|
||||
}
|
||||
if is_key_pressed(KeyCode::R) {
|
||||
// The current behavior is reloading the file and unpausing.
|
||||
engine = Engine::new(read_u16s_from_file(&cli.program)?);
|
||||
paused = false;
|
||||
}
|
||||
@ -72,7 +79,9 @@ async fn main() -> Result<()> {
|
||||
if is_key_pressed(KeyCode::C) {
|
||||
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);
|
||||
if !paused {
|
||||
ipf = 0;
|
||||
@ -107,7 +116,7 @@ async fn main() -> Result<()> {
|
||||
if layout.cursor_in_window() {
|
||||
show_mouse(cli.cursor);
|
||||
} else {
|
||||
show_mouse(true);
|
||||
show_mouse(true); //The cursor is always shown when it is not on the virtual screen.
|
||||
}
|
||||
|
||||
draw_texture_ex(
|
||||
@ -122,6 +131,7 @@ async fn main() -> Result<()> {
|
||||
},
|
||||
);
|
||||
if cli.verbose {
|
||||
// Background of the performance metrics.
|
||||
draw_rectangle(
|
||||
layout.rect_x,
|
||||
layout.rect_y,
|
||||
@ -145,6 +155,7 @@ async fn main() -> Result<()> {
|
||||
std::thread::sleep(FRAMETIME - elapsed);
|
||||
} else {
|
||||
if cli.verbose {
|
||||
// If you see this, the program is running too slow on your PC.
|
||||
println!("Frame was not processed in time");
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,11 @@ impl Layout {
|
||||
let (raw_x, raw_y) = mouse_position();
|
||||
let clamped_x = (raw_x.clamp(self.x, self.x + self.size) - self.x) / self.size * 255.;
|
||||
let clamped_y = (raw_y.clamp(self.y, self.y + self.size) - self.y) / self.size * 255.;
|
||||
(clamped_x, clamped_y)
|
||||
// The mouse position is slightly modified so that the maximal position can be reached when the image takes up the entire window.
|
||||
(
|
||||
(clamped_x * 255. / 254.).min(255.),
|
||||
(clamped_y * 255. / 254.).min(255.),
|
||||
)
|
||||
}
|
||||
pub fn cursor_in_window(&self) -> bool {
|
||||
let mp = mouse_position();
|
||||
|
@ -40,7 +40,7 @@ fn rgb565_to_argb(rgb565: u16) -> (u8, u8, u8) {
|
||||
(r, g, b)
|
||||
}
|
||||
|
||||
pub fn update_image_buffer(imbuff: &mut [Color; RES * RES], screen: &[u16; RES * RES]) {
|
||||
pub fn update_image_buffer(imbuff: &mut Vec<Color>, screen: &Vec<u16>) {
|
||||
for i in 0..RES * RES {
|
||||
let col = rgb565_to_argb(screen[i]);
|
||||
imbuff[i] = Color {
|
||||
|
Loading…
x
Reference in New Issue
Block a user