diff --git a/scripts/core/io.js b/scripts/core/io.js index b461c301..57d48bc4 100644 --- a/scripts/core/io.js +++ b/scripts/core/io.js @@ -3,162 +3,17 @@ $_.unneeded(_ => { var subscribers = [] -var windows = [] -var renderers = [] - var os = use('os') $_.receiver(e => { - if (e.type === "subscribe") { + console.log(json.encode(e)) + return + if (e.op === "subscribe") { if (!e.actor) throw Error('Got a subscribe message with no actor.'); subscribers.push(e.actor) return; } - if (e.type === "window") { - var window = windows[e.id] - - switch (e.fn) { - case "create": - window = os.engine_start(e.config) - windows[e.id] = window - break; - - case "fullscreen": - window.fullscreen() - break; - - case "make_renderer": - var renderer = window.make_renderer(e.config || {}) - renderers[e.renderer_id] = renderer - break; - - case "keyboard_shown": - return window.keyboard_shown() - - case "theme": - return window.theme() - - case "safe_area": - return window.safe_area() - - case "bordered": - window.bordered(e.value) - break; - - case "set_icon": - window.set_icon(e.icon) - break; - - case "set_title": - window.title = e.title - break; - - case "get_title": - return window.title - - case "set_size": - window.size = e.size - break; - - case "get_size": - return window.size - - case "mouse_grab": - window.mouse_grab(e.value) - break; - } - } - - if (e.type === "render") { - var renderer = renderers[e.id] - - switch (e.fn) { - case "draw_color": - renderer.draw_color(e.color) - break; - - case "present": - renderer.present() - break; - - case "clear": - renderer.clear() - break; - - case "line": - renderer.line(e.config) - break; - - case "point": - renderer.point(e.config) - break; - - case "texture": - renderer.texture(e.texture, e.src_rect, e.dst_rect, e.angle, e.center) - break; - - case "rects": - renderer.rects(e.rects) - break; - - case "geometry": - renderer.geometry(e.vertices, e.indices) - break; - - case "geometry2": - renderer.geometry2(e.vertices, e.indices) - break; - - case "sprite": - renderer.sprite(e.config) - break; - - case "load_texture": - renderer.load_texture(e.path) - break; - - case "get_image": - renderer.get_image(e.config) - break; - - case "scale": - renderer.scale(e.scale) - break; - - case "logical_size": - renderer.logical_size(e.size) - break; - - case "viewport": - renderer.viewport(e.viewport) - break; - - case "clip": - renderer.clip(e.rect) - break; - - case "vsync": - renderer.vsync(e.enable) - break; - - case "coords": - renderer.coords(e.config) - break; - - case "camera": - renderer.camera(e.cam, e.layer) - break; - - case "screen2world": - return renderer.screen2world(e.point) - - case "target": - renderer.target(e.target) - break; - } - } - for (var a of subscribers) send(a, e); }); diff --git a/scripts/modules/input.js b/scripts/modules/input.js index 04801578..cd70698f 100644 --- a/scripts/modules/input.js +++ b/scripts/modules/input.js @@ -12,12 +12,6 @@ input.mouse_lock[prosperon.DOC] = `Capture or release the mouse, confining it wi :return: None ` -input.cursor_set[prosperon.DOC] = `Set the given cursor (created by os.make_cursor) as the active mouse cursor. - -:param cursor: The cursor to set. -:return: None -` - input.keyname[prosperon.DOC] = `Given a numeric keycode, return the corresponding key name (e.g., from SDL). :param keycode: A numeric SDL keycode. diff --git a/source/jsffi.c b/source/jsffi.c index 89c25523..45ac200c 100644 --- a/source/jsffi.c +++ b/source/jsffi.c @@ -375,8 +375,6 @@ char *js2strdup(JSContext *js, JSValue v) { #include "qjs_macros.h" - - void SDL_GPUCommandBuffer_free(JSRuntime *rt, SDL_GPUCommandBuffer *c) { @@ -1589,7 +1587,6 @@ void ffi_load(JSContext *js) // extra arrput(rt->module_registry, ((ModuleEntry){"io", js_io_use})); arrput(rt->module_registry, ((ModuleEntry){"os", js_os_use})); - arrput(rt->module_registry, ((ModuleEntry){"input", js_input_use})); arrput(rt->module_registry, MISTLINE(qr)); arrput(rt->module_registry, MISTLINE(http)); arrput(rt->module_registry, MISTLINE(crypto)); @@ -1604,6 +1601,7 @@ void ffi_load(JSContext *js) // prosperon arrput(rt->module_registry, ((ModuleEntry){"sdl_audio", js_sdl_audio_use})); arrput(rt->module_registry, ((ModuleEntry){"sdl_video", js_sdl_video_use})); + arrput(rt->module_registry, ((ModuleEntry){"input", js_input_use})); arrput(rt->module_registry, ((ModuleEntry){"surface", js_sdl_surface_use})); arrput(rt->module_registry, MISTLINE(spline)); arrput(rt->module_registry, ((ModuleEntry){"geometry", js_geometry_use})); @@ -1679,6 +1677,11 @@ void ffi_load(JSContext *js) JS_SetPropertyStr(js, prosp, "hidden", hidden_fn); - JS_FreeValue(js,globalThis); + +/* + prosperon_rt *actor = JS_GetContextOpaque(js); + actor->actor_sym = JS_NewSymbol(js, "actor symbol", 0); + JS_SetPropertyStr(js, hidden_fn, "ACTORDATA", actor->actor_sym); +*/ } diff --git a/source/prosperon.c b/source/prosperon.c index 340f02d5..a726a096 100644 --- a/source/prosperon.c +++ b/source/prosperon.c @@ -49,7 +49,9 @@ static struct { char *key; prosperon_rt *value; } *actors = NULL; static unsigned char *zip_buffer_global = NULL; static char *prosperon = NULL; -prosperon_rt *io_actor = NULL; +/* Event watch subscribers */ +char **event_watchers = NULL; +SDL_Mutex *event_watchers_mutex = NULL; static Uint32 queue_event; @@ -252,7 +254,10 @@ const char *register_actor(const char *id, prosperon_rt *actor, int mainthread) const char *send_message(const char *id, void *msg) { prosperon_rt *target = get_actor(id); - if (!target) return "Could not get actor from id."; + if (!target) { + free(msg); + return "Could not get actor from id."; + } SDL_LockMutex(target->msg_mutex); arrput(target->messages, msg); @@ -1328,11 +1333,67 @@ static void event2wota_write(WotaBuffer *wb, const SDL_Event *e, int c) { } static WotaBuffer event2wota(const SDL_Event *event) { - WotaBuffer wb; - wota_buffer_init(&wb, 8); - int n = event2wota_count_props(event); - event2wota_write(&wb, event, n); - return wb; + WotaBuffer wb; + wota_buffer_init(&wb, 8); + int n = event2wota_count_props(event); + event2wota_write(&wb, event, n); + return wb; +} + +bool event_watch(void *data, SDL_Event *e) +{ + if (e->type == queue_event) return true; + + SDL_LockMutex(event_watchers_mutex); + int n_watchers = arrlen(event_watchers); + if (n_watchers == 0) { + SDL_UnlockMutex(event_watchers_mutex); + return true; + } + + /* Create a copy of watcher IDs while holding the lock */ + char **watchers_copy = NULL; + arrsetcap(watchers_copy, n_watchers); + for (int i = 0; i < n_watchers; i++) { + arrput(watchers_copy, strdup(event_watchers[i])); + } + SDL_UnlockMutex(event_watchers_mutex); + + WotaBuffer wb = event2wota(e); + + for (int i = 0; i < arrlen(watchers_copy); i++) { + if (actor_exists(watchers_copy[i])) { + const char *err = send_message(watchers_copy[i], wota_dup_data(&wb)); + if (err) { + /* Remove dead actor from watchers */ + SDL_LockMutex(event_watchers_mutex); + for (int j = 0; j < arrlen(event_watchers); j++) { + if (strcmp(event_watchers[j], watchers_copy[i]) == 0) { + free(event_watchers[j]); + arrdel(event_watchers, j); + break; + } + } + SDL_UnlockMutex(event_watchers_mutex); + } + } else { + /* Remove dead actor from watchers */ + SDL_LockMutex(event_watchers_mutex); + for (int j = 0; j < arrlen(event_watchers); j++) { + if (strcmp(event_watchers[j], watchers_copy[i]) == 0) { + free(event_watchers[j]); + arrdel(event_watchers, j); + break; + } + } + SDL_UnlockMutex(event_watchers_mutex); + } + free(watchers_copy[i]); + } + + arrfree(watchers_copy); + wota_buffer_free(&wb); + return true; } int main(int argc, char **argv) @@ -1375,12 +1436,7 @@ int main(int argc, char **argv) queue_mutex = SDL_CreateMutex(); queue_cond = SDL_CreateCondition(); actors_mutex = SDL_CreateMutex(); - - const char *io_script = "scripts/core/io.js"; - char **io_argv = malloc(sizeof(char*) * 2); - io_argv[0] = strdup(argv[0]); - io_argv[1] = strdup(io_script); - io_actor = create_actor(2, io_argv, NULL); + event_watchers_mutex = SDL_CreateMutex(); /* Create the initial actor from the main command line. */ create_actor(argc, argv, NULL); @@ -1399,14 +1455,12 @@ int main(int argc, char **argv) signal(SIGSEGV, signal_handler); signal(SIGABRT, signal_handler); atexit(exit_handler); + + SDL_AddEventWatch(event_watch, NULL); SDL_Event event; while (SDL_WaitEvent(&event)) { - if (event.type == queue_event) goto QUEUE; - - WotaBuffer wb = event2wota(&event); - send_message(io_actor->id, wb.data); - continue; + if (event.type != queue_event) continue; QUEUE: SDL_LockMutex(queue_mutex); diff --git a/source/prosperon.h b/source/prosperon.h index 4a4ff94c..de8035fd 100644 --- a/source/prosperon.h +++ b/source/prosperon.h @@ -65,6 +65,8 @@ typedef struct prosperon_rt { Uint32 ar; int need_stop; int main_thread_only; + + JSValue actor_sym; } prosperon_rt; extern SDL_ThreadID main_thread; @@ -82,6 +84,7 @@ void script_evalf(JSContext *js, const char *format, ...); JSValue script_eval(JSContext *js, const char *file, const char *script); int uncaught_exception(JSContext *js, JSValue v); int actor_exists(const char *id); +prosperon_rt *get_actor(char *id); void set_actor_state(prosperon_rt *actor); int prosperon_mount_core(void); diff --git a/source/qjs_actor.c b/source/qjs_actor.c index 0b6ff6ce..54d592b2 100644 --- a/source/qjs_actor.c +++ b/source/qjs_actor.c @@ -8,13 +8,44 @@ #include #include -// External variables -extern prosperon_rt *io_actor; - // External function declarations JSValue js_actor_delay(JSContext *js, JSValue self, int argc, JSValue *argv); JSValue js_actor_removetimer(JSContext *js, JSValue self, int argc, JSValue *argv); +prosperon_rt *js2actor(JSContext *js, JSValue v) +{ + if (!JS_IsObject(v)) + return NULL; + + /* Check if it has __ACTORDATA__ property */ + JSValue actor_data = JS_GetPropertyStr(js, v, "__ACTORDATA__"); + if (JS_IsUndefined(actor_data)) { + JS_FreeValue(js, actor_data); + return NULL; + } + + /* Get the id from __ACTORDATA__ */ + JSValue id_val = JS_GetPropertyStr(js, actor_data, "id"); + JS_FreeValue(js, actor_data); + + if (JS_IsUndefined(id_val)) { + JS_FreeValue(js, id_val); + return NULL; + } + + const char *id = JS_ToCString(js, id_val); + JS_FreeValue(js, id_val); + + if (!id) + return NULL; + + /* Look up the actor by ID */ + prosperon_rt *actor = get_actor((char*)id); + JS_FreeCString(js, id); + + return actor; +} + JSC_CCALL(os_createactor, int margc = JS_ArrayLength(js, argv[0]); diff --git a/source/qjs_actor.h b/source/qjs_actor.h index 80b2650e..0f94a8db 100644 --- a/source/qjs_actor.h +++ b/source/qjs_actor.h @@ -2,7 +2,9 @@ #define QJS_ACTOR_H #include +#include "prosperon.h" JSValue js_actor_use(JSContext *js); +prosperon_rt *js2actor(JSContext *js, JSValue v); #endif \ No newline at end of file diff --git a/source/qjs_sdl.c b/source/qjs_sdl.c index 2f2da6e7..5438df74 100644 --- a/source/qjs_sdl.c +++ b/source/qjs_sdl.c @@ -1,6 +1,9 @@ #include "qjs_sdl.h" #include "jsffi.h" #include "qjs_macros.h" +#include "prosperon.h" +#include "stb_ds.h" +#include "qjs_actor.h" #include @@ -82,12 +85,66 @@ JSC_CCALL(input_mousestate, return m; ) +// watch events +extern char **event_watchers; +extern SDL_Mutex *event_watchers_mutex; + +JSC_CCALL(input_watch, + /* Use js2actor to get the actor from the JS object */ + prosperon_rt *actor = js2actor(js, argv[0]); + if (!actor) + return JS_ThrowTypeError(js, "First argument must be a valid actor object"); + + SDL_LockMutex(event_watchers_mutex); + + /* Check if already watching */ + int already_watching = 0; + for (int i = 0; i < arrlen(event_watchers); i++) { + if (strcmp(event_watchers[i], actor->id) == 0) { + already_watching = 1; + break; + } + } + + if (!already_watching) { + arrput(event_watchers, strdup(actor->id)); + } + + SDL_UnlockMutex(event_watchers_mutex); + + return JS_UNDEFINED; +) + +JSC_CCALL(input_unwatch, + /* Use js2actor to get the actor from the JS object */ + prosperon_rt *actor = js2actor(js, argv[0]); + if (!actor) + return JS_ThrowTypeError(js, "First argument must be a valid actor object"); + + SDL_LockMutex(event_watchers_mutex); + + /* Find and remove from watchers */ + for (int i = 0; i < arrlen(event_watchers); i++) { + if (strcmp(event_watchers[i], actor->id) == 0) { + free(event_watchers[i]); + arrdel(event_watchers, i); + break; + } + } + + SDL_UnlockMutex(event_watchers_mutex); + + return JS_UNDEFINED; +) + static const JSCFunctionListEntry js_input_funcs[] = { MIST_FUNC_DEF(input, mouse_show, 1), MIST_FUNC_DEF(input, mouse_lock, 1), MIST_FUNC_DEF(input, keyname, 1), MIST_FUNC_DEF(input, keymod, 0), MIST_FUNC_DEF(input, mousestate, 0), + MIST_FUNC_DEF(input, watch, 1), + MIST_FUNC_DEF(input, unwatch, 1), }; JSValue js_input_use(JSContext *js) { diff --git a/source/wota.h b/source/wota.h index 429a93e2..98765f61 100644 --- a/source/wota.h +++ b/source/wota.h @@ -64,6 +64,8 @@ typedef struct WotaBuffer { size_t capacity; /* allocated capacity in 64-bit words */ } WotaBuffer; +void *wota_dup_data(struct WotaBuffer *wb); + /* Buffer management */ void wota_buffer_init(WotaBuffer *wb, size_t initial_capacity_in_words); void wota_buffer_free(WotaBuffer *wb); @@ -118,6 +120,13 @@ static inline uint64_t wota_bswap64(uint64_t x) #endif } +void *wota_dup_data(WotaBuffer *wb) +{ + void *copy = malloc(wb->size*8); + memcpy(copy, wb->data, wb->size*8); + return copy; +} + /* ================================================================ Helper: Grow the buffer to fit 'min_add' more 64-bit words ================================================================ */ diff --git a/tests/draw2d.js b/tests/draw2d.js index c794a699..ec0004c1 100644 --- a/tests/draw2d.js +++ b/tests/draw2d.js @@ -2,6 +2,8 @@ var draw2d var graphics var os = use('os'); +var input = use('input') +input.watch($_) // Create SDL video actor var video = use('sdl_video'); @@ -10,6 +12,10 @@ var video_actor = {__ACTORDATA__:{id:video}}; var window_id = null; var renderer_id = null; +$_.receiver(e => { + console.log(json.encode(e)) +}) + // Create window send(video_actor, { kind: "window",