diff options
author | Ben Bridle <ben@derelict.engineering> | 2025-03-25 12:46:49 +1300 |
---|---|---|
committer | Ben Bridle <ben@derelict.engineering> | 2025-03-25 12:48:49 +1300 |
commit | ab84ad75629b0a4124221023ca91411d2cd62a32 (patch) | |
tree | 0c333f06bec5270084aaec71cf173c798420207b /src | |
parent | 07ae3438917fd854a46924a410f6890cd0651f1b (diff) | |
download | bedrock-pc-ab84ad75629b0a4124221023ca91411d2cd62a32.zip |
Restructure program
This commit also includes changes to devices according to the latest
devices specification, in particular the math and system devices.
Diffstat (limited to 'src')
37 files changed, 2235 insertions, 1875 deletions
diff --git a/src/bin/br.rs b/src/bin/br.rs deleted file mode 100644 index d7bde60..0000000 --- a/src/bin/br.rs +++ /dev/null @@ -1,461 +0,0 @@ -use bedrock_asm::*; -use bedrock_pc::*; -use phosphor::*; - -use log::{info, fatal}; -use switchboard::{Switchboard, SwitchQuery}; - -use std::cmp::max; -use std::io::{Read, Write}; -use std::path::{Path, PathBuf}; -use std::process::exit; - - -fn print_help() -> ! { - println!("\ -Usage: br [source] - br asm [source] [destination] [extension] - -Integrated Bedrock assembler and emulator. - -Usage: - To load a Bedrock program from a file, run `br <path>`, where - <path> is the path to an assembled Bedrock program. - - To load a Bedrock program from piped input, run `<command> | br`, - where <command> is a command that generates Bedrock bytecode. - - To assemble a Bedrock program from a source file and write to an - output file, run `br asm <source> <output>`, where <source> is the - path of the source file and <output> is the path to write to. - - To assemble and run a Bedrock program without saving to a file, - run `br asm <source> | br`, where <source> is the path of the - source file. - -Environment variables: - BEDROCK_PATH - A list of colon-separated paths which will be searched to find - a Bedrock program when the program doesn't exist in the current - directory. This allows the user to run frequently-used programs - from any directory. - BEDROCK_LIBS - A list of colon-separated paths which will be searched to find - Bedrock source code files to use as libraries when assembling a - Bedrock program. If a library file resolves an unresolved symbol - in the program being assembled, the library file will be merged - into the program. - -Arguments: - [program] Path to a Bedrock program to run - -Switches: - --verbose, (-v) Print additional debug information - --version Print the assembler version and exit - --help (-h) Prints help - --debug, (-d) Show debug information while the program is running - --fullscreen (-f) Start the program in fullscreen mode (toggle with F11) - --size=<dim> (-s) Set the initial window size in the format <width>x<height> - --zoom=<scale> (-z) Set the pixel size for the screen (change with F5/F6) - --palette=<pal> Set a debug colour palette in the format <rgb>,... (toggle with F2) - --show-cursor (-c) Show the operating system cursor over the window - --decode-stdin (-i) Decode standard input - --encode-stdout (-o) Encode standard output - -Arguments (asm mode): - [source] Bedrock source code file to assemble. - [destination] Destination path for assembler output. - [extension] File extension to identify source files. - -Switches (asm mode): - --no-libs Don't include libraries or resolve references. - --no-project-libs Don't include project libraries - --no-env-libs Don't include environment libraries. - --tree Show the resolved source file heirarchy - --check Assemble the program without saving any output - --resolve Only return resolved source code. - --symbols Generate debug symbols with file extension '.br.sym' -"); - std::process::exit(0); -} - - -fn print_version() -> ! { - let name = env!("CARGO_PKG_NAME"); - let version = env!("CARGO_PKG_VERSION"); - eprintln!("{name} v{version}"); - eprintln!("Written by Ben Bridle."); - std::process::exit(0); -} - - -fn main() { - let mut args = Switchboard::from_env(); - if args.named("help").short('h').as_bool() { - print_help(); - } - if args.named("version").as_bool() { - print_version(); - } - if args.named("verbose").short('v').as_bool() { - log::set_log_level(log::LogLevel::Info); - } - match args.peek() { - Some("run") => { args.pop(); main_run(args) }, - Some("asm") => { args.pop(); main_asm(args) }, - _ => main_run(args), - } -} - -fn main_run(mut args: Switchboard) { - let source = args.positional("source").as_path_opt(); - let debug = args.named("debug").short('d').as_bool(); - let fullscreen = args.named("fullscreen").short('f').as_bool(); - let scale = max(1, args.named("zoom").short('z').default("1").quick("3").as_u32()); - let dimensions = match args.named("size").short('s').as_string_opt() { - Some(string) => parse_dimensions(&string).unwrap_or_else(|| - fatal!("Invalid dimensions string {string:?}")), - None => DEFAULT_SCREEN_SIZE / (scale as u16), - }; - let debug_palette = match args.named("palette").as_string_opt() { - Some(string) => match parse_palette(&string) { - Some(palette) => Some(palette), - None => fatal!("Invalid palette string {string:?}"), - }, - None => None, - }; - let show_cursor = args.named("show-cursor").short('c').as_bool(); - let decode_stdin = args.named("decode-stdin").short('i').as_bool(); - let encode_stdout = args.named("encode-stdout").short('o').as_bool(); - - let Bytecode { bytes: bytecode, path } = load_bytecode(source.as_ref()); - let symbols_path = path.as_ref().map(|p| { - let mut path = p.to_path_buf(); - path.set_extension("br.sym"); - path - }); - - let metadata = parse_metadata(&bytecode); - if metadata.is_none() { - info!("Could not read program metadata"); - } - - let config = EmulatorConfig { - dimensions, - fullscreen, - scale, - debug_palette, - show_cursor, - initial_transmission: None, - decode_stdin, - encode_stdout, - symbols_path, - }; - let phosphor = Phosphor::new(); - - if phosphor.is_ok() && dimensions.area_usize() != 0 { - info!("Starting graphical emulator"); - let mut phosphor = phosphor.unwrap(); - let cursor = match config.show_cursor { - true => Some(CursorIcon::Default), - false => None, - }; - - let mut graphical = GraphicalEmulator::new(&config, debug); - graphical.load_program(&bytecode); - if let EmulatorSignal::Promote = graphical.run() { - let program_name = match &metadata { - Some(metadata) => match &metadata.name { - Some(name) => name.to_string(), - None => String::from("Bedrock"), - } - None => String::from("Bedrock"), - }; - let window = WindowBuilder { - dimensions: Some(graphical.dimensions()), - size_bounds: Some(graphical.size_bounds()), - fullscreen: graphical.fullscreen, - scale: graphical.scale, - title: Some(program_name), - cursor, - icon: None, - program: Box::new(graphical), - }; - - phosphor.create_window(window); - phosphor.run().unwrap(); - } - } else { - info!("Starting headless emulator"); - let mut headless = HeadlessEmulator::new(&config, debug); - headless.load_program(&bytecode); - headless.run(debug); - }; - - std::process::exit(0); -} - -fn load_bytecode(path: Option<&PathBuf>) -> Bytecode { - // TODO: Etch file location into bytecode as per metadata. - if let Some(path) = path { - if let Ok(bytecode) = load_bytecode_from_file(path) { - let length = bytecode.bytes.len(); - let path = bytecode.path(); - info!("Loaded program from {path:?} ({length} bytes)"); - return bytecode; - } else if let Some(bytecode) = load_bytecode_from_bedrock_path(path) { - let length = bytecode.bytes.len(); - let path = bytecode.path(); - info!("Loaded program from {path:?} ({length} bytes)"); - return bytecode; - } else { - fatal!("Could not read program from {path:?}"); - } - } else { - info!("Reading program from standard input..."); - if let Ok(bytecode) = load_bytecode_from_stdin() { - let length = bytecode.bytes.len(); - info!("Loaded program from standard input ({length} bytes)"); - return bytecode; - } else { - fatal!("Could not read program from standard input"); - } - } -} - -/// Attempt to load bytecode from a directory in the BEDROCK_PATH environment variable. -fn load_bytecode_from_bedrock_path(path: &Path) -> Option<Bytecode> { - if path.is_relative() && path.components().count() == 1 { - for base_path in std::env::var("BEDROCK_PATH").ok()?.split(':') { - let mut base_path = PathBuf::from(base_path); - if !base_path.is_absolute() { continue; } - base_path.push(path); - info!("Attempting to load program from {base_path:?}"); - if let Ok(bytecode) = load_bytecode_from_file(&base_path) { - return Some(bytecode); - } - if path.extension().is_some() { continue; } - base_path.set_extension("br"); - info!("Attempting to load program from {base_path:?}"); - if let Ok(bytecode) = load_bytecode_from_file(&base_path) { - return Some(bytecode); - } - } - } - return None; -} - -/// Attempt to load bytecode from a file path. -fn load_bytecode_from_file(path: &Path) -> Result<Bytecode, std::io::Error> { - // Canonicalize paths so that symbolic links to program files resolve to - // the real program directory, which could contain a symbols file. - let path = match path.canonicalize() { - Ok(canonical) => canonical, - Err(_) => path.to_path_buf(), - }; - load_bytecode_from_readable_source(std::fs::File::open(&path)?, Some(&path)) -} - -/// Attempt to load bytecode from standard input. -fn load_bytecode_from_stdin() -> Result<Bytecode, std::io::Error> { - load_bytecode_from_readable_source(std::io::stdin(), None) -} - -/// Attempt to load bytecode from a source that implements std::io::Read. -fn load_bytecode_from_readable_source(source: impl Read, path: Option<&Path>) -> Result<Bytecode, std::io::Error> { - let mut bytes = Vec::<u8>::new(); - source.take(65536).read_to_end(&mut bytes)?; - return Ok(Bytecode { bytes, path: path.map(|p| p.to_path_buf()) }); -} - -struct Bytecode { - bytes: Vec<u8>, - path: Option<PathBuf>, -} - -impl Bytecode { - fn path(&self) -> String { - match &self.path { - Some(path) => path.as_os_str().to_string_lossy().to_string(), - None => String::from("<unknown>"), - } - } -} - - -fn main_asm(mut args: Switchboard) { - let source = args.positional("source").as_path_opt(); - let destination = args.positional("source").as_path_opt(); - let extension = args.positional("extension").default("brc").as_string(); - - let no_libs = args.named("no-libs").as_bool(); - let no_project_libs = args.named("no-project-libs").as_bool(); - let no_environment_libs = args.named("no-env-libs").as_bool(); - - let print_tree = args.named("tree").as_bool(); - let dry_run = args.named("check").as_bool(); - let only_resolve = args.named("resolve").as_bool(); - let export_symbols = args.named("symbols").as_bool(); - - // ----------------------------------------------------------------------- - // RESOLVE syntactic symbols - let source_path = source.clone().map(|p| { - p.canonicalize().unwrap_or(p) - }); - - let mut resolver = if let Some(path) = &source_path { - match SourceUnit::from_path(&path, &extension) { - Ok(source_unit) => SymbolResolver::from_source_unit(source_unit), - Err(err) => { - match err { - ParseError::InvalidExtension => fatal!( - "File {path:?} has invalid extension, must be '.{extension}'"), - ParseError::NotFound => fatal!( - "File {path:?} was not found"), - ParseError::InvalidUtf8 => fatal!( - "File {path:?} does not contain valid UTF-8 text"), - ParseError::NotReadable => fatal!( - "File {path:?} is not readable"), - ParseError::IsADirectory => fatal!( - "File {path:?} is a directory"), - ParseError::Unknown => fatal!( - "Unknown error while attempting to read from {path:?}") - }; - } - } - } else { - let mut source_code = String::new(); - info!("Reading program source from standard input"); - if let Err(err) = std::io::stdin().read_to_string(&mut source_code) { - fatal!("Could not read from standard input\n{err:?}"); - } - let path = "<standard input>"; - let source_unit = SourceUnit::from_source_code(source_code, path); - SymbolResolver::from_source_unit(source_unit) - }; - // Load project libraries. - if let Some(path) = &source_path { - if !no_libs && !no_project_libs { - let project_library = gather_project_libraries(path, &extension); - resolver.add_library_units(project_library); - } - } - // Load environment libraries. - if !no_libs && !no_environment_libs { - for env_library in gather_environment_libraries(&extension) { - resolver.add_library_units(env_library); - } - } - resolver.resolve(); - - // ----------------------------------------------------------------------- - // PRINT information, generate merged source code - if print_tree { - print_source_tree(&resolver); - } - if print_resolver_errors(&resolver) { - std::process::exit(1); - }; - let merged_source = match resolver.get_merged_source_code() { - Ok(merged_source) => merged_source, - Err(ids) => { - print_cyclic_source_units(&ids, &resolver); - std::process::exit(1); - }, - }; - if only_resolve && !dry_run { - write_bytes_and_exit(merged_source.as_bytes(), destination.as_ref()); - } - - // ----------------------------------------------------------------------- - // PARSE semantic tokens from merged source code - let path = Some("<merged source>"); - let mut semantic_tokens = generate_semantic_tokens(&merged_source, path); - if print_semantic_errors(&semantic_tokens, &merged_source) { - std::process::exit(1); - }; - - // ----------------------------------------------------------------------- - // GENERATE symbols file and bytecode - let bytecode = generate_bytecode(&mut semantic_tokens); - - if export_symbols && !dry_run { - if let Some(path) = &destination { - let mut symbols_path = path.to_path_buf(); - symbols_path.set_extension("br.sym"); - let symbols = generate_symbols_file(&semantic_tokens); - if let Err(err) = std::fs::write(&symbols_path, symbols) { - info!("Could not write to symbols path {symbols_path:?}"); - eprintln!("{err:?}"); - } else { - info!("Saved debug symbols to {symbols_path:?}"); - } - } - } - - let length = bytecode.len(); - let percentage = (length as f32 / 65536.0 * 100.0).round() as u16; - info!("Assembled program in {length} bytes ({percentage}% of maximum)"); - - if !dry_run { - write_bytes_and_exit(&bytecode, destination.as_ref()); - } -} - -fn write_bytes_and_exit<P: AsRef<Path>>(bytes: &[u8], path: Option<&P>) -> ! { - if let Some(path) = path { - if let Err(err) = std::fs::write(path, bytes) { - fatal!("Could not write to path {:?}\n{err:?}", path.as_ref()); - } - } else { - if let Err(err) = std::io::stdout().write_all(bytes) { - fatal!("Could not write to standard output\n{err:?}"); - } - } - exit(0); -} - - -fn parse_dimensions(string: &str) -> Option<ScreenDimensions> { - if string.trim().to_lowercase() == "none" { - return Some(ScreenDimensions::ZERO); - } - let (w_str, h_str) = string.trim().split_once('x')?; - Some( ScreenDimensions { - width: w_str.parse().ok()?, - height: h_str.parse().ok()?, - } ) -} - -fn parse_palette(string: &str) -> Option<[Colour; 16]> { - fn decode_ascii_hex_digit(ascii: u8) -> Option<u8> { - match ascii { - b'0'..=b'9' => Some(ascii - b'0'), - b'a'..=b'f' => Some(ascii - b'a' + 10), - b'A'..=b'F' => Some(ascii - b'A' + 10), - _ => { None } - } - } - let mut c = Vec::new(); - for token in string.split(',') { - let mut bytes = token.bytes(); - if bytes.len() != 3 { return None; } - let r = decode_ascii_hex_digit(bytes.next().unwrap())?; - let g = decode_ascii_hex_digit(bytes.next().unwrap())?; - let b = decode_ascii_hex_digit(bytes.next().unwrap())?; - c.push(Colour::from_rgb(r*17, g*17, b*17)); - } - Some(match c.len() { - 2 => [ c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1], - c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1] ], - 3 => [ c[0], c[1], c[0], c[2], c[0], c[1], c[0], c[2], - c[0], c[1], c[0], c[2], c[0], c[1], c[0], c[2] ], - 4 => [ c[0], c[1], c[2], c[3], c[0], c[1], c[2], c[3], - c[0], c[1], c[2], c[3], c[0], c[1], c[2], c[3] ], - 8 => [ c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], - c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7] ], - 16 => [ c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], - c[8], c[9], c[10], c[11], c[12], c[13], c[14], c[15] ], - _ => return None, - }) -} diff --git a/src/bin/br/asm.rs b/src/bin/br/asm.rs new file mode 100644 index 0000000..f4620cf --- /dev/null +++ b/src/bin/br/asm.rs @@ -0,0 +1,152 @@ +use crate::formats::*; + +use bedrock_asm::*; +use log::{info, fatal}; +use switchboard::*; + +use std::io::{Read, Write}; +use std::path::Path; + + +pub fn main(mut args: Switchboard) { + args.positional("source"); + args.positional("destination"); + args.named("extension").default("brc"); + + args.named("no-libs"); + args.named("no-project-libs"); + args.named("no-env-libs"); + + args.named("format").default("binary"); + args.named("dry-run").short('n'); + args.named("tree"); + args.named("with-symbols"); + args.raise_errors(); + + let source_path = args.get("source").as_path_opt().map( + |p| p.canonicalize().unwrap_or_else(|e| fatal!("{p:?}: {e:?}"))); + let destination_path = args.get("destination").as_path_opt(); + let extension = args.get("extension").as_string(); + let opt_extension = Some(extension.as_str()); + + let no_libs = args.get("no-libs").as_bool(); + let no_project_libs = args.get("no-project-libs").as_bool(); + let no_env_libs = args.get("no-env-libs").as_bool(); + + let format = Format::from_str(args.get("format").as_str()); + let dry_run = args.get("dry-run").as_bool(); + let print_tree = args.get("tree").as_bool(); + let export_symbols = args.get("with-symbols").as_bool(); + + // ----------------------------------------------------------------------- + + let mut compiler = new_compiler(); + + if let Some(path) = &source_path { + info!("Reading program source from {path:?}"); + compiler.root_from_path(path).unwrap_or_else(|err| fatal!("{err:?}: {path:?}")); + } else { + let mut source_code = String::new(); + info!("Reading program source from standard input"); + if let Err(err) = std::io::stdin().read_to_string(&mut source_code) { + fatal!("Could not read from standard input\n{err:?}"); + } + compiler.root_from_string(source_code, "<standard input>") + }; + if compiler.error().is_some() && !no_libs && !no_project_libs { + compiler.include_libs_from_parent(opt_extension); + } + if compiler.error().is_some() && !no_libs && !no_env_libs { + compiler.include_libs_from_path_variable("BEDROCK_LIBS", opt_extension); + } + + if print_tree { + compiler.hierarchy().report() + } + if let Some(error) = compiler.error() { + error.report(); + std::process::exit(1); + } + + let merged_source = compiler.get_compiled_source().unwrap_or_else(|error| { + error.report(); + std::process::exit(1); + }); + + if !dry_run && format == Format::Source { + write_bytes_and_exit(merged_source.as_bytes(), destination_path.as_ref()); + } + + // ----------------------------------------------------------------------- + + let path = Some("<merged source>"); + let syntactic = match parse_syntactic(&merged_source, path) { + Ok(tokens) => tokens, + Err(errors) => { + report_syntactic_errors(&errors, &merged_source); + std::process::exit(1); + } + }; + + let semantic = match parse_semantic(syntactic) { + Ok(tokens) => tokens, + Err(errors) => { + report_semantic_errors(&errors, &merged_source); + std::process::exit(1); + } + }; + + let AssembledProgram { mut bytecode, symbols } = generate_bytecode(&semantic); + // Remove null bytes from end of bytecode. + while let Some(0) = bytecode.last() { + bytecode.pop(); + } + + let length = bytecode.len(); + let percentage = (length as f32 / 65536.0 * 100.0).round() as u16; + info!("Assembled program in {length} bytes ({percentage}% of maximum)"); + + if !dry_run { + if export_symbols { + if let Some(path) = &destination_path { + let mut symbols_path = path.to_path_buf(); + symbols_path.add_extension("sym"); + let mut symbols_string = String::new(); + for symbol in &symbols { + let address = &symbol.address; + let name = &symbol.name; + let location = &symbol.source.location(); + symbols_string.push_str(&format!( + "{address:04x} {name} {location}\n" + )); + } + match std::fs::write(&symbols_path, symbols_string) { + Ok(_) => info!("Saved symbols to {symbols_path:?}"), + Err(err) => info!("Could not write symbols to {symbols_path:?}\n{err:?}"), + } + } + } + + let bytes = match format { + Format::Binary => bytecode, + Format::Clang => format_clang(&bytecode), + Format::Source => unreachable!("Source output is handled before full assembly"), + }; + write_bytes_and_exit(&bytes, destination_path.as_ref()); + } +} + + +fn write_bytes_and_exit<P: AsRef<Path>>(bytes: &[u8], path: Option<&P>) -> ! { + match path { + Some(path) => match std::fs::write(path, bytes) { + Ok(_) => info!("Wrote output to {:?}", path.as_ref()), + Err(err) => fatal!("Could not write to {:?}\n{err:?}", path.as_ref()), + } + None => match std::io::stdout().write_all(bytes) { + Ok(_) => info!("Wrote output to standard output"), + Err(err) => fatal!("Could not write to standard output\n{err:?}"), + } + } + std::process::exit(0); +} diff --git a/src/bin/br/formats/clang.rs b/src/bin/br/formats/clang.rs new file mode 100644 index 0000000..30d80de --- /dev/null +++ b/src/bin/br/formats/clang.rs @@ -0,0 +1,10 @@ +pub fn format_clang(bytecode: &[u8]) -> Vec<u8> { + let mut output = String::new(); + for chunk in bytecode.chunks(16) { + for byte in chunk { + output.push_str(&format!("0x{byte:02x}, ")); + } + output.push('\n'); + } + return output.into_bytes(); +} diff --git a/src/bin/br/formats/mod.rs b/src/bin/br/formats/mod.rs new file mode 100644 index 0000000..b159b16 --- /dev/null +++ b/src/bin/br/formats/mod.rs @@ -0,0 +1,24 @@ +mod clang; + +pub use clang::*; + +use log::fatal; + + +#[derive(Clone, Copy, PartialEq)] +pub enum Format { + Binary, + Source, + Clang, +} + +impl Format { + pub fn from_str(string: &str) -> Self { + match string { + "binary" => Self::Binary, + "source" => Self::Source, + "c" => Self::Clang, + _ => fatal!("Unknown format '{string}', expected 'binary', 'c', or 'source'"), + } + } +} diff --git a/src/bin/br/main.rs b/src/bin/br/main.rs new file mode 100644 index 0000000..52c53b0 --- /dev/null +++ b/src/bin/br/main.rs @@ -0,0 +1,111 @@ +#![feature(path_add_extension)] + +mod asm; +mod run; +mod formats; + +use switchboard::*; + + +fn main() { + let mut args = Switchboard::from_env(); + args.named("help").short('h'); + args.named("version"); + args.named("verbose").short('v'); + + if args.get("help").as_bool() { + print_help(); + } + if args.get("version").as_bool() { + print_version(); + } + if args.get("verbose").as_bool() { + log::set_log_level(log::LogLevel::Info); + } + match args.peek() { + Some("run") => { args.pop(); run::main(args) }, + Some("asm") => { args.pop(); asm::main(args) }, + _ => run::main(args), + } +} + + +fn print_help() -> ! { + eprintln!("\ +Usage: br [source] + br asm [source] [destination] + +Integrated Bedrock assembler and emulator. + +Usage: + To load a Bedrock program from a file, run `br <path>`, where + <path> is the path to an assembled Bedrock program. + + To load a Bedrock program from piped input, run `<command> | br`, + where <command> is a command that generates Bedrock bytecode. + + To assemble a Bedrock program from a source file and write to an + output file, run `br asm <source> <output>`, where <source> is the + path of the source file and <output> is the path to write to. + + To assemble and run a Bedrock program without saving to a file, + run `br asm <source> | br`, where <source> is the path of the + source file. + +Environment variables: + BEDROCK_PATH + A list of colon-separated paths which will be searched to find + a Bedrock program when the program doesn't exist in the current + directory. This allows the user to run frequently-used programs + from any directory. + BEDROCK_LIBS + A list of colon-separated paths which will be searched to find + Bedrock source code files to use as libraries when assembling a + Bedrock program. If a library file resolves an unresolved symbol + in the program being assembled, the library file will be merged + into the program. + +Arguments: + [program] Path to a Bedrock program to run + +Switches: + --fullscreen (-f) Start the program in fullscreen mode (toggle with F11) + --size=<dim> (-s) Set the initial window size in the format <width>x<height> + --zoom=<scale> (-z) Set the pixel size for the screen (change with F5/F6) + --show-cursor (-c) Show the operating system cursor over the window + --palette=<pal> Set a debug colour palette in the format <rgb>,... (toggle with F2) + --debug, (-d) Show debug information while the program is running + --decode-stdin (-i) Decode transmissions on standard input from text lines. + --encode-stdout (-o) Encode transmissions on standard output as text lines. + --help (-h) Print this help information + --verbose, (-v) Print additional information + --version Print the program version and exit + +Arguments (asm mode): + [source] Bedrock source code file to assemble. + [destination] Destination path for assembler output. + +Switches (asm mode): + --dry-run (-n) Assemble and show errors only, don't write any output + --extension File extension to identify source files (default is 'brc') + --format=<fmt> Output format to use for assembled program (default is 'binary') + --no-project-libs Don't search for libraries in the source parent folder + --no-env-libs Don't search for libraries in the BEDROCK_LIBS path variable + --no-libs Combination of --no-project-libs and --no-env-libs + --tree Display a tree visualisation of all included library files + --with-symbols Also generate debug symbols file with extension '.sym' + --help (-h) Print this help information + --verbose, (-v) Print additional information + --version Print the program version and exit +"); + std::process::exit(0); +} + + +fn print_version() -> ! { + let name = env!("CARGO_PKG_NAME"); + let version = env!("CARGO_PKG_VERSION"); + eprintln!("{name} v{version}"); + eprintln!("Written by Ben Bridle."); + std::process::exit(0); +} diff --git a/src/bin/br/run.rs b/src/bin/br/run.rs new file mode 100644 index 0000000..86fdd43 --- /dev/null +++ b/src/bin/br/run.rs @@ -0,0 +1,186 @@ +use log::*; +use crate::*; + +use bedrock_pc::*; +use phosphor::*; + +use std::cmp::{min, max}; +use std::num::NonZeroU32; + + +pub fn main(mut args: Switchboard) { + args.positional("source"); + + args.named("debug").short('d'); + args.named("mode").default("dynamic"); + + args.named("palette"); + args.named("fullscreen").short('f'); + args.named("show-cursor").short('c'); + args.named("zoom").short('z').quick("3").default("1"); + args.named("size").short('s'); + + args.named("decode-stdin").short('i'); + args.named("encode-stdout").short('o'); + args.raise_errors(); + + let source = args.get("source").as_path_opt(); + let debug = args.get("debug").as_bool(); + let mode = Mode::from_str(args.get("mode").as_str()); + let override_palette = args.get("palette").as_str_opt().map(parse_palette); + let fullscreen = args.get("fullscreen").as_bool(); + let show_cursor = args.get("show-cursor").as_bool(); + let zoom_raw = min(10, max(1, args.get("zoom").as_u32())); + let zoom = unsafe { NonZeroU32::new_unchecked(zoom_raw) }; + let initial_dimensions = match args.get("size").as_str_opt() { + Some(string) => parse_dimensions(string), + None => DEFAULT_SCREEN_SIZE / (zoom_raw as u16), + }; + let decode_stdin = args.get("decode-stdin").as_bool(); + let encode_stdout = args.get("encode-stdout").as_bool(); + + // ----------------------------------------------------------------------- + + let Program { bytecode, path } = load_program(source.as_ref()); + + let symbols_path = path.as_ref().map(|p| { + let mut path = p.to_path_buf(); + path.add_extension("sym"); + path + }); + + let metadata = parse_metadata(&bytecode); + if metadata.is_none() { + info!("Could not read program metadata"); + } + + let config = EmulatorConfig { + initial_dimensions, + fullscreen, + zoom, + override_palette, + show_cursor, + + decode_stdin, + encode_stdout, + + symbols_path, + metadata, + }; + + if let Ok(phosphor) = Phosphor::new() { + match mode { + Mode::Dynamic => { + info!("Starting dynamic emulator"); + let mut emulator = DynamicEmulator::new(&config, debug); + emulator.load_program(&bytecode); + emulator.run(); + info!("Upgrading dynamic emulator to graphical emulator"); + let emulator = emulator.to_graphical(config); + emulator.run(phosphor); + } + Mode::Graphical => { + info!("Starting graphical emulator"); + let emulator = GraphicalEmulator::new(config, debug); + emulator.run(phosphor); + } + Mode::Headless => { + info!("Starting headless emulator"); + let mut emulator = HeadlessEmulator::new(&config, debug); + emulator.run(); + } + } + } else { + match mode { + Mode::Dynamic => { + info!("Could not start graphical event loop"); + info!("Starting headless emulator"); + let mut emulator = HeadlessEmulator::new(&config, debug); + emulator.run(); + } + Mode::Graphical => { + fatal!("Could not start graphical event loop"); + } + Mode::Headless => { + info!("Starting headless emulator"); + let mut emulator = HeadlessEmulator::new(&config, debug); + emulator.run(); + } + } + } +} + + +fn parse_dimensions(string: &str) -> ScreenDimensions { + fn parse_inner(string: &str) -> Option<ScreenDimensions> { + let (w_str, h_str) = string.trim().split_once('x')?; + Some( ScreenDimensions { + width: w_str.parse().ok()?, + height: h_str.parse().ok()?, + } ) + } + let dimensions = parse_inner(string).unwrap_or_else(|| { + fatal!("Invalid dimensions string '{string}'"); + }); + if dimensions.is_zero() { + fatal!("Screen dimensions must be greater than zero"); + } + return dimensions; +} + +fn parse_palette(string: &str) -> [Colour; 16] { + fn decode_ascii_hex_digit(ascii: u8) -> Option<u8> { + match ascii { + b'0'..=b'9' => Some(ascii - b'0'), + b'a'..=b'f' => Some(ascii - b'a' + 10), + b'A'..=b'F' => Some(ascii - b'A' + 10), + _ => { None } + } + } + fn parse_inner(string: &str) -> Option<[Colour; 16]> { + let mut c = Vec::new(); + for token in string.split(',') { + let mut bytes = token.bytes(); + if bytes.len() != 3 { return None; } + let r = decode_ascii_hex_digit(bytes.next().unwrap())?; + let g = decode_ascii_hex_digit(bytes.next().unwrap())?; + let b = decode_ascii_hex_digit(bytes.next().unwrap())?; + c.push(Colour::from_rgb(r*17, g*17, b*17)); + } + Some(match c.len() { + 2 => [ c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1], + c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1] ], + 3 => [ c[0], c[1], c[0], c[2], c[0], c[1], c[0], c[2], + c[0], c[1], c[0], c[2], c[0], c[1], c[0], c[2] ], + 4 => [ c[0], c[1], c[2], c[3], c[0], c[1], c[2], c[3], + c[0], c[1], c[2], c[3], c[0], c[1], c[2], c[3] ], + 8 => [ c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], + c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7] ], + 16 => [ c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], + c[8], c[9], c[10], c[11], c[12], c[13], c[14], c[15] ], + _ => return None, + }) + } + parse_inner(string).unwrap_or_else(|| { + fatal!("Invalid palette string '{string}'"); + }) +} + + +#[derive(Copy, Clone, PartialEq)] +enum Mode { + Graphical, + Headless, + Dynamic, +} + +impl Mode { + pub fn from_str(string: &str) -> Self { + match string { + "g" | "graphical" => Self::Graphical, + "h" | "headless" => Self::Headless, + "d" | "dynamic" => Self::Dynamic, + _ => fatal!("Invalid mode string '{string}'"), + } + } +} diff --git a/src/debug.rs b/src/debug.rs deleted file mode 100644 index 7fd4ea5..0000000 --- a/src/debug.rs +++ /dev/null @@ -1,121 +0,0 @@ -use bedrock_core::*; - -use std::path::Path; -use std::time::Instant; - - -const NORMAL: &str = "\x1b[0m"; -const DIM: &str = "\x1b[2m"; -const YELLOW: &str = "\x1b[33m"; -const BLUE: &str = "\x1b[34m"; - - -pub struct DebugState { - pub enabled: bool, - last_cycle: usize, - last_mark: Instant, - symbols: DebugSymbols, -} - -impl DebugState { - pub fn new<P: AsRef<Path>>(enabled: bool, symbols_path: Option<P>) -> Self { - Self { - enabled, - last_cycle: 0, - last_mark: Instant::now(), - symbols: DebugSymbols::from_path_opt(symbols_path), - } - } - - pub fn debug_summary(&mut self, core: &BedrockCore) { - if self.enabled { - let prev_pc = core.mem.pc.wrapping_sub(1); - eprintln!("\n PC: 0x{:04x} Cycles: {} (+{} in {:.2?})", - prev_pc, core.cycle, - core.cycle.saturating_sub(self.last_cycle), - self.last_mark.elapsed(), - ); - eprint!("WST: "); - debug_stack(&core.wst, 0x10); - eprint!("RST: "); - debug_stack(&core.rst, 0x10); - // Print information about the closest symbol. - if let Some(symbol) = self.symbols.for_address(prev_pc) { - let name = &symbol.name; - let address = &symbol.address; - eprint!("SYM: {BLUE}@{name}{NORMAL} 0x{address:04x}"); - if let Some(location) = &symbol.location { - eprint!(" {DIM}{location}{NORMAL}"); - } - eprintln!(); - } - } - self.last_cycle = core.cycle; - self.last_mark = Instant::now(); - } -} - - -fn debug_stack(stack: &Stack, len: usize) { - for i in 0..len { - if i == stack.sp as usize { eprint!("{YELLOW}"); } - eprint!("{:02x} ", stack.mem[i]); - } - eprintln!("{NORMAL}"); -} - - -struct DebugSymbols { - symbols: Vec<DebugSymbol> -} - -impl DebugSymbols { - /// Load debug symbols from a symbols file. - pub fn from_path_opt<P: AsRef<Path>>(path: Option<P>) -> Self { - let mut symbols = Vec::new(); - if let Some(path) = path { - if let Ok(string) = std::fs::read_to_string(path) { - for line in string.lines() { - if let Some(symbol) = DebugSymbol::from_line(line) { - symbols.push(symbol); - } - } - } - } - symbols.sort_by_key(|s| s.address); - Self { symbols } - } - - pub fn for_address(&self, address: u16) -> Option<&DebugSymbol> { - if self.symbols.is_empty() { return None; } - let symbol = match self.symbols.binary_search_by_key(&address, |s| s.address) { - Ok(index) => self.symbols.get(index)?, - Err(index) => self.symbols.get(index.checked_sub(1)?)?, - }; - Some(&symbol) - } -} - -struct DebugSymbol { - address: u16, - name: String, - location: Option<String>, -} - -impl DebugSymbol { - pub fn from_line(line: &str) -> Option<Self> { - if let Some((address, line)) = line.split_once(' ') { - let address = u16::from_str_radix(address, 16).ok()?; - if let Some((name, location)) = line.split_once(' ') { - let name = name.to_string(); - let location = Some(location.to_string()); - Some( DebugSymbol { address, name, location } ) - } else { - let name = line.to_string(); - Some( DebugSymbol { address, name, location: None } ) - } - } else { - None - } - } -} diff --git a/src/devices.rs b/src/devices.rs deleted file mode 100644 index 2221152..0000000 --- a/src/devices.rs +++ /dev/null @@ -1,20 +0,0 @@ -mod system_device; -mod memory_device; -mod math_device; -mod clock_device; -mod input_device; -mod screen_device; -mod local_device; -mod remote_device; -mod file_device; - -pub use system_device::SystemDevice; -pub use memory_device::MemoryDevice; -pub use math_device::MathDevice; -pub use clock_device::ClockDevice; -pub use input_device::InputDevice; -pub use screen_device::ScreenDevice; -pub use local_device::LocalDevice; -pub use remote_device::RemoteDevice; -pub use file_device::FileDevice; - diff --git a/src/devices/clock_device.rs b/src/devices/clock_device.rs index 7cc877e..b7c5414 100644 --- a/src/devices/clock_device.rs +++ b/src/devices/clock_device.rs @@ -1,114 +1,32 @@ -use bedrock_core::*; +use crate::*; use chrono::prelude::*; -use std::time::{Duration, Instant}; - - -macro_rules! fn_read_timer { - ($fn_name:ident($read:ident, $end:ident)) => { - pub fn $fn_name(&mut self) { - let uptime = self.uptime(); - if self.$end > uptime { - self.$read = (self.$end.saturating_sub(uptime)) as u16; - } else { - if self.$end > 0 { - self.$end = 0; - self.wake = true; - } - self.$read = 0; - } - } - }; -} - -macro_rules! fn_set_timer { - ($fn_name:ident($write:ident, $end:ident)) => { - pub fn $fn_name(&mut self) { - let uptime = self.uptime(); - if self.$write > 0 { - self.$end = uptime.saturating_add(self.$write as u32); - } else { - self.$end = 0; - } - } - }; -} pub struct ClockDevice { - pub wake: bool, - - pub start: Instant, + pub epoch: Instant, pub uptime_read: u16, - pub t1_end: u32, - pub t2_end: u32, - pub t3_end: u32, - pub t4_end: u32, + // End time for each timer as ticks since epoch, zero if not set. + pub t1_end: u64, + pub t2_end: u64, + pub t3_end: u64, + pub t4_end: u64, + // Cached read value for each timer. pub t1_read: u16, pub t2_read: u16, pub t3_read: u16, pub t4_read: u16, + // Cached write value for each timer. pub t1_write: u16, pub t2_write: u16, pub t3_write: u16, pub t4_write: u16, -} - -impl ClockDevice { - pub fn new() -> Self { - Self { - start: Instant::now(), - uptime_read: 0, - wake: false, - - t1_end: 0, - t2_end: 0, - t3_end: 0, - t4_end: 0, - t1_read: 0, - t2_read: 0, - t3_read: 0, - t4_read: 0, - t1_write: 0, - t2_write: 0, - t3_write: 0, - t4_write: 0, - } - } - pub fn uptime(&self) -> u32 { - (self.start.elapsed().as_millis() / 4) as u32 - } - - pub fn read_uptime(&mut self) { - self.uptime_read = self.uptime() as u16; - } - - fn_read_timer!{ read_t1(t1_read, t1_end) } - fn_read_timer!{ read_t2(t2_read, t2_end) } - fn_read_timer!{ read_t3(t3_read, t3_end) } - fn_read_timer!{ read_t4(t4_read, t4_end) } - - fn_set_timer!{ set_t1(t1_write, t1_end) } - fn_set_timer!{ set_t2(t2_write, t2_end) } - fn_set_timer!{ set_t3(t3_write, t3_end) } - fn_set_timer!{ set_t4(t4_write, t4_end) } - - - pub fn time_remaining(&mut self) -> Option<Duration> { - use std::cmp::max; - let uptime = self.uptime(); - - let end = max(self.t1_end, max(self.t2_end, max(self.t3_end, self.t4_end))); - let remaining = end.saturating_sub(uptime); - match remaining > 0 { - true => Some(Duration::from_millis(remaining as u64) * 4), - false => None, - } - } + pub wake: bool, } + impl Device for ClockDevice { fn read(&mut self, port: u8) -> u8 { match port { @@ -118,16 +36,17 @@ impl Device for ClockDevice { 0x3 => Local::now().hour() as u8, 0x4 => Local::now().minute() as u8, 0x5 => Local::now().second() as u8, - 0x6 => { self.read_uptime(); read_h!(self.uptime_read) }, - 0x7 => read_l!(self.uptime_read), + 0x6 => { self.uptime_read = self.uptime() as u16; + read_h!(self.uptime_read) }, + 0x7 => read_l!(self.uptime_read), 0x8 => { self.read_t1(); read_h!(self.t1_read) }, - 0x9 => read_l!(self.t1_read), + 0x9 => read_l!(self.t1_read), 0xa => { self.read_t2(); read_h!(self.t2_read) }, - 0xb => read_l!(self.t2_read), + 0xb => read_l!(self.t2_read), 0xc => { self.read_t3(); read_h!(self.t3_read) }, - 0xd => read_l!(self.t3_read), + 0xd => read_l!(self.t3_read), 0xe => { self.read_t4(); read_h!(self.t4_read) }, - 0xf => read_l!(self.t4_read), + 0xf => read_l!(self.t4_read), _ => unreachable!(), } } @@ -142,13 +61,13 @@ impl Device for ClockDevice { 0x5 => (), 0x6 => (), 0x7 => (), - 0x8 => write_h!(self.t1_write, value), + 0x8 => write_h!(self.t1_write, value), 0x9 => { write_l!(self.t1_write, value); self.set_t1() }, - 0xa => write_h!(self.t2_write, value), + 0xa => write_h!(self.t2_write, value), 0xb => { write_l!(self.t2_write, value); self.set_t2() }, - 0xc => write_h!(self.t3_write, value), + 0xc => write_h!(self.t3_write, value), 0xd => { write_l!(self.t3_write, value); self.set_t3() }, - 0xe => write_h!(self.t4_write, value), + 0xe => write_h!(self.t4_write, value), 0xf => { write_l!(self.t4_write, value); self.set_t4() }, _ => unreachable!(), }; @@ -157,18 +76,100 @@ impl Device for ClockDevice { fn wake(&mut self) -> bool { let uptime = self.uptime(); - macro_rules! check_timer { - ($end:ident) => { - if self.$end > 0 && self.$end <= uptime { + + if self.t1_end > 0 && self.t1_end <= uptime { + self.t1_end = 0; self.wake = true; } + if self.t2_end > 0 && self.t2_end <= uptime { + self.t2_end = 0; self.wake = true; } + if self.t3_end > 0 && self.t3_end <= uptime { + self.t3_end = 0; self.wake = true; } + if self.t4_end > 0 && self.t4_end <= uptime { + self.t4_end = 0; self.wake = true; } + + return std::mem::take(&mut self.wake); + } +} + + +impl ClockDevice { + pub fn new() -> Self { + Self { + epoch: Instant::now(), + uptime_read: 0, + wake: false, + + t1_end: 0, + t2_end: 0, + t3_end: 0, + t4_end: 0, + t1_read: 0, + t2_read: 0, + t3_read: 0, + t4_read: 0, + t1_write: 0, + t2_write: 0, + t3_write: 0, + t4_write: 0, + } + } + + pub fn uptime(&self) -> u64 { + (self.epoch.elapsed().as_nanos() * 256 / 1_000_000_000) as u64 + } + + /// Duration remaining until the next timer expires, if any timer is set. + pub fn duration_remaining(&mut self) -> Option<Duration> { + let mut end = u64::MAX; + if self.t1_end > 0 { end = std::cmp::min(end, self.t1_end); } + if self.t2_end > 0 { end = std::cmp::min(end, self.t2_end); } + if self.t3_end > 0 { end = std::cmp::min(end, self.t3_end); } + if self.t4_end > 0 { end = std::cmp::min(end, self.t4_end); } + + if end != u64::MAX { + let remaining = end.saturating_sub(self.uptime()); + Some(Duration::from_nanos(remaining * 1_000_000_000 / 256)) + } else { + None + } + } +} + +macro_rules! fn_read_timer { + ($fn_name:ident($read:ident, $end:ident)) => { + pub fn $fn_name(&mut self) { + let uptime = self.uptime(); + if self.$end > uptime { + self.$read = (self.$end.saturating_sub(uptime)) as u16; + } else { + if self.$end > 0 { self.$end = 0; self.wake = true; } + self.$read = 0; + } + } + }; +} + +macro_rules! fn_set_timer { + ($fn_name:ident($write:ident, $end:ident)) => { + pub fn $fn_name(&mut self) { + match self.$write > 0 { + true => self.$end = self.uptime().saturating_add(self.$write as u64), + false => self.$end = 0, }; } - check_timer!(t1_end); - check_timer!(t2_end); - check_timer!(t3_end); - check_timer!(t4_end); - return std::mem::take(&mut self.wake); - } + }; +} + +impl ClockDevice { + fn_read_timer!{ read_t1(t1_read, t1_end) } + fn_read_timer!{ read_t2(t2_read, t2_end) } + fn_read_timer!{ read_t3(t3_read, t3_end) } + fn_read_timer!{ read_t4(t4_read, t4_end) } + + fn_set_timer!{ set_t1(t1_write, t1_end) } + fn_set_timer!{ set_t2(t2_write, t2_end) } + fn_set_timer!{ set_t3(t3_write, t3_end) } + fn_set_timer!{ set_t4(t4_write, t4_end) } } diff --git a/src/devices/file_device.rs b/src/devices/file_device.rs index 61966b1..f8d8fa0 100644 --- a/src/devices/file_device.rs +++ b/src/devices/file_device.rs @@ -1,20 +1,4 @@ -mod bedrock_file_path; -mod bedrock_path_buffer; -mod buffered_file; -mod directory_listing; -mod entry; -mod operations; - -use buffered_file::BufferedFile; -use bedrock_file_path::BedrockFilePath; -use bedrock_path_buffer::BedrockPathBuffer; -use directory_listing::DirectoryListing; -use entry::{Entry, EntryType}; -use operations::{create_file, move_entry, delete_entry}; - -use bedrock_core::*; - -use std::path::{Component, Path, PathBuf}; +use crate::*; pub struct FileDevice { @@ -39,6 +23,59 @@ pub struct FileDevice { pub enable_delete: bool, } + +impl Device for FileDevice { + fn read(&mut self, port: u8) -> u8 { + match port { + 0x0 => read_b!(self.entry.is_some()), + 0x1 => read_b!(self.success), + 0x2 => self.path_buffer.read(), + 0x3 => read_b!(self.entry_type()), + 0x4 => self.read_byte(), + 0x5 => self.read_byte(), + 0x6 => self.read_child_path(), + 0x7 => read_b!(self.child_type()), + 0x8 => read_hh!(self.pointer()), + 0x9 => read_hl!(self.pointer()), + 0xa => read_lh!(self.pointer()), + 0xb => read_ll!(self.pointer()), + 0xc => read_hh!(self.length()), + 0xd => read_hl!(self.length()), + 0xe => read_lh!(self.length()), + 0xf => read_ll!(self.length()), + _ => unreachable!(), + } + } + + fn write(&mut self, port: u8, value: u8) -> Option<Signal> { + match port { + 0x0 => self.write_to_entry_port(value), + 0x1 => self.write_to_action_port(value), + 0x2 => self.path_buffer.set_pointer(value), + 0x3 => self.ascend_to_parent(), + 0x4 => self.write_byte(value), + 0x5 => self.write_byte(value), + 0x6 => self.set_child_path(value), + 0x7 => self.descend_to_child(), + 0x8 => write_hh!(self.pointer_write, value), + 0x9 => write_hl!(self.pointer_write, value), + 0xa => write_lh!(self.pointer_write, value), + 0xb => {write_ll!(self.pointer_write, value); self.commit_pointer()}, + 0xc => write_hh!(self.length_write, value), + 0xd => write_hl!(self.length_write, value), + 0xe => write_lh!(self.length_write, value), + 0xf => {write_ll!(self.length_write, value); self.commit_length()}, + _ => unreachable!(), + }; + return None; + } + + fn wake(&mut self) -> bool { + false + } +} + + impl FileDevice { pub fn new() -> Self { #[cfg(target_family = "unix")] @@ -279,59 +316,57 @@ impl FileDevice { } } + impl Drop for FileDevice { fn drop(&mut self) { self.flush(); } } -impl Device for FileDevice { - fn read(&mut self, port: u8) -> u8 { - match port { - 0x0 => read_b!(self.entry.is_some()), - 0x1 => read_b!(self.success), - 0x2 => self.path_buffer.read(), - 0x3 => read_b!(self.entry_type()), - 0x4 => self.read_byte(), - 0x5 => self.read_byte(), - 0x6 => self.read_child_path(), - 0x7 => read_b!(self.child_type()), - 0x8 => read_hh!(self.pointer()), - 0x9 => read_hl!(self.pointer()), - 0xa => read_lh!(self.pointer()), - 0xb => read_ll!(self.pointer()), - 0xc => read_hh!(self.length()), - 0xd => read_hl!(self.length()), - 0xe => read_lh!(self.length()), - 0xf => read_ll!(self.length()), - _ => unreachable!(), + +/// Create a new file if it doesn't already exist, returning true if successful. +pub fn create_file(destination: &Path) -> bool { + if entry_exists(destination) { + false + } else { + if let Some(parent_path) = destination.parent() { + let _ = std::fs::create_dir_all(parent_path); } + std::fs::OpenOptions::new().write(true).create_new(true) + .open(destination).is_ok() } +} - fn write(&mut self, port: u8, value: u8) -> Option<Signal> { - match port { - 0x0 => self.write_to_entry_port(value), - 0x1 => self.write_to_action_port(value), - 0x2 => self.path_buffer.set_pointer(value), - 0x3 => self.ascend_to_parent(), - 0x4 => self.write_byte(value), - 0x5 => self.write_byte(value), - 0x6 => self.set_child_path(value), - 0x7 => self.descend_to_child(), - 0x8 => write_hh!(self.pointer_write, value), - 0x9 => write_hl!(self.pointer_write, value), - 0xa => write_lh!(self.pointer_write, value), - 0xb => {write_ll!(self.pointer_write, value); self.commit_pointer()}, - 0xc => write_hh!(self.length_write, value), - 0xd => write_hl!(self.length_write, value), - 0xe => write_lh!(self.length_write, value), - 0xf => {write_ll!(self.length_write, value); self.commit_length()}, - _ => unreachable!(), - }; - return None; +/// Move an entry from one location to another, returning true if successful. +pub fn move_entry(source: &Path, destination: &Path) -> bool { + if !entry_exists(source) || entry_exists(destination) { + return false; } + std::fs::rename(source, destination).is_ok() +} - fn wake(&mut self) -> bool { - false +/// Delete an entry, returning true if successful. +pub fn delete_entry(source: &Path) -> bool { + use std::fs::{remove_file, remove_dir_all}; + use std::io::ErrorKind; + + match remove_file(source) { + Ok(_) => true, + Err(error) => match error.kind() { + ErrorKind::NotFound => true, + ErrorKind::IsADirectory => match remove_dir_all(source) { + Ok(_) => true, + Err(error) => match error.kind() { + ErrorKind::NotFound => true, + _ => false, + } + } + _ => false, + } } } + +/// Returns true if an entry already exists at the given path. +fn entry_exists(source: &Path) -> bool { + std::fs::metadata(source).is_ok() +} diff --git a/src/devices/file_device/operations.rs b/src/devices/file_device/operations.rs deleted file mode 100644 index 3a3f81b..0000000 --- a/src/devices/file_device/operations.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::io::ErrorKind; -use std::path::Path; - - -/// Create a new file if it doesn't already exist, returning true if successful. -pub fn create_file(destination: &Path) -> bool { - if entry_exists(destination) { - false - } else { - if let Some(parent_path) = destination.parent() { - let _ = std::fs::create_dir_all(parent_path); - } - std::fs::OpenOptions::new().write(true).create_new(true) - .open(destination).is_ok() - } -} - -/// Move an entry from one location to another, returning true if successful. -pub fn move_entry(source: &Path, destination: &Path) -> bool { - if !entry_exists(source) || entry_exists(destination) { - return false; - } - std::fs::rename(source, destination).is_ok() -} - -/// Delete an entry, returning true if successful. -pub fn delete_entry(source: &Path) -> bool { - match std::fs::remove_file(source) { - Ok(_) => true, - Err(e) => match e.kind() { - ErrorKind::NotFound => true, - ErrorKind::IsADirectory => match std::fs::remove_dir_all(source) { - Ok(_) => true, - Err(e) => match e.kind() { - ErrorKind::NotFound => true, - _ => false, - } - } - _ => false, - } - } -} - -/// Returns true if an entry already exists at the given path. -fn entry_exists(source: &Path) -> bool { - std::fs::metadata(source).is_ok() -} diff --git a/src/devices/input_device.rs b/src/devices/input_device.rs index 9b7038c..d2dd682 100644 --- a/src/devices/input_device.rs +++ b/src/devices/input_device.rs @@ -1,35 +1,10 @@ use crate::*; -use bedrock_core::*; -use phosphor::*; use std::collections::VecDeque; -macro_rules! fn_on_scroll { - ($fn_name:ident($value:ident, $delta:ident)) => { - pub fn $fn_name(&mut self, delta: f32) { - self.$delta += delta; - while self.$delta >= 1.0 { - self.$value = self.$value.saturating_add(1); - self.$delta -= 1.0; - self.wake = true; - } - while self.$delta <= -1.0 { - self.$value = self.$value.saturating_sub(1); - self.$delta += 1.0; - self.wake = true; - } - } - }; -} - pub struct InputDevice { - pub wake: bool, - pub accessed: bool, - - pub pointer_active: bool, - pub pointer_buttons: u8, - pub position: ScreenPosition, + pub cursor: ScreenPosition, pub x_read: u16, pub y_read: u16, @@ -38,27 +13,83 @@ pub struct InputDevice { pub h_scroll_delta: f32, pub v_scroll_delta: f32, - pub keyboard_active: bool, - pub characters: VecDeque<u8>, + pub pointer_buttons: u8, + pub pointer_active: bool, + pub navigation: u8, pub modifiers: u8, + pub characters: VecDeque<u8>, + pub keyboard_active: bool, pub gamepad_1: u8, pub gamepad_2: u8, pub gamepad_3: u8, pub gamepad_4: u8, + + pub accessed: bool, + pub wake: bool, } + +impl Device for InputDevice { + fn read(&mut self, port: u8) -> u8 { + self.accessed = true; + match port { + 0x0 => { self.x_read = self.cursor.x; read_h!(self.x_read) }, + 0x1 => read_l!(self.cursor.x), + 0x2 => { self.y_read = self.cursor.y; read_h!(self.y_read) }, + 0x3 => read_l!(self.cursor.y), + 0x4 => self.read_horizontal_scroll(), + 0x5 => self.read_vertical_scroll(), + 0x6 => self.pointer_buttons, + 0x7 => read_b!(self.pointer_active), + 0x8 => self.navigation, + 0x9 => self.modifiers, + 0xa => self.characters.pop_front().unwrap_or(0), + 0xb => read_b!(self.keyboard_active), + 0xc => self.gamepad_1, + 0xd => self.gamepad_2, + 0xe => self.gamepad_3, + 0xf => self.gamepad_4, + _ => unreachable!(), + } + } + + fn write(&mut self, port: u8, _value: u8) -> Option<Signal> { + self.accessed = true; + match port { + 0x0 => (), + 0x1 => (), + 0x2 => (), + 0x3 => (), + 0x4 => (), + 0x5 => (), + 0x6 => (), + 0x7 => (), + 0x8 => (), + 0x9 => (), + 0xa => self.characters.clear(), + 0xb => (), + 0xc => (), + 0xd => (), + 0xe => (), + 0xf => (), + _ => unreachable!(), + }; + return None; + } + + fn wake(&mut self) -> bool { + self.accessed = true; + std::mem::take(&mut self.wake) + } +} + + impl InputDevice { pub fn new() -> Self { Self { - wake: false, - accessed: false, - - pointer_active: false, - pointer_buttons: 0, - - position: ScreenPosition::ZERO, + cursor: ScreenPosition::ZERO, x_read: 0, y_read: 0, @@ -67,15 +98,21 @@ impl InputDevice { h_scroll_delta: 0.0, v_scroll_delta: 0.0, - keyboard_active: true, - characters: VecDeque::new(), - modifiers: 0, + pointer_active: false, + pointer_buttons: 0, + navigation: 0, + modifiers: 0, + characters: VecDeque::new(), + keyboard_active: true, gamepad_1: 0, gamepad_2: 0, gamepad_3: 0, gamepad_4: 0, + + accessed: false, + wake: false, } } @@ -90,12 +127,12 @@ impl InputDevice { } pub fn on_cursor_move(&mut self, position: Position) { - let screen_position = ScreenPosition { + let cursor_position = ScreenPosition { x: position.x as i16 as u16, y: position.y as i16 as u16, }; - if self.position != screen_position { - self.position = screen_position; + if self.cursor != cursor_position { + self.cursor = cursor_position; self.wake = true; } } @@ -117,8 +154,33 @@ impl InputDevice { } } - fn_on_scroll!(on_horizontal_scroll(h_scroll, h_scroll_delta)); - fn_on_scroll!(on_vertical_scroll(v_scroll, v_scroll_delta)); + pub fn on_horizontal_scroll(&mut self, delta: f32) { + self.h_scroll_delta += delta; + while self.h_scroll_delta >= 1.0 { + self.h_scroll = self.h_scroll.saturating_add(1); + self.h_scroll_delta -= 1.0; + self.wake = true; + } + while self.h_scroll_delta <= -1.0 { + self.h_scroll = self.h_scroll.saturating_sub(1); + self.h_scroll_delta += 1.0; + self.wake = true; + } + } + + pub fn on_vertical_scroll(&mut self, delta: f32) { + self.v_scroll_delta += delta; + while self.v_scroll_delta >= 1.0 { + self.v_scroll = self.v_scroll.saturating_add(1); + self.v_scroll_delta -= 1.0; + self.wake = true; + } + while self.v_scroll_delta <= -1.0 { + self.v_scroll = self.v_scroll.saturating_sub(1); + self.v_scroll_delta += 1.0; + self.wake = true; + } + } pub fn read_horizontal_scroll(&mut self) -> u8 { std::mem::take(&mut self.h_scroll) as u8 @@ -178,57 +240,3 @@ impl InputDevice { } } } - -impl Device for InputDevice { - fn read(&mut self, port: u8) -> u8 { - self.accessed = true; - match port { - 0x0 => read_b!(self.pointer_active), - 0x1 => self.pointer_buttons, - 0x2 => self.read_horizontal_scroll(), - 0x3 => self.read_vertical_scroll(), - 0x4 => { self.x_read = self.position.x as u16; read_h!(self.x_read) }, - 0x5 => read_l!(self.position.x), - 0x6 => { self.y_read = self.position.y as u16; read_h!(self.y_read) }, - 0x7 => read_l!(self.position.y), - 0x8 => read_b!(self.keyboard_active), - 0x9 => self.characters.pop_front().unwrap_or(0), - 0xa => self.navigation, - 0xb => self.modifiers, - 0xc => self.gamepad_1, - 0xd => self.gamepad_2, - 0xe => self.gamepad_3, - 0xf => self.gamepad_4, - _ => unreachable!(), - } - } - - fn write(&mut self, port: u8, _value: u8) -> Option<Signal> { - self.accessed = true; - match port { - 0x0 => (), - 0x1 => (), - 0x2 => (), - 0x3 => (), - 0x4 => (), - 0x5 => (), - 0x6 => (), - 0x7 => (), - 0x8 => (), - 0x9 => self.characters.clear(), - 0xa => (), - 0xb => (), - 0xc => (), - 0xd => (), - 0xe => (), - 0xf => (), - _ => unreachable!(), - }; - return None; - } - - fn wake(&mut self) -> bool { - self.accessed = true; - std::mem::take(&mut self.wake) - } -} diff --git a/src/devices/math_device.rs b/src/devices/math_device.rs index 015545e..7944b48 100644 --- a/src/devices/math_device.rs +++ b/src/devices/math_device.rs @@ -1,59 +1,155 @@ -use bedrock_core::*; +use crate::*; +const ANGLE_SCALE: f64 = 10430.378350470453; // 65536 / 2Ï€ -pub struct MathDevice { - pub op1: u16, - pub op2: u16, - pub sqrt: Option<u16>, - pub atan: Option<u16>, - pub prod: Option<(u16, u16)>, // (low, high) +pub struct MathDevice { + pub x: u16, + pub y: u16, + pub r: u16, + pub t: u16, + pub x_read: Option<u16>, + pub y_read: Option<u16>, + pub r_read: Option<u16>, + pub t_read: Option<u16>, + /// (low, high) + pub prod: Option<(u16, u16)>, pub quot: Option<u16>, pub rem: Option<u16>, } + +impl Device for MathDevice { + fn read(&mut self, port: u8) -> u8 { + match port { + 0x0 => read_h!(self.x()), + 0x1 => read_l!(self.x()), + 0x2 => read_h!(self.y()), + 0x3 => read_l!(self.y()), + 0x4 => read_h!(self.r()), + 0x5 => read_l!(self.r()), + 0x6 => read_h!(self.t()), + 0x7 => read_l!(self.t()), + 0x8 => read_h!(self.prod().1), + 0x9 => read_l!(self.prod().1), + 0xa => read_h!(self.prod().0), + 0xb => read_l!(self.prod().0), + 0xc => read_h!(self.quot()), + 0xd => read_l!(self.quot()), + 0xe => read_h!(self.rem()), + 0xf => read_l!(self.rem()), + _ => unreachable!(), + } + } + + fn write(&mut self, port: u8, value: u8) -> Option<Signal> { + match port { + 0x0 => { write_h!(self.x, value); self.clear_polar(); }, + 0x1 => { write_l!(self.x, value); self.clear_polar(); }, + 0x2 => { write_h!(self.y, value); self.clear_polar(); }, + 0x3 => { write_l!(self.y, value); self.clear_polar(); }, + 0x4 => { write_h!(self.r, value); self.clear_cartesian(); }, + 0x5 => { write_l!(self.r, value); self.clear_cartesian(); }, + 0x6 => { write_h!(self.t, value); self.clear_cartesian(); }, + 0x7 => { write_l!(self.t, value); self.clear_cartesian(); }, + 0x8 => (), + 0x9 => (), + 0xa => (), + 0xb => (), + 0xc => (), + 0xd => (), + 0xe => (), + 0xf => (), + _ => unreachable!(), + }; + return None; + } + + fn wake(&mut self) -> bool { + false + } +} + + impl MathDevice { pub fn new() -> Self { Self { - op1: 0, - op2: 0, + x: 0, + y: 0, + r: 0, + t: 0, + x_read: None, + y_read: None, + r_read: None, + t_read: None, - sqrt: None, - atan: None, prod: None, quot: None, rem: None, } } - pub fn clear(&mut self) { - self.sqrt = None; - self.atan = None; + pub fn clear_cartesian(&mut self) { + self.x_read = None; + self.y_read = None; + } + + pub fn clear_polar(&mut self) { + self.r_read = None; + self.t_read = None; self.prod = None; self.quot = None; - self.rem = None; + self.rem = None; + } + + pub fn x(&mut self) -> u16 { + match self.x_read { + Some(x) => x, + None => { + let r = self.r as f64; + let t = self.t as f64; + let angle = t / ANGLE_SCALE; + let x = angle.cos() * r; + self.x_read = Some(x as i16 as u16); + self.x_read.unwrap() + } + } } - pub fn atan(&mut self) -> u16 { - match self.atan { - Some(atan) => atan, + pub fn y(&mut self) -> u16 { + match self.y_read { + Some(y) => y, None => { - let x = self.op1 as i16 as f64; - let y = self.op2 as i16 as f64; - const SCALE: f64 = 10430.378350470453; // PI * 32768 - self.atan = Some((f64::atan2(x, y) * SCALE) as i16 as u16); - self.atan.unwrap() + let r = self.r as f64; + let t = self.t as f64; + let angle = t / ANGLE_SCALE; + let y = angle.sin() * r; + self.y_read = Some(y as i16 as u16); + self.y_read.unwrap() } } } - pub fn sqrt(&mut self) -> u16 { - match self.sqrt { - Some(sqrt) => sqrt, + pub fn r(&mut self) -> u16 { + match self.r_read { + Some(r) => r, None => { - let input = ((self.op1 as u32) << 16) | (self.op2 as u32); - self.sqrt = Some((input as f64).sqrt() as u16); - self.sqrt.unwrap() + let sum = (self.x as f64).powi(2) + (self.y as f64).powi(2); + self.r_read = Some(sum.sqrt() as u16); + self.r_read.unwrap() + } + } + } + + pub fn t(&mut self) -> u16 { + match self.t_read { + Some(t) => t, + None => { + let x = self.x as i16 as f64; + let y = self.x as i16 as f64; + let angle = f64::atan2(y, x) * ANGLE_SCALE; + self.t_read = Some(angle as i16 as u16); + self.t_read.unwrap() } } } @@ -62,7 +158,7 @@ impl MathDevice { match self.prod { Some(prod) => prod, None => { - self.prod = Some(self.op1.widening_mul(self.op2)); + self.prod = Some(self.x.widening_mul(self.y)); self.prod.unwrap() } } @@ -72,7 +168,7 @@ impl MathDevice { match self.quot { Some(quot) => quot, None => { - self.quot = Some(self.op1.checked_div(self.op2).unwrap_or(0)); + self.quot = Some(self.x.checked_div(self.y).unwrap_or(0)); self.quot.unwrap() } } @@ -82,60 +178,9 @@ impl MathDevice { match self.rem { Some(rem) => rem, None => { - self.rem = Some(self.op1.checked_rem(self.op2).unwrap_or(0)); + self.rem = Some(self.x.checked_rem(self.y).unwrap_or(0)); self.rem.unwrap() } } } } - -impl Device for MathDevice { - fn read(&mut self, port: u8) -> u8 { - match port { - 0x0 => read_h!(self.op1), - 0x1 => read_l!(self.op1), - 0x2 => read_h!(self.op2), - 0x3 => read_l!(self.op2), - 0x4 => read_h!(self.sqrt()), - 0x5 => read_l!(self.sqrt()), - 0x6 => read_h!(self.atan()), - 0x7 => read_l!(self.atan()), - 0x8 => read_h!(self.prod().1), - 0x9 => read_l!(self.prod().1), - 0xa => read_h!(self.prod().0), - 0xb => read_l!(self.prod().0), - 0xc => read_h!(self.quot()), - 0xd => read_l!(self.quot()), - 0xe => read_h!(self.rem()), - 0xf => read_l!(self.rem()), - _ => unreachable!(), - } - } - - fn write(&mut self, port: u8, value: u8) -> Option<Signal> { - match port { - 0x0 => { write_h!(self.op1, value); self.clear(); }, - 0x1 => { write_l!(self.op1, value); self.clear(); }, - 0x2 => { write_h!(self.op2, value); self.clear(); }, - 0x3 => { write_l!(self.op2, value); self.clear(); }, - 0x4 => (), - 0x5 => (), - 0x6 => (), - 0x7 => (), - 0x8 => (), - 0x9 => (), - 0xa => (), - 0xb => (), - 0xc => (), - 0xd => (), - 0xe => (), - 0xf => (), - _ => unreachable!(), - }; - return None; - } - - fn wake(&mut self) -> bool { - false - } -} diff --git a/src/devices/memory_device.rs b/src/devices/memory_device.rs index 0128d55..8efb12a 100644 --- a/src/devices/memory_device.rs +++ b/src/devices/memory_device.rs @@ -1,53 +1,73 @@ -use bedrock_core::*; +use crate::*; -type Page = [u8; 256]; - -macro_rules! fn_read_head { - ($fn_name:ident($offset:ident, $address:ident)) => { - pub fn $fn_name(&mut self) -> u8 { - let page_i = (self.$offset + (self.$address / 256)) as usize; - let byte_i = (self.$address % 256) as usize; - self.$address = self.$address.wrapping_add(1); - match self.pages.get(page_i) { - Some(page) => page[byte_i], - None => 0, - } - } - }; -} +use std::cmp::min; -macro_rules! fn_write_head { - ($fn_name:ident($offset:ident, $address:ident)) => { - pub fn $fn_name(&mut self, byte: u8) { - let page_i = (self.$offset + (self.$address / 256)) as usize; - let byte_i = (self.$address % 256) as usize; - self.$address = self.$address.wrapping_add(1); - match self.pages.get_mut(page_i) { - Some(page) => page[byte_i] = byte, - None => if page_i < self.provisioned { - self.pages.resize(page_i + 1, [0; 256]); - self.pages[page_i][byte_i] = byte; - } - } - } - }; -} +type Page = [u8; 256]; pub struct MemoryDevice { pub limit: u16, // maximum provisionable number of pages pub requested: u16, // number of pages requested by program pub provisioned: usize, // number of pages provisioned for use pub pages: Vec<Page>, // all allocated pages + pub head_1: HeadAddress, + pub head_2: HeadAddress, + pub copy_length: u16, +} - pub offset_1: u16, - pub address_1: u16, - pub offset_2: u16, - pub address_2: u16, - pub copy_length: u16, +impl Device for MemoryDevice { + fn read(&mut self, port: u8) -> u8 { + match port { + 0x0 => self.read_head_1(), + 0x1 => self.read_head_1(), + 0x2 => read_h!(self.head_1.offset), + 0x3 => read_l!(self.head_1.offset), + 0x4 => read_h!(self.head_1.address), + 0x5 => read_l!(self.head_1.address), + 0x6 => read_h!(self.provisioned), + 0x7 => read_l!(self.provisioned), + 0x8 => self.read_head_2(), + 0x9 => self.read_head_2(), + 0xa => read_h!(self.head_2.offset), + 0xb => read_l!(self.head_2.offset), + 0xc => read_h!(self.head_2.address), + 0xd => read_l!(self.head_2.address), + 0xe => 0x00, + 0xf => 0x00, + _ => unreachable!(), + } + } + + fn write(&mut self, port: u8, value: u8) -> Option<Signal> { + match port { + 0x0 => self.write_head_1(value), + 0x1 => self.write_head_1(value), + 0x2 => write_h!(self.head_1.offset, value), + 0x3 => write_l!(self.head_1.offset, value), + 0x4 => write_h!(self.head_1.address, value), + 0x5 => write_l!(self.head_1.address, value), + 0x6 => write_h!(self.requested, value), + 0x7 => { write_l!(self.requested, value); self.provision(); }, + 0x8 => self.write_head_2(value), + 0x9 => self.write_head_2(value), + 0xa => write_h!(self.head_2.offset, value), + 0xb => write_l!(self.head_2.offset, value), + 0xc => write_h!(self.head_2.address, value), + 0xd => write_l!(self.head_2.address, value), + 0xe => write_h!(self.copy_length, value), + 0xf => { write_l!(self.copy_length, value); self.copy(); }, + _ => unreachable!(), + }; + return None; + } + + fn wake(&mut self) -> bool { + false + } } + impl MemoryDevice { pub fn new() -> Self { Self { @@ -55,34 +75,63 @@ impl MemoryDevice { requested: 0, provisioned: 0, pages: Vec::new(), + head_1: HeadAddress::new(), + head_2: HeadAddress::new(), + copy_length: 0, + } + } + + pub fn read_head_1(&mut self) -> u8 { + let (page_i, byte_i) = self.head_1.get_page_address(); + self.read_byte(page_i, byte_i) + } - offset_1: 0, - address_1: 0, - offset_2: 0, - address_2: 0, + pub fn read_head_2(&mut self) -> u8 { + let (page_i, byte_i) = self.head_2.get_page_address(); + self.read_byte(page_i, byte_i) + } - copy_length: 0, + fn read_byte(&self, page_i: usize, byte_i: usize) -> u8 { + match self.pages.get(page_i) { + Some(page) => page[byte_i], + None => 0, } } - fn_read_head! { read_head_1( offset_1, address_1) } - fn_read_head! { read_head_2( offset_2, address_2) } - fn_write_head!{ write_head_1(offset_1, address_1) } - fn_write_head!{ write_head_2(offset_2, address_2) } + pub fn write_head_1(&mut self, value: u8) { + let (page_i, byte_i) = self.head_1.get_page_address(); + self.write_byte(page_i, byte_i, value); + } + + pub fn write_head_2(&mut self, value: u8) { + let (page_i, byte_i) = self.head_2.get_page_address(); + self.write_byte(page_i, byte_i, value); + } + + // Write a byte to a page of memory. + fn write_byte(&mut self, page_i: usize, byte_i: usize, value: u8) { + match self.pages.get_mut(page_i) { + Some(page) => page[byte_i] = value, + None => if page_i < self.provisioned { + self.pages.resize(page_i + 1, [0; 256]); + self.pages[page_i][byte_i] = value; + } + } + } pub fn provision(&mut self) { - self.provisioned = std::cmp::min(self.requested, self.limit) as usize; + self.provisioned = min(self.requested, self.limit) as usize; // Defer allocation of new pages. self.pages.truncate(self.provisioned as usize); } pub fn copy(&mut self) { - let src = self.offset_2 as usize; - let dest = self.offset_1 as usize; + let src = self.head_2.offset as usize; + let dest = self.head_1.offset as usize; let count = self.copy_length as usize; // Pre-allocate destination pages as needed. - let pages_needed = std::cmp::min(dest + count, self.provisioned); + let pages_needed = min(dest + count, self.provisioned); if pages_needed > self.pages.len() { self.pages.resize(pages_needed, [0; 256]); } @@ -100,53 +149,24 @@ impl MemoryDevice { } } -impl Device for MemoryDevice { - fn read(&mut self, port: u8) -> u8 { - match port { - 0x0 => self.read_head_1(), - 0x1 => self.read_head_1(), - 0x2 => read_h!(self.offset_1), - 0x3 => read_l!(self.offset_1), - 0x4 => read_h!(self.address_1), - 0x5 => read_l!(self.address_1), - 0x6 => read_h!(self.provisioned), - 0x7 => read_l!(self.provisioned), - 0x8 => self.read_head_2(), - 0x9 => self.read_head_2(), - 0xa => read_h!(self.offset_2), - 0xb => read_l!(self.offset_2), - 0xc => read_h!(self.address_2), - 0xd => read_l!(self.address_2), - 0xe => 0x00, - 0xf => 0x00, - _ => unreachable!(), - } - } - fn write(&mut self, port: u8, value: u8) -> Option<Signal> { - match port { - 0x0 => self.write_head_1(value), - 0x1 => self.write_head_1(value), - 0x2 => write_h!(self.offset_1, value), - 0x3 => write_l!(self.offset_1, value), - 0x4 => write_h!(self.address_1, value), - 0x5 => write_l!(self.address_1, value), - 0x6 => write_h!(self.requested, value), - 0x7 => { write_l!(self.requested, value); self.provision(); }, - 0x8 => self.write_head_2(value), - 0x9 => self.write_head_2(value), - 0xa => write_h!(self.offset_2, value), - 0xb => write_l!(self.offset_2, value), - 0xc => write_h!(self.address_2, value), - 0xd => write_l!(self.address_2, value), - 0xe => write_h!(self.copy_length, value), - 0xf => { write_l!(self.copy_length, value); self.copy(); }, - _ => unreachable!(), - }; - return None; +pub struct HeadAddress { + pub offset: u16, + pub address: u16, +} + +impl HeadAddress { + pub fn new() -> Self { + Self { + offset: 0, + address: 0, + } } - fn wake(&mut self) -> bool { - false + fn get_page_address(&mut self) -> (usize, usize) { + let page_i = (self.offset + (self.address / 256)) as usize; + let byte_i = (self.address % 256) as usize; + self.address = self.address.wrapping_add(1); + (page_i, byte_i) } } diff --git a/src/devices/mod.rs b/src/devices/mod.rs new file mode 100644 index 0000000..aa98a49 --- /dev/null +++ b/src/devices/mod.rs @@ -0,0 +1,17 @@ +mod system_device; +mod memory_device; +mod math_device; +mod clock_device; +mod input_device; +mod screen_device; +mod stream_device; +mod file_device; + +pub use system_device::*; +pub use memory_device::*; +pub use math_device::*; +pub use clock_device::*; +pub use input_device::*; +pub use screen_device::*; +pub use stream_device::*; +pub use file_device::*; diff --git a/src/devices/remote_device.rs b/src/devices/remote_device.rs deleted file mode 100644 index f50ac7a..0000000 --- a/src/devices/remote_device.rs +++ /dev/null @@ -1,35 +0,0 @@ -use bedrock_core::*; - - -pub struct RemoteDevice { - -} - -impl RemoteDevice { - pub fn new() -> Self { - Self { - - } - } -} - -impl Device for RemoteDevice { - fn read(&mut self, _port: u8) -> u8 { - todo!() - // match port { - // _ => unreachable!(), - // } - } - - fn write(&mut self, _port: u8, _value: u8) -> Option<Signal> { - todo!() - // match port { - // _ => unreachable!(), - // }; - // return None; - } - - fn wake(&mut self) -> bool { - false - } -} diff --git a/src/devices/screen_device.rs b/src/devices/screen_device.rs index a10ab20..4c3f5ab 100644 --- a/src/devices/screen_device.rs +++ b/src/devices/screen_device.rs @@ -1,21 +1,18 @@ use crate::*; -use bedrock_core::*; use geometry::*; use phosphor::*; -type Sprite = [[u8; 8]; 8]; + +pub type Sprite = [[u8; 8]; 8]; + #[derive(Clone, Copy)] pub enum Layer { Fg, Bg } pub struct ScreenDevice { - pub wake: bool, - pub accessed: bool, - /// Each byte represents a screen pixel, left-to-right and top-to-bottom. // Only the bottom four bits of each byte are used. - // TODO: Consider using the high bit of each pixel byte as a dirty bit. pub fg: Vec<u8>, pub bg: Vec<u8>, pub dirty: bool, @@ -32,18 +29,81 @@ pub struct ScreenDevice { pub palette_write: u16, pub palette: [Colour; 16], - pub colours: u16, + pub sprite_colours: u16, pub sprite: SpriteBuffer, + + pub accessed: bool, + pub wake: bool, +} + + +impl HasDimensions<u16> for ScreenDevice { + fn dimensions(&self) -> ScreenDimensions { + self.dimensions + } } + +impl Device for ScreenDevice { + fn read(&mut self, port: u8) -> u8 { + self.accessed = true; + match port { + 0x0 => read_h!(self.cursor.x), + 0x1 => read_l!(self.cursor.x), + 0x2 => read_h!(self.cursor.y), + 0x3 => read_l!(self.cursor.y), + 0x4 => read_h!(self.dimensions.width), + 0x5 => read_l!(self.dimensions.width), + 0x6 => read_h!(self.dimensions.height), + 0x7 => read_l!(self.dimensions.height), + 0x8 => 0, + 0x9 => 0, + 0xa => 0, + 0xb => 0, + 0xc => 0, + 0xd => 0, + 0xe => 0, + 0xf => 0, + _ => unreachable!(), + } + } + + fn write(&mut self, port: u8, value: u8) -> Option<Signal> { + self.accessed = true; + match port { + 0x0 => write_h!(self.cursor.x, value), + 0x1 => write_l!(self.cursor.x, value), + 0x2 => write_h!(self.cursor.y, value), + 0x3 => write_l!(self.cursor.y, value), + 0x4 => write_h!(self.width_write, value), + 0x5 => { write_l!(self.width_write, value); self.resize_width() } + 0x6 => write_h!(self.height_write, value), + 0x7 => { write_l!(self.height_write, value); self.resize_height() } + 0x8 => write_h!(self.palette_write, value), + 0x9 => { write_l!(self.palette_write, value); self.set_palette() } + 0xa => write_h!(self.sprite_colours, value), + 0xb => write_l!(self.sprite_colours, value), + 0xc => self.sprite.push_byte(value), + 0xd => self.sprite.push_byte(value), + 0xe => self.draw_dispatch(value), + 0xf => self.move_cursor(value), + _ => unreachable!(), + }; + return None; + } + + fn wake(&mut self) -> bool { + self.accessed = true; + std::mem::take(&mut self.wake) + } +} + + impl ScreenDevice { pub fn new(config: &EmulatorConfig) -> Self { - let area = config.dimensions.area_usize(); + let area = config.initial_dimensions.area_usize(); Self { - wake: false, - accessed: false, - fg: vec![0; area], bg: vec![0; area], dirty: false, @@ -51,7 +111,7 @@ impl ScreenDevice { cursor: ScreenPosition::ZERO, vector: ScreenPosition::ZERO, - dimensions: config.dimensions, + dimensions: config.initial_dimensions, dirty_dimensions: true, width_write: 0, height_write: 0, @@ -60,12 +120,15 @@ impl ScreenDevice { palette_write: 0, palette: [Colour::BLACK; 16], - colours: 0, + sprite_colours: 0, sprite: SpriteBuffer::new(), + + accessed: false, + wake: false, } } - /// External resize. + /// Resize screen to match window dimensions. pub fn resize(&mut self, dimensions: phosphor::Dimensions) { // Replace dimensions with fixed dimensions. let screen_dimensions = ScreenDimensions { @@ -231,10 +294,10 @@ impl ScreenDevice { false => self.sprite.read_1bit_sprite(draw), }; let colours = [ - (self.colours >> 12 & 0x000f) as u8, - (self.colours >> 8 & 0x000f) as u8, - (self.colours >> 4 & 0x000f) as u8, - (self.colours & 0x000f) as u8, + (self.sprite_colours >> 12 & 0x000f) as u8, + (self.sprite_colours >> 8 & 0x000f) as u8, + (self.sprite_colours >> 4 & 0x000f) as u8, + (self.sprite_colours & 0x000f) as u8, ]; let cx = self.cursor.x; let cy = self.cursor.y; @@ -279,8 +342,8 @@ impl ScreenDevice { if draw & 0x10 != 0 { // Draw 1-bit textured line. let sprite = self.sprite.read_1bit_sprite(draw); - let c1 = (self.colours >> 8 & 0xf) as u8; - let c0 = (self.colours >> 12 & 0xf) as u8; + let c1 = (self.sprite_colours >> 8 & 0xf) as u8; + let c0 = (self.sprite_colours >> 12 & 0xf) as u8; let opaque = draw & 0x08 == 0; loop { let sprite_pixel = sprite[(y as usize) % 8][(x as usize) % 8]; @@ -333,8 +396,8 @@ impl ScreenDevice { if draw & 0x10 != 0 { // Draw 1-bit textured rectangle. let sprite = self.sprite.read_1bit_sprite(draw); - let c1 = (self.colours >> 8 & 0xf) as u8; - let c0 = (self.colours >> 12 & 0xf) as u8; + let c1 = (self.sprite_colours >> 8 & 0xf) as u8; + let c0 = (self.sprite_colours >> 12 & 0xf) as u8; let opaque = draw & 0x08 == 0; for y in t..=b { for x in l..=r { @@ -355,146 +418,4 @@ impl ScreenDevice { } } -impl Device for ScreenDevice { - fn read(&mut self, port: u8) -> u8 { - self.accessed = true; - match port { - 0x0 => read_h!(self.dimensions.width), - 0x1 => read_l!(self.dimensions.width), - 0x2 => read_h!(self.dimensions.height), - 0x3 => read_l!(self.dimensions.height), - 0x4 => read_h!(self.cursor.x), - 0x5 => read_l!(self.cursor.x), - 0x6 => read_h!(self.cursor.y), - 0x7 => read_l!(self.cursor.y), - 0x8 => 0, - 0x9 => 0, - 0xa => 0, - 0xb => 0, - 0xc => 0, - 0xd => 0, - 0xe => 0, - 0xf => 0, - _ => unreachable!(), - } - } - - fn write(&mut self, port: u8, value: u8) -> Option<Signal> { - self.accessed = true; - match port { - 0x0 => write_h!(self.width_write, value), - 0x1 => { write_l!(self.width_write, value); self.resize_width(); }, - 0x2 => write_h!(self.height_write, value), - 0x3 => { write_l!(self.height_write, value); self.resize_height(); }, - 0x4 => write_h!(self.cursor.x, value), - 0x5 => write_l!(self.cursor.x, value), - 0x6 => write_h!(self.cursor.y, value), - 0x7 => write_l!(self.cursor.y, value), - 0x8 => write_h!(self.palette_write, value), - 0x9 => { write_l!(self.palette_write, value); self.set_palette(); }, - 0xa => write_h!(self.colours, value), - 0xb => write_l!(self.colours, value), - 0xc => self.sprite.push_byte(value), - 0xd => self.sprite.push_byte(value), - 0xe => self.draw_dispatch(value), - 0xf => self.move_cursor(value), - _ => unreachable!(), - }; - return None; - } - - fn wake(&mut self) -> bool { - self.accessed = true; - std::mem::take(&mut self.wake) - } -} - -impl HasDimensions<u16> for ScreenDevice { - fn dimensions(&self) -> ScreenDimensions { - self.dimensions - } -} - - -pub struct SpriteBuffer { - pub mem: [u8; 16], - pub pointer: usize, - pub cached: Option<(Sprite, u8)>, -} - -impl SpriteBuffer { - pub fn new() -> Self { - Self { - mem: [0; 16], - pointer: 0, - cached: None, - } - } - - pub fn push_byte(&mut self, byte: u8) { - self.mem[self.pointer] = byte; - self.pointer = (self.pointer + 1) % 16; - self.cached = None; - } - - pub fn read_1bit_sprite(&mut self, draw: u8) -> Sprite { - if let Some((sprite, transform)) = self.cached { - if transform == (draw & 0x77) { - return sprite; - } - } - macro_rules! c { - ($v:ident=mem[$p:ident++]) => { let $v = self.mem[$p % 16]; $p = $p.wrapping_add(1); }; - ($v:ident=mem[--$p:ident]) => { $p = $p.wrapping_sub(1); let $v = self.mem[$p % 16]; }; - } - let mut sprite = [[0; 8]; 8]; - let mut p = match draw & 0x02 != 0 { - true => self.pointer, - false => self.pointer + 8, - }; - match draw & 0x07 { - 0x0 => { for y in 0..8 { c!(l=mem[p++]); for x in 0..8 { sprite[y][x] = l>>(7-x) & 1; } } }, - 0x1 => { for y in 0..8 { c!(l=mem[p++]); for x in 0..8 { sprite[y][x] = l>>( x) & 1; } } }, - 0x2 => { for y in 0..8 { c!(l=mem[--p]); for x in 0..8 { sprite[y][x] = l>>(7-x) & 1; } } }, - 0x3 => { for y in 0..8 { c!(l=mem[--p]); for x in 0..8 { sprite[y][x] = l>>( x) & 1; } } }, - 0x4 => { for y in 0..8 { c!(l=mem[p++]); for x in 0..8 { sprite[x][y] = l>>(7-x) & 1; } } }, - 0x5 => { for y in 0..8 { c!(l=mem[p++]); for x in 0..8 { sprite[x][y] = l>>( x) & 1; } } }, - 0x6 => { for y in 0..8 { c!(l=mem[--p]); for x in 0..8 { sprite[x][y] = l>>(7-x) & 1; } } }, - 0x7 => { for y in 0..8 { c!(l=mem[--p]); for x in 0..8 { sprite[x][y] = l>>( x) & 1; } } }, - _ => unreachable!(), - } - self.cached = Some((sprite, draw & 0x77)); - return sprite; - } - pub fn read_2bit_sprite(&mut self, draw: u8) -> Sprite { - if let Some((sprite, transform)) = self.cached { - if transform == (draw & 0x77) { - return sprite; - } - } - macro_rules! c { - ($v:ident=mem[$p:ident++]) => { let $v = self.mem[$p % 16]; $p = $p.wrapping_add(1); }; - ($v:ident=mem[--$p:ident]) => { $p = $p.wrapping_sub(1); let $v = self.mem[$p % 16]; }; - } - let mut sprite = [[0; 8]; 8]; - let mut p = match draw & 0x02 != 0 { - true => self.pointer, - false => self.pointer + 8, - }; - let mut s = p + 8; - match draw & 0x07 { - 0x0 => for y in 0..8 { c!(l=mem[p++]); c!(h=mem[s++]); for x in 0..8 { let i=7-x; sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }, - 0x1 => for y in 0..8 { c!(l=mem[p++]); c!(h=mem[s++]); for x in 0..8 { let i= x; sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }, - 0x2 => for y in 0..8 { c!(l=mem[--p]); c!(h=mem[--s]); for x in 0..8 { let i=7-x; sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }, - 0x3 => for y in 0..8 { c!(l=mem[--p]); c!(h=mem[--s]); for x in 0..8 { let i= x; sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }, - 0x4 => for y in 0..8 { c!(l=mem[p++]); c!(h=mem[s++]); for x in 0..8 { let i=7-x; sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }, - 0x5 => for y in 0..8 { c!(l=mem[p++]); c!(h=mem[s++]); for x in 0..8 { let i= x; sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }, - 0x6 => for y in 0..8 { c!(l=mem[--p]); c!(h=mem[--s]); for x in 0..8 { let i=7-x; sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }, - 0x7 => for y in 0..8 { c!(l=mem[--p]); c!(h=mem[--s]); for x in 0..8 { let i= x; sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }, - _ => unreachable!(), - } - self.cached = Some((sprite, draw & 0x77)); - return sprite; - } -} diff --git a/src/devices/local_device.rs b/src/devices/stream_device.rs index c6456de..e44ffb8 100644 --- a/src/devices/local_device.rs +++ b/src/devices/stream_device.rs @@ -1,36 +1,91 @@ use crate::*; -use bedrock_core::*; - use std::collections::VecDeque; use std::io::{BufRead, Stdout, Write}; use std::sync::mpsc::{self, TryRecvError}; -pub struct LocalDevice { - wake: bool, - +pub struct StreamDevice { + /// True if a source is connected to stdin. stdin_connected: bool, + /// True if a transmission is in progress. stdin_control: bool, stdin_rx: mpsc::Receiver<Vec<u8>>, + /// Bytes received in the current transmission. stdin_queue: VecDeque<u8>, + /// Bytes received since stdin end-of-transmission. stdin_excess: VecDeque<u8>, - stdout: Stdout, + stdout: Stdout, + /// True if a sink is connected to stdout. stdout_connected: bool, + /// True if stdin is transmission-encoded. decode_stdin: bool, + /// True if stdout should be transmission-encoded. encode_stdout: bool, + /// Half-byte buffer for decoding stdin. decode_buffer: Option<u8>, + + wake: bool, } -impl LocalDevice { - pub fn new(config: &EmulatorConfig) -> Self { - // Fill input queue with initial transmission. - let mut stdin_queue = VecDeque::new(); - if let Some(bytes) = &config.initial_transmission { - for byte in bytes { stdin_queue.push_front(*byte) } + +impl Device for StreamDevice { + fn read(&mut self, port: u8) -> u8 { + match port { + 0x0 => read_b!(self.stdin_connected), + 0x1 => read_b!(self.stdout_connected), + 0x2 => read_b!(self.stdin_control), + 0x3 => 0xff, + 0x4 => self.stdin_length(), + 0x5 => 0xff, + 0x6 => self.stdin_read(), + 0x7 => self.stdin_read(), + 0x8 => todo!(), + 0x9 => todo!(), + 0xa => todo!(), + 0xb => todo!(), + 0xc => todo!(), + 0xd => todo!(), + 0xe => todo!(), + 0xf => todo!(), + _ => unreachable!(), } + } + + fn write(&mut self, port: u8, value: u8) -> Option<Signal> { + match port { + 0x0 => (), + 0x1 => (), + 0x2 => self.stdin_enable(), + 0x3 => self.stdout_disable(), + 0x4 => (), + 0x5 => (), + 0x6 => self.stdout_write(value), + 0x7 => self.stdout_write(value), + 0x8 => todo!(), + 0x9 => todo!(), + 0xa => todo!(), + 0xb => todo!(), + 0xc => todo!(), + 0xd => todo!(), + 0xe => todo!(), + 0xf => todo!(), + _ => unreachable!(), + }; + return None; + } + + fn wake(&mut self) -> bool { + self.fetch_stdin_data(); + std::mem::take(&mut self.wake) + } +} + + +impl StreamDevice { + pub fn new(config: &EmulatorConfig) -> Self { // Spawn a thread to enable non-blocking reads of stdin. let (stdin_tx, stdin_rx) = std::sync::mpsc::channel(); std::thread::spawn(move || loop { @@ -46,20 +101,20 @@ impl LocalDevice { }); Self { - wake: true, - stdin_connected: true, stdin_control: false, stdin_rx, - stdin_queue, + stdin_queue: VecDeque::new(), stdin_excess: VecDeque::new(), - stdout: std::io::stdout(), + stdout: std::io::stdout(), stdout_connected: true, decode_stdin: config.decode_stdin, encode_stdout: config.encode_stdout, decode_buffer: None, + + wake: true, } } @@ -108,6 +163,7 @@ impl LocalDevice { } } + /// Fetch all pending data from stdin. pub fn fetch_stdin_data(&mut self) { while self.stdin_control { match self.stdin_excess.pop_front() { @@ -115,21 +171,26 @@ impl LocalDevice { None => break, } } - match self.stdin_rx.try_recv() { - Ok(tx) => { - for byte in tx { - match self.stdin_control { - true => self.fetch_byte(byte), - false => self.stdin_excess.push_back(byte), + loop { + match self.stdin_rx.try_recv() { + Ok(tx) => { + for byte in tx { + match self.stdin_control { + true => self.fetch_byte(byte), + false => self.stdin_excess.push_back(byte), + } } } - } - Err(TryRecvError::Empty) => (), - Err(TryRecvError::Disconnected) => { - self.stdin_control = false; - if self.stdin_connected { - self.stdin_connected = false; - self.wake = true; // wake because stdin was disconnected. + Err(TryRecvError::Empty) => { + break; + } + Err(TryRecvError::Disconnected) => { + self.stdin_control = false; + if self.stdin_connected { + self.stdin_connected = false; + self.wake = true; // wake because stdin was disconnected. + } + break; } } } @@ -167,61 +228,8 @@ impl LocalDevice { } -impl Drop for LocalDevice { +impl Drop for StreamDevice { fn drop(&mut self) { self.flush(); } } - - -impl Device for LocalDevice { - fn read(&mut self, port: u8) -> u8 { - match port { - 0x0 => read_b!(self.stdin_connected), - 0x1 => 0xff, - 0x2 => read_b!(self.stdin_control), - 0x3 => 0xff, - 0x4 => self.stdin_length(), - 0x5 => 0xff, - 0x6 => self.stdin_read(), - 0x7 => self.stdin_read(), - 0x8 => todo!(), - 0x9 => todo!(), - 0xa => todo!(), - 0xb => todo!(), - 0xc => todo!(), - 0xd => todo!(), - 0xe => todo!(), - 0xf => todo!(), - _ => unreachable!(), - } - } - - fn write(&mut self, port: u8, value: u8) -> Option<Signal> { - match port { - 0x0 => (), - 0x1 => (), - 0x2 => self.stdin_enable(), - 0x3 => self.stdout_disable(), - 0x4 => self.stdin_queue.clear(), - 0x5 => (), - 0x6 => self.stdout_write(value), - 0x7 => self.stdout_write(value), - 0x8 => todo!(), - 0x9 => todo!(), - 0xa => todo!(), - 0xb => todo!(), - 0xc => todo!(), - 0xd => todo!(), - 0xe => todo!(), - 0xf => todo!(), - _ => unreachable!(), - }; - return None; - } - - fn wake(&mut self) -> bool { - self.fetch_stdin_data(); - std::mem::take(&mut self.wake) - } -} diff --git a/src/devices/system_device.rs b/src/devices/system_device.rs index 383bb08..10ddad1 100644 --- a/src/devices/system_device.rs +++ b/src/devices/system_device.rs @@ -1,76 +1,69 @@ -use bedrock_core::*; +use crate::*; pub struct SystemDevice { + /// Name and version of this system. pub name: ReadBuffer, + /// Authors of this system. pub authors: ReadBuffer, - pub can_wake: u16, - pub wake_id: u8, + /// Mask of all devices waiting to wake from sleep. + pub wakers: u16, + /// Device that most recently woke the system. + pub waker: u8, + /// True if the system has been put to sleep. pub asleep: bool, + /// Mask of all available devices. + pub devices: u16, + /// Name of the first custom devices. + pub custom1: ReadBuffer, + /// Name of the second custom devices. + pub custom2: ReadBuffer, + /// Name of the third custom devices. + pub custom3: ReadBuffer, + /// Name of the fourth custom devices. + pub custom4: ReadBuffer, } -impl SystemDevice { - pub fn new() -> Self { - let pkg_version = env!("CARGO_PKG_VERSION"); - let pkg_name = env!("CARGO_PKG_NAME"); - let pkg_authors = env!("CARGO_PKG_AUTHORS"); - let name_str = format!("{pkg_name}/{pkg_version}"); - let authors_str = pkg_authors.replace(":", "\n"); - Self { - name: ReadBuffer::from_str(&name_str), - authors: ReadBuffer::from_str(&authors_str), - can_wake: 0, - wake_id: 0, - asleep: false, - } - } - - pub fn can_wake(&self, dev_id: u8) -> bool { - test_bit!(self.can_wake, 0x8000 >> dev_id) - } -} impl Device for SystemDevice { fn read(&mut self, port: u8) -> u8 { match port { - 0x0 => self.name.read(), - 0x1 => self.authors.read(), - 0x2 => 0x00, + 0x0 => 0x00, + 0x1 => 0x00, + 0x2 => self.waker, 0x3 => 0x00, 0x4 => 0x00, 0x5 => 0x00, - 0x6 => 0b1111_1100, - 0x7 => 0b0000_0000, // TODO: Update when fs and stream implemented - 0x8 => 0x00, - 0x9 => 0x00, - 0xa => self.wake_id, + 0x6 => 0x00, + 0x7 => 0x00, + 0x8 => self.name.read(), + 0x9 => self.authors.read(), + 0xa => 0x00, 0xb => 0x00, 0xc => 0x00, 0xd => 0x00, - 0xe => 0x00, - 0xf => 0x00, + 0xe => read_h!(self.devices), + 0xf => read_l!(self.devices), _ => unreachable!(), } } fn write(&mut self, port: u8, value: u8) -> Option<Signal> { match port { - 0x0 => self.name.pointer = 0, - 0x1 => self.authors.pointer = 0, + 0x0 => write_h!(self.wakers, value), + 0x1 => { write_l!(self.wakers, value); + self.asleep = true; + return Some(Signal::Sleep); }, 0x2 => (), - 0x3 => (), + 0x3 => return Some(Signal::Fork), 0x4 => (), 0x5 => (), 0x6 => (), 0x7 => (), - 0x8 => write_h!(self.can_wake, value), - 0x9 => { - write_l!(self.can_wake, value); - self.asleep = true; - return Some(Signal::Sleep); - }, + 0x8 => self.name.pointer = 0, + 0x9 => self.authors.pointer = 0, 0xa => (), - 0xb => return Some(Signal::Fork), + 0xb => (), 0xc => (), 0xd => (), 0xe => (), @@ -81,31 +74,41 @@ impl Device for SystemDevice { } fn wake(&mut self) -> bool { - true + false } } -pub struct ReadBuffer { - pub bytes: Vec<u8>, - pub pointer: usize, -} - -impl ReadBuffer { - pub fn from_str(text: &str) -> Self { +impl SystemDevice { + pub fn new(devices: u16) -> Self { Self { - bytes: text.bytes().collect(), - pointer: 0, + name: get_name(), + authors: get_authors(), + wakers: 0, + waker: 0, + asleep: false, + devices, + custom1: ReadBuffer::new(), + custom2: ReadBuffer::new(), + custom3: ReadBuffer::new(), + custom4: ReadBuffer::new(), } } +} - pub fn read(&mut self) -> u8 { - let pointer = self.pointer; - self.pointer += 1; - match self.bytes.get(pointer) { - Some(byte) => *byte, - None => 0, - } - } + +fn get_name() -> ReadBuffer { + let pkg_version = env!("CARGO_PKG_VERSION"); + let pkg_name = env!("CARGO_PKG_NAME"); + ReadBuffer::from_str(&format!("{pkg_name}/{pkg_version}")) } +fn get_authors() -> ReadBuffer { + let pkg_authors = env!("CARGO_PKG_AUTHORS"); + let mut authors_string = String::new(); + for author in pkg_authors.split(':') { + authors_string.push_str(author); + authors_string.push('\n'); + } + ReadBuffer::from_str(&authors_string) +} diff --git a/src/emulators.rs b/src/emulators.rs deleted file mode 100644 index 56f7181..0000000 --- a/src/emulators.rs +++ /dev/null @@ -1,32 +0,0 @@ -mod headless_emulator; -mod graphical_emulator; - -pub use headless_emulator::{HeadlessEmulator, HeadlessDeviceBus}; -pub use graphical_emulator::{GraphicalEmulator, GraphicalDeviceBus}; - -use crate::*; - -use phosphor::Colour; - -use std::path::PathBuf; - - -pub enum EmulatorSignal { - Promote, - Halt, -} - - -pub struct EmulatorConfig { - pub dimensions: ScreenDimensions, - pub fullscreen: bool, - pub scale: u32, - pub debug_palette: Option<[Colour; 16]>, - pub show_cursor: bool, - - pub initial_transmission: Option<Vec<u8>>, - pub decode_stdin: bool, - pub encode_stdout: bool, - - pub symbols_path: Option<PathBuf>, -} diff --git a/src/emulators/dynamic_emulator.rs b/src/emulators/dynamic_emulator.rs new file mode 100644 index 0000000..b5126d0 --- /dev/null +++ b/src/emulators/dynamic_emulator.rs @@ -0,0 +1,170 @@ +use crate::*; + + +pub struct DynamicEmulator { + pub br: BedrockEmulator<DynamicDeviceBus>, + pub debug: DebugState, +} + + +impl DynamicEmulator { + pub fn new(config: &EmulatorConfig, debug: bool) -> Self { + Self { + br: BedrockEmulator::new(DynamicDeviceBus::new(config)), + debug: DebugState::new(debug, config.symbols_path.as_ref()), + } + } + + pub fn load_program(&mut self, bytecode: &[u8]) { + self.br.core.mem.load_program(bytecode); + } + + pub fn run(&mut self) { + loop { + match self.br.evaluate(BATCH_SIZE, self.debug.enabled) { + Some(Signal::Fork) => { + self.br.core.mem.pc = 0; + self.br.core.wst.sp = 0; + self.br.core.rst.sp = 0; + } + Some(Signal::Sleep) => loop { + if self.br.dev.wake() { break; } + std::thread::sleep(MIN_TICK_DURATION); + } + Some(Signal::Halt) => { + self.br.dev.stream.flush(); + info!("Program halted, exiting."); + self.debug.debug_summary(&self.br.core); + std::process::exit(0); + } + Some(Signal::Debug(Debug::Debug1)) => { + self.debug.debug_summary(&self.br.core); + } + _ => (), + } + if self.br.dev.input.accessed || self.br.dev.screen.accessed { + return; + } + } + } + + pub fn to_graphical(self, config: EmulatorConfig) -> GraphicalEmulator { + GraphicalEmulator { + br: BedrockEmulator { + core: self.br.core, + dev: GraphicalDeviceBus { + system: self.br.dev.system, + memory: self.br.dev.memory, + math: self.br.dev.math, + clock: self.br.dev.clock, + input: self.br.dev.input, + screen: self.br.dev.screen, + stream: self.br.dev.stream, + file: self.br.dev.file, + } + }, + debug: self.debug, + + fullscreen: config.fullscreen, + scale: config.zoom.into(), + show_override_palette: config.override_palette.is_some(), + render_mark: Instant::now(), + + config, + } + } + + pub fn to_headless(self) -> HeadlessEmulator { + HeadlessEmulator { + br: BedrockEmulator { + core: self.br.core, + dev: HeadlessDeviceBus { + system: self.br.dev.system, + memory: self.br.dev.memory, + math: self.br.dev.math, + clock: self.br.dev.clock, + stream: self.br.dev.stream, + file: self.br.dev.file, + } + }, + debug: self.debug, + } + } +} + + +pub struct DynamicDeviceBus { + pub system: SystemDevice, + pub memory: MemoryDevice, + pub math: MathDevice, + pub clock: ClockDevice, + pub input: InputDevice, + pub screen: ScreenDevice, + pub stream: StreamDevice, + pub file: FileDevice, +} + +impl DynamicDeviceBus { + pub fn new(config: &EmulatorConfig) -> Self { + Self { + system: SystemDevice::new(0b1111_1100_1010_0000), + memory: MemoryDevice::new(), + math: MathDevice::new(), + clock: ClockDevice::new(), + input: InputDevice::new(), + screen: ScreenDevice::new(&config), + stream: StreamDevice::new(&config), + file: FileDevice::new(), + } + } +} + + +impl DeviceBus for DynamicDeviceBus { + fn read(&mut self, port: u8) -> u8 { + match port & 0xf0 { + 0x00 => self.system.read(port & 0x0f), + 0x10 => self.memory.read(port & 0x0f), + 0x20 => self.math .read(port & 0x0f), + 0x30 => self.clock .read(port & 0x0f), + 0x40 => self.input .read(port & 0x0f), + 0x50 => self.screen.read(port & 0x0f), + 0x80 => self.stream.read(port & 0x0f), + 0xa0 => self.file .read(port & 0x0f), + _ => 0 + } + } + + fn write(&mut self, port: u8, value: u8) -> Option<Signal> { + match port & 0xf0 { + 0x00 => self.system.write(port & 0x0f, value), + 0x10 => self.memory.write(port & 0x0f, value), + 0x20 => self.math .write(port & 0x0f, value), + 0x30 => self.clock .write(port & 0x0f, value), + 0x40 => { self.input .write(port & 0x0f, value); Some(Signal::Interrupt) } + 0x50 => { self.screen.write(port & 0x0f, value); Some(Signal::Interrupt) } + 0x80 => self.stream.write(port & 0x0f, value), + 0xa0 => self.file .write(port & 0x0f, value), + _ => None + } + } + + fn wake(&mut self) -> bool { + macro_rules! rouse { + ($id:expr, $dev:ident) => { + let is_eligible = test_bit!(self.system.wakers, 0x8000 >> $id); + if is_eligible && self.$dev.wake() { + self.system.waker = $id; + self.system.asleep = false; + return true; + } + }; + } + rouse!(0xa, file ); + rouse!(0x8, stream); + rouse!(0x5, screen); + rouse!(0x4, input ); + rouse!(0x3, clock ); + return false; + } +} diff --git a/src/emulators/graphical_emulator.rs b/src/emulators/graphical_emulator.rs index 1c58a34..c4b06c8 100644 --- a/src/emulators/graphical_emulator.rs +++ b/src/emulators/graphical_emulator.rs @@ -1,128 +1,30 @@ use crate::*; -use bedrock_core::*; - -use phosphor::*; - -use std::time::Instant; - - -pub struct GraphicalDeviceBus { - pub sys: SystemDevice, - pub mem: MemoryDevice, - pub mat: MathDevice, - pub clk: ClockDevice, - pub inp: InputDevice, - pub scr: ScreenDevice, - pub loc: LocalDevice, - pub rem: RemoteDevice, - pub fs1: FileDevice, - pub fs2: FileDevice, -} - -impl GraphicalDeviceBus { - pub fn new(config: &EmulatorConfig) -> Self { - Self { - sys: SystemDevice::new(), - mem: MemoryDevice::new(), - mat: MathDevice::new(), - clk: ClockDevice::new(), - inp: InputDevice::new(), - scr: ScreenDevice::new(config), - loc: LocalDevice::new(config), - rem: RemoteDevice::new(), - fs1: FileDevice::new(), - fs2: FileDevice::new(), - } - } - - pub fn graphical(&self) -> bool { - self.inp.accessed || self.scr.accessed - } -} - -impl DeviceBus for GraphicalDeviceBus { - fn read(&mut self, port: u8) -> u8 { - match port & 0xf0 { - 0x00 => self.sys.read(port & 0x0f), - 0x10 => self.mem.read(port & 0x0f), - 0x20 => self.mat.read(port & 0x0f), - 0x30 => self.clk.read(port & 0x0f), - 0x40 => self.inp.read(port & 0x0f), - 0x50 => self.scr.read(port & 0x0f), - 0x80 => self.loc.read(port & 0x0f), - 0x90 => self.rem.read(port & 0x0f), - 0xa0 => self.fs1.read(port & 0x0f), - 0xb0 => self.fs2.read(port & 0x0f), - _ => 0 - } - } - - fn write(&mut self, port: u8, value: u8) -> Option<Signal> { - match port & 0xf0 { - 0x00 => self.sys.write(port & 0x0f, value), - 0x10 => self.mem.write(port & 0x0f, value), - 0x20 => self.mat.write(port & 0x0f, value), - 0x30 => self.clk.write(port & 0x0f, value), - 0x40 => self.inp.write(port & 0x0f, value), - 0x50 => self.scr.write(port & 0x0f, value), - 0x80 => self.loc.write(port & 0x0f, value), - 0x90 => self.rem.write(port & 0x0f, value), - 0xa0 => self.fs1.write(port & 0x0f, value), - 0xb0 => self.fs2.write(port & 0x0f, value), - _ => None - } - } - - fn wake(&mut self) -> bool { - macro_rules! rouse { - ($id:expr, $dev:ident) => { - if self.sys.can_wake($id) && self.$dev.wake() { - self.sys.wake_id = $id; - self.sys.asleep = false; - return true; - } - }; - } - rouse!(0xb, fs2); - rouse!(0xa, fs1); - rouse!(0x9, rem); - rouse!(0x8, loc); - rouse!(0x5, scr); - rouse!(0x4, inp); - rouse!(0x3, clk); - rouse!(0x2, mat); - rouse!(0x1, mem); - rouse!(0x0, sys); - return false; - } -} pub struct GraphicalEmulator { pub br: BedrockEmulator<GraphicalDeviceBus>, pub debug: DebugState, - pub dimensions: ScreenDimensions, + pub fullscreen: bool, pub scale: u32, pub render_mark: Instant, - pub debug_palette: Option<[Colour; 16]>, - pub show_debug_palette: bool, - pub show_cursor: bool, + pub show_override_palette: bool, + + pub config: EmulatorConfig, } impl GraphicalEmulator { - pub fn new(config: &EmulatorConfig, debug: bool) -> Self { - let devices = GraphicalDeviceBus::new(config); + pub fn new(config: EmulatorConfig, debug: bool) -> Self { Self { - br: BedrockEmulator::new(devices), + br: BedrockEmulator::new(GraphicalDeviceBus::new(&config)), debug: DebugState::new(debug, config.symbols_path.as_ref()), - dimensions: config.dimensions, + fullscreen: config.fullscreen, - scale: config.scale, + scale: config.zoom.into(), + show_override_palette: config.override_palette.is_some(), render_mark: Instant::now(), - debug_palette: config.debug_palette, - show_debug_palette: config.debug_palette.is_some(), - show_cursor: config.show_cursor, + + config, } } @@ -130,60 +32,47 @@ impl GraphicalEmulator { self.br.core.mem.load_program(bytecode); } - pub fn run(&mut self) -> EmulatorSignal { - loop { - match self.br.evaluate(BATCH_SIZE, self.debug.enabled) { - Some(Signal::Fork) => { - self.br.core.mem.pc = 0; - self.br.core.wst.sp = 0; - self.br.core.rst.sp = 0; - } - Some(Signal::Sleep) => loop { - if self.br.dev.graphical() { - return EmulatorSignal::Promote; - } - if self.br.dev.wake() { break; } - std::thread::sleep(MIN_TICK_DURATION); - } - Some(Signal::Halt) => { - self.br.dev.loc.flush(); - log::info!("Program halted, exiting."); - self.debug.debug_summary(&self.br.core); - return EmulatorSignal::Halt; - } - Some(Signal::Debug1) => { - self.debug.debug_summary(&self.br.core); - } - _ => (), - } + pub fn run(self, mut phosphor: Phosphor) { + let cursor = match self.config.show_cursor { + true => Some(CursorIcon::Default), + false => None, + }; - if self.br.dev.graphical() { - return EmulatorSignal::Promote; + let program_name = match &self.config.metadata { + Some(metadata) => match &metadata.name { + Some(name) => name.to_string(), + None => String::from("Bedrock"), } - } + None => String::from("Bedrock"), + }; + let window = WindowBuilder { + dimensions: Some(self.dimensions()), + size_bounds: Some(self.size_bounds()), + fullscreen: self.fullscreen, + scale: self.scale, + title: Some(program_name), + cursor, + icon: parse_icon_from_config(&self.config), + program: Box::new(self), + }; + + phosphor.create_window(window); + phosphor.run().unwrap(); } pub fn size_bounds(&self) -> SizeBounds { - macro_rules! to_u32 { - ($opt:expr) => { - match $opt { - Some(a) => Some(u32::from(a)), - None => None, - } - }; - } match self.fullscreen { true => SizeBounds { - min_width: None, - max_width: None, + min_width: None, + max_width: None, min_height: None, max_height: None, }, false => SizeBounds { - min_width: to_u32!(self.br.dev.scr.fixed_width), - max_width: to_u32!(self.br.dev.scr.fixed_width), - min_height: to_u32!(self.br.dev.scr.fixed_height), - max_height: to_u32!(self.br.dev.scr.fixed_height), + min_width: self.br.dev.screen.fixed_width.map(u32::from), + max_width: self.br.dev.screen.fixed_width.map(u32::from), + min_height: self.br.dev.screen.fixed_height.map(u32::from), + max_height: self.br.dev.screen.fixed_height.map(u32::from), }, } @@ -191,44 +80,121 @@ impl GraphicalEmulator { pub fn dimensions(&self) -> Dimensions { Dimensions { - width: u32::from(self.br.dev.scr.dimensions.width), - height: u32::from(self.br.dev.scr.dimensions.height), + width: self.br.dev.screen.dimensions.width.into(), + height: self.br.dev.screen.dimensions.height.into(), + } + } +} + + +pub struct GraphicalDeviceBus { + pub system: SystemDevice, + pub memory: MemoryDevice, + pub math: MathDevice, + pub clock: ClockDevice, + pub input: InputDevice, + pub screen: ScreenDevice, + pub stream: StreamDevice, + pub file: FileDevice, +} + +impl GraphicalDeviceBus { + pub fn new(config: &EmulatorConfig) -> Self { + Self { + system: SystemDevice::new(0b1111_1100_1010_0000), + memory: MemoryDevice::new(), + math: MathDevice::new(), + clock: ClockDevice::new(), + input: InputDevice::new(), + screen: ScreenDevice::new(&config), + stream: StreamDevice::new(&config), + file: FileDevice::new(), } } } +impl DeviceBus for GraphicalDeviceBus { + fn read(&mut self, port: u8) -> u8 { + match port & 0xf0 { + 0x00 => self.system.read(port & 0x0f), + 0x10 => self.memory.read(port & 0x0f), + 0x20 => self.math .read(port & 0x0f), + 0x30 => self.clock .read(port & 0x0f), + 0x40 => self.input .read(port & 0x0f), + 0x50 => self.screen.read(port & 0x0f), + 0x80 => self.stream.read(port & 0x0f), + 0xa0 => self.file .read(port & 0x0f), + _ => 0 + } + } + + fn write(&mut self, port: u8, value: u8) -> Option<Signal> { + match port & 0xf0 { + 0x00 => self.system.write(port & 0x0f, value), + 0x10 => self.memory.write(port & 0x0f, value), + 0x20 => self.math .write(port & 0x0f, value), + 0x30 => self.clock .write(port & 0x0f, value), + 0x40 => self.input .write(port & 0x0f, value), + 0x50 => self.screen.write(port & 0x0f, value), + 0x80 => self.stream.write(port & 0x0f, value), + 0xa0 => self.file .write(port & 0x0f, value), + _ => None + } + } + + fn wake(&mut self) -> bool { + macro_rules! rouse { + ($id:expr, $dev:ident) => { + let is_eligible = test_bit!(self.system.wakers, 0x8000 >> $id); + if is_eligible && self.$dev.wake() { + self.system.waker = $id; + self.system.asleep = false; + return true; + } + }; + } + rouse!(0xa, file ); + rouse!(0x8, stream); + rouse!(0x5, screen); + rouse!(0x4, input ); + rouse!(0x3, clock ); + return false; + } +} + + impl WindowProgram for GraphicalEmulator { fn handle_event(&mut self, event: Event, r: &mut EventWriter<Request>) { match event { Event::CloseRequest => r.write(Request::CloseWindow), - Event::CursorEnter => self.br.dev.inp.on_cursor_enter(), - Event::CursorExit => self.br.dev.inp.on_cursor_exit(), - Event::CursorMove(p) => self.br.dev.inp.on_cursor_move(p), - Event::Resize(d) => self.br.dev.scr.resize(d), - Event::CharacterInput(c) => self.br.dev.inp.on_character(c), - Event::ModifierChange(m) => self.br.dev.inp.on_modifier(m), + Event::CursorEnter => self.br.dev.input.on_cursor_enter(), + Event::CursorExit => self.br.dev.input.on_cursor_exit(), + Event::CursorMove(p) => self.br.dev.input.on_cursor_move(p), + Event::Resize(d) => self.br.dev.screen.resize(d), + Event::CharacterInput(c) => self.br.dev.input.on_character(c), + Event::ModifierChange(m) => self.br.dev.input.on_modifier(m), Event::MouseButton { button, action } => - self.br.dev.inp.on_mouse_button(button, action), + self.br.dev.input.on_mouse_button(button, action), Event::FocusChange(_) => (), Event::Initialise => (), Event::ScrollLines { axis, distance } => match axis { - Axis::Horizontal => self.br.dev.inp.on_horizontal_scroll(distance), - Axis::Vertical => self.br.dev.inp.on_vertical_scroll(distance), + Axis::Horizontal => self.br.dev.input.on_horizontal_scroll(distance), + Axis::Vertical => self.br.dev.input.on_vertical_scroll(distance), } Event::ScrollPixels { axis, distance } => match axis { - Axis::Horizontal => self.br.dev.inp.on_horizontal_scroll(distance / 20.0), - Axis::Vertical => self.br.dev.inp.on_vertical_scroll(distance / 20.0), + Axis::Horizontal => self.br.dev.input.on_horizontal_scroll(distance / 20.0), + Axis::Vertical => self.br.dev.input.on_vertical_scroll(distance / 20.0), } Event::FileDrop(_path) => todo!("FileDrop"), Event::Close => (), Event::KeyboardInput { key, action } => { - self.br.dev.inp.on_keypress(key, action); + self.br.dev.input.on_keypress(key, action); if action == Action::Pressed { match key { KeyCode::F2 => { - self.show_debug_palette = !self.show_debug_palette; + self.show_override_palette = !self.show_override_palette; r.write(Request::Redraw); }, KeyCode::F5 => { @@ -251,12 +217,12 @@ impl WindowProgram for GraphicalEmulator { } fn process(&mut self, requests: &mut EventWriter<Request>) { - self.br.dev.loc.flush(); + self.br.dev.stream.flush(); - if self.br.dev.sys.asleep { + if self.br.dev.system.asleep { // Stay asleep if there are no pending wake events. if !self.br.dev.wake() { - if self.br.dev.scr.dirty { + if self.br.dev.screen.dirty { requests.write(Request::Redraw); } std::thread::sleep(MIN_TICK_DURATION); @@ -264,7 +230,7 @@ impl WindowProgram for GraphicalEmulator { } // Wait for the current frame to be rendered. - if self.br.dev.scr.dirty { + if self.br.dev.screen.dirty { if self.render_mark.elapsed() > MIN_FRAME_DURATION { requests.write(Request::Redraw); } @@ -281,30 +247,30 @@ impl WindowProgram for GraphicalEmulator { todo!("Fork") } Some(Signal::Sleep) => { - self.br.dev.sys.asleep = true; + self.br.dev.system.asleep = true; break; } Some(Signal::Halt) => { - self.br.dev.loc.flush(); + self.br.dev.stream.flush(); log::info!("Program halted, exiting."); self.debug.debug_summary(&self.br.core); requests.write(Request::CloseWindow); break; } - Some(Signal::Debug1) => { + Some(Signal::Debug(Debug::Debug1)) => { self.debug.debug_summary(&self.br.core); } _ => (), } } - if std::mem::take(&mut self.br.dev.scr.dirty_dimensions) { + if std::mem::take(&mut self.br.dev.screen.dirty_dimensions) { requests.write(Request::SetSizeBounds(self.size_bounds())); } - if self.br.dev.scr.dirty { + if self.br.dev.screen.dirty { let elapsed = self.render_mark.elapsed(); - if self.br.dev.sys.asleep && elapsed > MIN_FRAME_DURATION { + if self.br.dev.system.asleep && elapsed > MIN_FRAME_DURATION { requests.write(Request::Redraw); } else if elapsed > MAX_FRAME_DURATION { requests.write(Request::Redraw); @@ -315,15 +281,15 @@ impl WindowProgram for GraphicalEmulator { } fn render(&mut self, buffer: &mut Buffer, _full: bool) { - let screen = &mut self.br.dev.scr; + let screen = &mut self.br.dev.screen; // Generate table for calculating pixel colours from layer values. // A given screen pixel will be rendered as the colour given by // table[fg][bg], where fg and bg are the corresponding layer values. let mut table = [Colour::BLACK; 256]; - let palette = match self.debug_palette { - Some(debug_palette) => match self.show_debug_palette { - true => debug_palette, + let palette = match self.config.override_palette { + Some(override_palette) => match self.show_override_palette { + true => override_palette, false => screen.palette, } None => screen.palette, @@ -376,3 +342,38 @@ impl WindowProgram for GraphicalEmulator { self.render_mark = Instant::now(); } } + + +fn parse_icon_from_config(config: &EmulatorConfig) -> Option<Icon> { + let metadata = config.metadata.as_ref()?; + let bytes = metadata.small_icon.as_ref()?; + let bg = metadata.bg_colour.unwrap_or(Colour::BLACK); + let fg = metadata.bg_colour.unwrap_or(Colour::WHITE); + let rgba = sprite_icon_to_rgb(bytes, 3, bg, fg); + match Icon::from_rgba(rgba, 24, 24) { + Ok(icon) => Some(icon), + Err(err) => unreachable!("Error while parsing small icon data: {err}"), + } +} + +fn sprite_icon_to_rgb(bytes: &[u8], size: usize, bg: Colour, fg: Colour) -> Vec<u8> { + let sprites: Vec<&[u8]> = bytes.chunks_exact(8).collect(); + let mut rgba = Vec::new(); + for sprite_row in 0..size { + for pixel_row in 0..8 { + for sprite_column in 0..size { + let sprite = &sprites[sprite_column + (sprite_row * size)]; + let row = &sprite[pixel_row]; + for bit in 0..8 { + let state = row & (0x80 >> bit); + let colour = match state != 0 { + true => fg, + false => bg, + }; + rgba.extend_from_slice(&colour.as_rgba_array()); + } + } + } + } + return rgba; +} diff --git a/src/emulators/headless_emulator.rs b/src/emulators/headless_emulator.rs index 9207b3d..b07b06e 100644 --- a/src/emulators/headless_emulator.rs +++ b/src/emulators/headless_emulator.rs @@ -1,83 +1,4 @@ use crate::*; -use bedrock_core::*; - - -pub struct HeadlessDeviceBus { - pub sys: SystemDevice, - pub mem: MemoryDevice, - pub mat: MathDevice, - pub clk: ClockDevice, - pub loc: LocalDevice, - pub rem: RemoteDevice, - pub fs1: FileDevice, - pub fs2: FileDevice, -} - -impl HeadlessDeviceBus { - pub fn new(config: &EmulatorConfig) -> Self { - Self { - sys: SystemDevice::new(), - mem: MemoryDevice::new(), - mat: MathDevice::new(), - clk: ClockDevice::new(), - loc: LocalDevice::new(config), - rem: RemoteDevice::new(), - fs1: FileDevice::new(), - fs2: FileDevice::new(), - } - } -} - -impl DeviceBus for HeadlessDeviceBus { - fn read(&mut self, port: u8) -> u8 { - match port & 0xf0 { - 0x00 => self.sys.read(port & 0x0f), - 0x10 => self.mem.read(port & 0x0f), - 0x20 => self.mat.read(port & 0x0f), - 0x30 => self.clk.read(port & 0x0f), - 0x80 => self.loc.read(port & 0x0f), - 0x90 => self.rem.read(port & 0x0f), - 0xa0 => self.fs1.read(port & 0x0f), - 0xb0 => self.fs2.read(port & 0x0f), - _ => 0 - } - } - - fn write(&mut self, port: u8, value: u8) -> Option<Signal> { - match port & 0xf0 { - 0x00 => self.sys.write(port & 0x0f, value), - 0x10 => self.mem.write(port & 0x0f, value), - 0x20 => self.mat.write(port & 0x0f, value), - 0x30 => self.clk.write(port & 0x0f, value), - 0x80 => self.loc.write(port & 0x0f, value), - 0x90 => self.rem.write(port & 0x0f, value), - 0xa0 => self.fs1.write(port & 0x0f, value), - 0xb0 => self.fs2.write(port & 0x0f, value), - _ => None - } - } - - fn wake(&mut self) -> bool { - macro_rules! rouse { - ($id:expr, $dev:ident) => { - if self.sys.can_wake($id) && self.$dev.wake() { - self.sys.wake_id = $id; - self.sys.asleep = false; - return true; - } - }; - } - rouse!(0xb, fs2); - rouse!(0xa, fs1); - rouse!(0x9, rem); - rouse!(0x8, loc); - rouse!(0x3, clk); - rouse!(0x2, mat); - rouse!(0x1, mem); - rouse!(0x0, sys); - return false; - } -} pub struct HeadlessEmulator { @@ -85,6 +6,7 @@ pub struct HeadlessEmulator { pub debug: DebugState, } + impl HeadlessEmulator { pub fn new(config: &EmulatorConfig, debug: bool) -> Self { Self { @@ -97,7 +19,7 @@ impl HeadlessEmulator { self.br.core.mem.load_program(bytecode); } - pub fn run(&mut self, debug: bool) -> EmulatorSignal { + pub fn run(&mut self) -> ! { loop { match self.br.evaluate(BATCH_SIZE, self.debug.enabled) { Some(Signal::Fork) => { @@ -110,12 +32,12 @@ impl HeadlessEmulator { std::thread::sleep(MIN_TICK_DURATION); } Some(Signal::Halt) => { - self.br.dev.loc.flush(); - log::info!("Program halted, exiting."); + self.br.dev.stream.flush(); + info!("Program halted, exiting."); self.debug.debug_summary(&self.br.core); - return EmulatorSignal::Halt; + std::process::exit(0); } - Some(Signal::Debug1) => if debug { + Some(Signal::Debug(Debug::Debug1)) => { self.debug.debug_summary(&self.br.core); } _ => (), @@ -123,3 +45,70 @@ impl HeadlessEmulator { } } } + + +pub struct HeadlessDeviceBus { + pub system: SystemDevice, + pub memory: MemoryDevice, + pub math: MathDevice, + pub clock: ClockDevice, + pub stream: StreamDevice, + pub file: FileDevice, +} + +impl HeadlessDeviceBus { + pub fn new(config: &EmulatorConfig) -> Self { + Self { + system: SystemDevice::new(0b1111_0000_1010_0000), + memory: MemoryDevice::new(), + math: MathDevice::new(), + clock: ClockDevice::new(), + stream: StreamDevice::new(&config), + file: FileDevice::new(), + } + } +} + + +impl DeviceBus for HeadlessDeviceBus { + fn read(&mut self, port: u8) -> u8 { + match port & 0xf0 { + 0x00 => self.system.read(port & 0x0f), + 0x10 => self.memory.read(port & 0x0f), + 0x20 => self.math .read(port & 0x0f), + 0x30 => self.clock .read(port & 0x0f), + 0x80 => self.stream.read(port & 0x0f), + 0xa0 => self.file .read(port & 0x0f), + _ => 0 + } + } + + fn write(&mut self, port: u8, value: u8) -> Option<Signal> { + match port & 0xf0 { + 0x00 => self.system.write(port & 0x0f, value), + 0x10 => self.memory.write(port & 0x0f, value), + 0x20 => self.math .write(port & 0x0f, value), + 0x30 => self.clock .write(port & 0x0f, value), + 0x80 => self.stream.write(port & 0x0f, value), + 0xa0 => self.file .write(port & 0x0f, value), + _ => None + } + } + + fn wake(&mut self) -> bool { + macro_rules! rouse { + ($id:expr, $dev:ident) => { + let is_eligible = test_bit!(self.system.wakers, 0x8000 >> $id); + if is_eligible && self.$dev.wake() { + self.system.waker = $id; + self.system.asleep = false; + return true; + } + }; + } + rouse!(0xa, file ); + rouse!(0x8, stream); + rouse!(0x3, clock ); + return false; + } +} diff --git a/src/emulators/mod.rs b/src/emulators/mod.rs new file mode 100644 index 0000000..e723862 --- /dev/null +++ b/src/emulators/mod.rs @@ -0,0 +1,81 @@ +mod headless_emulator; +mod graphical_emulator; +mod dynamic_emulator; + +pub use headless_emulator::*; +pub use graphical_emulator::*; +pub use dynamic_emulator::*; + +use crate::*; + +use ansi::*; + + +pub struct EmulatorConfig { + pub initial_dimensions: ScreenDimensions, + pub fullscreen: bool, + pub zoom: NonZeroU32, + pub override_palette: Option<[Colour; 16]>, + pub show_cursor: bool, + + pub decode_stdin: bool, + pub encode_stdout: bool, + + pub symbols_path: Option<PathBuf>, + pub metadata: Option<ProgramMetadata>, +} + + +pub struct DebugState { + pub enabled: bool, + last_cycle: usize, + last_mark: Instant, + symbols: DebugSymbols, +} + +impl DebugState { + pub fn new<P: AsRef<Path>>(enabled: bool, symbols_path: Option<P>) -> Self { + Self { + enabled, + last_cycle: 0, + last_mark: Instant::now(), + symbols: DebugSymbols::from_path_opt(symbols_path), + } + } + + pub fn debug_summary(&mut self, core: &BedrockCore) { + if self.enabled { + let prev_pc = core.mem.pc.wrapping_sub(1); + eprintln!("\n PC: 0x{:04x} Cycles: {} (+{} in {:.2?})", + prev_pc, core.cycle, + core.cycle.saturating_sub(self.last_cycle), + self.last_mark.elapsed(), + ); + eprint!("WST: "); + debug_stack(&core.wst, 0x10); + eprint!("RST: "); + debug_stack(&core.rst, 0x10); + // Print information about the closest symbol. + if let Some(symbol) = self.symbols.for_address(prev_pc) { + let name = &symbol.name; + let address = &symbol.address; + eprint!("SYM: {BLUE}@{name}{NORMAL} 0x{address:04x}"); + if let Some(location) = &symbol.location { + eprint!(" {DIM}{location}{NORMAL}"); + } + eprintln!(); + } + } + self.last_cycle = core.cycle; + self.last_mark = Instant::now(); + } +} + + +fn debug_stack(stack: &Stack, len: usize) { + for i in 0..len { + if i == stack.sp as usize { eprint!("{YELLOW}"); } + eprint!("{:02x} ", stack.mem[i]); + } + eprintln!("{NORMAL}"); +} @@ -1,20 +1,25 @@ #![feature(bigint_helper_methods)] -#![feature(unchecked_shifts)] #![feature(seek_stream_len)] -#![feature(io_error_more)] +#![feature(unchecked_shifts)] -mod debug; mod devices; mod emulators; -mod metadata; +mod types; +mod load_program; -pub use debug::DebugState; pub use devices::*; pub use emulators::*; -pub use metadata::*; +pub use types::*; +pub use load_program::*; + +use bedrock_core::*; +use log::*; +use phosphor::*; use std::num::NonZeroU32; -use std::time::Duration; +use std::path::{Path, PathBuf}; +use std::time::{Duration, Instant}; + pub const BATCH_SIZE: usize = 1000; pub const MIN_TICK_DURATION: Duration = Duration::from_millis( 4 ); pub const MIN_FRAME_DURATION: Duration = Duration::from_millis( 14 ); diff --git a/src/load_program.rs b/src/load_program.rs new file mode 100644 index 0000000..0473207 --- /dev/null +++ b/src/load_program.rs @@ -0,0 +1,93 @@ +use crate::*; + +use std::io::Read; + + +pub struct Program { + pub bytecode: Vec<u8>, + pub path: Option<PathBuf>, +} + +impl Program { + fn path(&self) -> String { + match &self.path { + Some(path) => path.as_os_str().to_string_lossy().to_string(), + None => String::from("<unknown>"), + } + } +} + + +/// Load program from path or standard input. +pub fn load_program(path: Option<&PathBuf>) -> Program { + if let Some(path) = path { + if let Ok(program) = load_program_from_file(path) { + let length = program.bytecode.len(); + let path = program.path(); + info!("Loaded program from {path:?} ({length} bytes)"); + return program; + } else if let Some(program) = load_program_from_bedrock_path(path) { + let length = program.bytecode.len(); + let path = program.path(); + info!("Loaded program from {path:?} ({length} bytes)"); + return program; + } else { + fatal!("Could not read program from {path:?}"); + } + } else { + info!("Reading program from standard input..."); + if let Ok(program) = load_program_from_stdin() { + let length = program.bytecode.len(); + info!("Loaded program from standard input ({length} bytes)"); + return program; + } else { + fatal!("Could not read program from standard input"); + } + } +} + +/// Attempt to load program from a directory in the BEDROCK_PATH environment variable. +fn load_program_from_bedrock_path(path: &Path) -> Option<Program> { + // Only if the path can be treated as a file name. + if path.is_relative() && path.components().count() == 1 { + for base_path in std::env::var("BEDROCK_PATH").ok()?.split(':') { + let mut base_path = PathBuf::from(base_path); + if !base_path.is_absolute() { continue; } + base_path.push(path); + info!("Attempting to load program from {base_path:?}"); + if let Ok(program) = load_program_from_file(&base_path) { + return Some(program); + } + if path.extension().is_some() { continue; } + base_path.set_extension("br"); + info!("Attempting to load program from {base_path:?}"); + if let Ok(program) = load_program_from_file(&base_path) { + return Some(program); + } + } + } + return None; +} + +/// Attempt to load program from a specific file path. +fn load_program_from_file(path: &Path) -> Result<Program, std::io::Error> { + // Canonicalize paths so that symbolic links to program files resolve to + // the real program directory, which could contain a symbols file. + let path = match path.canonicalize() { + Ok(canonical) => canonical, + Err(_) => path.to_path_buf(), + }; + load_program_from_readable_source(std::fs::File::open(&path)?, Some(&path)) +} + +/// Attempt to load program from standard input. +fn load_program_from_stdin() -> Result<Program, std::io::Error> { + load_program_from_readable_source(std::io::stdin(), None) +} + +/// Attempt to load program from a source that implements std::io::Read. +fn load_program_from_readable_source(source: impl Read, path: Option<&Path>) -> Result<Program, std::io::Error> { + let mut bytecode = Vec::<u8>::new(); + source.take(65536).read_to_end(&mut bytecode)?; + return Ok(Program { bytecode, path: path.map(|p| p.to_path_buf()) }); +} diff --git a/src/metadata.rs b/src/metadata.rs deleted file mode 100644 index 7692434..0000000 --- a/src/metadata.rs +++ /dev/null @@ -1,127 +0,0 @@ -use phosphor::Colour; - - -pub fn parse_metadata(bytecode: &[u8]) -> Option<ProgramMetadata> { - MetadataParser::from_bytecode(bytecode).parse() -} - - -struct MetadataParser<'a> { - bytecode: &'a [u8], -} - -impl<'a> MetadataParser<'a> { - pub fn from_bytecode(bytecode: &'a [u8]) -> Self { - Self { bytecode } - } - - pub fn parse(self) -> Option<ProgramMetadata> { - macro_rules! array { - ($len:expr, $slice:expr) => {{ - let mut array = [0; $len]; - for i in 0..$len { array[i] = *$slice.get(i).unwrap_or(&0); } - array - }}; - } - - if self.range(0x00, 3) != &[0x41, 0x00, 0x20] { - return None; - } - - let (name, version) = split_name_version(self.string(self.double(0x10))); - let authors = self.string(self.double(0x12)).map(|string| { - string.lines().map(|line| line.to_string()).collect() - }); - - Some( ProgramMetadata { - bedrock_string: array!(7, self.range(0x03, 7)), - program_memory_size: match self.double(0x0a) { - 0 => 65536, double => double as usize }, - working_stack_size: match self.byte(0x0c) { - 0 => 256, byte => byte as usize }, - return_stack_size: match self.byte(0x0d) { - 0 => 256, byte => byte as usize }, - required_devices: self.double(0x0e), - name, - version, - authors, - description: self.string(self.double(0x14)), - path_buffer: match self.double(0x16) { - 0 => None, addr => Some(addr as usize) }, - small_icon: match self.double(0x18) { - 0 => None, addr => Some(array!(72, self.range(addr, 72))) }, - large_icon: match self.double(0x1a) { - 0 => None, addr => Some(array!(512, self.range(addr, 512))) }, - bg_colour: parse_colour(self.double(0x1c)), - fg_colour: parse_colour(self.double(0x1e)), - } ) - } - - fn byte(&self, address: u16) -> u8 { - *self.bytecode.get(address as usize).unwrap_or(&0) - } - - fn double(&self, address: u16) -> u16 { - u16::from_be_bytes([ - *self.bytecode.get(address as usize).unwrap_or(&0), - *self.bytecode.get(address as usize + 1).unwrap_or(&0), - ]) - } - - fn range(&self, address: u16, length: usize) -> &[u8] { - let start = address as usize; - let end = start + length; - self.bytecode.get(start..end).unwrap_or(&[]) - } - - fn string(&self, address: u16) -> Option<String> { - if address == 0 { return None; } - let start = address as usize; - let mut end = start; - while let Some(byte) = self.bytecode.get(end) { - match byte { 0 => break, _ => end += 1 } - } - let range = self.bytecode.get(start..end)?; - Some( String::from_utf8_lossy(range).to_string() ) - } -} - -fn split_name_version(string: Option<String>) -> (Option<String>, Option<String>) { - if let Some(string) = string { - match string.split_once('/') { - Some((left, right)) => (Some(left.to_string()), Some(right.to_string())), - None => (Some(string), None), - } - } else { - (None, None) - } -} - -fn parse_colour(double: u16) -> Option<Colour> { - let i = (double >> 12 ) as usize; - let r = (double >> 8 & 0xf) as u8 * 17; - let g = (double >> 4 & 0xf) as u8 * 17; - let b = (double & 0xf) as u8 * 17; - match i { - 0 => Some(Colour::from_rgb(r, g, b)), - _ => None, - } -} - - -pub struct ProgramMetadata { - pub bedrock_string: [u8; 7], - pub program_memory_size: usize, - pub working_stack_size: usize, - pub return_stack_size: usize, - pub required_devices: u16, - pub name: Option<String>, - pub version: Option<String>, - pub authors: Option<Vec<String>>, - pub description: Option<String>, - pub path_buffer: Option<usize>, - pub small_icon: Option<[u8; 72]>, - pub large_icon: Option<[u8; 512]>, - pub bg_colour: Option<Colour>, - pub fg_colour: Option<Colour>, -} diff --git a/src/devices/file_device/buffered_file.rs b/src/types/buffered_file.rs index 29e1fa3..5cdf0ea 100644 --- a/src/devices/file_device/buffered_file.rs +++ b/src/types/buffered_file.rs @@ -1,6 +1,5 @@ use std::fs::File; -use std::io::{BufReader, BufWriter}; -use std::io::{Read, Write}; +use std::io::{BufReader, BufWriter, Read, Write}; use std::io::{ErrorKind, Seek, SeekFrom}; diff --git a/src/types/debug_symbols.rs b/src/types/debug_symbols.rs new file mode 100644 index 0000000..f4fc412 --- /dev/null +++ b/src/types/debug_symbols.rs @@ -0,0 +1,59 @@ +use crate::*; + + +pub struct DebugSymbols { + symbols: Vec<DebugSymbol> +} + +impl DebugSymbols { + /// Load debug symbols from a symbols file. + pub fn from_path_opt<P: AsRef<Path>>(path: Option<P>) -> Self { + let mut symbols = Vec::new(); + if let Some(path) = path { + if let Ok(string) = std::fs::read_to_string(path) { + for line in string.lines() { + if let Some(symbol) = DebugSymbol::from_line(line) { + symbols.push(symbol); + } + } + } + } + symbols.sort_by_key(|s| s.address); + Self { symbols } + } + + /// Return the symbol matching a given address. + pub fn for_address(&self, address: u16) -> Option<&DebugSymbol> { + if self.symbols.is_empty() { return None; } + let symbol = match self.symbols.binary_search_by_key(&address, |s| s.address) { + Ok(index) => self.symbols.get(index)?, + Err(index) => self.symbols.get(index.checked_sub(1)?)?, + }; + Some(&symbol) + } +} + + +pub struct DebugSymbol { + pub address: u16, + pub name: String, + pub location: Option<String>, +} + +impl DebugSymbol { + pub fn from_line(line: &str) -> Option<Self> { + if let Some((address, line)) = line.split_once(' ') { + let address = u16::from_str_radix(address, 16).ok()?; + if let Some((name, location)) = line.split_once(' ') { + let name = name.to_string(); + let location = Some(location.to_string()); + Some( DebugSymbol { address, name, location } ) + } else { + let name = line.to_string(); + Some( DebugSymbol { address, name, location: None } ) + } + } else { + None + } + } +} diff --git a/src/devices/file_device/directory_listing.rs b/src/types/directory_listing.rs index 465efc7..f079217 100644 --- a/src/devices/file_device/directory_listing.rs +++ b/src/types/directory_listing.rs @@ -1,4 +1,4 @@ -use super::*; +use crate::*; pub struct DirectoryListing { diff --git a/src/devices/file_device/entry.rs b/src/types/entry_type.rs index d604bb7..6a9ac2d 100644 --- a/src/devices/file_device/entry.rs +++ b/src/types/entry_type.rs @@ -1,7 +1,8 @@ -use super::*; +use crate::*; use std::cmp::Ordering; + pub enum Entry { File(BufferedFile), Directory(DirectoryListing), diff --git a/src/devices/file_device/bedrock_file_path.rs b/src/types/file_path.rs index fdd8f79..7e6dbe8 100644 --- a/src/devices/file_device/bedrock_file_path.rs +++ b/src/types/file_path.rs @@ -1,7 +1,8 @@ -use super::*; +use crate::*; use std::cmp::Ordering; use std::ffi::OsString; +use std::path::Component; #[derive(Clone)] diff --git a/src/types/metadata.rs b/src/types/metadata.rs new file mode 100644 index 0000000..dde86fa --- /dev/null +++ b/src/types/metadata.rs @@ -0,0 +1,125 @@ +use crate::*; + + +const SMALL_ICON_LEN: usize = 3*3*8; +const LARGE_ICON_LEN: usize = 8*8*8; + +pub fn parse_metadata(bytecode: &[u8]) -> Option<ProgramMetadata> { + MetadataParser::from_bytecode(bytecode).parse() +} + + +pub struct ProgramMetadata { + pub name: Option<String>, + pub version: Option<String>, + pub authors: Option<Vec<String>>, + pub description: Option<String>, + pub bg_colour: Option<Colour>, + pub fg_colour: Option<Colour>, + pub small_icon: Option<[u8; SMALL_ICON_LEN]>, + pub large_icon: Option<[u8; LARGE_ICON_LEN]>, +} + + +struct MetadataParser<'a> { + bytecode: &'a [u8], +} + +impl<'a> MetadataParser<'a> { + pub fn from_bytecode(bytecode: &'a [u8]) -> Self { + Self { bytecode } + } + + pub fn parse(self) -> Option<ProgramMetadata> { + // Verify metadata identifier. + let identifier = self.vec(0x00, 10); + if identifier != &[0x41,0x00,0x18,0x42,0x45,0x44,0x52,0x4F,0x43,0x4B] { + return None; + } + + let (name, version) = if let Some(pointer) = self.pointer(0x0a) { + let string = self.string(pointer); + if let Some((name, version)) = string.split_once('/') { + (Some(name.trim().to_string()), Some(version.trim().to_string())) + } else { + (Some(string.trim().to_string()), None) + } + } else { + (None, None) + }; + + let authors = self.pointer(0x0c).map(|p| { + self.string(p).lines().map(|s| s.trim().to_string()).collect() + }); + + let description = self.pointer(0x0e).map(|p| self.string(p)); + let bg_colour = self.pointer(0x10).map(|p| self.colour(p)); + let fg_colour = self.pointer(0x12).map(|p| self.colour(p)); + + let small_icon = if let Some(pointer) = self.pointer(0x14) { + let vec = self.vec(pointer, SMALL_ICON_LEN); + Some(vec.try_into().unwrap()) + } else { + None + }; + + let large_icon = if let Some(pointer) = self.pointer(0x16) { + let vec = self.vec(pointer, LARGE_ICON_LEN); + Some(vec.try_into().unwrap()) + } else { + None + }; + + let metadata = ProgramMetadata { + name, version, authors, description, + bg_colour, fg_colour, small_icon, large_icon, + }; + return Some(metadata); + } + + fn byte(&self, address: usize) -> u8 { + match self.bytecode.get(address) { + Some(byte) => *byte, + None => 0, + } + } + + fn double(&self, address: usize) -> u16 { + u16::from_be_bytes([ + self.byte(address), + self.byte(address + 1), + ]) + } + + fn pointer(&self, address: usize) -> Option<usize> { + match self.double(address) { + 0 => None, + v => Some(v as usize), + } + } + + fn vec(&self, address: usize, length: usize) -> Vec<u8> { + let mut vec = Vec::new(); + for i in 0..length { + vec.push(self.byte(address + i)); + } + return vec; + } + + fn string(&self, address: usize) -> String { + let mut i = address; + while self.byte(i) != 0 { + i += 1; + } + let slice = &self.bytecode[address..i]; + String::from_utf8_lossy(slice).to_string() + } + + fn colour(&self, address: usize) -> Colour { + let double = self.double(address); + let r = (double >> 8 & 0xf) as u8 * 17; + let g = (double >> 4 & 0xf) as u8 * 17; + let b = (double & 0xf) as u8 * 17; + Colour::from_rgb(r, g, b) + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs new file mode 100644 index 0000000..730f053 --- /dev/null +++ b/src/types/mod.rs @@ -0,0 +1,19 @@ +mod buffered_file; +mod debug_symbols; +mod directory_listing; +mod entry_type; +mod file_path; +mod metadata; +mod path_buffer; +mod read_buffer; +mod sprite_buffer; + +pub use buffered_file::*; +pub use debug_symbols::*; +pub use directory_listing::*; +pub use entry_type::*; +pub use file_path::*; +pub use metadata::*; +pub use path_buffer::*; +pub use read_buffer::*; +pub use sprite_buffer::*; diff --git a/src/devices/file_device/bedrock_path_buffer.rs b/src/types/path_buffer.rs index d6a0861..d6a0861 100644 --- a/src/devices/file_device/bedrock_path_buffer.rs +++ b/src/types/path_buffer.rs diff --git a/src/types/read_buffer.rs b/src/types/read_buffer.rs new file mode 100644 index 0000000..7128048 --- /dev/null +++ b/src/types/read_buffer.rs @@ -0,0 +1,34 @@ +pub struct ReadBuffer { + pub bytes: Vec<u8>, + pub pointer: usize, +} + +impl ReadBuffer { + pub fn new() -> Self { + Self { + bytes: Vec::new(), + pointer: 0, + } + } + + pub fn from_str(text: &str) -> Self { + Self { + bytes: text.bytes().collect(), + pointer: 0, + } + } + + pub fn set_str(&mut self, text: &str) { + self.bytes = text.bytes().collect(); + self.pointer = 0; + } + + pub fn read(&mut self) -> u8 { + let pointer = self.pointer; + self.pointer += 1; + match self.bytes.get(pointer) { + Some(byte) => *byte, + None => 0, + } + } +} diff --git a/src/types/sprite_buffer.rs b/src/types/sprite_buffer.rs new file mode 100644 index 0000000..74c7b55 --- /dev/null +++ b/src/types/sprite_buffer.rs @@ -0,0 +1,85 @@ +use crate::*; + + +pub struct SpriteBuffer { + pub mem: [u8; 16], + pub pointer: usize, + pub cached: Option<(Sprite, u8)>, +} + +impl SpriteBuffer { + pub fn new() -> Self { + Self { + mem: [0; 16], + pointer: 0, + cached: None, + } + } + + pub fn push_byte(&mut self, byte: u8) { + self.mem[self.pointer] = byte; + self.pointer = (self.pointer + 1) % 16; + self.cached = None; + } + + pub fn read_1bit_sprite(&mut self, draw: u8) -> Sprite { + if let Some((sprite, transform)) = self.cached { + if transform == (draw & 0x77) { + return sprite; + } + } + macro_rules! c { + ($v:ident=mem[$p:ident++]) => { let $v = self.mem[$p % 16]; $p = $p.wrapping_add(1); }; + ($v:ident=mem[--$p:ident]) => { $p = $p.wrapping_sub(1); let $v = self.mem[$p % 16]; }; + } + let mut sprite = [[0; 8]; 8]; + let mut p = match draw & 0x02 != 0 { + true => self.pointer, + false => self.pointer + 8, + }; + match draw & 0x07 { + 0x0 => { for y in 0..8 { c!(l=mem[p++]); for x in 0..8 { sprite[y][x] = l>>(7-x) & 1; } } }, + 0x1 => { for y in 0..8 { c!(l=mem[p++]); for x in 0..8 { sprite[y][x] = l>>( x) & 1; } } }, + 0x2 => { for y in 0..8 { c!(l=mem[--p]); for x in 0..8 { sprite[y][x] = l>>(7-x) & 1; } } }, + 0x3 => { for y in 0..8 { c!(l=mem[--p]); for x in 0..8 { sprite[y][x] = l>>( x) & 1; } } }, + 0x4 => { for y in 0..8 { c!(l=mem[p++]); for x in 0..8 { sprite[x][y] = l>>(7-x) & 1; } } }, + 0x5 => { for y in 0..8 { c!(l=mem[p++]); for x in 0..8 { sprite[x][y] = l>>( x) & 1; } } }, + 0x6 => { for y in 0..8 { c!(l=mem[--p]); for x in 0..8 { sprite[x][y] = l>>(7-x) & 1; } } }, + 0x7 => { for y in 0..8 { c!(l=mem[--p]); for x in 0..8 { sprite[x][y] = l>>( x) & 1; } } }, + _ => unreachable!(), + } + self.cached = Some((sprite, draw & 0x77)); + return sprite; + } + + pub fn read_2bit_sprite(&mut self, draw: u8) -> Sprite { + if let Some((sprite, transform)) = self.cached { + if transform == (draw & 0x77) { + return sprite; + } + } + macro_rules! c { + ($v:ident=mem[$p:ident++]) => { let $v = self.mem[$p % 16]; $p = $p.wrapping_add(1); }; + ($v:ident=mem[--$p:ident]) => { $p = $p.wrapping_sub(1); let $v = self.mem[$p % 16]; }; + } + let mut sprite = [[0; 8]; 8]; + let mut p = match draw & 0x02 != 0 { + true => self.pointer, + false => self.pointer + 8, + }; + let mut s = p + 8; + match draw & 0x07 { + 0x0 => for y in 0..8 { c!(l=mem[p++]); c!(h=mem[s++]); for x in 0..8 { let i=7-x; sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }, + 0x1 => for y in 0..8 { c!(l=mem[p++]); c!(h=mem[s++]); for x in 0..8 { let i= x; sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }, + 0x2 => for y in 0..8 { c!(l=mem[--p]); c!(h=mem[--s]); for x in 0..8 { let i=7-x; sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }, + 0x3 => for y in 0..8 { c!(l=mem[--p]); c!(h=mem[--s]); for x in 0..8 { let i= x; sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }, + 0x4 => for y in 0..8 { c!(l=mem[p++]); c!(h=mem[s++]); for x in 0..8 { let i=7-x; sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }, + 0x5 => for y in 0..8 { c!(l=mem[p++]); c!(h=mem[s++]); for x in 0..8 { let i= x; sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }, + 0x6 => for y in 0..8 { c!(l=mem[--p]); c!(h=mem[--s]); for x in 0..8 { let i=7-x; sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }, + 0x7 => for y in 0..8 { c!(l=mem[--p]); c!(h=mem[--s]); for x in 0..8 { let i= x; sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }, + _ => unreachable!(), + } + self.cached = Some((sprite, draw & 0x77)); + return sprite; + } +} |