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 | |
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.
39 files changed, 2271 insertions, 1903 deletions
@@ -58,12 +58,27 @@ dependencies = [ ] [[package]] +name = "ansi" +version = "1.0.0" +source = "git+git://benbridle.com/ansi?tag=v1.0.0#81d47867c2c97a9ae1d1c8fdfcd42c582410ad2a" + +[[package]] name = "as-raw-xcb-connection" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" [[package]] +name = "assembler" +version = "2.2.1" +source = "git+git://benbridle.com/assembler?tag=v2.2.1#648151b7684214a86e894d0ca813d7a89317722c" +dependencies = [ + "ansi", + "log 1.1.2", + "vagabond", +] + +[[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -77,17 +92,16 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bedrock-asm" -version = "4.0.6" -source = "git+git://benbridle.com/bedrock-asm?tag=v4.0.6#c211280e077703903d968f14e8c6699d9f728aa2" +version = "5.0.0" +source = "git+git://benbridle.com/bedrock-asm?tag=v5.0.0#e99c683d66b5e20a610094cbe0fad75729383dd1" dependencies = [ - "vagabond", - "xflags", + "assembler", ] [[package]] name = "bedrock-core" -version = "5.0.0" -source = "git+git://benbridle.com/bedrock-core?tag=v5.0.0#179bd6a13d91f0a1137ee8ed6aebb7e226e99b5d" +version = "5.1.0" +source = "git+git://benbridle.com/bedrock-core?tag=v5.1.0#341df6405e3097b3c58aa57e1b08a663d34b4e89" [[package]] name = "bedrock-pc" @@ -97,7 +111,7 @@ dependencies = [ "bedrock-core", "chrono", "geometry", - "log 1.1.1", + "log 1.1.2", "phosphor", "switchboard", "windows", @@ -638,6 +652,14 @@ version = "1.1.1" source = "git+git://benbridle.com/log?tag=v1.1.1#930f3d0e2b82df1243f423c092a38546ea7533c3" [[package]] +name = "log" +version = "1.1.2" +source = "git+git://benbridle.com/log?tag=v1.1.2#3d5d1f7a19436151ba1dd52a2b50664969d90db6" +dependencies = [ + "ansi", +] + +[[package]] name = "memchr" version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1198,8 +1220,8 @@ dependencies = [ [[package]] name = "switchboard" -version = "1.0.0" -source = "git+git://benbridle.com/switchboard?tag=v1.0.0#ea70fa89659e5cf1a9d4ca6ea31fb67f7a2cc633" +version = "2.1.0" +source = "git+git://benbridle.com/switchboard?tag=v2.1.0#e6435712ba5b3ca36e99fc8cbe7755940f8b1f3f" dependencies = [ "log 1.1.1", "paste", @@ -1910,21 +1932,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d491ee231a51ae64a5b762114c3ac2104b967aadba1de45c86ca42cf051513b7" [[package]] -name = "xflags" -version = "0.4.0-pre.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4697c0db52cfb7277cf997ed334c92c739fafc7c5d44a948a906a5bf4b41a63f" -dependencies = [ - "xflags-macros", -] - -[[package]] -name = "xflags-macros" -version = "0.4.0-pre.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94d18ac1a136311770ed587356f8a828c9b86261f68761f34e6cdc6d5b4c435c" - -[[package]] name = "xkbcommon-dl" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -6,18 +6,19 @@ edition = "2021" description = "Emulator for running Bedrock programs" [dependencies] -bedrock-asm = { git = "git://benbridle.com/bedrock-asm", tag = "v4.0.6" } -bedrock-core = { git = "git://benbridle.com/bedrock-core", tag = "v5.0.0" } +bedrock-asm = { git = "git://benbridle.com/bedrock-asm", tag = "v5.0.0" } +bedrock-core = { git = "git://benbridle.com/bedrock-core", tag = "v5.1.0" } phosphor = { git = "git://benbridle.com/phosphor", tag = "v3.2.2" } geometry = { git = "git://benbridle.com/geometry", tag = "v1.0.0" } -log = { git = "git://benbridle.com/log", tag = "v1.1.1" } -switchboard = { git = "git://benbridle.com/switchboard", tag = "v1.0.0" } +log = { git = "git://benbridle.com/log", tag = "v1.1.2" } +switchboard = { git = "git://benbridle.com/switchboard", tag = "v2.1.0" } chrono = { version = "0.4.38" } [target.'cfg(target_os = "windows")'.dependencies] windows = { version = "0.58.0", features = ["Win32_Storage_FileSystem"] } + [profile.release] lto=true opt-level="s" 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; + } +} |