diff --git a/.gitignore b/.gitignore index 9b06c6b1d2000ef1f32d00778b7082175bf8b419..e0b05f96af902e3cdfb027cfa928654340ad4942 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ Cargo.lock /target /res/roms.prop /frontends/*/target + +/src/gen.rs diff --git a/Cargo.toml b/Cargo.toml index d40bb1e7ed3fd02f69e2abb06d39d2c08c0ac0bc..45a76b8cf6e57af8ec8240c8b7d4497f3a75ebcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,8 @@ repository = "https://github.com/joamag/boytacean" keywords = ["gameboy", "emulator", "rust"] edition = "2018" exclude = ["/frontends", "/res/roms", "/res/screens", "/res/videos"] +build = "build.rs" +readme = "README.md" [lib] crate-type = ["cdylib", "rlib"] @@ -19,6 +21,11 @@ debug = [] [dependencies] wasm-bindgen = { version = "0.2", optional = true } +[build-dependencies] +chrono = "0.4" +num_cpus = "1" +regex = "1" + [profile.release] debug = false lto = true diff --git a/README.md b/README.md index 679c4939aafd6871677e0d2fad1f4bf0899fea38..dc449caa07a4a76afd5b754904794ae391c47c38 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ To get some information about the resources that inspired me through the emulati ## License -Boytacian is currently licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/). +Boytacean is currently licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/). ## Build Automation diff --git a/build.rs b/build.rs new file mode 100644 index 0000000000000000000000000000000000000000..67bcc46753a20751db2886bacf7c80d585e241ca --- /dev/null +++ b/build.rs @@ -0,0 +1,153 @@ +/// Build script (https://doc.rust-lang.org/cargo/reference/build-scripts.html) +/// This script is executed as the first step in the compilation process. +/// Here we export metadata constants to a `constants/generated.rs` file which is then +/// imported and used by the remaining crate. +/// +/// # Examples +/// +/// In C you can use the preprocessor macro `__DATE__` to save the compilation date like: +/// +/// ```c +/// #define COMPILATION_DATE __DATE__ +/// ``` +/// +/// Rust does not have such preprocessor macros, so we use this script and do: +/// +/// ```rust +/// let now_utc = chrono::Utc::now(); +/// write_str_constant( +/// &mut file, +/// "COMPILATION_DATE", +/// &format!("{}", now_utc.format("%b %d %Y")), +/// ); +/// ``` +use chrono::Utc; +use regex::Regex; +use std::fs::{File, OpenOptions}; +use std::io::Write; +use std::path::Path; +use std::process::Command; +use std::str; + +const BUILD_OUT_FILE: &str = "gen.rs"; +const SOURCE_DIR: &str = "./src"; + +fn main() { + // in case we're running under docs.rs then we must return the control + // flow immediately as it's not possible to generate files under the + // expected read only file system present in `docs.rs` build environment + if std::env::var("DOCS_RS").is_ok() { + return; + } + + // opens the target destination file panicking with a proper message in + // case it was not possible to open it (eg: directory inexistent) + let dest_path = Path::new(SOURCE_DIR).join(Path::new(BUILD_OUT_FILE)); + let mut file = OpenOptions::new() + .truncate(true) + .write(true) + .create(true) + .open(dest_path) + .unwrap_or_else(|_| panic!("Can't open '{}'", BUILD_OUT_FILE)); + + let module_doc_string = "//! Global constants, such as compiler version used, algorithms, compression and filters supported and others\n"; + writeln!(file, "{}", module_doc_string).unwrap(); + + let generated_annotation = "// @generated\n"; + writeln!(file, "{}", generated_annotation).unwrap(); + + let now_utc = Utc::now(); + write_str_constant( + &mut file, + "COMPILATION_DATE", + &format!("{}", now_utc.format("%b %d %Y")), + ); + write_str_constant( + &mut file, + "COMPILATION_TIME", + &format!("{}", now_utc.format("%H:%M:%S")), + ); + + write_str_constant( + &mut file, + "VERSION", + option_env!("CARGO_PKG_VERSION").unwrap_or("UNKNOWN"), + ); + + write_str_constant(&mut file, "COMPILER", "rustc"); + + let compiler_version = Command::new("rustc") + .arg("--version") + .output() + .ok() + .and_then(|output| String::from_utf8(output.stdout).ok()) + .unwrap_or_else(|| "UNKNOWN".to_string()); + let re = Regex::new("rustc ([\\d.\\d.\\d]*)").unwrap(); + let compiler_version = re + .captures(&compiler_version) + .unwrap() + .get(1) + .unwrap() + .as_str(); + write_str_constant(&mut file, "COMPILER_VERSION", compiler_version); + + let mut features = vec!["cpu"]; + + if cfg!(feature = "wasm-extension") { + features.push("wasm") + } + + if cfg!(feature = "python-extension") { + features.push("python") + } + + write_vec_constant(&mut file, "FEATURES", features); + + write_str_constant( + &mut file, + "PLATFORM_CPU_BITS", + &(std::mem::size_of::<usize>() * 8).to_string(), + ); + + write_constant(&mut file, "DEFAULT_THREAD_POOL_SIZE", num_cpus::get()); + write_constant(&mut file, "MAX_THREAD_POOL_SIZE", num_cpus::get() * 10); +} + +fn write_constant<T>(file: &mut File, key: &str, val: T) +where + T: std::fmt::Display, +{ + writeln!( + file, + "pub const {}: {} = {};", + key, + std::any::type_name::<T>(), + val + ) + .unwrap_or_else(|_| panic!("Failed to write '{}' to 'build_constants.rs'", key)); +} + +fn write_str_constant(file: &mut File, key: &str, val: &str) { + writeln!(file, "pub const {}: &str = \"{}\";", key, val) + .unwrap_or_else(|_| panic!("Failed to write '{}' to 'build_constants.rs'", key)); +} + +fn write_vec_constant<T>(file: &mut File, key: &str, vec: Vec<T>) +where + T: std::fmt::Display, +{ + let mut list_str = String::new(); + for value in &vec { + list_str.push_str(&format!("\"{}\", ", value)) + } + list_str.pop(); + writeln!( + file, + "pub const {}: [{}; {}] = [{}];", + key, + std::any::type_name::<T>(), + vec.len(), + list_str + ) + .unwrap_or_else(|_| panic!("Failed to write '{}' to 'build_constants.rs'", key)); +} diff --git a/frontends/web/react/components/help/help.tsx b/frontends/web/react/components/help/help.tsx index d9003320f924889b6ecee3f503706381950a40e5..42ea60c648b1ba260e1f9f6047485e595cbaae47 100644 --- a/frontends/web/react/components/help/help.tsx +++ b/frontends/web/react/components/help/help.tsx @@ -78,7 +78,9 @@ export const HelpFaqs: FC = () => ( play around 90% of the Game Boy games. </p> <h3>Why there's no sound?</h3> - <p>It's under development, I'm hopping to have it before end of 2023.</p> + <p> + It's under development, I'm hopping to have it before end of 2023. + </p> <h3>Can I use my Xbox One/PS4/PS5 Gamepad?</h3> <p> Yes, just plug it in, press a button, and you're ready to go. diff --git a/frontends/web/ts/gb.ts b/frontends/web/ts/gb.ts index 5b6b6f6cf6b67e75a17d365273dd37f5fb7d5a60..b954d9472504e12338fccb0dc8b0d44af658bc20 100644 --- a/frontends/web/ts/gb.ts +++ b/frontends/web/ts/gb.ts @@ -1,5 +1,7 @@ import { BenchmarkResult, + Compilation, + Compiler, Emulator, EmulatorBase, Entry, @@ -511,6 +513,22 @@ export class GameboyEmulator extends EmulatorBase implements Emulator { }; } + get compiler(): Compiler | null { + if (!this.gameBoy) return null; + return { + name: this.gameBoy.get_compiler(), + version: this.gameBoy.get_compiler_version() + }; + } + + get compilation(): Compilation | null { + if (!this.gameBoy) return null; + return { + date: this.gameBoy.get_compilation_date(), + time: this.gameBoy.get_compilation_time() + }; + } + get framerate(): number { return this.fps; } diff --git a/node_modules/emukit b/node_modules/emukit new file mode 160000 index 0000000000000000000000000000000000000000..e02cc19ffebf0d87cde8c7fe6e37eb5c1813baa0 --- /dev/null +++ b/node_modules/emukit @@ -0,0 +1 @@ +Subproject commit e02cc19ffebf0d87cde8c7fe6e37eb5c1813baa0 diff --git a/src/gb.rs b/src/gb.rs index fc7ebdd00b1e3bfc6d45a29087c037636157b67c..29c29bf0a271760f371db97b8855b0e75928070d 100644 --- a/src/gb.rs +++ b/src/gb.rs @@ -1,6 +1,7 @@ use crate::{ cpu::Cpu, data::{BootRom, CGB_BOOT, DMG_BOOT, DMG_BOOTIX, MGB_BOOTIX, SGB_BOOT}, + gen::{COMPILATION_DATE, COMPILATION_TIME, COMPILER, COMPILER_VERSION}, mmu::Mmu, pad::{Pad, PadKey}, ppu::{Ppu, PpuMode, Tile, FRAME_BUFFER_SIZE}, @@ -194,6 +195,25 @@ impl GameBoy { let tile = self.get_tile(index); tile.palette_buffer(self.ppu().palette()) } + + /// Obtains the name of the compiler that has been + /// used in the compilation of the base Boytacean + /// library. Can be used for diagnostics. + pub fn get_compiler(&self) -> String { + String::from(COMPILER) + } + + pub fn get_compiler_version(&self) -> String { + String::from(COMPILER_VERSION) + } + + pub fn get_compilation_date(&self) -> String { + String::from(COMPILATION_DATE) + } + + pub fn get_compilation_time(&self) -> String { + String::from(COMPILATION_TIME) + } } /// Gameboy implementations that are meant with performance diff --git a/src/lib.rs b/src/lib.rs index fbcf0572db10053930ca0f5d135b533d8569a007..0bc2f37c3e2d1185f3ecec18c9d1d306b39da464 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ pub mod cpu; pub mod data; pub mod gb; +pub mod gen; pub mod inst; pub mod macros; pub mod mmu;