mirror of
https://github.com/libretro/RetroArch
synced 2025-04-25 09:02:44 +00:00
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
This commit is contained in:
parent
55b59262b9
commit
c413bcc626
@ -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
|
||||
|
||||
|
57
command.c
57
command.c
@ -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;
|
||||
@ -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;
|
||||
|
15
command.h
15
command.h
@ -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);
|
||||
command_t* command_stdin_new(void);
|
||||
command_t* command_uds_new(void);
|
||||
|
||||
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
|
||||
|
||||
|
||||
void command_event_set_mixer_volume(
|
||||
settings_t *settings,
|
||||
|
@ -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() {
|
||||
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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 */
|
||||
@ -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);
|
||||
@ -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();
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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: [],
|
||||
|
@ -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});
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user