Skip to content

Commit

Permalink
Add exec command driver and API functions for emscripten.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
JoeOsborn committed Feb 22, 2025
1 parent 8638ee6 commit 5e870ec
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 5 deletions.
2 changes: 1 addition & 1 deletion Makefile.emscripten
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ 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

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
27 changes: 27 additions & 0 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
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

0 comments on commit 5e870ec

Please sign in to comment.