summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bin/br.rs521
-rw-r--r--src/bin/br/config.rs122
-rw-r--r--src/bin/br/load.rs81
-rw-r--r--src/bin/br/main.rs233
-rw-r--r--src/debug.rs127
-rw-r--r--src/devices.rs20
-rw-r--r--src/devices/clock_device.rs251
-rw-r--r--src/devices/file_device.rs300
-rw-r--r--src/devices/file_device/operations.rs47
-rw-r--r--src/devices/input_device.rs265
-rw-r--r--src/devices/math_device.rs224
-rw-r--r--src/devices/memory_device.rs240
-rw-r--r--src/devices/mod.rs17
-rw-r--r--src/devices/remote_device.rs35
-rw-r--r--src/devices/screen_device.rs296
-rw-r--r--src/devices/stream_device.rs (renamed from src/devices/local_device.rs)198
-rw-r--r--src/devices/system_device.rs160
-rw-r--r--src/emulators.rs32
-rw-r--r--src/emulators/graphical_emulator.rs396
-rw-r--r--src/emulators/headless_emulator.rs164
-rw-r--r--src/emulators/mod.rs24
-rw-r--r--src/lib.rs47
-rw-r--r--src/metadata.rs127
-rw-r--r--src/types/buffered_file.rs (renamed from src/devices/file_device/buffered_file.rs)7
-rw-r--r--src/types/controller.rs166
-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/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/sprite_buffer.rs85
-rw-r--r--src/types/string_buffer.rs37
-rw-r--r--src/types/wake_queue.rs51
33 files changed, 2231 insertions, 2069 deletions
diff --git a/src/bin/br.rs b/src/bin/br.rs
deleted file mode 100644
index 431ae39..0000000
--- a/src/bin/br.rs
+++ /dev/null
@@ -1,521 +0,0 @@
-use bedrock_asm::*;
-use bedrock_pc::*;
-use phosphor::*;
-
-use std::io::{Read, Write};
-use std::path::{Path, PathBuf};
-use std::process::exit;
-
-
-const NORMAL: &str = "\x1b[0m";
-const BOLD: &str = "\x1b[1m";
-const WHITE: &str = "\x1b[37m";
-const RED: &str = "\x1b[31m";
-const BLUE: &str = "\x1b[34m";
-
-static mut VERBOSE: bool = false;
-
-macro_rules! verbose {
- ($($tokens:tt)*) => { if unsafe { VERBOSE } {
- eprint!("{BOLD}{BLUE}[INFO]{NORMAL}: "); eprint!($($tokens)*);
- eprintln!("{NORMAL}");
- } };
-}
-macro_rules! error {
- ($($tokens:tt)*) => {{
- eprint!("{BOLD}{RED}[ERROR]{WHITE}: "); eprint!($($tokens)*);
- eprintln!("{NORMAL}");
- }};
-}
-
-
-fn main() {
- let args = Arguments::from_env_or_exit();
- if args.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);
- }
- if args.verbose {
- unsafe { VERBOSE = true; }
- }
- match args.subcommand {
- ArgumentsCmd::Run(run) => main_run(run),
- ArgumentsCmd::Asm(asm) => main_asm(asm),
- }
-}
-
-fn main_run(args: Run) {
- let program_path = args.program.as_ref().map(|p| p.as_path());
- let Bytecode { bytes: bytecode, path } = load_bytecode(program_path);
- 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() {
- verbose!("Could not read program metadata");
- }
-
- let mut config = EmulatorConfig {
- dimensions: ScreenDimensions::ZERO,
- fullscreen: args.fullscreen,
- scale: args.scale(),
- debug_palette: args.palette(),
- show_cursor: args.show_cursor,
- initial_transmission: None,
- decode_stdin: args.decode_stdin,
- encode_stdout: args.encode_stdout,
- symbols_path,
- };
- let phosphor = Phosphor::new();
-
- if phosphor.is_ok() && args.dimensions().is_some() {
- verbose!("Starting graphical emulator");
- let mut phosphor = phosphor.unwrap();
- config.dimensions = args.dimensions().unwrap();
- let cursor = match config.show_cursor {
- true => Some(CursorIcon::Default),
- false => None,
- };
-
- let mut graphical = GraphicalEmulator::new(&config, args.debug, unsafe {VERBOSE});
- 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 {
- verbose!("Starting headless emulator");
- let mut headless = HeadlessEmulator::new(&config, args.debug, unsafe {VERBOSE});
- headless.load_program(&bytecode);
- headless.run(args.debug);
- };
-
- std::process::exit(0);
-}
-
-fn load_bytecode(path: Option<&Path>) -> Bytecode {
- // TODO: Etch file location into bytecode.
- if let Some(path) = path {
- if let Ok(bytecode) = load_bytecode_from_file(path) {
- let length = bytecode.bytes.len();
- let path = bytecode.path();
- verbose!("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();
- verbose!("Loaded program from {path:?} ({length} bytes)");
- return bytecode;
- } else {
- error!("Could not read program from {path:?}, exiting");
- exit(1);
- }
- } else {
- verbose!("Reading program from standard input...");
- if let Ok(bytecode) = load_bytecode_from_stdin() {
- let length = bytecode.bytes.len();
- verbose!("Loaded program from standard input ({length} bytes)");
- return bytecode;
- } else {
- error!("Could not read program from standard input, exiting");
- exit(1);
- }
- }
-}
-
-/// 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);
- verbose!("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");
- verbose!("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(args: Asm) {
- // -----------------------------------------------------------------------
- // RESOLVE syntactic symbols
- let ext = args.ext.unwrap_or(String::from("brc"));
- let source_path = args.source.clone().map(|p| {
- p.canonicalize().unwrap_or(p)
- });
-
- let mut resolver = if let Some(path) = &source_path {
- match SourceUnit::from_path(&path, &ext) {
- Ok(source_unit) => SymbolResolver::from_source_unit(source_unit),
- Err(err) => {
- match err {
- ParseError::InvalidExtension => error!(
- "File {path:?} has invalid extension, must be '.{ext}'"),
- ParseError::NotFound => error!(
- "File {path:?} was not found"),
- ParseError::InvalidUtf8 => error!(
- "File {path:?} does not contain valid UTF-8 text"),
- ParseError::NotReadable => error!(
- "File {path:?} is not readable"),
- ParseError::IsADirectory => error!(
- "File {path:?} is a directory"),
- ParseError::Unknown => error!(
- "Unknown error while attempting to read from {path:?}")
- };
- exit(1);
- }
- }
- } else {
- let mut source_code = String::new();
- verbose!("Reading program source from standard input");
- if let Err(err) = std::io::stdin().read_to_string(&mut source_code) {
- error!("Could not read from standard input");
- eprintln!("{err:?}");
- exit(1);
- }
- 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 !args.no_libs && !args.no_project_libs {
- let project_library = gather_project_libraries(path, &ext);
- resolver.add_library_units(project_library);
- }
- }
- // Load environment libraries.
- if !args.no_libs && !args.no_env_libs {
- for env_library in gather_environment_libraries(&ext) {
- resolver.add_library_units(env_library);
- }
- }
- resolver.resolve();
-
- // -----------------------------------------------------------------------
- // PRINT information, generate merged source code
- if args.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 args.resolve && !args.check {
- write_bytes_and_exit(merged_source.as_bytes(), args.output.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 args.symbols && !args.check {
- if let Some(path) = &args.output {
- 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) {
- verbose!("Could not write to symbols path {symbols_path:?}");
- eprintln!("{err:?}");
- } else {
- verbose!("Saved debug symbols to {symbols_path:?}");
- }
- }
- }
-
- let length = bytecode.len();
- let percentage = (length as f32 / 65536.0 * 100.0).round() as u16;
- verbose!("Assembled program in {length} bytes ({percentage}% of maximum)");
-
- if !args.check {
- write_bytes_and_exit(&bytecode, args.output.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) {
- error!("Could not write to path {:?}", path.as_ref());
- eprintln!("{err:?}");
- exit(1);
- }
- } else {
- if let Err(err) = std::io::stdout().write_all(bytes) {
- error!("Could not write to standard output");
- eprintln!("{err:?}");
- exit(1);
- }
- }
- exit(0);
-}
-
-
-xflags::xflags! {
- /// Integrated Bedrock assembler and emulator.
- ///
- /// The arguments and options shown are for running a Bedrock program.
- /// To see the documentation for the assembler, run `br asm --help`
- ///
- /// Usage:
- /// To load a Bedrock program from a file, run `br [path]`, where
- /// [path] is the path of 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.
- cmd arguments {
- /// Print additional debug information
- optional -v, --verbose
- /// Print the assembler version and exit
- optional --version
-
- /// Run a Bedrock program (this is the default command)
- default cmd run {
- /// Path to a Bedrock program to run
- optional program: PathBuf
-
- /// Show debug information while the program is running
- optional -d, --debug
- /// Start the program in fullscreen mode (toggle with F11)
- optional -f, --fullscreen
- /// Set the initial window size in the format <width>x<height>
- optional -s, --screen size: ParsedDimensions
- /// Set the pixel size for the screen (change with F5/F6)
- optional -z, --zoom factor: u32
- /// Set a debug colour palette in the format <rgb>,... (toggle with F2)
- optional --palette colours: ParsedPalette
- /// Show the operating system cursor over the window
- optional --show-cursor
- /// Decode standard input
- optional -i, --decode-stdin
- /// Encode standard output
- optional -o, --encode-stdout
- }
-
- /// Assemble a Bedrock program from source code
- cmd asm {
- /// Bedrock source code file to assemble.
- optional source: PathBuf
- /// Destination path for assembler output.
- optional output: PathBuf
- /// File extension to identify source files.
- optional ext: String
-
- /// Don't include libraries or resolve references.
- optional --no-libs
- /// Don't include project libraries
- optional --no-project-libs
- /// Don't include environment libraries.
- optional --no-env-libs
-
- /// Show the resolved source file heirarchy
- optional --tree
- /// Assemble the program without saving any output
- optional --check
- /// Only return resolved source code.
- optional --resolve
- /// Generate debug symbols with file extension '.br.sym'
- optional --symbols
- }
- }
-}
-
-
-impl Run {
- pub fn dimensions(&self) -> Option<geometry::Dimensions<u16>> {
- let size = match &self.screen {
- Some(parsed) => geometry::Dimensions::new(parsed.width, parsed.height),
- None => DEFAULT_SCREEN_SIZE / (self.scale() as u16),
- };
- match size.is_zero() {
- true => None,
- false => Some(size),
- }
- }
-
- pub fn scale(&self) -> u32 {
- std::cmp::max(1, self.zoom.unwrap_or(1))
- }
-
- pub fn palette(&self) -> Option<[Colour; 16]> {
- self.palette.as_ref().map(|p| p.palette)
- }
-}
-
-
-#[derive(Debug)]
-struct ParsedDimensions { width: u16, height: u16 }
-impl std::str::FromStr for ParsedDimensions {
- type Err = ParsedDimensionsError;
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- if s == "none" {
- Ok( Self { width: 0, height: 0 } )
- } else {
- let (w_str, h_str) = s.split_once('x').ok_or(ParsedDimensionsError)?;
- Ok( Self {
- width: u16::from_str(w_str).or(Err(ParsedDimensionsError))?,
- height: u16::from_str(h_str).or(Err(ParsedDimensionsError))?,
- } )
- }
- }
-}
-
-pub struct ParsedDimensionsError;
-impl std::fmt::Display for ParsedDimensionsError {
- fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
- f.write_str("try giving a value like 800x600")
- }
-}
-
-
-#[derive(Debug)]
-pub struct ParsedPalette { palette: [Colour; 16] }
-impl std::str::FromStr for ParsedPalette {
- type Err = ParsedPaletteError;
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- fn decode_ascii_hex_digit(ascii: u8) -> Result<u8, ParsedPaletteError> {
- match ascii {
- b'0'..=b'9' => Ok(ascii - b'0'),
- b'a'..=b'f' => Ok(ascii - b'a' + 10),
- b'A'..=b'F' => Ok(ascii - b'A' + 10),
- _ => { Err(ParsedPaletteError)}
- }
- }
- let mut colours = Vec::new();
- for token in s.split(',') {
- let mut bytes = token.bytes();
- if bytes.len() != 3 { return Err(ParsedPaletteError); }
- 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())?;
- colours.push(Colour::from_rgb(r*17, g*17, b*17));
- }
- let c = &colours;
- let palette = match colours.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 Err(ParsedPaletteError),
- };
- Ok( Self { palette })
- }
-}
-
-pub struct ParsedPaletteError;
-impl std::fmt::Display for ParsedPaletteError {
- fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
- f.write_str("try giving a value like 000,fff,880,808")
- }
-}
diff --git a/src/bin/br/config.rs b/src/bin/br/config.rs
new file mode 100644
index 0000000..56d5190
--- /dev/null
+++ b/src/bin/br/config.rs
@@ -0,0 +1,122 @@
+use crate::*;
+
+
+#[derive(Copy, Clone, PartialEq)]
+pub 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}'"),
+ }
+ }
+}
+
+
+pub 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;
+}
+
+
+pub 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}'");
+ })
+}
+
+
+pub fn parse_metadata_colour(colour: Option<MetadataColour>) -> Option<Colour> {
+ let c = colour?;
+ Some(Colour::from_rgb(c.red, c.green, c.blue))
+}
+
+
+pub fn parse_small_icon(bytes: Option<Vec<u8>>, bg: Colour, fg: Colour) -> Option<Icon> {
+ let rgba = sprite_data_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}"),
+ }
+}
+
+pub fn parse_large_icon(bytes: Option<Vec<u8>>, bg: Colour, fg: Colour) -> Option<Icon> {
+ let rgba = sprite_data_to_rgb(&bytes?, 8, bg, fg);
+ match Icon::from_rgba(rgba, 64, 64) {
+ Ok(icon) => Some(icon),
+ Err(err) => unreachable!("Error while parsing large icon data: {err}"),
+ }
+}
+
+fn sprite_data_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/bin/br/load.rs b/src/bin/br/load.rs
new file mode 100644
index 0000000..93a748c
--- /dev/null
+++ b/src/bin/br/load.rs
@@ -0,0 +1,81 @@
+use crate::*;
+
+use std::io::Read;
+use std::path::{Path, PathBuf};
+
+
+pub struct LoadedProgram {
+ pub bytecode: Vec<u8>,
+ pub path: Option<PathBuf>,
+}
+
+/// Load program from path or standard input.
+pub fn load_program(path: Option<&PathBuf>) -> LoadedProgram {
+ if let Some(path) = path {
+ if let Ok(program) = load_program_from_file(path) {
+ let length = program.bytecode.len();
+ info!("Loaded program from {path:?} ({length} bytes)");
+ return program;
+ } else if let Some(program) = load_program_from_env(path) {
+ let length = program.bytecode.len();
+ 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_env(path: &Path) -> Option<LoadedProgram> {
+ // Check that the path is a bare program 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);
+ // Skip relative paths.
+ if !base_path.is_absolute() { continue; }
+ base_path.push(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");
+ if let Ok(program) = load_program_from_file(&base_path) {
+ return Some(program);
+ }
+ }
+ }
+ return None;
+}
+
+/// Attempt to load program from a file path.
+fn load_program_from_file(path: &Path) -> Result<LoadedProgram, 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<LoadedProgram, 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<LoadedProgram, std::io::Error> {
+ let mut bytecode = Vec::<u8>::new();
+ source.take(65536).read_to_end(&mut bytecode)?;
+ return Ok(LoadedProgram { bytecode, path: path.map(|p| p.to_path_buf()) });
+}
diff --git a/src/bin/br/main.rs b/src/bin/br/main.rs
new file mode 100644
index 0000000..da11a18
--- /dev/null
+++ b/src/bin/br/main.rs
@@ -0,0 +1,233 @@
+#![feature(path_add_extension)]
+
+mod config;
+mod load;
+pub use config::*;
+pub use load::*;
+
+use bedrock_asm::*;
+use bedrock_core::*;
+use bedrock_pc::*;
+use log::*;
+use switchboard::*;
+use phosphor::*;
+
+use std::cmp::{min, max};
+use std::num::NonZeroU32;
+
+
+fn main() {
+ let mut args = Switchboard::from_env();
+ if let Some("asm") = args.peek() {
+ args.pop();
+ assemble(args, "br asm");
+ }
+
+ // -----------------------------------------------------------------------
+
+ args.named("help").short('h');
+ args.named("version");
+ args.named("verbose").short('v');
+
+ if args.get("help").as_bool() {
+ print_help();
+ std::process::exit(0);
+ }
+ if args.get("version").as_bool() {
+ 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);
+ }
+ if args.get("verbose").as_bool() {
+ log::set_log_level(log::LogLevel::Info);
+ }
+
+ 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.named("trust-files");
+ 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 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 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 trust_files = args.get("trust-files").as_bool();
+
+ // -----------------------------------------------------------------------
+
+ let LoadedProgram { bytecode, path } = load_program(source.as_ref());
+ let mut title = String::from("Bedrock program");
+ let mut icon = None;
+
+ let metadata = Metadata::from(&bytecode);
+
+ if let Some(ref metadata) = metadata {
+ let name = metadata.name().unwrap_or("unnamed".to_string());
+ let authors = metadata.authors().unwrap_or_else(Vec::new);
+ let mut metadata_string = format!("Program is '{name}'");
+ if authors.len() > 0 {
+ metadata_string.push_str(&format!(", by {}", authors[0])); }
+ if authors.len() > 1 {
+ metadata_string.push_str(" and others"); }
+ info!("{metadata_string}");
+
+ match name.split_once('/') {
+ Some((name, _version)) if !name.is_empty() => title = name.to_string(),
+ _ => title = name.to_string(),
+ }
+ let bg = parse_metadata_colour(metadata.bg_colour()).unwrap_or(Colour::rgb(0x1E1C26));
+ let fg = parse_metadata_colour(metadata.fg_colour()).unwrap_or(Colour::rgb(0xED614F));
+ match parse_large_icon(metadata.large_icon(), bg, fg) {
+ Some(large_icon) => icon = Some(large_icon),
+ None => match parse_small_icon(metadata.small_icon(), bg, fg) {
+ Some(small_icon) => icon = Some(small_icon),
+ None => (),
+ }
+ }
+ } else {
+ info!("Program does not contain metadata");
+ }
+
+ let symbols_path = path.as_ref().map(|p| {
+ let mut path = p.to_path_buf();
+ path.add_extension("sym"); path
+ });
+
+ let name = metadata.and_then(|m| m.name()).and_then(|n| match n.split_once('/') {
+ Some((name, _)) => Some(name.to_string()),
+ None => Some(n),
+ });
+ let identifier = name.as_ref().and_then(
+ |n| Some(n.to_lowercase().chars().filter_map(
+ |c| c.is_alphanumeric().then_some(c)
+ ).collect())
+ );
+
+ let config = EmulatorConfig {
+ dimensions, fullscreen, zoom, palette, show_cursor,
+ decode_stdin, encode_stdout, trust_files,
+ symbols_path, name, identifier, title, icon,
+ };
+
+ match Phosphor::new() {
+ Ok(phosphor) => match mode {
+ Mode::Dynamic => {
+ info!("Starting graphical emulator (hidden)");
+ let mut emulator = GraphicalEmulator::new(config, debug);
+ emulator.load_program(&bytecode);
+ emulator.run(phosphor, false);
+ }
+ Mode::Graphical => {
+ info!("Starting graphical emulator");
+ let mut emulator = GraphicalEmulator::new(config, debug);
+ emulator.load_program(&bytecode);
+ emulator.run(phosphor, true);
+ }
+ Mode::Headless => {
+ info!("Starting headless emulator");
+ let mut emulator = HeadlessEmulator::new(&config, debug);
+ emulator.load_program(&bytecode);
+ emulator.run();
+ }
+ }
+ Err(err) => match mode {
+ Mode::Dynamic => {
+ eprintln!("EventLoopError: {err:?}");
+ info!("Could not start graphical event loop");
+ info!("Starting headless emulator");
+ let mut emulator = HeadlessEmulator::new(&config, debug);
+ emulator.load_program(&bytecode);
+ emulator.run();
+ }
+ Mode::Graphical => {
+ eprintln!("EventLoopError: {err:?}");
+ fatal!("Could not start graphical event loop");
+ }
+ Mode::Headless => {
+ info!("Starting headless emulator");
+ let mut emulator = HeadlessEmulator::new(&config, debug);
+ emulator.load_program(&bytecode);
+ emulator.run();
+ }
+ }
+ }
+}
+
+
+fn print_help() {
+ eprintln!("\
+Usage: br [source]
+ br asm [source] [destination]
+
+Emulator and assembler for the Bedrock computer system.
+
+To access the assembler, run `br asm`. To learn how to use the
+assembler, run `br asm --help`.
+
+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 that 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 that 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.
+ --trust-files Give the program unrestricted access to the file system.
+ --help (-h) Print this help information
+ --verbose, (-v) Print additional information
+ --version Print the program version and exit
+");
+}
diff --git a/src/debug.rs b/src/debug.rs
index 6270948..a0746e7 100644
--- a/src/debug.rs
+++ b/src/debug.rs
@@ -1,86 +1,85 @@
-use bedrock_core::*;
+use crate::*;
-use std::path::Path;
-use std::time::Instant;
-
-
-const NORMAL: &str = "\x1b[0m";
-const BOLD: &str = "\x1b[1m";
-const DIM: &str = "\x1b[2m";
-const YELLOW: &str = "\x1b[33m";
-const BLUE: &str = "\x1b[34m";
+use inked::{ink, InkedString};
pub struct DebugState {
pub enabled: bool,
- pub verbose: bool,
last_cycle: usize,
- last_mark: Instant,
+ mark: Instant,
symbols: DebugSymbols,
}
impl DebugState {
- pub fn new<P: AsRef<Path>>(enabled: bool, verbose: bool, symbols_path: Option<P>) -> Self {
+ pub fn new<P: AsRef<Path>>(enabled: bool, symbols_path: Option<P>) -> Self {
Self {
enabled,
- verbose,
last_cycle: 0,
- last_mark: Instant::now(),
- symbols: DebugSymbols::from_path_opt(symbols_path),
- }
- }
-
- pub fn info(&self, string: &str) {
- if self.verbose {
- eprintln!("{BOLD}{BLUE}[INFO]{NORMAL}: {string}{NORMAL}");
+ mark: Instant::now(),
+ symbols: DebugSymbols::from_path(symbols_path),
}
}
- pub fn debug_summary(&mut self, core: &BedrockCore) {
+ pub fn debug_full(&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.
+ let cycle = core.cycle;
+ let delta = core.cycle.saturating_sub(self.last_cycle);
+ let elapsed = self.mark.elapsed();
+
+ eprintln!(" PC: 0x{prev_pc:04x} Cycle: {cycle} (+{delta} in {elapsed:.2?})");
+ eprint!("WST: "); debug_stack(&core.wst);
+ eprint!("RST: "); debug_stack(&core.rst);
+ // Print information about the nearest 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}");
+ let mut string = InkedString::new();
+ string.push(ink!("SYM: "));
+ string.push(ink!("@{name}").blue());
+ string.push(ink!(" 0x{address:04X}"));
if let Some(location) = &symbol.location {
- eprint!(" {DIM}{location}{NORMAL}");
+ string.push(ink!(" "));
+ string.push(ink!("{location}").dim());
}
- eprintln!();
+ string.eprintln();
}
+ eprintln!();
+ }
+ self.last_cycle = core.cycle;
+ self.mark = Instant::now();
+ }
+
+ pub fn debug_timing(&mut self, core: &BedrockCore) {
+ if self.enabled {
+ let cycle = core.cycle;
+ let delta = core.cycle.saturating_sub(self.last_cycle);
+ let elapsed = self.mark.elapsed();
+
+ eprintln!("Cycle: {cycle} (+{delta} in {elapsed:.2?})");
}
self.last_cycle = core.cycle;
- self.last_mark = Instant::now();
+ self.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]);
+fn debug_stack(stack: &Stack) {
+ for i in 0..(stack.sp as usize) {
+ eprint!("{:02X} ", stack.mem[i]);
}
- eprintln!("{NORMAL}");
+ eprintln!();
}
-struct DebugSymbols {
- symbols: Vec<DebugSymbol>
+
+pub struct DebugSymbols {
+ pub symbols: Vec<DebugSymbol>
}
impl DebugSymbols {
/// Load debug symbols from a symbols file.
- pub fn from_path_opt<P: AsRef<Path>>(path: Option<P>) -> Self {
+ pub fn from_path<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) {
@@ -95,36 +94,34 @@ impl DebugSymbols {
Self { symbols }
}
+ /// Return the symbol matching a given program 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)
+ 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)?),
+ }
}
}
-struct DebugSymbol {
- address: u16,
- name: String,
- location: Option<String>,
+
+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 } )
- }
+ let (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 {
- None
+ let name = line.to_string();
+ Some( DebugSymbol { address, name, location: 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..d06a92c 100644
--- a/src/devices/clock_device.rs
+++ b/src/devices/clock_device.rs
@@ -1,113 +1,18 @@
-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,
- pub t1_read: u16,
- pub t2_read: u16,
- pub t3_read: u16,
- pub t4_read: u16,
- pub t1_write: u16,
- pub t2_write: u16,
- pub t3_write: u16,
- pub t4_write: u16,
+ pub t1: CountdownTimer,
+ pub t2: CountdownTimer,
+ pub t3: CountdownTimer,
+ pub t4: CountdownTimer,
}
-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,
- }
- }
-}
impl Device for ClockDevice {
fn read(&mut self, port: u8) -> u8 {
@@ -118,16 +23,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),
- 0x8 => { self.read_t1(); read_h!(self.t1_read) },
- 0x9 => read_l!(self.t1_read),
- 0xa => { self.read_t2(); read_h!(self.t2_read) },
- 0xb => read_l!(self.t2_read),
- 0xc => { self.read_t3(); read_h!(self.t3_read) },
- 0xd => read_l!(self.t3_read),
- 0xe => { self.read_t4(); read_h!(self.t4_read) },
- 0xf => read_l!(self.t4_read),
+ 0x6 => { self.uptime_read = self.uptime() as u16;
+ read_h!(self.uptime_read) },
+ 0x7 => read_l!(self.uptime_read),
+ 0x8 => { self.t1.update(); read_h!(self.t1.read) },
+ 0x9 => read_l!(self.t1.read),
+ 0xA => { self.t2.update(); read_h!(self.t2.read) },
+ 0xB => read_l!(self.t2.read),
+ 0xC => { self.t3.update(); read_h!(self.t3.read) },
+ 0xD => read_l!(self.t3.read),
+ 0xE => { self.t4.update(); read_h!(self.t4.read) },
+ 0xF => read_l!(self.t4.read),
_ => unreachable!(),
}
}
@@ -142,33 +48,114 @@ impl Device for ClockDevice {
0x5 => (),
0x6 => (),
0x7 => (),
- 0x8 => write_h!(self.t1_write, value),
- 0x9 => { write_l!(self.t1_write, value); self.set_t1() },
- 0xa => write_h!(self.t2_write, value),
- 0xb => { write_l!(self.t2_write, value); self.set_t2() },
- 0xc => write_h!(self.t3_write, value),
- 0xd => { write_l!(self.t3_write, value); self.set_t3() },
- 0xe => write_h!(self.t4_write, value),
- 0xf => { write_l!(self.t4_write, value); self.set_t4() },
+ 0x8 => write_h!(self.t1.write, value),
+ 0x9 => { write_l!(self.t1.write, value); self.t1.commit() },
+ 0xA => write_h!(self.t2.write, value),
+ 0xB => { write_l!(self.t2.write, value); self.t2.commit() },
+ 0xC => write_h!(self.t3.write, value),
+ 0xD => { write_l!(self.t3.write, value); self.t3.commit() },
+ 0xE => write_h!(self.t4.write, value),
+ 0xF => { write_l!(self.t4.write, value); self.t4.commit() },
_ => unreachable!(),
};
return None;
}
fn wake(&mut self) -> bool {
- let uptime = self.uptime();
- macro_rules! check_timer {
- ($end:ident) => {
- if self.$end > 0 && self.$end <= uptime {
- self.$end = 0;
- self.wake = true;
- }
- };
+ let t1 = self.t1.wake();
+ let t2 = self.t2.wake();
+ let t3 = self.t3.wake();
+ let t4 = self.t4.wake();
+ return t1 | t2 | t3 | t4;
+ }
+
+ fn reset(&mut self) {
+ self.epoch = Instant::now();
+ self.uptime_read = 0;
+ self.t1.reset();
+ self.t2.reset();
+ self.t3.reset();
+ self.t4.reset();
+ }
+}
+
+
+impl ClockDevice {
+ pub fn new() -> Self {
+ Self {
+ epoch: Instant::now(),
+ uptime_read: 0,
+
+ t1: CountdownTimer::new(),
+ t2: CountdownTimer::new(),
+ t3: CountdownTimer::new(),
+ t4: CountdownTimer::new(),
+ }
+ }
+
+ pub fn uptime(&self) -> u64 {
+ (self.epoch.elapsed().as_nanos() * 256 / 1_000_000_000) as u64
+ }
+}
+
+
+
+pub struct CountdownTimer {
+ pub end: Option<Instant>,
+ pub read: u16,
+ pub write: u16,
+ pub wake: bool,
+}
+
+impl CountdownTimer {
+ pub fn new() -> Self {
+ Self {
+ end: None,
+ read: 0,
+ write: 0,
+ wake: false,
+ }
+ }
+
+ pub fn reset(&mut self) {
+ self.end = None;
+ self.read = 0;
+ self.write = 0;
+ self.wake = false;
+ }
+
+ pub fn wake(&mut self) -> bool {
+ if let Some(end) = self.end {
+ if end <= Instant::now() {
+ self.end = None;
+ self.wake = true;
+ }
+ }
+ std::mem::take(&mut self.wake)
+ }
+
+ pub fn update(&mut self) {
+ if let Some(end) = self.end {
+ let now = Instant::now();
+ if end > now {
+ let nanos = (end - now).as_nanos();
+ self.read = (nanos * 256 / 1_000_000_000) as u16;
+ } else {
+ self.read = 0;
+ self.end = None;
+ self.wake = true;
+ }
+ } else {
+ self.read = 0;
+ }
+ }
+
+ pub fn commit(&mut self) {
+ if self.write > 0 {
+ let nanos = (self.write as u64) * 1_000_000_000 / 256;
+ self.end = Some(Instant::now() + Duration::from_nanos(nanos));
+ } else {
+ self.end = None;
}
- check_timer!(t1_end);
- check_timer!(t2_end);
- check_timer!(t3_end);
- check_timer!(t4_end);
- return std::mem::take(&mut self.wake);
}
}
diff --git a/src/devices/file_device.rs b/src/devices/file_device.rs
index 61966b1..83f0a56 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 {
@@ -25,13 +9,14 @@ pub struct FileDevice {
pub action_buffer: BedrockPathBuffer,
pub path_buffer: BedrockPathBuffer,
- pub entry: Option<(Entry, BedrockFilePath)>,
- pub cached_dir: Option<(Entry, BedrockFilePath)>,
+ pub entry: Option<(Entry, BedrockFilePath, Instant)>,
+ pub cached_dir: Option<(Entry, BedrockFilePath, Instant)>,
- pub success: bool,
+ pub error: bool,
pub pointer_write: u32,
pub length_write: u32,
+ pub enable: bool,
pub enable_read: bool,
pub enable_write: bool,
pub enable_create: bool,
@@ -39,21 +24,95 @@ pub struct FileDevice {
pub enable_delete: bool,
}
+
+impl Device for FileDevice {
+ fn read(&mut self, port: u8) -> u8 {
+ if !self.enable { return 0x00; }
+ match port {
+ 0x0 => read_b!(self.entry.is_some()),
+ 0x1 => read_b!(std::mem::take(&mut self.error)),
+ 0x2 => self.read_byte(),
+ 0x3 => self.read_byte(),
+ 0x4 => self.path_buffer.read(),
+ 0x5 => read_b!(self.entry_type()),
+ 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> {
+ if !self.enable { return None; }
+ match port {
+ 0x0 => self.write_to_entry_port(value),
+ 0x1 => self.write_to_action_port(value),
+ 0x2 => self.write_byte(value),
+ 0x3 => self.write_byte(value),
+ 0x4 => self.path_buffer.set_pointer(value),
+ 0x5 => self.ascend_to_parent(),
+ 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
+ }
+
+ fn reset(&mut self) {
+ todo!()
+ }
+}
+
+
impl FileDevice {
- pub fn new() -> Self {
+ pub fn new(config: &EmulatorConfig) -> Self {
#[cfg(target_family = "unix")]
let default_base: PathBuf = PathBuf::from("/");
#[cfg(target_family = "windows")]
let default_base: PathBuf = PathBuf::from("");
+ let current_dir = match std::env::current_dir() {
+ Ok(dir) => PathBuf::from(dir),
+ Err(_) => PathBuf::from(""),
+ };
+
+ let (enable, base_path, default_path) = if config.trust_files {
+ (true, default_base, current_dir)
+ } else if let Some(config_dir) = dirs_next::config_dir() {
+ let bedrock_dir = config_dir.join("bedrock");
+ let identifier = config.identifier.clone().unwrap_or("default".to_string());
+ let sandbox_dir = bedrock_dir.join(identifier);
+ vagabond::make_directory(&sandbox_dir).unwrap();
+ (true, sandbox_dir.clone(), sandbox_dir)
+ } else {
+ error!("Could not determine sandbox path for file device");
+ (false, default_base, current_dir)
+ };
+
// TODO: I'm not at all confident that the default path is correct
// when not being set as the current directory.
Self {
- base_path: default_base,
- default_path: match std::env::current_dir() {
- Ok(dir) => PathBuf::from(dir),
- Err(_) => PathBuf::from(""),
- },
+ base_path,
+ default_path,
entry_buffer: BedrockPathBuffer::new(),
action_buffer: BedrockPathBuffer::new(),
@@ -62,10 +121,11 @@ impl FileDevice {
entry: None,
cached_dir: None,
- success: false,
+ error: false,
pointer_write: 0,
length_write: 0,
+ enable,
enable_read: true,
enable_write: true,
enable_create: true,
@@ -74,6 +134,12 @@ impl FileDevice {
}
}
+ pub fn check_success(&mut self, success: bool) {
+ if !success {
+ self.error = true;
+ }
+ }
+
/// Safely close the current entry, cleaning up entry variables.
pub fn close(&mut self) {
self.entry_buffer.clear();
@@ -81,10 +147,10 @@ impl FileDevice {
self.path_buffer.clear();
self.flush();
- if let Some((Entry::Directory(mut dir), path)) = std::mem::take(&mut self.entry) {
+ if let Some((Entry::Directory(mut dir), path, time)) = std::mem::take(&mut self.entry) {
// Prevent the selected child from persisting when loading from cache.
dir.deselect_child();
- self.cached_dir = Some((Entry::Directory(dir), path));
+ self.cached_dir = Some((Entry::Directory(dir), path, time));
}
}
@@ -100,17 +166,17 @@ impl FileDevice {
if let Ok(file) = open_result {
self.close();
self.path_buffer.populate(path.as_buffer());
- self.entry = Some((Entry::File(BufferedFile::new(file)), path));
+ self.entry = Some((Entry::File(BufferedFile::new(file)), path, Instant::now()));
return Ok(());
};
}
Some(EntryType::Directory) => {
- // Attempt to use the cached directory.
- if let Some((dir, cached_path)) = std::mem::take(&mut self.cached_dir) {
- if cached_path == path {
+ // Attempt to use the cached directory if not too old.
+ if let Some((dir, cached_path, time)) = std::mem::take(&mut self.cached_dir) {
+ if cached_path == path && time.elapsed() < Duration::from_secs(1) {
self.close();
self.path_buffer.populate(cached_path.as_buffer());
- self.entry = Some((dir, cached_path));
+ self.entry = Some((dir, cached_path, time));
return Ok(());
}
}
@@ -118,7 +184,7 @@ impl FileDevice {
if let Some(listing) = DirectoryListing::from_path(&path) {
self.close();
self.path_buffer.populate(path.as_buffer());
- self.entry = Some((Entry::Directory(listing), path));
+ self.entry = Some((Entry::Directory(listing), path, Instant::now()));
return Ok(());
};
}
@@ -132,10 +198,16 @@ impl FileDevice {
pub fn write_to_entry_port(&mut self, byte: u8) {
if let Some(buffer) = self.entry_buffer.write(byte) {
self.close();
- match BedrockFilePath::from_buffer(buffer, &self.base_path) {
- Some(path) => self.success = self.open(path).is_ok(),
- None => self.success = false,
- };
+ // Attempt to open file if buffer was not empty.
+ if buffer[0] != 0 {
+ let success = match BedrockFilePath::from_buffer(buffer, &self.base_path) {
+ Some(path) => self.open(path).is_ok(),
+ None => false,
+ };
+ self.check_success(success);
+ } else {
+ self.check_success(true);
+ }
}
}
@@ -144,22 +216,23 @@ impl FileDevice {
if let Some(buffer) = self.action_buffer.write(byte) {
let destination_blank = buffer[0] == 0x00;
let destination = BedrockFilePath::from_buffer(buffer, &self.base_path);
- self.success = false;
- if let Some((_, source)) = &self.entry {
+ if let Some((_, source, _)) = &self.entry {
if destination_blank {
if self.enable_delete {
- self.success = delete_entry(&source.as_path());
+ self.check_success(delete_entry(&source.as_path()));
}
} else if let Some(dest) = destination {
if self.enable_move {
- self.success = move_entry(&source.as_path(), &dest.as_path());
+ self.check_success(move_entry(&source.as_path(), &dest.as_path()));
}
}
} else if let Some(dest) = destination {
if self.enable_create {
- self.success = create_file(&dest.as_path());
+ self.check_success(create_file(&dest.as_path()));
}
+ } else {
+ self.check_success(false);
}
self.close();
}
@@ -167,35 +240,38 @@ impl FileDevice {
/// Attempt to open the parent directory of the current entry.
pub fn ascend_to_parent(&mut self) {
- if let Some((_, path)) = &self.entry {
- match path.parent() {
- Some(parent) => self.success = self.open(parent).is_ok(),
- None => self.success = false,
+ if let Some((_, path, _)) = &self.entry {
+ let success = match path.parent() {
+ Some(parent) => self.open(parent).is_ok(),
+ None => false,
};
+ self.check_success(success);
} else {
- match BedrockFilePath::from_path(&self.default_path, &self.base_path) {
- Some(default) => self.success = self.open(default).is_ok(),
- None => self.success = false,
+ let success = match BedrockFilePath::from_path(&self.default_path, &self.base_path) {
+ Some(default) => self.open(default).is_ok(),
+ None => false,
};
+ self.check_success(success);
}
}
/// Attempt to open the selected child of the current directory.
pub fn descend_to_child(&mut self) {
- if let Some((Entry::Directory(dir), _)) = &self.entry {
- match dir.child_path() {
- Some(child) => self.success = self.open(child).is_ok(),
- None => self.success = false,
+ if let Some((Entry::Directory(dir), _, _)) = &self.entry {
+ let success = match dir.child_path() {
+ Some(child) => self.open(child).is_ok(),
+ None => false,
};
+ self.check_success(success);
} else {
- self.success = false;
+ self.check_success(false);
}
}
/// Return true if the current entry is a directory.
pub fn entry_type(&self) -> bool {
match self.entry {
- Some((Entry::Directory(_), _)) => true,
+ Some((Entry::Directory(_), _, _)) => true,
_ => false,
}
}
@@ -203,13 +279,13 @@ impl FileDevice {
/// Read a byte from the path buffer of the selected child.
pub fn read_child_path(&mut self) -> u8 {
match &mut self.entry {
- Some((Entry::Directory(dir), _)) => dir.child_path_buffer().read(),
+ Some((Entry::Directory(dir), _, _)) => dir.child_path_buffer().read(),
_ => 0,
}
}
pub fn set_child_path(&mut self, byte: u8) {
- if let Some((Entry::Directory(dir), _)) = &mut self.entry {
+ if let Some((Entry::Directory(dir), _, _)) = &mut self.entry {
dir.child_path_buffer().set_pointer(byte);
}
}
@@ -217,7 +293,7 @@ impl FileDevice {
/// Return true if the selected child is a directory.
pub fn child_type(&self) -> bool {
match &self.entry {
- Some((Entry::Directory(dir), _)) => match dir.child_type() {
+ Some((Entry::Directory(dir), _, _)) => match dir.child_type() {
Some(EntryType::Directory) => true,
_ => false,
}
@@ -228,7 +304,7 @@ impl FileDevice {
/// Read a byte from the current file.
pub fn read_byte(&mut self) -> u8 {
match &mut self.entry {
- Some((Entry::File(file), _)) => file.read(),
+ Some((Entry::File(file), _, _)) => file.read(),
_ => 0,
}
}
@@ -236,102 +312,100 @@ impl FileDevice {
/// Writes a byte to the currently-open file.
pub fn write_byte(&mut self, byte: u8) {
match &mut self.entry {
- Some((Entry::File(file), _)) => file.write(byte),
+ Some((Entry::File(file), _, _)) => file.write(byte),
_ => (),
}
}
pub fn pointer(&mut self) -> u32 {
match &mut self.entry {
- Some((Entry::File(file), _)) => file.pointer(),
- Some((Entry::Directory(dir), _)) => dir.selected(),
+ Some((Entry::File(file), _, _)) => file.pointer(),
+ Some((Entry::Directory(dir), _, _)) => dir.selected(),
_ => 0,
}
}
pub fn commit_pointer(&mut self) {
match &mut self.entry {
- Some((Entry::File(file), _)) => file.set_pointer(self.pointer_write),
- Some((Entry::Directory(dir), _)) => dir.set_selected(self.pointer_write),
+ Some((Entry::File(file), _, _)) => file.set_pointer(self.pointer_write),
+ Some((Entry::Directory(dir), _, _)) => dir.set_selected(self.pointer_write),
_ => (),
}
}
pub fn length(&mut self) -> u32 {
match &mut self.entry {
- Some((Entry::File(file), _)) => file.length(),
- Some((Entry::Directory(dir), _)) => dir.length(),
+ Some((Entry::File(file), _, _)) => file.length(),
+ Some((Entry::Directory(dir), _, _)) => dir.length(),
_ => 0,
}
}
pub fn commit_length(&mut self) {
match &mut self.entry {
- Some((Entry::File(file), _)) => file.set_length(self.length_write),
+ Some((Entry::File(file), _, _)) => file.set_length(self.length_write),
_ => (),
}
}
pub fn flush(&mut self) {
- if let Some((Entry::File(buffered_file), _)) = &mut self.entry {
+ if let Some((Entry::File(buffered_file), _, _)) = &mut self.entry {
let _ = buffered_file;
}
}
}
+
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..3ebeb4c 100644
--- a/src/devices/input_device.rs
+++ b/src/devices/input_device.rs
@@ -1,84 +1,149 @@
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,
+ pub h_scroll_read: i16,
+ pub v_scroll_read: i16,
+ pub h_scroll: f32,
+ pub v_scroll: f32,
+ pub pointer_buttons: u8,
+ pub pointer_active: bool,
- pub h_scroll: i8,
- pub v_scroll: i8,
- pub h_scroll_delta: f32,
- pub v_scroll_delta: f32,
-
- pub keyboard_active: bool,
- pub characters: VecDeque<u8>,
pub navigation: u8,
pub modifiers: u8,
+ pub characters: VecDeque<u8>,
+ pub gamepad_1: OwnedGamepad,
+ pub gamepad_2: OwnedGamepad,
+ pub gamepad_3: OwnedGamepad,
+ pub gamepad_4: OwnedGamepad,
+
+ 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.update_horizontal_scroll(); read_h!(self.h_scroll_read) },
+ 0x5 => read_l!(self.h_scroll_read),
+ 0x6 => { self.update_vertical_scroll(); read_h!(self.v_scroll_read) },
+ 0x7 => read_l!(self.v_scroll_read),
+ 0x8 => read_b!(self.pointer_active),
+ 0x9 => self.pointer_buttons,
+ 0xA => self.characters.pop_front().unwrap_or(0),
+ 0xB => self.modifiers,
+ 0xC => self.gamepad_1.state() | self.navigation,
+ 0xD => self.gamepad_2.state(),
+ 0xE => self.gamepad_3.state(),
+ 0xF => self.gamepad_4.state(),
+ _ => unreachable!(),
+ }
+ }
- pub gamepad_1: u8,
- pub gamepad_2: u8,
- pub gamepad_3: u8,
- pub gamepad_4: u8,
+ fn write(&mut self, port: u8, _value: u8) -> Option<Signal> {
+ let signal = if self.accessed { None } else { Some(Signal::Break) };
+ 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 signal;
+ }
+
+ fn wake(&mut self) -> bool {
+ self.accessed = true;
+ std::mem::take(&mut self.wake)
+ }
+
+ fn reset(&mut self) {
+ self.cursor = ScreenPosition::ZERO;
+ self.x_read = 0;
+ self.y_read = 0;
+ self.h_scroll_read = 0;
+ self.v_scroll_read = 0;
+ self.h_scroll = 0.0;
+ self.v_scroll = 0.0;
+ self.pointer_active = false;
+ self.pointer_buttons = 0;
+
+ self.navigation = 0;
+ self.modifiers = 0;
+ self.characters.clear();
+ self.gamepad_1.reset();
+ self.gamepad_2.reset();
+ self.gamepad_3.reset();
+ self.gamepad_4.reset();
+
+ self.accessed = false;
+ self.wake = false;
+ }
}
+
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,
+ h_scroll_read: 0,
+ v_scroll_read: 0,
+ h_scroll: 0.0,
+ v_scroll: 0.0,
+ pointer_active: false,
+ pointer_buttons: 0,
- h_scroll: 0,
- v_scroll: 0,
- h_scroll_delta: 0.0,
- v_scroll_delta: 0.0,
-
- keyboard_active: true,
- characters: VecDeque::new(),
- modifiers: 0,
navigation: 0,
+ modifiers: 0,
+ characters: VecDeque::new(),
+ gamepad_1: OwnedGamepad::new(1),
+ gamepad_2: OwnedGamepad::new(2),
+ gamepad_3: OwnedGamepad::new(3),
+ gamepad_4: OwnedGamepad::new(4),
- gamepad_1: 0,
- gamepad_2: 0,
- gamepad_3: 0,
- gamepad_4: 0,
+ accessed: false,
+ wake: false,
}
}
+ #[cfg(feature = "gamepad")]
+ pub fn on_gamepad_event(&mut self, event: gilrs::Event) {
+ if let Some(g) = self.gamepad_1.register(event.id) {
+ self.wake |= g.process_event(&event); return; }
+ if let Some(g) = self.gamepad_2.register(event.id) {
+ self.wake |= g.process_event(&event); return; }
+ if let Some(g) = self.gamepad_3.register(event.id) {
+ self.wake |= g.process_event(&event); return; }
+ if let Some(g) = self.gamepad_4.register(event.id) {
+ self.wake |= g.process_event(&event); return; }
+ }
+
pub fn on_cursor_enter(&mut self) {
self.pointer_active = true;
self.wake = true;
@@ -90,12 +155,13 @@ impl InputDevice {
}
pub fn on_cursor_move(&mut self, position: Position) {
- let screen_position = ScreenPosition {
+ self.pointer_active = true;
+ 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;
}
}
@@ -103,8 +169,8 @@ impl InputDevice {
pub fn on_mouse_button(&mut self, button: MouseButton, action: Action) {
let mask = match button {
MouseButton::Left => 0x80,
- MouseButton::Middle => 0x40,
- MouseButton::Right => 0x20,
+ MouseButton::Right => 0x40,
+ MouseButton::Middle => 0x20,
_ => return,
};
let pointer_buttons = match action {
@@ -117,15 +183,26 @@ 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;
+ self.h_scroll = self.h_scroll.clamp(-32768.0, 32767.0);
+ if self.h_scroll.abs() >= 1.0 { self.wake = true; }
+ }
- pub fn read_horizontal_scroll(&mut self) -> u8 {
- std::mem::take(&mut self.h_scroll) as u8
+ pub fn on_vertical_scroll(&mut self, delta: f32) {
+ self.v_scroll += delta;
+ self.v_scroll = self.v_scroll.clamp(i16::MIN as f32, i16::MAX as f32);
+ if self.v_scroll.abs() >= 1.0 { self.wake = true; }
}
- pub fn read_vertical_scroll(&mut self) -> u8 {
- std::mem::take(&mut self.v_scroll) as u8
+ pub fn update_horizontal_scroll(&mut self) {
+ self.h_scroll_read = self.h_scroll.trunc() as i16;
+ self.h_scroll -= self.h_scroll.trunc();
+ }
+
+ pub fn update_vertical_scroll(&mut self) {
+ self.v_scroll_read = self.v_scroll.trunc() as i16;
+ self.v_scroll -= self.v_scroll.trunc();
}
pub fn on_character(&mut self, character: char) {
@@ -178,57 +255,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..e7043b9 100644
--- a/src/devices/math_device.rs
+++ b/src/devices/math_device.rs
@@ -1,59 +1,168 @@
-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 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>,
pub prod: Option<(u16, u16)>, // (low, high)
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
+ }
+
+ fn reset(&mut self) {
+ self.x = 0;
+ self.y = 0;
+ self.r = 0;
+ self.t = 0;
+ self.clear_cartesian();
+ self.clear_polar();
+ }
+}
+
+
impl MathDevice {
pub fn new() -> Self {
Self {
- op1: 0,
- op2: 0,
-
- sqrt: None,
- atan: None,
+ x: 0,
+ y: 0,
+ r: 0,
+ t: 0,
+ x_read: None,
+ y_read: None,
+ r_read: None,
+ t_read: 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 atan(&mut self) -> u16 {
- match self.atan {
- Some(atan) => atan,
+ pub fn x(&mut self) -> u16 {
+ match self.x_read {
+ Some(x) => x,
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 x = angle.cos() * r;
+ self.x_read = match x > (i16::MIN as f64) && x < (i16::MAX as f64) {
+ true => Some(x as i16 as u16),
+ false => Some(0),
+ };
+ self.x_read.unwrap()
}
}
}
- pub fn sqrt(&mut self) -> u16 {
- match self.sqrt {
- Some(sqrt) => sqrt,
+ pub fn y(&mut self) -> u16 {
+ match self.y_read {
+ Some(y) => y,
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 r = self.r as f64;
+ let t = self.t as f64;
+ let angle = t / ANGLE_SCALE;
+ let y = angle.sin() * r;
+ self.y_read = match y > (i16::MIN as f64) && y < (i16::MAX as f64) {
+ true => Some(y as i16 as u16),
+ false => Some(0),
+ };
+ self.y_read.unwrap()
+ }
+ }
+ }
+
+ pub fn r(&mut self) -> u16 {
+ match self.r_read {
+ Some(r) => r,
+ None => {
+ 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 +171,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 +181,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 +191,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..d116ca7 100644
--- a/src/devices/memory_device.rs
+++ b/src/devices/memory_device.rs
@@ -1,93 +1,151 @@
-use bedrock_core::*;
+use crate::*;
-type Page = [u8; 256];
+use std::cmp::min;
-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,
- }
- }
- };
-}
-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 limit: u16, // maximum allocateable number of pages
pub pages: Vec<Page>, // all allocated pages
+ pub count_write: u16, // number of pages requested by program
+ pub count: usize, // number of pages allocated for use
+ pub copy_write: u16,
+ pub head_1: HeadAddress,
+ pub head_2: HeadAddress,
+}
- 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 => read_h!(self.count),
+ 0x1 => read_l!(self.count),
+ 0x2 => read_h!(self.head_1.page),
+ 0x3 => read_l!(self.head_1.page),
+ 0x4 => read_h!(self.head_1.address),
+ 0x5 => read_l!(self.head_1.address),
+ 0x6 => self.read_head_1(),
+ 0x7 => self.read_head_1(),
+ 0x8 => 0x00,
+ 0x9 => 0x00,
+ 0xA => read_h!(self.head_2.page),
+ 0xB => read_l!(self.head_2.page),
+ 0xC => read_h!(self.head_2.address),
+ 0xD => read_l!(self.head_2.address),
+ 0xE => self.read_head_2(),
+ 0xF => self.read_head_2(),
+ _ => unreachable!(),
+ }
+ }
+
+ fn write(&mut self, port: u8, value: u8) -> Option<Signal> {
+ match port {
+ 0x0 => write_h!(self.count_write, value),
+ 0x1 => { write_l!(self.count_write, value); self.allocate(); },
+ 0x2 => write_h!(self.head_1.page, value),
+ 0x3 => write_l!(self.head_1.page, value),
+ 0x4 => write_h!(self.head_1.address, value),
+ 0x5 => write_l!(self.head_1.address, value),
+ 0x6 => self.write_head_1(value),
+ 0x7 => self.write_head_1(value),
+ 0x8 => write_h!(self.copy_write, value),
+ 0x9 => { write_l!(self.copy_write, value); self.copy(); },
+ 0xA => write_h!(self.head_2.page, value),
+ 0xB => write_l!(self.head_2.page, value),
+ 0xC => write_h!(self.head_2.address, value),
+ 0xD => write_l!(self.head_2.address, value),
+ 0xE => self.write_head_2(value),
+ 0xF => self.write_head_2(value),
+ _ => unreachable!(),
+ };
+ return None;
+ }
+
+ fn wake(&mut self) -> bool {
+ false
+ }
+
+ fn reset(&mut self) {
+ self.pages.clear();
+ self.count_write = 0;
+ self.count = 0;
+ self.copy_write = 0;
+ self.head_1.reset();
+ self.head_2.reset();
+ }
}
+
impl MemoryDevice {
pub fn new() -> Self {
Self {
limit: u16::MAX,
- requested: 0,
- provisioned: 0,
pages: Vec::new(),
+ count_write: 0,
+ count: 0,
+ copy_write: 0,
+ head_1: HeadAddress::new(),
+ head_2: HeadAddress::new(),
+ }
+ }
- offset_1: 0,
- address_1: 0,
- offset_2: 0,
- address_2: 0,
+ pub fn read_head_1(&mut self) -> u8 {
+ let (page_i, byte_i) = self.head_1.get_indices();
+ self.read_byte(page_i, byte_i)
+ }
- copy_length: 0,
+ pub fn read_head_2(&mut self) -> u8 {
+ let (page_i, byte_i) = self.head_2.get_indices();
+ self.read_byte(page_i, byte_i)
+ }
+
+ 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_indices();
+ 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_indices();
+ self.write_byte(page_i, byte_i, value);
+ }
- pub fn provision(&mut self) {
- self.provisioned = std::cmp::min(self.requested, self.limit) as usize;
+ 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.count {
+ self.pages.resize(page_i + 1, [0; 256]);
+ self.pages[page_i][byte_i] = value;
+ }
+ }
+ }
+
+ pub fn allocate(&mut self) {
+ self.count = min(self.count_write, self.limit) as usize;
// Defer allocation of new pages.
- self.pages.truncate(self.provisioned as usize);
+ self.pages.truncate(self.count as usize);
}
pub fn copy(&mut self) {
- let src = self.offset_2 as usize;
- let dest = self.offset_1 as usize;
- let count = self.copy_length as usize;
+ let src = self.head_2.page as usize;
+ let dest = self.head_1.page as usize;
+ let n = self.copy_write as usize;
// Pre-allocate destination pages as needed.
- let pages_needed = std::cmp::min(dest + count, self.provisioned);
- if pages_needed > self.pages.len() {
- self.pages.resize(pages_needed, [0; 256]);
+ let allocate = min(dest + n, self.count);
+ if allocate > self.pages.len() {
+ self.pages.resize(allocate, [0; 256]);
}
- for i in 0..count {
+ for i in 0..n {
let src_page = match self.pages.get(src + i) {
Some(src_page) => src_page.to_owned(),
None => [0; 256],
@@ -100,53 +158,29 @@ 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!(),
+
+pub struct HeadAddress {
+ pub page: u16,
+ pub address: u16,
+}
+
+impl HeadAddress {
+ pub fn new() -> Self {
+ Self {
+ page: 0,
+ address: 0,
}
}
- 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 fn reset(&mut self) {
+ self.page = 0;
+ self.address = 0;
}
- fn wake(&mut self) -> bool {
- false
+ pub fn get_indices(&mut self) -> (usize, usize) {
+ let page_i = (self.page + (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..483bcca 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,110 @@ 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> {
+ let signal = if self.accessed { None } else { Some(Signal::Break) };
+ 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 signal;
+ }
+
+ fn wake(&mut self) -> bool {
+ self.accessed = true;
+ std::mem::take(&mut self.wake)
+ }
+
+ fn reset(&mut self) {
+ self.fg.clear();
+ self.bg.clear();
+ self.dirty = false;
+
+ self.cursor = ScreenPosition::ZERO;
+ self.vector = ScreenPosition::ZERO;
+
+ self.dirty_dimensions = true;
+ self.width_write = 0;
+ self.height_write = 0;
+ self.fixed_width = None;
+ self.fixed_height = None;
+
+ self.palette_write = 0;
+ self.palette = [
+ Colour::grey(0x00), Colour::grey(0xFF), Colour::grey(0x55), Colour::grey(0xAA),
+ Colour::grey(0x00), Colour::grey(0xFF), Colour::grey(0x55), Colour::grey(0xAA),
+ Colour::grey(0x00), Colour::grey(0xFF), Colour::grey(0x55), Colour::grey(0xAA),
+ Colour::grey(0x00), Colour::grey(0xFF), Colour::grey(0x55), Colour::grey(0xAA),
+ ];
+ self.sprite_colours = 0;
+ self.sprite = SpriteBuffer::new();
+
+ self.accessed = false;
+ self.wake = false;
+ }
}
+
impl ScreenDevice {
pub fn new(config: &EmulatorConfig) -> Self {
let area = config.dimensions.area_usize();
Self {
- wake: false,
- accessed: false,
-
fg: vec![0; area],
bg: vec![0; area],
dirty: false,
@@ -60,12 +149,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 {
@@ -158,9 +250,9 @@ impl ScreenDevice {
pub fn set_palette(&mut self) {
let i = (self.palette_write >> 12 ) as usize;
- let r = (self.palette_write >> 8 & 0xf) as u8 * 17;
- let g = (self.palette_write >> 4 & 0xf) as u8 * 17;
- let b = (self.palette_write & 0xf) as u8 * 17;
+ let r = (self.palette_write >> 8 & 0xF) as u8 * 17;
+ let g = (self.palette_write >> 4 & 0xF) as u8 * 17;
+ let b = (self.palette_write & 0xF) as u8 * 17;
let colour = Colour::from_rgb(r, g, b);
if self.palette[i] != colour {
self.palette[i] = colour;
@@ -193,7 +285,7 @@ impl ScreenDevice {
}
pub fn move_cursor(&mut self, value: u8) {
- let distance = (value & 0x3f) as u16;
+ let distance = (value & 0x3F) as u16;
match value >> 6 {
0b00 => self.cursor.x = self.cursor.x.wrapping_add(distance),
0b01 => self.cursor.y = self.cursor.y.wrapping_add(distance),
@@ -203,7 +295,7 @@ impl ScreenDevice {
};
}
- /// Colour must already be masked by 0xf.
+ /// Colour must already be masked by 0xF.
pub fn draw_pixel(&mut self, layer: Layer, x: u16, y: u16, colour: u8) {
if x < self.dimensions.width && y < self.dimensions.height {
let index = x as usize + (self.dimensions.width as usize * y as usize);
@@ -215,13 +307,13 @@ impl ScreenDevice {
}
fn op_draw_pixel(&mut self, layer: Layer, draw: u8) {
- self.draw_pixel(layer, self.cursor.x, self.cursor.y, draw & 0xf);
+ self.draw_pixel(layer, self.cursor.x, self.cursor.y, draw & 0xF);
}
fn op_fill_layer(&mut self, layer: Layer, draw: u8) {
match layer {
- Layer::Fg => self.fg.fill(draw & 0xf),
- Layer::Bg => self.bg.fill(draw & 0xf),
+ Layer::Fg => self.fg.fill(draw & 0xF),
+ Layer::Bg => self.bg.fill(draw & 0xF),
}
}
@@ -231,10 +323,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 +371,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];
@@ -293,7 +385,7 @@ impl ScreenDevice {
}
} else {
// Draw solid line.
- let colour = draw & 0xf;
+ let colour = draw & 0xF;
loop {
self.draw_pixel(layer, x as u16, y as u16, colour);
if x == x_end && y == y_end { break; }
@@ -307,7 +399,7 @@ impl ScreenDevice {
fn op_draw_rect(&mut self, layer: Layer, draw: u8) {
macro_rules! clamp {
($v:expr, $max:expr) => {
- if $v > 0x7fff { 0 } else if $v > $max { $max } else { $v }
+ if $v > 0x7FFF { 0 } else if $v > $max { $max } else { $v }
};
}
macro_rules! out_of_bounds {
@@ -333,8 +425,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 {
@@ -345,7 +437,7 @@ impl ScreenDevice {
}
} else {
// Draw solid rectangle.
- let colour = draw & 0xf;
+ let colour = draw & 0xF;
for y in t..=b {
for x in l..=r {
self.draw_pixel(layer, x, y, colour);
@@ -355,146 +447,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..1e67166 100644
--- a/src/devices/local_device.rs
+++ b/src/devices/stream_device.rs
@@ -1,36 +1,95 @@
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 => read_b!(true),
+ 0x4 => self.stdin_length(),
+ 0x5 => read_b!(true),
+ 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_start_transmission(),
+ 0x3 => self.stdout_end_transmission(),
+ 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)
+ }
+
+ fn reset(&mut self) {
+ todo!()
+ }
+}
+
+
+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 +105,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,
}
}
@@ -68,7 +127,8 @@ impl LocalDevice {
self.stdin_queue.len().try_into().unwrap_or(u8::MAX)
}
- pub fn stdin_enable(&mut self) {
+ /// Start a transmission on stdin.
+ pub fn stdin_start_transmission(&mut self) {
self.stdin_control = true;
}
@@ -81,12 +141,12 @@ impl LocalDevice {
macro_rules! hex {
($value:expr) => { match $value {
0x0..=0x9 => $value + b'0',
- 0xa..=0xf => $value - 0x0a + b'a',
- _ => unreachable!("Cannot encode value as hex digit: 0x{:02x}", $value),
+ 0xA..=0xF => $value - 0x0A + b'A',
+ _ => unreachable!("Cannot encode value as hex digit: 0x{:02X}", $value),
} };
}
if self.encode_stdout {
- let encoded = [hex!(value >> 4), hex!(value & 0xf), b' '];
+ let encoded = [hex!(value >> 4), hex!(value & 0xF), b' '];
self.stdout_write_raw(&encoded);
} else {
self.stdout_write_raw(&[value]);
@@ -102,12 +162,12 @@ impl LocalDevice {
}
}
- pub fn stdout_disable(&mut self) {
- if self.encode_stdout {
- self.stdout_write_raw(&[b'\n']);
- }
+ /// End the current transmission on stdout.
+ pub fn stdout_end_transmission(&mut self) {
+ self.stdout_write_raw(&[b'\n']);
}
+ /// 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 +175,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;
}
}
}
@@ -139,8 +204,8 @@ impl LocalDevice {
if self.decode_stdin {
let decoded = match byte {
b'0'..=b'9' => byte - b'0',
- b'a'..=b'f' => byte - b'a' + 0x0a,
- b'A'..=b'F' => byte - b'A' + 0x0a,
+ b'a'..=b'f' => byte - b'a' + 0x0A,
+ b'A'..=b'F' => byte - b'A' + 0x0A,
b'\n' => {
self.decode_buffer = None;
self.stdin_control = false;
@@ -167,61 +232,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..097c616 100644
--- a/src/devices/system_device.rs
+++ b/src/devices/system_device.rs
@@ -1,80 +1,78 @@
-use bedrock_core::*;
+use crate::*;
pub struct SystemDevice {
- pub name: ReadBuffer,
- pub authors: ReadBuffer,
- pub can_wake: u16,
- pub wake_id: u8,
+ /// Name and version of this system.
+ pub name: StringBuffer,
+ /// Authors of this system.
+ pub authors: StringBuffer,
+ /// Mask of all devices permitted to wake from sleep.
+ pub wake_mask: u16,
+ /// Slot number of device that most recently woke the system.
+ pub wake_slot: u8,
+ /// True if the system has been put to sleep.
pub asleep: bool,
+ /// Mask of all connected devices.
+ pub connected_devices: u16,
+ /// Name of the first custom device.
+ pub custom1: StringBuffer,
+ /// Name of the second custom device.
+ pub custom2: StringBuffer,
+ /// Name of the third custom device.
+ pub custom3: StringBuffer,
+ /// Name of the fourth custom device.
+ pub custom4: StringBuffer,
}
-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.wake_slot,
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,
- 0xb => 0x00,
- 0xc => 0x00,
- 0xd => 0x00,
- 0xe => 0x00,
- 0xf => 0x00,
+ 0x4 => self.custom1.read(),
+ 0x5 => self.custom2.read(),
+ 0x6 => self.custom3.read(),
+ 0x7 => self.custom4.read(),
+ 0x8 => self.name.read(),
+ 0x9 => self.authors.read(),
+ 0xA => 0x00,
+ 0xB => 0x00,
+ 0xC => 0x00,
+ 0xD => 0x00,
+ 0xE => read_h!(self.connected_devices),
+ 0xF => read_l!(self.connected_devices),
_ => unreachable!(),
}
}
fn write(&mut self, port: u8, value: u8) -> Option<Signal> {
match port {
- 0x0 => self.name.pointer = 0,
- 0x1 => self.authors.pointer = 0,
- 0x2 => (),
- 0x3 => (),
- 0x4 => (),
- 0x5 => (),
- 0x6 => (),
- 0x7 => (),
- 0x8 => write_h!(self.can_wake, value),
- 0x9 => {
- write_l!(self.can_wake, value);
+ 0x0 => write_h!(self.wake_mask, value),
+ 0x1 => {
+ write_l!(self.wake_mask, value);
self.asleep = true;
return Some(Signal::Sleep);
- },
- 0xa => (),
- 0xb => return Some(Signal::Fork),
- 0xc => (),
- 0xd => (),
- 0xe => (),
- 0xf => (),
+ }
+ 0x2 => (),
+ 0x3 => match value {
+ 0 => return Some(Signal::Reset),
+ _ => return Some(Signal::Fork),
+ }
+ 0x4 => self.custom1.restart(),
+ 0x5 => self.custom2.restart(),
+ 0x6 => self.custom3.restart(),
+ 0x7 => self.custom4.restart(),
+ 0x8 => self.name.restart(),
+ 0x9 => self.authors.restart(),
+ 0xA => (),
+ 0xB => (),
+ 0xC => (),
+ 0xD => (),
+ 0xE => (),
+ 0xF => (),
_ => unreachable!(),
};
return None;
@@ -83,29 +81,39 @@ impl Device for SystemDevice {
fn wake(&mut self) -> bool {
true
}
+
+ fn reset(&mut self) {
+ self.wake_mask = 0;
+ self.wake_slot = 0;
+ self.custom1.restart();
+ self.custom2.restart();
+ self.custom3.restart();
+ self.custom4.restart();
+ self.name.restart();
+ self.authors.restart();
+ }
}
-pub struct ReadBuffer {
- pub bytes: Vec<u8>,
- pub pointer: usize,
-}
+impl SystemDevice {
+ pub fn new(connected_devices: u16) -> Self {
+ let pkg_name = env!("CARGO_PKG_NAME");
+ let pkg_version = env!("CARGO_PKG_VERSION");
+ let pkg_authors = env!("CARGO_PKG_AUTHORS");
+ let name = format!("{pkg_name}/{pkg_version}");
+ let authors = pkg_authors.replace(':', "\n");
-impl ReadBuffer {
- pub fn from_str(text: &str) -> Self {
Self {
- bytes: text.bytes().collect(),
- 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,
+ name: StringBuffer::from_str(&name),
+ authors: StringBuffer::from_str(&authors),
+ wake_mask: 0,
+ wake_slot: 0,
+ asleep: false,
+ connected_devices,
+ custom1: StringBuffer::new(),
+ custom2: StringBuffer::new(),
+ custom3: StringBuffer::new(),
+ custom4: StringBuffer::new(),
}
}
}
-
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/graphical_emulator.rs b/src/emulators/graphical_emulator.rs
index 14848c6..503a8f4 100644
--- a/src/emulators/graphical_emulator.rs
+++ b/src/emulators/graphical_emulator.rs
@@ -1,128 +1,42 @@
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,
+ #[cfg(feature = "gamepad")]
+ pub gilrs: Option<Gilrs>,
+
pub fullscreen: bool,
+ pub visible: bool,
pub scale: u32,
- pub render_mark: Instant,
- pub debug_palette: Option<[Colour; 16]>,
- pub show_debug_palette: bool,
- pub show_cursor: bool,
+ pub render_mark: Instant, // last time screen was rendered
+ pub frame_mark: Instant, // refreshes when clean
+ pub replace_palette: bool,
+ pub config: EmulatorConfig,
}
impl GraphicalEmulator {
- pub fn new(config: &EmulatorConfig, debug: bool, verbose: bool) -> Self {
- let devices = GraphicalDeviceBus::new(config);
+ pub fn new(config: EmulatorConfig, debug: bool) -> Self {
Self {
- br: BedrockEmulator::new(devices),
- debug: DebugState::new(debug, verbose, config.symbols_path.as_ref()),
- dimensions: config.dimensions,
+ br: BedrockEmulator::new(GraphicalDeviceBus::new(&config)),
+ debug: DebugState::new(debug, config.symbols_path.as_ref()),
+
+ #[cfg(feature = "gamepad")]
+ gilrs: match Gilrs::new() {
+ Ok(gilrs) => Some(gilrs),
+ Err(err) => {
+ info!("Could not start gamepad listener: {}", err);
+ None
+ }
+ },
+
fullscreen: config.fullscreen,
- scale: config.scale,
+ scale: config.zoom.into(),
+ replace_palette: config.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,
+ frame_mark: Instant::now(),
+ config,
+ visible: false,
}
}
@@ -130,70 +44,108 @@ 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();
- self.debug.info("Program halted, exiting.");
- self.debug.debug_summary(&self.br.core);
- return EmulatorSignal::Halt;
- }
- Some(Signal::Debug1) => {
- self.debug.debug_summary(&self.br.core);
- }
- _ => (),
- }
-
- if self.br.dev.graphical() {
- return EmulatorSignal::Promote;
- }
- }
+ pub fn run(mut self, mut phosphor: Phosphor, visible: bool) {
+ self.visible = visible;
+ let window = WindowBuilder {
+ dimensions: Some(self.dimensions()),
+ size_bounds: Some(self.size_bounds()),
+ fullscreen: self.fullscreen,
+ scale: self.scale,
+ title: Some(self.config.title.clone()),
+ cursor: match self.config.show_cursor {
+ true => Some(CursorIcon::Default),
+ false => None,
+ },
+ icon: self.config.icon.clone(),
+ program: Box::new(self),
+ visible,
+ };
+ phosphor.add_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),
},
-
}
}
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,
+ pub wake_queue: WakeQueue,
+}
+
+impl GraphicalDeviceBus {
+ pub fn new(config: &EmulatorConfig) -> Self {
+ Self {
+ system: SystemDevice::new(0b1111_1100_1100_0000),
+ memory: MemoryDevice::new(),
+ math: MathDevice::new(),
+ clock: ClockDevice::new(),
+ input: InputDevice::new(),
+ screen: ScreenDevice::new(&config),
+ stream: StreamDevice::new(&config),
+ file: FileDevice::new(&config),
+ wake_queue: WakeQueue::new(),
+ }
+ }
+}
+
+
+impl DeviceBus for GraphicalDeviceBus {
+ fn get_device(&mut self, slot: u8) -> Option<&mut dyn Device> {
+ match slot {
+ 0x0 => Some(&mut self.system),
+ 0x1 => Some(&mut self.memory),
+ 0x2 => Some(&mut self.math ),
+ 0x3 => Some(&mut self.clock ),
+ 0x4 => Some(&mut self.input ),
+ 0x5 => Some(&mut self.screen),
+ 0x8 => Some(&mut self.stream),
+ 0x9 => Some(&mut self.file ),
+ _ => None
+ }
+ }
+
+ fn wake(&mut self) -> bool {
+ for slot in self.wake_queue.iter(self.system.wake_mask) {
+ if let Some(device) = self.get_device(slot) {
+ if device.wake() {
+ self.system.wake_slot = slot;
+ self.system.asleep = false;
+ self.wake_queue.wake(slot);
+ return true;
+ }
+ }
}
+ return false;
}
}
@@ -202,33 +154,31 @@ 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::MouseButton { button, action } =>
- self.br.dev.inp.on_mouse_button(button, action),
+ 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.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.replace_palette = !self.replace_palette;
r.write(Request::Redraw);
},
KeyCode::F5 => {
@@ -247,83 +197,101 @@ impl WindowProgram for GraphicalEmulator {
}
}
}
+ _ => (),
}
}
fn process(&mut self, requests: &mut EventWriter<Request>) {
- self.br.dev.loc.flush();
+ self.br.dev.stream.flush();
+
+ #[cfg(feature = "gamepad")]
+ if let Some(gilrs) = &mut self.gilrs {
+ while let Some(event) = gilrs.next_event() {
+ self.br.dev.input.on_gamepad_event(event);
+ }
+ }
- 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);
+ std::thread::sleep(TICK_DURATION);
return;
}
// Wait for the current frame to be rendered.
- if self.br.dev.scr.dirty {
- if self.render_mark.elapsed() > MIN_FRAME_DURATION {
- requests.write(Request::Redraw);
- }
- std::thread::sleep(MIN_TICK_DURATION);
+ if self.br.dev.screen.dirty {
+ requests.write(Request::Redraw);
+ std::thread::sleep(TICK_DURATION);
return;
}
}
// Run the processor for the remainder of the frame.
- let frame_end = Instant::now() + MIN_TICK_DURATION;
+ let frame_end = Instant::now() + TICK_DURATION;
while Instant::now() < frame_end {
- match self.br.evaluate(BATCH_SIZE, self.debug.enabled) {
- Some(Signal::Fork) => {
- todo!("Fork")
- }
- Some(Signal::Sleep) => {
- self.br.dev.sys.asleep = true;
- break;
- }
- Some(Signal::Halt) => {
- self.br.dev.loc.flush();
- self.debug.info("Program halted, exiting.");
- self.debug.debug_summary(&self.br.core);
- requests.write(Request::CloseWindow);
- break;
- }
- Some(Signal::Debug1) => {
- self.debug.debug_summary(&self.br.core);
+ if let Some(signal) = self.br.evaluate(BATCH_SIZE, self.debug.enabled) {
+ match signal {
+ Signal::Break => {
+ }
+ Signal::Fork | Signal::Reset => {
+ self.br.reset();
+ }
+ Signal::Sleep => {
+ self.br.dev.system.asleep = true;
+ break;
+ }
+ Signal::Halt => {
+ self.br.dev.stream.flush();
+ info!("Program halted, exiting.");
+ self.debug.debug_full(&self.br.core);
+ requests.write(Request::CloseWindow);
+ break;
+ }
+ Signal::Debug(debug_signal) => match debug_signal {
+ Debug::Debug1 => self.debug.debug_full(&self.br.core),
+ Debug::Debug2 => self.debug.debug_timing(&self.br.core),
+ _ => (),
+ }
}
- _ => (),
}
}
- if std::mem::take(&mut self.br.dev.scr.dirty_dimensions) {
+ if !self.visible {
+ if self.br.dev.input.accessed || self.br.dev.screen.accessed {
+ info!("Making window visible");
+ requests.write(Request::SetVisible(true));
+ self.visible = true;
+ }
+ }
+
+ if std::mem::take(&mut self.br.dev.screen.dirty_dimensions) {
requests.write(Request::SetSizeBounds(self.size_bounds()));
}
- if self.br.dev.scr.dirty {
- let elapsed = self.render_mark.elapsed();
- if self.br.dev.sys.asleep && elapsed > MIN_FRAME_DURATION {
+ if self.br.dev.screen.dirty {
+ if self.br.dev.system.asleep {
requests.write(Request::Redraw);
- } else if elapsed > MAX_FRAME_DURATION {
+ } else if self.frame_mark.elapsed() > MAX_FRAME_DURATION {
requests.write(Request::Redraw);
}
} else {
- self.render_mark = Instant::now();
+ self.frame_mark = Instant::now();
}
}
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.palette {
+ Some(palette) => match self.replace_palette {
+ true => palette,
false => screen.palette,
}
None => screen.palette,
@@ -338,7 +306,6 @@ impl WindowProgram for GraphicalEmulator {
let bg = screen.bg[i];
let index = unsafe { fg.unchecked_shl(4) | bg };
*colour = table[index as usize];
-
// TODO: merge fg and bg: *colour = table[screen.bg[i] as usize];
}
// Copy pixels to buffer when it is a different size to the screen.
@@ -374,5 +341,6 @@ impl WindowProgram for GraphicalEmulator {
screen.dirty = false;
self.render_mark = Instant::now();
+ self.frame_mark = Instant::now();
}
}
diff --git a/src/emulators/headless_emulator.rs b/src/emulators/headless_emulator.rs
index f215db3..770bae3 100644
--- a/src/emulators/headless_emulator.rs
+++ b/src/emulators/headless_emulator.rs
@@ -1,125 +1,105 @@
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,
+pub struct HeadlessEmulator {
+ pub br: BedrockEmulator<HeadlessDeviceBus>,
+ pub debug: DebugState,
}
-impl HeadlessDeviceBus {
- pub fn new(config: &EmulatorConfig) -> Self {
+impl HeadlessEmulator {
+ pub fn new(config: &EmulatorConfig, debug: bool) -> 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(),
+ br: BedrockEmulator::new(HeadlessDeviceBus::new(config)),
+ debug: DebugState::new(debug, config.symbols_path.as_ref()),
}
}
-}
-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
- }
+ pub fn load_program(&mut self, bytecode: &[u8]) {
+ self.br.core.mem.load_program(bytecode);
}
- 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 sleep(&mut self) {
+ loop {
+ if self.br.dev.wake() { break; }
+ std::thread::sleep(TICK_DURATION);
}
}
- 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;
+ fn halt(&mut self) {
+ self.br.dev.stream.flush();
+ info!("Program halted, exiting.");
+ self.debug.debug_full(&self.br.core);
+ std::process::exit(0);
+ }
+
+ pub fn run(&mut self) -> ! {
+ loop {
+ if let Some(signal) = self.br.evaluate(BATCH_SIZE, self.debug.enabled) {
+ match signal {
+ Signal::Fork | Signal::Reset => self.br.reset(),
+ Signal::Sleep => self.sleep(),
+ Signal::Halt => self.halt(),
+ Signal::Debug(debug_signal) => match debug_signal {
+ Debug::Debug1 => self.debug.debug_full(&self.br.core),
+ Debug::Debug2 => self.debug.debug_timing(&self.br.core),
+ _ => (),
+ }
+ _ => (),
}
- };
+ }
}
- 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 {
- pub br: BedrockEmulator<HeadlessDeviceBus>,
- pub debug: DebugState,
+pub struct HeadlessDeviceBus {
+ pub system: SystemDevice,
+ pub memory: MemoryDevice,
+ pub math: MathDevice,
+ pub clock: ClockDevice,
+ pub stream: StreamDevice,
+ pub file: FileDevice,
+ pub wake_queue: WakeQueue,
}
-impl HeadlessEmulator {
- pub fn new(config: &EmulatorConfig, debug: bool, verbose: bool) -> Self {
+impl HeadlessDeviceBus {
+ pub fn new(config: &EmulatorConfig) -> Self {
Self {
- br: BedrockEmulator::new(HeadlessDeviceBus::new(config)),
- debug: DebugState::new(debug, verbose, config.symbols_path.as_ref()),
+ system: SystemDevice::new(0b1111_0000_1100_0000),
+ memory: MemoryDevice::new(),
+ math: MathDevice::new(),
+ clock: ClockDevice::new(),
+ stream: StreamDevice::new(&config),
+ file: FileDevice::new(&config),
+ wake_queue: WakeQueue::new(),
}
}
+}
- pub fn load_program(&mut self, bytecode: &[u8]) {
- self.br.core.mem.load_program(bytecode);
+impl DeviceBus for HeadlessDeviceBus {
+ fn get_device(&mut self, slot: u8) -> Option<&mut dyn Device> {
+ match slot {
+ 0x0 => Some(&mut self.system),
+ 0x1 => Some(&mut self.memory),
+ 0x2 => Some(&mut self.math ),
+ 0x3 => Some(&mut self.clock ),
+ 0x8 => Some(&mut self.stream),
+ 0x9 => Some(&mut self.file ),
+ _ => None
+ }
}
- pub fn run(&mut self, debug: bool) -> 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.wake() { break; }
- std::thread::sleep(MIN_TICK_DURATION);
- }
- Some(Signal::Halt) => {
- self.br.dev.loc.flush();
- self.debug.info("Program halted, exiting.");
- self.debug.debug_summary(&self.br.core);
- return EmulatorSignal::Halt;
- }
- Some(Signal::Debug1) => if debug {
- self.debug.debug_summary(&self.br.core);
+ fn wake(&mut self) -> bool {
+ for slot in self.wake_queue.iter(self.system.wake_mask) {
+ if let Some(device) = self.get_device(slot) {
+ if device.wake() {
+ self.system.wake_slot = slot;
+ self.system.asleep = false;
+ self.wake_queue.wake(slot);
+ return true;
}
- _ => (),
}
}
+ return false;
}
}
diff --git a/src/emulators/mod.rs b/src/emulators/mod.rs
new file mode 100644
index 0000000..d4a58f9
--- /dev/null
+++ b/src/emulators/mod.rs
@@ -0,0 +1,24 @@
+mod headless_emulator;
+mod graphical_emulator;
+
+pub use headless_emulator::*;
+pub use graphical_emulator::*;
+
+use crate::*;
+
+
+pub struct EmulatorConfig {
+ pub dimensions: ScreenDimensions,
+ pub fullscreen: bool,
+ pub zoom: NonZeroU32,
+ pub palette: Option<[Colour; 16]>,
+ pub show_cursor: bool,
+ pub decode_stdin: bool,
+ pub encode_stdout: bool,
+ pub trust_files: bool,
+ pub symbols_path: Option<PathBuf>,
+ pub name: Option<String>,
+ pub identifier: Option<String>,
+ pub title: String,
+ pub icon: Option<Icon>,
+}
diff --git a/src/lib.rs b/src/lib.rs
index 4ff35eb..20e4e4e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,23 +1,28 @@
#![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;
-pub use debug::DebugState;
+pub use debug::*;
pub use devices::*;
pub use emulators::*;
-pub use metadata::*;
+pub use types::*;
+
+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 );
+pub const TICK_DURATION: Duration = Duration::from_nanos( 1_000_000_000/256 );
+pub const MIN_FRAME_DURATION: Duration = Duration::from_millis( 10 );
pub const MAX_FRAME_DURATION: Duration = Duration::from_millis( 500 );
pub const DEFAULT_SCREEN_SIZE: ScreenDimensions = ScreenDimensions::new(800,600);
pub const DEFAULT_SCREEN_SCALE: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(1) };
@@ -25,10 +30,24 @@ pub const DEFAULT_SCREEN_SCALE: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(
pub type ScreenPosition = geometry::Point<u16>;
pub type ScreenDimensions = geometry::Dimensions<u16>;
-#[macro_export]
-macro_rules! error {
- ($source:expr, $($tokens:tt)*) => {{
- eprint!("[ERROR] [{}]: ", $source);
- eprintln!($($tokens)*);
- }};
+
+pub fn run_program(bytecode: &[u8], config: EmulatorConfig) {
+ let mut args = switchboard::Switchboard::from_env();
+ args.named("verbose").short('v');
+ if args.get("verbose").as_bool() {
+ log::set_log_level(log::LogLevel::Info);
+ }
+
+ match Phosphor::new() {
+ Ok(phosphor) => {
+ info!("Starting graphical emulator");
+ let mut emulator = GraphicalEmulator::new(config, false);
+ emulator.load_program(&bytecode);
+ emulator.run(phosphor, true);
+ }
+ Err(err) => {
+ eprintln!("EventLoopError: {err:?}");
+ fatal!("Could not start graphical event loop");
+ }
+ }
}
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 f965950..5cdf0ea 100644
--- a/src/devices/file_device/buffered_file.rs
+++ b/src/types/buffered_file.rs
@@ -1,10 +1,7 @@
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};
-use crate::*;
-
pub struct BufferedFile {
file: AccessMode,
@@ -42,7 +39,7 @@ impl BufferedFile {
Ok(_) => buffer[0],
Err(error) => match error.kind() {
ErrorKind::UnexpectedEof => 0,
- _ => { error!("BufferedFile::read", "{error:?}"); 0 },
+ _ => { log::error!("BufferedFile::read: {error:?}"); 0 },
}
}
}
diff --git a/src/types/controller.rs b/src/types/controller.rs
new file mode 100644
index 0000000..42d3f8c
--- /dev/null
+++ b/src/types/controller.rs
@@ -0,0 +1,166 @@
+use crate::*;
+
+#[cfg(feature = "gamepad")]
+pub use gilrs::{Gilrs, GamepadId};
+
+
+pub struct OwnedGamepad {
+ tag: usize,
+ #[cfg(feature = "gamepad")]
+ id: Option<GamepadId>,
+ #[cfg(not(feature = "gamepad"))]
+ id: Option<()>,
+ gamepad: Gamepad,
+}
+
+impl OwnedGamepad {
+ pub fn new(tag: usize) -> Self {
+ Self { tag, id: None, gamepad: Gamepad::new() }
+ }
+
+ /// Returns Some if the ID owns this gamepad.
+ #[cfg(feature = "gamepad")]
+ pub fn register(&mut self, new_id: GamepadId) -> Option<&mut Gamepad> {
+ if let Some(id) = self.id {
+ match id == new_id {
+ true => Some(&mut self.gamepad),
+ false => None,
+ }
+ } else {
+ self.id = Some(new_id);
+ info!("Registered gamepad {}", self.tag);
+ Some(&mut self.gamepad)
+ }
+ }
+
+ pub fn state(&self) -> u8 {
+ self.gamepad.state
+ }
+
+ pub fn reset(&mut self) {
+ self.gamepad.reset();
+ }
+}
+
+
+pub struct Gamepad {
+ pub state: u8,
+ l_up: bool,
+ l_down: bool,
+ l_left: bool,
+ l_right: bool,
+ r_up: bool,
+ r_down: bool,
+ r_left: bool,
+ r_right: bool,
+ d_up: bool,
+ d_down: bool,
+ d_left: bool,
+ d_right: bool,
+ a: bool,
+ b: bool,
+ x: bool,
+ y: bool,
+}
+
+impl Gamepad {
+ pub fn new() -> Self {
+ Self {
+ state: 0,
+ l_up: false,
+ l_down: false,
+ l_left: false,
+ l_right: false,
+ r_up: false,
+ r_down: false,
+ r_left: false,
+ r_right: false,
+ d_up: false,
+ d_down: false,
+ d_left: false,
+ d_right: false,
+ a: false,
+ b: false,
+ x: false,
+ y: false,
+ }
+ }
+
+ pub fn reset(&mut self) {
+ self.state = 0;
+ self.l_up = false;
+ self.l_down = false;
+ self.l_left = false;
+ self.l_right = false;
+ self.r_up = false;
+ self.r_down = false;
+ self.r_left = false;
+ self.r_right = false;
+ self.d_up = false;
+ self.d_down = false;
+ self.d_left = false;
+ self.d_right = false;
+ self.a = false;
+ self.b = false;
+ self.x = false;
+ self.y = false;
+ }
+
+ // Returns true if the state changed.
+ #[cfg(feature = "gamepad")]
+ pub fn process_event(&mut self, event: &gilrs::Event) -> bool {
+ macro_rules! schmitt {
+ ($name_neg:ident, $name_pos:ident, $v:expr) => {{
+ if self.$name_neg { if $v > -0.40 { self.$name_neg = false; } }
+ else { if $v < -0.50 { self.$name_neg = true; } }
+ if self.$name_pos { if $v < 0.40 { self.$name_pos = false; } }
+ else { if $v > 0.50 { self.$name_pos = true; } }
+ }};
+ }
+
+ match event.event {
+ gilrs::EventType::ButtonPressed(button, _) => match button {
+ gilrs::Button::South => self.a = true,
+ gilrs::Button::East => self.b = true,
+ gilrs::Button::West => self.x = true,
+ gilrs::Button::North => self.y = true,
+ gilrs::Button::DPadUp => self.d_up = true,
+ gilrs::Button::DPadDown => self.d_down = true,
+ gilrs::Button::DPadLeft => self.d_left = true,
+ gilrs::Button::DPadRight => self.d_right = true,
+ _ => (),
+ }
+ gilrs::EventType::ButtonReleased(button, _) => match button {
+ gilrs::Button::South => self.a = false,
+ gilrs::Button::East => self.b = false,
+ gilrs::Button::West => self.x = false,
+ gilrs::Button::North => self.y = false,
+ gilrs::Button::DPadUp => self.d_up = false,
+ gilrs::Button::DPadDown => self.d_down = false,
+ gilrs::Button::DPadLeft => self.d_left = false,
+ gilrs::Button::DPadRight => self.d_right = false,
+ _ => (),
+ }
+ gilrs::EventType::AxisChanged(axis, v, _) => match axis {
+ gilrs::Axis::LeftStickX => schmitt!(l_left, l_right, v),
+ gilrs::Axis::LeftStickY => schmitt!(l_down, l_up, v),
+ gilrs::Axis::RightStickX => schmitt!(r_left, r_right, v),
+ gilrs::Axis::RightStickY => schmitt!(r_down, r_up, v),
+ _ => (),
+ }
+ _ => (),
+ }
+
+ let old_state = self.state;
+ self.state = 0;
+ if self.l_up | self.r_up | self.d_up { self.state |= 0x80; }
+ if self.l_down | self.r_down | self.d_down { self.state |= 0x40; }
+ if self.l_left | self.r_left | self.d_left { self.state |= 0x20; }
+ if self.l_right | self.r_right | self.d_right { self.state |= 0x10; }
+ if self.a { self.state |= 0x08; }
+ if self.b { self.state |= 0x04; }
+ if self.x { self.state |= 0x02; }
+ if self.y { self.state |= 0x01; }
+ old_state != self.state
+ }
+}
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/mod.rs b/src/types/mod.rs
new file mode 100644
index 0000000..1cc90d3
--- /dev/null
+++ b/src/types/mod.rs
@@ -0,0 +1,19 @@
+mod buffered_file;
+mod controller;
+mod directory_listing;
+mod entry_type;
+mod file_path;
+mod path_buffer;
+mod sprite_buffer;
+mod string_buffer;
+mod wake_queue;
+
+pub use buffered_file::*;
+pub use controller::*;
+pub use directory_listing::*;
+pub use entry_type::*;
+pub use file_path::*;
+pub use path_buffer::*;
+pub use sprite_buffer::*;
+pub use string_buffer::*;
+pub use wake_queue::*;
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/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;
+ }
+}
diff --git a/src/types/string_buffer.rs b/src/types/string_buffer.rs
new file mode 100644
index 0000000..7751d9f
--- /dev/null
+++ b/src/types/string_buffer.rs
@@ -0,0 +1,37 @@
+pub struct StringBuffer {
+ pub bytes: Vec<u8>,
+ pub pointer: usize,
+}
+
+impl StringBuffer {
+ pub fn new() -> Self {
+ Self {
+ bytes: Vec::new(),
+ pointer: 0,
+ }
+ }
+
+ pub fn from_str(text: &str) -> Self {
+ let mut new = Self::new();
+ new.set_str(text);
+ new
+ }
+
+ pub fn set_str(&mut self, text: &str) {
+ self.bytes = text.bytes().collect();
+ self.pointer = 0;
+ }
+
+ pub fn read(&mut self) -> u8 {
+ if let Some(byte) = self.bytes.get(self.pointer) {
+ self.pointer += 1;
+ *byte
+ } else {
+ 0
+ }
+ }
+
+ pub fn restart(&mut self) {
+ self.pointer = 0;
+ }
+}
diff --git a/src/types/wake_queue.rs b/src/types/wake_queue.rs
new file mode 100644
index 0000000..41d815b
--- /dev/null
+++ b/src/types/wake_queue.rs
@@ -0,0 +1,51 @@
+pub struct WakeQueue {
+ queue: Vec<u8>,
+}
+
+
+impl WakeQueue {
+ pub fn new() -> Self {
+ Self {
+ queue: [0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xA,0xB,0xC,0xD,0xE,0xF].into(),
+ }
+ }
+
+ /// Iterate over all masked devices in last-woken order.
+ pub fn iter(&self, mask: u16) -> WakeIterator {
+ let mut queue = Vec::new();
+ for i in &self.queue {
+ if mask & (0x8000 >> i) != 0 {
+ queue.push(*i);
+ }
+ }
+ // Add system device last.
+ if mask & 0x8000 != 0 {
+ queue.push(0);
+ }
+ WakeIterator { queue, pointer: 0 }
+ }
+
+ /// Push a device to the back of the queue.
+ pub fn wake(&mut self, id: u8) {
+ if let Some(index) = self.queue.iter().position(|e| *e == id) {
+ self.queue.remove(index);
+ self.queue.push(id);
+ }
+ }
+}
+
+
+pub struct WakeIterator {
+ queue: Vec<u8>,
+ pointer: usize,
+}
+
+impl Iterator for WakeIterator {
+ type Item = u8;
+
+ fn next(&mut self) -> Option<u8> {
+ let pointer = self.pointer;
+ self.pointer += 1;
+ self.queue.get(pointer).copied()
+ }
+}