213 lines
6.2 KiB
C
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);
|
|
}
|