Skip to content

Commit

Permalink
Merge pull request #31 from floooh/jsapi
Browse files Browse the repository at this point in the history
Add a "web API" and other additions for running inside a VSCode extension.
  • Loading branch information
floooh authored Jan 2, 2024
2 parents b40d5dd + 57ac173 commit 6a496b7
Show file tree
Hide file tree
Showing 20 changed files with 1,027 additions and 46 deletions.
7 changes: 6 additions & 1 deletion examples/common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ fips_begin_lib(common)
fs.c fs.h
gfx.c gfx.h
keybuf.c keybuf.h
prof.c prof.h)
prof.c prof.h
webapi.c webapi.h)
sokol_shader(shaders.glsl ${slang})
if (FIPS_OSX)
fips_files(sokol.m)
Expand Down Expand Up @@ -65,4 +66,8 @@ fips_end_lib()
# a separate library with just keybuf (for the ASCII emulators)
fips_begin_lib(keybuf)
fips_files(keybuf.c keybuf.h)
fips_end_lib()

fips_begin_lib(webapi)
fips_files(webapi.c webapi.h)
fips_end_lib()
1 change: 1 addition & 0 deletions examples/common/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@
#include "fs.h"
#include "gfx.h"
#include "keybuf.h"
#include "webapi.h"
#include <ctype.h> // isupper, islower, toupper, tolower
2 changes: 0 additions & 2 deletions examples/common/fs.c
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,6 @@ EM_JS(void, fs_js_load_snapshot, (const char* system_name_cstr, int snapshot_ind
const db_name = 'chips';
const db_store_name = 'store';
const system_name = UTF8ToString(system_name_cstr);
console.log('fs_js_load_snapshot: called with', system_name, snapshot_index);
let open_request;
try {
open_request = window.indexedDB.open(db_name, 1);
Expand Down Expand Up @@ -501,7 +500,6 @@ EM_JS(void, fs_js_load_snapshot, (const char* system_name_cstr, int snapshot_ind
HEAPU8.set(get_request.result, ptr);
_fs_emsc_load_snapshot_callback(context, ptr, num_bytes);
} else {
console.log('fs_js_load_snapshot:', key, 'does not exist');
_fs_emsc_load_snapshot_callback(context, 0, 0);
}
};
Expand Down
9 changes: 8 additions & 1 deletion examples/common/gfx.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

typedef struct {
bool valid;
bool disable_speaker_icon;
gfx_border_t border;
struct {
sg_image img; // framebuffer texture, RGBA8 or R8 if paletted
Expand Down Expand Up @@ -151,6 +152,11 @@ void gfx_flash_error(void) {
state.flash_error_count = 20;
}

void gfx_disable_speaker_icon(void) {
assert(state.valid);
state.disable_speaker_icon = true;
}

sg_image gfx_create_icon_texture(const uint8_t* packed_pixels, int width, int height, int stride) {
const size_t pixel_data_size = width * height * sizeof(uint32_t);
uint32_t* pixels = malloc(pixel_data_size);
Expand Down Expand Up @@ -256,6 +262,7 @@ void gfx_init(const gfx_desc_t* desc) {
});

state.valid = true;
state.disable_speaker_icon = desc->disable_speaker_icon;
state.border = desc->border;
state.display.portrait = desc->display_info.portrait;
state.draw_extra_cb = desc->draw_extra_cb;
Expand Down Expand Up @@ -413,7 +420,7 @@ void gfx_draw(chips_display_info_t display_info) {
}

// if audio is off, draw speaker icon via sokol-gl
if (saudio_suspended()) {
if (!state.disable_speaker_icon && saudio_suspended()) {
const float x0 = display.width - (float)state.icon.dim.width - 10.0f;
const float x1 = x0 + (float)state.icon.dim.width;
const float y0 = 10.0f;
Expand Down
2 changes: 2 additions & 0 deletions examples/common/gfx.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ typedef struct {
} gfx_border_t;

typedef struct {
bool disable_speaker_icon;
gfx_border_t border;
chips_display_info_t display_info;
chips_dim_t pixel_aspect; // optional pixel aspect ratio, default is 1:1
Expand All @@ -32,6 +33,7 @@ void gfx_draw(chips_display_info_t display_info);
void gfx_shutdown(void);
void gfx_flash_success(void);
void gfx_flash_error(void);
void gfx_disable_speaker_icon(void);
sg_image gfx_create_icon_texture(const uint8_t* packed_pixels, int width, int height, int stride);

#ifdef __cplusplus
Expand Down
8 changes: 2 additions & 6 deletions examples/common/shell.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,8 @@
canvas.addEventListener("webglcontextlost", function(e) { alert('FIXME: WebGL context lost, please reload the page'); e.preventDefault(); }, false);
return canvas;
})(),
setStatus: function(text) {
console.log("status: " + text);
},
monitorRunDependencies: function(left) {
console.log("monitor run deps: " + left);
}
setStatus: function(text) { },
monitorRunDependencies: function(left) { },
};
window.onerror = function() {
console.log("onerror: " + event);
Expand Down
223 changes: 223 additions & 0 deletions examples/common/webapi.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
#include "webapi.h"
#include <assert.h>
#include <string.h>
#if defined(__EMSCRIPTEN__)
#include <emscripten/emscripten.h>
#endif
#include "gfx.h"

static struct {
bool dbg_connect_requested;
} before_init_state;

static struct {
bool inited;
webapi_interface_t funcs;
} state;

void webapi_init(const webapi_desc_t* desc) {
assert(desc);
state.inited = true;
state.funcs = desc->funcs;
if (before_init_state.dbg_connect_requested && state.funcs.dbg_connect) {
state.funcs.dbg_connect();
}
}

#if defined(__EMSCRIPTEN__)

EM_JS(void, webapi_js_event_stopped, (int stop_reason, uint16_t addr), {
console.log("webapi_js_event_stopped()");
if (Module["webapi_onStopped"]) {
Module["webapi_onStopped"](stop_reason, addr);
} else {
console.log("no Module.webapi_onStopped function");
}
});

EM_JS(void, webapi_js_event_continued, (), {
console.log("webapi_js_event_continued()");
if (Module["webapi_onContinued"]) {
Module["webapi_onContinued"]();
} else {
console.log("no Module.webapi_onContinued function");
}
});

EM_JS(void, webapi_js_event_reboot, (), {
console.log("webapi_js_event_reboot()");
if (Module["webapi_onReboot"]) {
Module["webapi_onReboot"]();
} else {
console.log("no Module.webapi_onReboot function");
}
});

EM_JS(void, webapi_js_event_reset, (), {
console.log("webapi_js_event_reset()");
if (Module["webapi_onReset"]) {
Module["webapi_onReset"]();
} else {
console.log("no Module.webapi_onReset function");
}
});

EMSCRIPTEN_KEEPALIVE void webapi_dbg_connect(void) {
if (state.inited) {
if (state.funcs.dbg_connect) {
state.funcs.dbg_connect();
}
} else {
before_init_state.dbg_connect_requested = true;
}
}

EMSCRIPTEN_KEEPALIVE void webapi_dbg_disconnect(void) {
if (state.inited && state.funcs.dbg_disconnect) {
state.funcs.dbg_disconnect();
}
}

EMSCRIPTEN_KEEPALIVE void* webapi_alloc(int size) {
return malloc((size_t)size);
}

EMSCRIPTEN_KEEPALIVE void webapi_free(void* ptr) {
if (ptr) {
free(ptr);
}
}

EMSCRIPTEN_KEEPALIVE void webapi_boot(void) {
if (state.inited && state.funcs.boot) {
state.funcs.boot();
}
}

EMSCRIPTEN_KEEPALIVE void webapi_reset(void) {
if (state.inited && state.funcs.reset) {
state.funcs.reset();
}
}

EMSCRIPTEN_KEEPALIVE bool webapi_ready(void) {
if (state.inited && state.funcs.ready) {
return state.funcs.ready();
} else {
return false;
}
}

EMSCRIPTEN_KEEPALIVE bool webapi_load(void* ptr, int size) {
if (state.inited && state.funcs.load && ptr && ((size_t)size > sizeof(webapi_fileheader_t))) {
const webapi_fileheader_t* hdr = (webapi_fileheader_t*)ptr;
if ((hdr->magic[0] != 'C') || (hdr->magic[1] != 'H') || (hdr->magic[2] != 'I') || (hdr->magic[3] != 'P')) {
return false;
}
return state.funcs.load((chips_range_t){ .ptr = ptr, .size = (size_t)size });
}
return false;
}

EMSCRIPTEN_KEEPALIVE void webapi_dbg_add_breakpoint(uint16_t addr) {
if (state.inited && state.funcs.dbg_add_breakpoint) {
state.funcs.dbg_add_breakpoint(addr);
}
}

EMSCRIPTEN_KEEPALIVE void webapi_dbg_remove_breakpoint(uint16_t addr) {
if (state.inited && state.funcs.dbg_remove_breakpoint) {
state.funcs.dbg_remove_breakpoint(addr);
}
}

EMSCRIPTEN_KEEPALIVE void webapi_dbg_break(void) {
if (state.inited && state.funcs.dbg_break) {
state.funcs.dbg_break();
}
}

EMSCRIPTEN_KEEPALIVE void webapi_dbg_continue(void) {
if (state.inited && state.funcs.dbg_continue) {
state.funcs.dbg_continue();
}
}

EMSCRIPTEN_KEEPALIVE void webapi_dbg_step_next(void) {
if (state.inited && state.funcs.dbg_step_next) {
state.funcs.dbg_step_next();
}
}

EMSCRIPTEN_KEEPALIVE void webapi_dbg_step_into(void) {
if (state.inited && state.funcs.dbg_step_into) {
state.funcs.dbg_step_into();
}
}

// return emulator state as JSON-formatted string pointer into WASM heap
EMSCRIPTEN_KEEPALIVE uint16_t* webapi_dbg_cpu_state(void) {
static webapi_cpu_state_t res;
if (state.inited && state.funcs.dbg_cpu_state) {
res = state.funcs.dbg_cpu_state();
} else {
memset(&res, 0, sizeof(res));
}
return &res.items[0];
}

// request a disassembly, returns ptr to heap-allocated array of 'num_lines' webapi_dasm_line_t structs which must be freed with webapi_free()
// NOTE: may return 0!
EMSCRIPTEN_KEEPALIVE webapi_dasm_line_t* webapi_dbg_request_disassembly(uint16_t addr, int offset_lines, int num_lines) {
if (num_lines <= 0) {
return 0;
}
if (state.inited && state.funcs.dbg_request_disassembly) {
webapi_dasm_line_t* out_lines = calloc((size_t)num_lines, sizeof(webapi_dasm_line_t));
state.funcs.dbg_request_disassembly(addr, offset_lines, num_lines, out_lines);
return out_lines;
} else {
return 0;
}
}

// reads a memory chunk, returns heap-allocated buffer which must be freed with webapi_free()
// NOTE: may return 0!
EMSCRIPTEN_KEEPALIVE uint8_t* webapi_dbg_read_memory(uint16_t addr, int num_bytes) {
if (state.inited && state.funcs.dbg_read_memory) {
uint8_t* ptr = calloc((size_t)num_bytes, 1);
state.funcs.dbg_read_memory(addr, num_bytes, ptr);
return ptr;
} else {
return 0;
}
}

#endif // __EMSCRIPTEN__

// stop_reason is UI_DBG_STOP_REASON_xxx
void webapi_event_stopped(int stop_reason, uint16_t addr) {
#if defined(__EMSCRIPTEN__)
webapi_js_event_stopped(stop_reason, addr);
#else
(void)stop_reason; (void)addr;
#endif
}

void webapi_event_continued(void) {
#if defined(__EMSCRIPTEN__)
webapi_js_event_continued();
#endif
}

void webapi_event_reboot(void) {
#if defined(__EMSCRIPTEN__)
webapi_js_event_reboot();
#endif
}

void webapi_event_reset(void) {
#if defined(__EMSCRIPTEN__)
webapi_js_event_reset();
#endif
}
Loading

0 comments on commit 6a496b7

Please sign in to comment.