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,\
|
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_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_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
|
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_t *cmd;
|
||||||
command_stdin_t *stdincmd;
|
command_stdin_t *stdincmd;
|
||||||
|
|
||||||
#ifndef _WIN32
|
#if !(defined(_WIN32) || defined(EMSCRIPTEN))
|
||||||
#ifdef HAVE_NETWORKING
|
#ifdef HAVE_NETWORKING
|
||||||
if (!socket_nonblock(STDIN_FILENO))
|
if (!socket_nonblock(STDIN_FILENO))
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -363,6 +363,61 @@ command_t* command_stdin_new(void)
|
|||||||
}
|
}
|
||||||
#endif
|
#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)
|
bool command_get_config_param(command_t *cmd, const char* arg)
|
||||||
{
|
{
|
||||||
size_t _len;
|
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);
|
bool command_event(enum event_command action, void *data);
|
||||||
|
|
||||||
/* Constructors for the supported drivers */
|
/* Constructors for the supported drivers */
|
||||||
|
#ifdef HAVE_NETWORK_CMD
|
||||||
command_t* command_network_new(uint16_t port);
|
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_);
|
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(
|
void command_event_set_mixer_volume(
|
||||||
settings_t *settings,
|
settings_t *settings,
|
||||||
|
@ -12,7 +12,9 @@ var LibraryPlatformEmscripten = {
|
|||||||
RPE.powerState.dischargeTime = Number.isFinite(e.target.dischargingTime) ? e.target.dischargingTime : 0x7FFFFFFF;
|
RPE.powerState.dischargeTime = Number.isFinite(e.target.dischargingTime) ? e.target.dischargingTime : 0x7FFFFFFF;
|
||||||
RPE.powerState.level = e.target.level;
|
RPE.powerState.level = e.target.level;
|
||||||
RPE.powerState.charging = e.target.charging;
|
RPE.powerState.charging = e.target.charging;
|
||||||
}
|
},
|
||||||
|
command_queue:[],
|
||||||
|
command_reply_queue:[],
|
||||||
},
|
},
|
||||||
|
|
||||||
PlatformEmscriptenPowerStateInit: function() {
|
PlatformEmscriptenPowerStateInit: function() {
|
||||||
@ -49,6 +51,15 @@ var LibraryPlatformEmscripten = {
|
|||||||
PlatformEmscriptenGetFreeMem: function() {
|
PlatformEmscriptenGetFreeMem: function() {
|
||||||
if (!performance.memory) return 0;
|
if (!performance.memory) return 0;
|
||||||
return (performance.memory.jsHeapSizeLimit || 0) - (performance.memory.usedJSHeapSize || 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 PlatformEmscriptenGetTotalMem(void);
|
||||||
uint64_t PlatformEmscriptenGetFreeMem(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 */
|
/* begin exported functions */
|
||||||
|
|
||||||
/* saves and states */
|
/* saves and states */
|
||||||
@ -350,32 +377,46 @@ void PlatformEmscriptenMountFilesystems(void *info) {
|
|||||||
*/
|
*/
|
||||||
int max_line_len = 1024;
|
int max_line_len = 1024;
|
||||||
printf("[FetchFS] read fetch manifest from %s\n",fetch_manifest);
|
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);
|
char *line = calloc(sizeof(char), max_line_len);
|
||||||
size_t len = 0;
|
size_t len = max_line_len;
|
||||||
while (getline(&line, &len, file) != -1) {
|
while (getline(&line, &len, file) != -1) {
|
||||||
char *path = strstr(line, " ");
|
char *path = strstr(line, " ");
|
||||||
backend_t fetch;
|
backend_t fetch;
|
||||||
int fd;
|
int fd;
|
||||||
if (!path) {
|
if(len <= 2 || !path) {
|
||||||
printf("Manifest file has invalid line %s\n",line);
|
printf("[FetchFS] Manifest file has invalid line %s\n",line);
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
*path = '\0';
|
*path = '\0';
|
||||||
path += 1;
|
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);
|
char *parent = strdup(path);
|
||||||
path_parent_dir(parent, strlen(parent));
|
path_parent_dir(parent, strlen(parent));
|
||||||
if(!path_mkdir(parent)) {
|
if(!path_mkdir(parent)) {
|
||||||
printf("mkdir error %d\n",errno);
|
printf("[FetchFS] mkdir error %d\n",errno);
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
free(parent);
|
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);
|
fd = wasmfs_create_file(path, 0777, fetch);
|
||||||
|
if(!fd) {
|
||||||
|
printf("[FetchFS] couldn't create fetch file\n");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
close(fd);
|
close(fd);
|
||||||
|
len = max_line_len;
|
||||||
}
|
}
|
||||||
fclose(file);
|
fclose(file);
|
||||||
free(line);
|
free(line);
|
||||||
@ -433,7 +474,9 @@ void emscripten_bootup_mainloop(void *argptr) {
|
|||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
args_t *args = calloc(sizeof(args_t), 1);
|
args_t *args = calloc(sizeof(args_t), 1);
|
||||||
|
args->argc = argc;
|
||||||
|
args->argv = argv;
|
||||||
|
|
||||||
PlatformEmscriptenWatchCanvasSize();
|
PlatformEmscriptenWatchCanvasSize();
|
||||||
PlatformEmscriptenPowerStateInit();
|
PlatformEmscriptenPowerStateInit();
|
||||||
|
|
||||||
|
@ -5104,10 +5104,14 @@ void input_driver_init_command(input_driver_state_t *input_st,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_LAKKA
|
#if defined(HAVE_LAKKA)
|
||||||
if (!(input_st->command[2] = command_uds_new()))
|
if (!(input_st->command[2] = command_uds_new()))
|
||||||
RARCH_ERR("Failed to initialize the UDS command interface.\n");
|
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
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void input_driver_deinit_command(input_driver_state_t *input_st)
|
void input_driver_deinit_command(input_driver_state_t *input_st)
|
||||||
|
@ -14,64 +14,15 @@ var Module = {
|
|||||||
noImageDecoding: true,
|
noImageDecoding: true,
|
||||||
noAudioDecoding: true,
|
noAudioDecoding: true,
|
||||||
|
|
||||||
encoder: new TextEncoder(),
|
retroArchSend: function(msg) {
|
||||||
message_queue: [],
|
this.EmscriptenSendCommand(msg);
|
||||||
message_out: [],
|
},
|
||||||
message_accum: "",
|
retroArchRecv: function() {
|
||||||
|
return this.EmscriptenReceiveCommandReply();
|
||||||
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;
|
|
||||||
},
|
|
||||||
preRun: [
|
preRun: [
|
||||||
function(module) {
|
function(module) {
|
||||||
Module.ENV['OPFS'] = "/home/web_user/retroarch";
|
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: [],
|
postRun: [],
|
||||||
|
@ -60,7 +60,7 @@ onmessage = async (msg) => {
|
|||||||
}
|
}
|
||||||
postMessage({command:"loaded_bundle", time:resp.headers.get("last-modified")});
|
postMessage({command:"loaded_bundle", time:resp.headers.get("last-modified")});
|
||||||
} else if(msg.data.command == "upload_file") {
|
} 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});
|
postMessage({command:"uploaded_file",name:msg.data.name});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user