Skip to content

Commit c413bcc

Browse files
authored
Threaded emscripten fixes (#17614)
* 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
1 parent 55b5926 commit c413bcc

File tree

8 files changed

+144
-71
lines changed

8 files changed

+144
-71
lines changed

Makefile.emscripten

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,9 @@ OBJDIR := obj-emscripten
102102
EXPORTED_FUNCTIONS = _main,_malloc,_free,_cmd_savefiles,_cmd_save_state,_cmd_load_state,_cmd_undo_save_state,_cmd_undo_load_state,_cmd_take_screenshot,\
103103
_cmd_toggle_menu,_cmd_reload_config,_cmd_toggle_grab_mouse,_cmd_toggle_game_focus,_cmd_reset,_cmd_toggle_pause,_cmd_pause,_cmd_unpause,\
104104
_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,\
105-
_cmd_cheat_get_size,_cmd_cheat_apply_cheats
105+
_cmd_cheat_get_size,_cmd_cheat_apply_cheats,EmscriptenSendCommand,EmscriptenReceiveCommandReply
106106

107-
EXPORTS := callMain,FS,PATH,ERRNO_CODES,ENV,stringToNewUTF8,UTF8ToString,Browser,GL
107+
EXPORTS := callMain,FS,PATH,ERRNO_CODES,ENV,stringToNewUTF8,UTF8ToString,Browser,GL,EmscriptenSendCommand,EmscriptenReceiveCommandReply
108108

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

command.c

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ command_t* command_stdin_new(void)
337337
command_t *cmd;
338338
command_stdin_t *stdincmd;
339339

340-
#ifndef _WIN32
340+
#if !(defined(_WIN32) || defined(EMSCRIPTEN))
341341
#ifdef HAVE_NETWORKING
342342
if (!socket_nonblock(STDIN_FILENO))
343343
return NULL;
@@ -363,6 +363,61 @@ command_t* command_stdin_new(void)
363363
}
364364
#endif
365365

366+
#if defined(EMSCRIPTEN)
367+
void PlatformEmscriptenCommandReply(const char *, size_t);
368+
int PlatformEmscriptenCommandRead(char **, size_t);
369+
typedef struct
370+
{
371+
char command_buf[CMD_BUF_SIZE];
372+
} command_emscripten_t;
373+
374+
static void emscripten_command_reply(command_t *_cmd,
375+
const char *s, size_t len)
376+
{
377+
PlatformEmscriptenCommandReply(s, len);
378+
}
379+
380+
static void emscripten_command_free(command_t *handle)
381+
{
382+
free(handle->userptr);
383+
free(handle);
384+
}
385+
386+
static void command_emscripten_poll(command_t *handle)
387+
{
388+
command_emscripten_t *emscriptencmd = (command_emscripten_t*)handle->userptr;
389+
ptrdiff_t msg_len = PlatformEmscriptenCommandRead((char **)(&emscriptencmd->command_buf), CMD_BUF_SIZE);
390+
if (msg_len == 0)
391+
return;
392+
command_parse_msg(handle, emscriptencmd->command_buf);
393+
}
394+
395+
command_t* command_emscripten_new(void)
396+
{
397+
command_t *cmd;
398+
command_emscripten_t *emscriptencmd;
399+
400+
cmd = (command_t*)calloc(1, sizeof(command_t));
401+
emscriptencmd = (command_emscripten_t*)calloc(1, sizeof(command_emscripten_t));
402+
403+
if (!cmd)
404+
return NULL;
405+
if (!emscriptencmd)
406+
{
407+
free(cmd);
408+
return NULL;
409+
}
410+
cmd->userptr = emscriptencmd;
411+
cmd->poll = command_emscripten_poll;
412+
cmd->replier = emscripten_command_reply;
413+
cmd->destroy = emscripten_command_free;
414+
415+
return cmd;
416+
}
417+
#endif
418+
419+
420+
366421
bool command_get_config_param(command_t *cmd, const char* arg)
367422
{
368423
size_t _len;

command.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,11 +329,20 @@ struct rarch_state;
329329
bool command_event(enum event_command action, void *data);
330330

331331
/* Constructors for the supported drivers */
332+
#ifdef HAVE_NETWORK_CMD
332333
command_t* command_network_new(uint16_t port);
334+
bool command_network_send(const char *cmd_);
335+
#endif
336+
#ifdef HAVE_STDIN_CMD
333337
command_t* command_stdin_new(void);
338+
#endif
339+
#ifdef LAKKA
334340
command_t* command_uds_new(void);
341+
#endif
342+
#ifdef EMSCRIPTEN
343+
command_t* command_emscripten_new(void);
344+
#endif
335345

336-
bool command_network_send(const char *cmd_);
337346

338347
void command_event_set_mixer_volume(
339348
settings_t *settings,

emscripten/library_platform_emscripten.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ var LibraryPlatformEmscripten = {
1212
RPE.powerState.dischargeTime = Number.isFinite(e.target.dischargingTime) ? e.target.dischargingTime : 0x7FFFFFFF;
1313
RPE.powerState.level = e.target.level;
1414
RPE.powerState.charging = e.target.charging;
15-
}
15+
},
16+
command_queue:[],
17+
command_reply_queue:[],
1618
},
1719

1820
PlatformEmscriptenPowerStateInit: function() {
@@ -49,6 +51,15 @@ var LibraryPlatformEmscripten = {
4951
PlatformEmscriptenGetFreeMem: function() {
5052
if (!performance.memory) return 0;
5153
return (performance.memory.jsHeapSizeLimit || 0) - (performance.memory.usedJSHeapSize || 0);
54+
},
55+
56+
$EmscriptenSendCommand__deps:["PlatformEmscriptenCommandRaiseFlag"],
57+
$EmscriptenSendCommand: function(str) {
58+
RPE.command_queue.push(str);
59+
_PlatformEmscriptenCommandRaiseFlag();
60+
},
61+
$EmscriptenReceiveCommandReply: function() {
62+
return RPE.command_reply_queue.shift();
5263
}
5364
};
5465

frontend/drivers/platform_emscripten.c

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,33 @@ bool PlatformEmscriptenPowerStateGetCharging(void);
9191
uint64_t PlatformEmscriptenGetTotalMem(void);
9292
uint64_t PlatformEmscriptenGetFreeMem(void);
9393

94+
void PlatformEmscriptenCommandReply(const char *msg, size_t len) {
95+
MAIN_THREAD_EM_ASM({
96+
var message = UTF8ToString($0,$1);
97+
RPE.command_reply_queue.push(message);
98+
}, msg, len);
99+
}
100+
static bool command_flag = false;
101+
size_t PlatformEmscriptenCommandRead(char **into, size_t max_len) {
102+
if(!command_flag) { return 0; }
103+
return MAIN_THREAD_EM_ASM_INT({
104+
var next_command = RPE.command_queue.shift();
105+
var length = lengthBytesUTF8(next_command);
106+
if(length > $2) {
107+
console.error("[CMD] Command too long, skipping",next_command);
108+
return 0;
109+
}
110+
stringToUTF8(next_command, $1, $2);
111+
if(RPE.command_queue.length == 0) {
112+
setValue($0, 0, 'i8');
113+
}
114+
return length;
115+
}, &command_flag, into, max_len);
116+
}
117+
void PlatformEmscriptenCommandRaiseFlag() {
118+
command_flag = true;
119+
}
120+
94121
/* begin exported functions */
95122

96123
/* saves and states */
@@ -350,32 +377,46 @@ void PlatformEmscriptenMountFilesystems(void *info) {
350377
*/
351378
int max_line_len = 1024;
352379
printf("[FetchFS] read fetch manifest from %s\n",fetch_manifest);
353-
FILE *file = fopen(fetch_manifest, O_RDONLY);
380+
FILE *file = fopen(fetch_manifest, "r");
381+
if(!file) {
382+
printf("[FetchFS] missing manifest file\n");
383+
abort();
384+
}
354385
char *line = calloc(sizeof(char), max_line_len);
355-
size_t len = 0;
386+
size_t len = max_line_len;
356387
while (getline(&line, &len, file) != -1) {
357388
char *path = strstr(line, " ");
358389
backend_t fetch;
359390
int fd;
360-
if (!path) {
361-
printf("Manifest file has invalid line %s\n",line);
362-
return;
391+
if(len <= 2 || !path) {
392+
printf("[FetchFS] Manifest file has invalid line %s\n",line);
393+
continue;
363394
}
364395
*path = '\0';
365396
path += 1;
366-
printf("Fetch %s from %s\n", path, line);
397+
path[strcspn(path, "\r\n")] = '\0';
398+
printf("[FetchFS] Fetch %s from %s\n", path, line);
367399
{
368400
char *parent = strdup(path);
369401
path_parent_dir(parent, strlen(parent));
370402
if(!path_mkdir(parent)) {
371-
printf("mkdir error %d\n",errno);
403+
printf("[FetchFS] mkdir error %d\n",errno);
372404
abort();
373405
}
374406
free(parent);
375407
}
376-
fetch = wasmfs_create_fetch_backend(line, 8*1024*1024);
408+
fetch = wasmfs_create_fetch_backend(line, 16*1024*1024);
409+
if(!fetch) {
410+
printf("[FetchFS] couldn't create fetch backend\n");
411+
abort();
412+
}
377413
fd = wasmfs_create_file(path, 0777, fetch);
414+
if(!fd) {
415+
printf("[FetchFS] couldn't create fetch file\n");
416+
abort();
417+
}
378418
close(fd);
419+
len = max_line_len;
379420
}
380421
fclose(file);
381422
free(line);
@@ -433,7 +474,9 @@ void emscripten_bootup_mainloop(void *argptr) {
433474
int main(int argc, char *argv[])
434475
{
435476
args_t *args = calloc(sizeof(args_t), 1);
436-
477+
args->argc = argc;
478+
args->argv = argv;
479+
437480
PlatformEmscriptenWatchCanvasSize();
438481
PlatformEmscriptenPowerStateInit();
439482

input/input_driver.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5104,10 +5104,14 @@ void input_driver_init_command(input_driver_state_t *input_st,
51045104
}
51055105
#endif
51065106

5107-
#ifdef HAVE_LAKKA
5107+
#if defined(HAVE_LAKKA)
51085108
if (!(input_st->command[2] = command_uds_new()))
51095109
RARCH_ERR("Failed to initialize the UDS command interface.\n");
5110+
#elif defined(EMSCRIPTEN)
5111+
if (!(input_st->command[2] = command_emscripten_new()))
5112+
RARCH_ERR("Failed to initialize the emscripten command interface.\n");
51105113
#endif
5114+
51115115
}
51125116

51135117
void input_driver_deinit_command(input_driver_state_t *input_st)

pkg/emscripten/libretro-thread/libretro.js

Lines changed: 6 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -14,64 +14,15 @@ var Module = {
1414
noImageDecoding: true,
1515
noAudioDecoding: true,
1616

17-
encoder: new TextEncoder(),
18-
message_queue: [],
19-
message_out: [],
20-
message_accum: "",
21-
22-
retroArchSend: function(msg) {
23-
let bytes = this.encoder.encode(msg + "\n");
24-
this.message_queue.push([bytes, 0]);
25-
},
26-
retroArchRecv: function() {
27-
let out = this.message_out.shift();
28-
if (out == null && this.message_accum != "") {
29-
out = this.message_accum;
30-
this.message_accum = "";
31-
}
32-
return out;
33-
},
17+
retroArchSend: function(msg) {
18+
this.EmscriptenSendCommand(msg);
19+
},
20+
retroArchRecv: function() {
21+
return this.EmscriptenReceiveCommandReply();
22+
},
3423
preRun: [
3524
function(module) {
3625
Module.ENV['OPFS'] = "/home/web_user/retroarch";
37-
},
38-
function(module) {
39-
function stdin() {
40-
// Return ASCII code of character, or null if no input
41-
while (module.message_queue.length > 0) {
42-
var msg = module.message_queue[0][0];
43-
var index = module.message_queue[0][1];
44-
if (index >= msg.length) {
45-
module.message_queue.shift();
46-
} else {
47-
module.message_queue[0][1] = index + 1;
48-
// assumption: msg is a uint8array
49-
return msg[index];
50-
}
51-
}
52-
return null;
53-
}
54-
55-
function stdout(c) {
56-
if (c == null) {
57-
// flush
58-
if (module.message_accum != "") {
59-
module.message_out.push(module.message_accum);
60-
module.message_accum = "";
61-
}
62-
} else {
63-
let s = String.fromCharCode(c);
64-
if (s == "\n") {
65-
if (module.message_accum != "") {
66-
module.message_out.push(module.message_accum);
67-
module.message_accum = "";
68-
}
69-
} else {
70-
module.message_accum = module.message_accum + s;
71-
}
72-
}
73-
}
74-
module.FS.init(stdin, stdout);
7526
}
7627
],
7728
postRun: [],

pkg/emscripten/libretro-thread/libretro.worker.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ onmessage = async (msg) => {
6060
}
6161
postMessage({command:"loaded_bundle", time:resp.headers.get("last-modified")});
6262
} else if(msg.data.command == "upload_file") {
63-
await writeFile("/home/web_user/retroarch/userdata/content/"+msg.data.name, new Uint8Array(msg.data.data));
63+
await writeFile("userdata/content/"+msg.data.name, new Uint8Array(msg.data.data));
6464
postMessage({command:"uploaded_file",name:msg.data.name});
6565
}
6666
}

0 commit comments

Comments
 (0)