Skip to content

Commit

Permalink
More Emglken progress. We must manually process the main args, as env…
Browse files Browse the repository at this point in the history
…::args doesn't work in WASM
  • Loading branch information
curiousdannii committed Mar 4, 2024
1 parent 33318d9 commit 9eb640a
Show file tree
Hide file tree
Showing 10 changed files with 104 additions and 52 deletions.
11 changes: 1 addition & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
lto = true
2 changes: 1 addition & 1 deletion emscripten_em_js/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ proc-macro = true

[dependencies]
quote = "1.0.35"
syn = {version="2.0.48", features=["full"]}
syn = {version="2.0.48", features=["full", "extra-traits"]}
45 changes: 24 additions & 21 deletions emscripten_em_js/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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), {
Expand All @@ -22,34 +22,36 @@ 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};
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).
*/
Expand All @@ -61,22 +63,22 @@ 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" {
#[link_name = #link_name]
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()
}

Expand All @@ -98,17 +100,18 @@ fn rust_args_to_c(args: &Punctuated<FnArg, Comma>) -> 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)
Expand Down
2 changes: 1 addition & 1 deletion remglk/src/glkapi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Window>,
windows_changed: bool,
Expand Down
3 changes: 0 additions & 3 deletions remglk_capi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
2 changes: 0 additions & 2 deletions remglk_capi/src/glk/support.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
5 changes: 2 additions & 3 deletions remglk_capi/src/glkstart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>) -> ArgProcessingResults {
#[derive(Error, Debug)]
pub enum ArgError {
#[error("{0} must be followed by a value")]
Expand Down Expand Up @@ -152,7 +152,6 @@ pub fn process_args() -> ArgProcessingResults {
usage
}

let args: Vec<String> = 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)),
Expand Down
15 changes: 11 additions & 4 deletions remglk_capi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand All @@ -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<String> = (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);
Expand Down Expand Up @@ -89,4 +95,5 @@ extern "C" fn main() {

unsafe{glk_main()};
glk_exit();
0
}
39 changes: 32 additions & 7 deletions remglk_capi/src/systems/emglken.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<EmglkenSystem>;

pub fn glkapi() -> &'static Mutex<GlkApi> {
Expand All @@ -28,7 +39,7 @@ pub fn glkapi() -> &'static Mutex<GlkApi> {

#[derive(Default)]
pub struct EmglkenSystem {
_cache: HashMap<String, Box<[u8]>>,
cache: HashMap<String, Box<[u8]>>,
}

impl GlkSystem for EmglkenSystem {
Expand All @@ -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<Box<[u8]>> {
unimplemented!()
fn fileref_read(&mut self, fileref: &SystemFileRef) -> Option<Box<[u8]>> {
// 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 {
Expand Down
32 changes: 32 additions & 0 deletions remglk_capi/src/systems/library_emglken.js
Original file line number Diff line number Diff line change
@@ -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
},
})

0 comments on commit 9eb640a

Please sign in to comment.