Skip to content

Commit

Permalink
Threaded emscripten fixes (#17614)
Browse files Browse the repository at this point in the history
* Actually read CLI args in emscripten

* Fix fetchfs manifest parsing, increase download chunk size

The chunk size should probably be made a parameter in the future.  The
larger chunk size trades longer hitches for fewer hitches.

* Add exec command driver and API functions for emscripten.

Under WASMFS, stdin/stdout can't be customized the way they can with
the JS FS implementation.  Also, this approach frees up stdin/stdout
and simplifies interaction with the command interface for web embedders.

* fixup upload paths, show use of new emscripten cmd interface

* Add JS library function names to EXPORTS as well as EXPORTED_FUNCTIONS for older emsdk versions
  • Loading branch information
JoeOsborn authored Feb 24, 2025
1 parent 55b5926 commit c413bcc
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 71 deletions.
4 changes: 2 additions & 2 deletions Makefile.emscripten
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,9 @@ OBJDIR := obj-emscripten
EXPORTED_FUNCTIONS = _main,_malloc,_free,_cmd_savefiles,_cmd_save_state,_cmd_load_state,_cmd_undo_save_state,_cmd_undo_load_state,_cmd_take_screenshot,\
_cmd_toggle_menu,_cmd_reload_config,_cmd_toggle_grab_mouse,_cmd_toggle_game_focus,_cmd_reset,_cmd_toggle_pause,_cmd_pause,_cmd_unpause,\
_cmd_set_volume,_cmd_set_shader,_cmd_cheat_set_code,_cmd_cheat_get_code,_cmd_cheat_toggle_index,_cmd_cheat_get_code_state,_cmd_cheat_realloc,\
_cmd_cheat_get_size,_cmd_cheat_apply_cheats
_cmd_cheat_get_size,_cmd_cheat_apply_cheats,EmscriptenSendCommand,EmscriptenReceiveCommandReply

EXPORTS := callMain,FS,PATH,ERRNO_CODES,ENV,stringToNewUTF8,UTF8ToString,Browser,GL
EXPORTS := callMain,FS,PATH,ERRNO_CODES,ENV,stringToNewUTF8,UTF8ToString,Browser,GL,EmscriptenSendCommand,EmscriptenReceiveCommandReply

LIBS := -s USE_ZLIB=1 -lbrowser.js

Expand Down
57 changes: 56 additions & 1 deletion command.c
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ command_t* command_stdin_new(void)
command_t *cmd;
command_stdin_t *stdincmd;

#ifndef _WIN32
#if !(defined(_WIN32) || defined(EMSCRIPTEN))
#ifdef HAVE_NETWORKING
if (!socket_nonblock(STDIN_FILENO))
return NULL;
Expand All @@ -363,6 +363,61 @@ command_t* command_stdin_new(void)
}
#endif

#if defined(EMSCRIPTEN)
void PlatformEmscriptenCommandReply(const char *, size_t);
int PlatformEmscriptenCommandRead(char **, size_t);
typedef struct
{
char command_buf[CMD_BUF_SIZE];
} command_emscripten_t;

static void emscripten_command_reply(command_t *_cmd,
const char *s, size_t len)
{
PlatformEmscriptenCommandReply(s, len);
}

static void emscripten_command_free(command_t *handle)
{
free(handle->userptr);
free(handle);
}

static void command_emscripten_poll(command_t *handle)
{
command_emscripten_t *emscriptencmd = (command_emscripten_t*)handle->userptr;
ptrdiff_t msg_len = PlatformEmscriptenCommandRead((char **)(&emscriptencmd->command_buf), CMD_BUF_SIZE);
if (msg_len == 0)
return;
command_parse_msg(handle, emscriptencmd->command_buf);
}

command_t* command_emscripten_new(void)
{
command_t *cmd;
command_emscripten_t *emscriptencmd;

cmd = (command_t*)calloc(1, sizeof(command_t));
emscriptencmd = (command_emscripten_t*)calloc(1, sizeof(command_emscripten_t));

if (!cmd)
return NULL;
if (!emscriptencmd)
{
free(cmd);
return NULL;
}
cmd->userptr = emscriptencmd;
cmd->poll = command_emscripten_poll;
cmd->replier = emscripten_command_reply;
cmd->destroy = emscripten_command_free;

return cmd;
}
#endif



bool command_get_config_param(command_t *cmd, const char* arg)
{
size_t _len;
Expand Down
11 changes: 10 additions & 1 deletion command.h
Original file line number Diff line number Diff line change
Expand Up @@ -329,11 +329,20 @@ struct rarch_state;
bool command_event(enum event_command action, void *data);

/* Constructors for the supported drivers */
#ifdef HAVE_NETWORK_CMD
command_t* command_network_new(uint16_t port);
bool command_network_send(const char *cmd_);
#endif
#ifdef HAVE_STDIN_CMD
command_t* command_stdin_new(void);
#endif
#ifdef LAKKA
command_t* command_uds_new(void);
#endif
#ifdef EMSCRIPTEN
command_t* command_emscripten_new(void);
#endif

bool command_network_send(const char *cmd_);

void command_event_set_mixer_volume(
settings_t *settings,
Expand Down
13 changes: 12 additions & 1 deletion emscripten/library_platform_emscripten.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ var LibraryPlatformEmscripten = {
RPE.powerState.dischargeTime = Number.isFinite(e.target.dischargingTime) ? e.target.dischargingTime : 0x7FFFFFFF;
RPE.powerState.level = e.target.level;
RPE.powerState.charging = e.target.charging;
}
},
command_queue:[],
command_reply_queue:[],
},

PlatformEmscriptenPowerStateInit: function() {
Expand Down Expand Up @@ -49,6 +51,15 @@ var LibraryPlatformEmscripten = {
PlatformEmscriptenGetFreeMem: function() {
if (!performance.memory) return 0;
return (performance.memory.jsHeapSizeLimit || 0) - (performance.memory.usedJSHeapSize || 0);
},

$EmscriptenSendCommand__deps:["PlatformEmscriptenCommandRaiseFlag"],
$EmscriptenSendCommand: function(str) {
RPE.command_queue.push(str);
_PlatformEmscriptenCommandRaiseFlag();
},
$EmscriptenReceiveCommandReply: function() {
return RPE.command_reply_queue.shift();
}
};

Expand Down
61 changes: 52 additions & 9 deletions frontend/drivers/platform_emscripten.c
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,33 @@ bool PlatformEmscriptenPowerStateGetCharging(void);
uint64_t PlatformEmscriptenGetTotalMem(void);
uint64_t PlatformEmscriptenGetFreeMem(void);

void PlatformEmscriptenCommandReply(const char *msg, size_t len) {
MAIN_THREAD_EM_ASM({
var message = UTF8ToString($0,$1);
RPE.command_reply_queue.push(message);
}, msg, len);
}
static bool command_flag = false;
size_t PlatformEmscriptenCommandRead(char **into, size_t max_len) {
if(!command_flag) { return 0; }
return MAIN_THREAD_EM_ASM_INT({
var next_command = RPE.command_queue.shift();
var length = lengthBytesUTF8(next_command);
if(length > $2) {
console.error("[CMD] Command too long, skipping",next_command);
return 0;
}
stringToUTF8(next_command, $1, $2);
if(RPE.command_queue.length == 0) {
setValue($0, 0, 'i8');
}
return length;
}, &command_flag, into, max_len);
}
void PlatformEmscriptenCommandRaiseFlag() {
command_flag = true;
}

/* begin exported functions */

/* saves and states */
Expand Down Expand Up @@ -350,32 +377,46 @@ void PlatformEmscriptenMountFilesystems(void *info) {
*/
int max_line_len = 1024;
printf("[FetchFS] read fetch manifest from %s\n",fetch_manifest);
FILE *file = fopen(fetch_manifest, O_RDONLY);
FILE *file = fopen(fetch_manifest, "r");
if(!file) {
printf("[FetchFS] missing manifest file\n");
abort();
}
char *line = calloc(sizeof(char), max_line_len);
size_t len = 0;
size_t len = max_line_len;
while (getline(&line, &len, file) != -1) {
char *path = strstr(line, " ");
backend_t fetch;
int fd;
if (!path) {
printf("Manifest file has invalid line %s\n",line);
return;
if(len <= 2 || !path) {
printf("[FetchFS] Manifest file has invalid line %s\n",line);
continue;
}
*path = '\0';
path += 1;
printf("Fetch %s from %s\n", path, line);
path[strcspn(path, "\r\n")] = '\0';
printf("[FetchFS] Fetch %s from %s\n", path, line);
{
char *parent = strdup(path);
path_parent_dir(parent, strlen(parent));
if(!path_mkdir(parent)) {
printf("mkdir error %d\n",errno);
printf("[FetchFS] mkdir error %d\n",errno);
abort();
}
free(parent);
}
fetch = wasmfs_create_fetch_backend(line, 8*1024*1024);
fetch = wasmfs_create_fetch_backend(line, 16*1024*1024);
if(!fetch) {
printf("[FetchFS] couldn't create fetch backend\n");
abort();
}
fd = wasmfs_create_file(path, 0777, fetch);
if(!fd) {
printf("[FetchFS] couldn't create fetch file\n");
abort();
}
close(fd);
len = max_line_len;
}
fclose(file);
free(line);
Expand Down Expand Up @@ -433,7 +474,9 @@ void emscripten_bootup_mainloop(void *argptr) {
int main(int argc, char *argv[])
{
args_t *args = calloc(sizeof(args_t), 1);

args->argc = argc;
args->argv = argv;

PlatformEmscriptenWatchCanvasSize();
PlatformEmscriptenPowerStateInit();

Expand Down
6 changes: 5 additions & 1 deletion input/input_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -5104,10 +5104,14 @@ void input_driver_init_command(input_driver_state_t *input_st,
}
#endif

#ifdef HAVE_LAKKA
#if defined(HAVE_LAKKA)
if (!(input_st->command[2] = command_uds_new()))
RARCH_ERR("Failed to initialize the UDS command interface.\n");
#elif defined(EMSCRIPTEN)
if (!(input_st->command[2] = command_emscripten_new()))
RARCH_ERR("Failed to initialize the emscripten command interface.\n");
#endif

}

void input_driver_deinit_command(input_driver_state_t *input_st)
Expand Down
61 changes: 6 additions & 55 deletions pkg/emscripten/libretro-thread/libretro.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,64 +14,15 @@ var Module = {
noImageDecoding: true,
noAudioDecoding: true,

encoder: new TextEncoder(),
message_queue: [],
message_out: [],
message_accum: "",

retroArchSend: function(msg) {
let bytes = this.encoder.encode(msg + "\n");
this.message_queue.push([bytes, 0]);
},
retroArchRecv: function() {
let out = this.message_out.shift();
if (out == null && this.message_accum != "") {
out = this.message_accum;
this.message_accum = "";
}
return out;
},
retroArchSend: function(msg) {
this.EmscriptenSendCommand(msg);
},
retroArchRecv: function() {
return this.EmscriptenReceiveCommandReply();
},
preRun: [
function(module) {
Module.ENV['OPFS'] = "/home/web_user/retroarch";
},
function(module) {
function stdin() {
// Return ASCII code of character, or null if no input
while (module.message_queue.length > 0) {
var msg = module.message_queue[0][0];
var index = module.message_queue[0][1];
if (index >= msg.length) {
module.message_queue.shift();
} else {
module.message_queue[0][1] = index + 1;
// assumption: msg is a uint8array
return msg[index];
}
}
return null;
}

function stdout(c) {
if (c == null) {
// flush
if (module.message_accum != "") {
module.message_out.push(module.message_accum);
module.message_accum = "";
}
} else {
let s = String.fromCharCode(c);
if (s == "\n") {
if (module.message_accum != "") {
module.message_out.push(module.message_accum);
module.message_accum = "";
}
} else {
module.message_accum = module.message_accum + s;
}
}
}
module.FS.init(stdin, stdout);
}
],
postRun: [],
Expand Down
2 changes: 1 addition & 1 deletion pkg/emscripten/libretro-thread/libretro.worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ onmessage = async (msg) => {
}
postMessage({command:"loaded_bundle", time:resp.headers.get("last-modified")});
} else if(msg.data.command == "upload_file") {
await writeFile("/home/web_user/retroarch/userdata/content/"+msg.data.name, new Uint8Array(msg.data.data));
await writeFile("userdata/content/"+msg.data.name, new Uint8Array(msg.data.data));
postMessage({command:"uploaded_file",name:msg.data.name});
}
}

0 comments on commit c413bcc

Please sign in to comment.