diff --git a/Cargo.toml b/Cargo.toml index eae91b7..1b2b740 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,18 +1,9 @@ [workspace] members = [ - "emscripten_em_js", "remglk", "remglk_capi", ] resolver = "2" [profile.release] -lto = true - -[profile.dev-wasm] -inherits = "dev" -panic = "abort" - -[profile.release-wasm] -inherits = "release" -panic = "abort" \ No newline at end of file +lto = true \ No newline at end of file diff --git a/emscripten_em_js/Cargo.toml b/emscripten_em_js/Cargo.toml index 22d41f9..47c0457 100644 --- a/emscripten_em_js/Cargo.toml +++ b/emscripten_em_js/Cargo.toml @@ -16,4 +16,4 @@ proc-macro = true [dependencies] quote = "1.0.35" -syn = {version="2.0.48", features=["full"]} \ No newline at end of file +syn = {version="2.0.48", features=["full", "extra-traits"]} \ No newline at end of file diff --git a/emscripten_em_js/src/lib.rs b/emscripten_em_js/src/lib.rs index af57157..1644c87 100644 --- a/emscripten_em_js/src/lib.rs +++ b/emscripten_em_js/src/lib.rs @@ -9,7 +9,7 @@ https://github.com/curiousdannii/remglk-rs */ -//! em_js!() declares a Javascript function. It is largely similar to the Emscripten macro `EM_JS`. +//! em_js!{} declares a Javascript function. It is largely similar to the Emscripten macro `EM_JS`. //! //! ```c //! EM_JS(int, add, (int x, int y), { @@ -22,26 +22,28 @@ https://github.com/curiousdannii/remglk-rs //! raw string. //! //! ``` -//! em_js!(fn add(x: i32, y: i32) -> i32 { r#" +//! em_js!{fn add(x: i32, y: i32) -> i32 { r#" //! return x + y; -//! "# }) +//! "# }} //! ``` //! //! You may also declare async functions. Unlike in Emscripten where you would use the `EM_ASYNC_JS` //! macro, these use the same macro, just declare the function as `async`: //! //! ``` -//! em_js!(async fn add(x: i32, y: i32) -> i32 { r#" +//! em_js!{async fn add(x: i32, y: i32) -> i32 { r#" //! return x + y; -//! "# }) +//! "# }} //! ``` //! //! Supported types: //! -//! | Type | Input | Output | -//! |-------|-------|--------| -//! | [f64] | Y | Y | -//! | [i32] | Y | Y | +//! | Type | Input | Output | +//! |---------|-------|--------| +//! | pointer | Y | ? | +//! | [f64] | Y | Y | +//! | [i32] | Y | Y | +//! | [usize] | Y | Y | use proc_macro::TokenStream; use quote::{format_ident, quote}; @@ -49,7 +51,7 @@ use syn::{Block, Expr, FnArg, ItemFn, Lit, Pat, Stmt, Type}; use syn::punctuated::Punctuated; use syn::token::Comma; -/** em_js!() declares a Javascript function. It is largely similar to the Emscripten macro `EM_JS`. +/** em_js!{} declares a Javascript function. It is largely similar to the Emscripten macro `EM_JS`. * * For examples, and supported types, see [the module documentation](crate). */ @@ -61,7 +63,9 @@ pub fn em_js(input: TokenStream) -> TokenStream { let js_name = format_ident!("__em_js__{}{}", if parsed.sig.asyncness.is_some() {"__asyncjs__"} else {""}, name); let inputs = parsed.sig.inputs; let output = parsed.sig.output; - let body = format!("({})<::>{{{}}}", rust_args_to_c(&inputs), get_body_str(parsed.block.as_ref())); + let body = format!("({})<::>{{{}}}\0", rust_args_to_c(&inputs), get_body_str(parsed.block.as_ref())); + let body = body.as_bytes(); + let body_len = body.len(); let result = quote! { extern "C" { @@ -69,14 +73,12 @@ pub fn em_js(input: TokenStream) -> TokenStream { pub fn #name(#inputs) #output; } - #[link_section = ".em_js"] + #[link_section = "em_js"] #[no_mangle] #[used] - static #js_name: &str = #body; + static #js_name: [u8; #body_len] = [#(#body),*]; }; - // Do I need to manually emit bytes? https://github.com/rust-lang/rust/issues/70239 - result.into() } @@ -98,17 +100,18 @@ fn rust_args_to_c(args: &Punctuated) -> String { &name.ident } else { - unreachable!(); + unreachable!("name: as_ref()"); }; - let rust_type = if let Type::Path(path) = arg.ty.as_ref() { - path.path.segments.first().unwrap().ident.to_string() - } - else { - unreachable!(); + let rust_type = match arg.ty.as_ref() { + Type::Path(path) => path.path.segments.first().unwrap().ident.to_string(), + Type::Ptr(_) => "*".to_owned(), + _ => panic!("unsupported rust_type: as_ref(), {:?}", arg.ty.as_ref()), }; let c_type = match rust_type.as_str() { + "*" => "int", "f64" => "double", "i32" => "int", + "usize" => "int", other => panic!("unsupported argument type: {}", other), }; format!("{} {}", c_type, name) diff --git a/remglk/src/glkapi/mod.rs b/remglk/src/glkapi/mod.rs index 51e50f4..727c8d2 100644 --- a/remglk/src/glkapi/mod.rs +++ b/remglk/src/glkapi/mod.rs @@ -62,7 +62,7 @@ where S: Default + GlkSystem { stylehints_buffer: WindowStyles, stylehints_grid: WindowStyles, support: SupportedFeatures, - system: S, + pub system: S, timer: TimerData, pub windows: GlkObjectStore, windows_changed: bool, diff --git a/remglk_capi/Cargo.toml b/remglk_capi/Cargo.toml index 60bded0..0e2cf03 100644 --- a/remglk_capi/Cargo.toml +++ b/remglk_capi/Cargo.toml @@ -22,8 +22,5 @@ serde_json = "1.0" thiserror = "1.0.40" widestring = "1.0.2" -[target.'cfg(target_os = "emscripten")'.dependencies] -emscripten_em_js = {path = "../emscripten_em_js", version = "0.1.0"} - [build-dependencies] cc = "1.0" \ No newline at end of file diff --git a/remglk_capi/src/glk/support.h b/remglk_capi/src/glk/support.h index e1a3f38..7a8ba88 100644 --- a/remglk_capi/src/glk/support.h +++ b/remglk_capi/src/glk/support.h @@ -8,6 +8,4 @@ extern void gidispatch_get_objrock_fileref(void *obj, gidispatch_rock_t *rock_pt extern void gidispatch_get_objrock_stream(void *obj, gidispatch_rock_t *rock_ptr); extern void gidispatch_get_objrock_window(void *obj, gidispatch_rock_t *rock_ptr); -glkunix_argumentlist_t *glkunix_arguments_addr(void); - #endif /* REMGLK_RS_SUPPORT_START_H */ \ No newline at end of file diff --git a/remglk_capi/src/glkstart.rs b/remglk_capi/src/glkstart.rs index a2b9e3b..233ea5f 100644 --- a/remglk_capi/src/glkstart.rs +++ b/remglk_capi/src/glkstart.rs @@ -40,8 +40,8 @@ pub struct LibraryOptions { } /** Process the command line arguments */ -// I didn't really want to reimplement the Zarf's logic, but none of the Rust argument parsing libraries really seem to do what we want. -pub fn process_args() -> ArgProcessingResults { +// I didn't really want to reimplement Zarf's logic, but none of the Rust argument parsing libraries really seem to do what we want. +pub fn process_args(args: Vec) -> ArgProcessingResults { #[derive(Error, Debug)] pub enum ArgError { #[error("{0} must be followed by a value")] @@ -152,7 +152,6 @@ pub fn process_args() -> ArgProcessingResults { usage } - let args: Vec = env::args().collect(); let app_arguments = unsafe {glkunix_arguments()}; match process_args_inner(&args, &app_arguments) { Ok(InnerResult::Help) => ArgProcessingResults::Msg(print_usage(&args[0], &app_arguments)), diff --git a/remglk_capi/src/lib.rs b/remglk_capi/src/lib.rs index 2c12b14..46b4530 100644 --- a/remglk_capi/src/lib.rs +++ b/remglk_capi/src/lib.rs @@ -15,7 +15,7 @@ mod dispatch; mod glkapi; mod glkstart; -use std::ffi::{c_char, c_int}; +use std::ffi::{c_char, c_int, CStr}; use remglk::glkapi::protocol::{Event, EventData, InitEvent, Metrics}; @@ -34,11 +34,17 @@ extern "C" { fn glkunix_startup_code(data: &GlkUnixArguments) -> c_int; } -/** Glk libraries are weird because they define `main`, rather than the eventual app that is linked against them. So control starts here, and then returns to the app when `glk_main` is called. */ +/// Glk libraries are weird because they define `main`, rather than the eventual app that is linked against them. So control starts here, and then returns to the app when `glk_main` is called. +/// +/// We must manually process the args instead of using `env::args`, because of this limitation in WASM: https://github.com/rust-lang/rust/issues/121883 #[no_mangle] -extern "C" fn main() { +extern "C" fn main(argc: c_int, argv: *const *const c_char) -> c_int { // Process the arguments, and optionally display an error/help message - let (processed_args, library_args) = match glkstart::process_args() { + let args: Vec = (0..argc) + .map(|i| unsafe {CStr::from_ptr(*argv.add(i as usize))}.to_str().unwrap().to_owned()) + .collect(); + + let (processed_args, library_args) = match glkstart::process_args(args) { ArgProcessingResults::ErrorMsg(msg) => { eprint!("{msg}"); std::process::exit(1); @@ -89,4 +95,5 @@ extern "C" fn main() { unsafe{glk_main()}; glk_exit(); + 0 } \ No newline at end of file diff --git a/remglk_capi/src/systems/emglken.rs b/remglk_capi/src/systems/emglken.rs index 19ac4ac..db863fc 100644 --- a/remglk_capi/src/systems/emglken.rs +++ b/remglk_capi/src/systems/emglken.rs @@ -10,13 +10,24 @@ https://github.com/curiousdannii/remglk-rs */ use std::collections::HashMap; - -//use emscripten_em_js::em_js; +use std::ptr; +use std::slice; use super::*; use remglk::GlkSystem; use glkapi::protocol::{Event, SystemFileRef, Update}; +extern "C" { + fn emglken_fileref_exists(filename_ptr: *const u8, filename_len: usize) -> bool; + fn emglken_fileref_read(filename_ptr: *const u8, filename_len: usize, buffer: &mut EmglkenBuffer) -> bool; +} + +#[repr(C)] +pub struct EmglkenBuffer { + pub ptr: *mut u8, + pub len: usize, +} + pub type GlkApi = glkapi::GlkApi; pub fn glkapi() -> &'static Mutex { @@ -28,7 +39,7 @@ pub fn glkapi() -> &'static Mutex { #[derive(Default)] pub struct EmglkenSystem { - _cache: HashMap>, + cache: HashMap>, } impl GlkSystem for EmglkenSystem { @@ -45,12 +56,26 @@ impl GlkSystem for EmglkenSystem { unimplemented!() } - fn fileref_exists(&mut self, _fileref: &SystemFileRef) -> bool { - unimplemented!() + fn fileref_exists(&mut self, fileref: &SystemFileRef) -> bool { + self.cache.contains_key(&fileref.filename) || unsafe {emglken_fileref_exists(fileref.filename.as_ptr(), fileref.filename.len())} } - fn fileref_read(&mut self, _fileref: &SystemFileRef) -> Option> { - unimplemented!() + fn fileref_read(&mut self, fileref: &SystemFileRef) -> Option> { + // Check the cache first + if let Some(buf) = self.cache.get(&fileref.filename) { + Some(buf.clone()) + } + else { + let mut buf = EmglkenBuffer { + ptr: ptr::null_mut(), + len: 0, + }; + let result = unsafe {emglken_fileref_read(fileref.filename.as_ptr(), fileref.filename.len(), &mut buf)}; + if result { + return unsafe {Some(Box::from_raw(slice::from_raw_parts_mut(buf.ptr, buf.len)))}; + } + None + } } fn fileref_temporary(&mut self, _filetype: FileType) -> SystemFileRef { diff --git a/remglk_capi/src/systems/library_emglken.js b/remglk_capi/src/systems/library_emglken.js new file mode 100644 index 0000000..c4f42d2 --- /dev/null +++ b/remglk_capi/src/systems/library_emglken.js @@ -0,0 +1,32 @@ +/* + +Emglken JS library +================== + +Copyright (c) 2024 Dannii Willis +MIT licenced +https://github.com/curiousdannii/emglken + +*/ + +addToLibrary({ + emglken_fileref_exists(filename_ptr, filename_len) { + const name = UTF8ToString(filename_ptr, filename_len) + if (name === storyfile_name) { + return true + } + return false + }, + + emglken_fileref_read(filename_ptr, filename_len, buffer) { + const name = UTF8ToString(filename_ptr, filename_len) + if (name === storyfile_name) { + const ptr = _malloc(storyfile_data.length) + HEAP8.set(storyfile_data, ptr) + {{{ makeSetValue('buffer', 0, 'ptr', 'i32') }}} + {{{ makeSetValue('buffer', 4, 'storyfile_data.length', 'i32') }}} + return true + } + return false + }, +}) \ No newline at end of file