Files
dusk/src/dusk/script/module/event/moduleevent.c
T
2026-06-08 11:32:59 -05:00

213 lines
6.2 KiB
C

/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "moduleevent.h"
#include "util/memory.h"
#include "assert/assert.h"
#define MODULE_EVENT_PENDING_MAX 32
scriptproto_t MODULE_EVENT_PROTO;
static scriptpromisepend_t MODULE_EVENT_PENDING[MODULE_EVENT_PENDING_MAX];
static uint32_t MODULE_EVENT_PENDING_COUNT = 0;
void moduleEventTrampoline0(void *params, void *user) {
jerry_value_t fn = (jerry_value_t)(uintptr_t)user;
jerry_value_t ret = jerry_call(fn, jerry_undefined(), NULL, 0);
jerry_value_free(ret);
}
void moduleEventTrampoline1(void *params, void *user) {
jerry_value_t fn = (jerry_value_t)(uintptr_t)user;
jerry_value_t ret = jerry_call(fn, jerry_undefined(), NULL, 0);
jerry_value_free(ret);
}
void moduleEventTrampoline2(void *params, void *user) {
jerry_value_t fn = (jerry_value_t)(uintptr_t)user;
jerry_value_t ret = jerry_call(fn, jerry_undefined(), NULL, 0);
jerry_value_free(ret);
}
void moduleEventTrampoline3(void *params, void *user) {
jerry_value_t fn = (jerry_value_t)(uintptr_t)user;
jerry_value_t ret = jerry_call(fn, jerry_undefined(), NULL, 0);
jerry_value_free(ret);
}
eventcallback_t MODULE_EVENT_TRAMPOLINES[MODULE_EVENT_MAX_SLOTS] = {
moduleEventTrampoline0,
moduleEventTrampoline1,
moduleEventTrampoline2,
moduleEventTrampoline3,
};
void moduleEventFree(void *ptr, jerry_object_native_info_t *info) {
jsevent_t *ev = (jsevent_t *)ptr;
if(ev) {
for(uint32_t i = 0; i < MODULE_EVENT_MAX_SLOTS; i++) {
if(jerry_value_is_function(ev->fns[i])) {
if(ev->event) {
eventUnsubscribe(ev->event, MODULE_EVENT_TRAMPOLINES[i]);
}
jerry_value_free(ev->fns[i]);
}
}
}
memoryFree(ptr);
}
moduleBaseFunction(moduleEventOn) {
moduleBaseRequireArgs(1);
jsevent_t *ev = (jsevent_t *)scriptProtoGetValue(
&MODULE_EVENT_PROTO, callInfo->this_value
);
if(!ev || !ev->event) {
return moduleBaseThrow("Event.on: invalid event");
}
if(!jerry_value_is_function(args[0])) {
return moduleBaseThrow("Event.on: expected function");
}
for(uint32_t i = 0; i < MODULE_EVENT_MAX_SLOTS; i++) {
if(!jerry_value_is_function(ev->fns[i])) {
ev->fns[i] = jerry_value_copy(args[0]);
eventSubscribe(
ev->event,
MODULE_EVENT_TRAMPOLINES[i],
(void *)(uintptr_t)ev->fns[i]
);
return jerry_value_copy(callInfo->this_value);
}
}
return moduleBaseThrow("Event.on: no available subscriber slots");
}
moduleBaseFunction(moduleEventOff) {
moduleBaseRequireArgs(1);
jsevent_t *ev = (jsevent_t *)scriptProtoGetValue(
&MODULE_EVENT_PROTO, callInfo->this_value
);
if(!ev || !ev->event) return jerry_value_copy(callInfo->this_value);
for(uint32_t i = 0; i < MODULE_EVENT_MAX_SLOTS; i++) {
if(!jerry_value_is_function(ev->fns[i])) continue;
jerry_value_t eq = jerry_binary_op(
JERRY_BIN_OP_STRICT_EQUAL, ev->fns[i], args[0]
);
bool_t match = jerry_value_is_true(eq);
jerry_value_free(eq);
if(match) {
eventUnsubscribe(ev->event, MODULE_EVENT_TRAMPOLINES[i]);
jerry_value_free(ev->fns[i]);
ev->fns[i] = jerry_undefined();
return jerry_value_copy(callInfo->this_value);
}
}
return jerry_value_copy(callInfo->this_value);
}
moduleBaseFunction(moduleEventWait) {
jsevent_t *ev = (jsevent_t *)scriptProtoGetValue(
&MODULE_EVENT_PROTO, callInfo->this_value
);
if(!ev || !ev->event) {
return moduleBaseThrow("Event.wait: invalid event");
}
if(MODULE_EVENT_PENDING_COUNT >= MODULE_EVENT_PENDING_MAX) {
return moduleBaseThrow("Event.wait: too many pending awaits");
}
if(!scriptPromisePendHas(
MODULE_EVENT_PENDING, MODULE_EVENT_PENDING_COUNT, ev->event
)) {
if(ev->event->count >= ev->event->size) {
return moduleBaseThrow(
"Event.wait: event subscriber capacity exceeded"
);
}
eventSubscribe(ev->event, moduleEventWaitFire, ev->event);
}
jerry_value_t promise = jerry_promise();
scriptPromisePendAdd(
MODULE_EVENT_PENDING, &MODULE_EVENT_PENDING_COUNT,
MODULE_EVENT_PENDING_MAX, ev->event, promise
);
return promise;
}
jerry_value_t moduleEventCreate(event_t *event) {
assertNotNull(event, "moduleEventCreate: event must not be NULL");
jsevent_t ev;
ev.event = event;
for(uint32_t i = 0; i < MODULE_EVENT_MAX_SLOTS; i++) {
ev.fns[i] = jerry_undefined();
}
return scriptProtoCreateValue(&MODULE_EVENT_PROTO, &ev);
}
jerry_value_t moduleEventGetOrCreate(
const jerry_call_info_t *callInfo,
event_t *event,
const char_t *pinKey
) {
jerry_value_t keyStr = jerry_string_sz(pinKey);
jerry_value_t existing = jerry_object_get(
callInfo->this_value, keyStr
);
if(!jerry_value_is_undefined(existing)) {
jerry_value_free(keyStr);
return existing;
}
jerry_value_free(existing);
jerry_value_t ev = moduleEventCreate(event);
jerry_object_set(callInfo->this_value, keyStr, ev);
jerry_value_free(keyStr);
return ev;
}
void moduleEventWaitFire(void *params, void *user) {
event_t *event = (event_t *)user;
jerry_value_t undef = jerry_undefined();
uint32_t n = scriptPromisePendResolve(
MODULE_EVENT_PENDING, &MODULE_EVENT_PENDING_COUNT, event, undef
);
jerry_value_free(undef);
if(!n) return;
eventUnsubscribe(event, moduleEventWaitFire);
}
void moduleEventInit(void) {
MODULE_EVENT_PENDING_COUNT = 0;
scriptProtoInit(
&MODULE_EVENT_PROTO, NULL,
sizeof(jsevent_t), NULL
);
MODULE_EVENT_PROTO.info.free_cb = moduleEventFree;
scriptProtoDefineFunc(&MODULE_EVENT_PROTO, "on", moduleEventOn);
scriptProtoDefineFunc(&MODULE_EVENT_PROTO, "off", moduleEventOff);
scriptProtoDefineFunc(&MODULE_EVENT_PROTO, "wait", moduleEventWait);
}
void moduleEventDispose(void) {
for(uint32_t i = 0; i < MODULE_EVENT_PENDING_COUNT; i++) {
bool_t seen = false;
for(uint32_t j = 0; j < i; j++) {
if(MODULE_EVENT_PENDING[j].key == MODULE_EVENT_PENDING[i].key) {
seen = true; break;
}
}
if(!seen) {
eventUnsubscribe(
(event_t *)MODULE_EVENT_PENDING[i].key, moduleEventWaitFire
);
}
}
scriptPromisePendFreeAll(
MODULE_EVENT_PENDING, &MODULE_EVENT_PENDING_COUNT
);
scriptProtoDispose(&MODULE_EVENT_PROTO);
}