summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Bridle <ben@derelict.engineering>2025-03-25 12:46:49 +1300
committerBen Bridle <ben@derelict.engineering>2025-03-25 12:48:49 +1300
commitab84ad75629b0a4124221023ca91411d2cd62a32 (patch)
tree0c333f06bec5270084aaec71cf173c798420207b
parent07ae3438917fd854a46924a410f6890cd0651f1b (diff)
downloadbedrock-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.
-rw-r--r--Cargo.lock55
-rw-r--r--Cargo.toml9
-rw-r--r--src/bin/br.rs461
-rw-r--r--src/bin/br/asm.rs152
-rw-r--r--src/bin/br/formats/clang.rs10
-rw-r--r--src/bin/br/formats/mod.rs24
-rw-r--r--src/bin/br/main.rs111
-rw-r--r--src/bin/br/run.rs186
-rw-r--r--src/debug.rs121
-rw-r--r--src/devices.rs20
-rw-r--r--src/devices/clock_device.rs225
-rw-r--r--src/devices/file_device.rs155
-rw-r--r--src/devices/file_device/operations.rs47
-rw-r--r--src/devices/input_device.rs202
-rw-r--r--src/devices/math_device.rs211
-rw-r--r--src/devices/memory_device.rs210
-rw-r--r--src/devices/mod.rs17
-rw-r--r--src/devices/remote_device.rs35
-rw-r--r--src/devices/screen_device.rs249
-rw-r--r--src/devices/stream_device.rs (renamed from src/devices/local_device.rs)174
-rw-r--r--src/devices/system_device.rs125
-rw-r--r--src/emulators.rs32
-rw-r--r--src/emulators/dynamic_emulator.rs170
-rw-r--r--src/emulators/graphical_emulator.rs363
-rw-r--r--src/emulators/headless_emulator.rs157
-rw-r--r--src/emulators/mod.rs81
-rw-r--r--src/lib.rs19
-rw-r--r--src/load_program.rs93
-rw-r--r--src/metadata.rs127
-rw-r--r--src/types/buffered_file.rs (renamed from src/devices/file_device/buffered_file.rs)3
-rw-r--r--src/types/debug_symbols.rs59
-rw-r--r--src/types/directory_listing.rs (renamed from src/devices/file_device/directory_listing.rs)2
-rw-r--r--src/types/entry_type.rs (renamed from src/devices/file_device/entry.rs)3
-rw-r--r--src/types/file_path.rs (renamed from src/devices/file_device/bedrock_file_path.rs)3
-rw-r--r--src/types/metadata.rs125
-rw-r--r--src/types/mod.rs19
-rw-r--r--src/types/path_buffer.rs (renamed from src/devices/file_device/bedrock_path_buffer.rs)0
-rw-r--r--src/types/read_buffer.rs34
-rw-r--r--src/types/sprite_buffer.rs85
39 files changed, 2271 insertions, 1903 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 81b9a4b..ae44e90 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 448a71f..bfad173 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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}");
+}
diff --git a/src/lib.rs b/src/lib.rs
index 0df1efb..ac72b98 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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;
+ }
+}