From da3db50ca87bfa92946ed414c65133404d78b2ba Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Mon, 8 Jun 2026 11:32:59 -0500 Subject: [PATCH] Want to test this in PSP --- assets/init.js | 7 + assets/testscene.js | 6 +- src/dusk/script/CMakeLists.txt | 1 + .../script/module/asset/moduleassetbatch.c | 104 ++++-------- .../script/module/asset/moduleassetbatch.h | 19 +++ .../script/module/asset/moduleassetentry.c | 110 ++++--------- .../script/module/asset/moduleassetentry.h | 19 +++ src/dusk/script/module/event/moduleevent.c | 79 ++++----- src/dusk/script/module/event/moduleevent.h | 10 ++ src/dusk/script/module/modulelist.c | 3 + .../script/module/require/modulerequire.c | 150 +++++++----------- .../script/module/require/modulerequire.h | 13 -- src/dusk/script/module/ui/CMakeLists.txt | 1 + src/dusk/script/module/ui/modulefullbox.c | 150 ++++++++++++++++++ src/dusk/script/module/ui/modulefullbox.h | 54 +++++++ src/dusk/script/scriptpromisepend.c | 83 ++++++++++ src/dusk/script/scriptpromisepend.h | 98 ++++++++++++ src/dusk/ui/ui.c | 2 + src/dusk/ui/uielement.c | 5 +- types/index.d.ts | 1 + types/ui/fullbox.d.ts | 39 +++++ 21 files changed, 645 insertions(+), 309 deletions(-) create mode 100644 src/dusk/script/module/ui/modulefullbox.c create mode 100644 src/dusk/script/module/ui/modulefullbox.h create mode 100644 src/dusk/script/scriptpromisepend.c create mode 100644 src/dusk/script/scriptpromisepend.h create mode 100644 types/ui/fullbox.d.ts diff --git a/assets/init.js b/assets/init.js index 16b0aadd..210ad6ff 100644 --- a/assets/init.js +++ b/assets/init.js @@ -1,3 +1,8 @@ +// Copyright (c) 2026 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + const platformNames = { [System.PLATFORM_LINUX]: 'Linux', [System.PLATFORM_KNULLI]: 'Knulli', @@ -8,6 +13,8 @@ const platformNames = { Console.print('Platform: ' + (platformNames[System.platform] || 'Unknown')); +UIFullboxOver.setColor(Color.BLACK); + requireAsync('testscene.js').then(Scene.set).catch(err => { Console.print('Error loading scene: ' + err); Engine.exit(); diff --git a/assets/testscene.js b/assets/testscene.js index 9999e85e..92f7aeca 100644 --- a/assets/testscene.js +++ b/assets/testscene.js @@ -18,7 +18,7 @@ scene.init = async function() { var cam = scene.cam.add(Component.CAMERA); camPos.localPosition = new Vec3(3, 3, 3); camPos.lookAt(new Vec3(0, 0, 0)); - + // Floor - large flat slab, no texture needed. scene.floor = Entity.create(); var floorPos = scene.floor.add(Component.POSITION); @@ -27,6 +27,8 @@ scene.init = async function() { floorR.color = Color.BLUE; // floorPos.localScale = new Vec3(16, 0.2, 16); // floorPos.localPosition = new Vec3(0, -0.1, 0); + + await UIFullboxOver.transition(Color.BLACK, Color.TRANSPARENT, 1.0); }; scene.update = function() { @@ -37,4 +39,4 @@ scene.dispose = function() { Entity.dispose(scene.cam); }; -module.exports = scene; +module.exports = scene; \ No newline at end of file diff --git a/src/dusk/script/CMakeLists.txt b/src/dusk/script/CMakeLists.txt index f9452d50..9f110960 100644 --- a/src/dusk/script/CMakeLists.txt +++ b/src/dusk/script/CMakeLists.txt @@ -7,6 +7,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC script.c + scriptpromisepend.c scriptproto.c ) diff --git a/src/dusk/script/module/asset/moduleassetbatch.c b/src/dusk/script/module/asset/moduleassetbatch.c index 314ec864..ea145c69 100644 --- a/src/dusk/script/module/asset/moduleassetbatch.c +++ b/src/dusk/script/module/asset/moduleassetbatch.c @@ -8,61 +8,35 @@ #include "moduleassetbatch.h" #include "util/string.h" -#define ASSET_BATCH_LOADED_MAX 8 - -typedef struct { - assetbatch_t *batch; - jerry_value_t promise; -} assetbatchloadedpend_t; +#define ASSET_BATCH_PEND_MAX 8 scriptproto_t MODULE_ASSET_BATCH_PROTO; -static assetbatchloadedpend_t ASSET_BATCH_LOADED[ASSET_BATCH_LOADED_MAX]; -static uint32_t ASSET_BATCH_LOADED_COUNT = 0; +static scriptpromisepend_t ASSET_BATCH_PEND[ASSET_BATCH_PEND_MAX]; +static uint32_t ASSET_BATCH_PEND_COUNT = 0; -static void assetBatchOnLoadedFire(void *params, void *user); -static void assetBatchOnErrorFire(void *params, void *user); - -static void assetBatchOnLoadedFire(void *params, void *user) { +void assetBatchOnLoadedFire(void *params, void *user) { assetbatch_t *batch = (assetbatch_t *)user; - uint32_t i = 0; - while(i < ASSET_BATCH_LOADED_COUNT) { - if(ASSET_BATCH_LOADED[i].batch != batch) { i++; continue; } - jerry_value_t undef = jerry_undefined(); - jerry_value_t r = jerry_promise_resolve( - ASSET_BATCH_LOADED[i].promise, undef - ); - jerry_value_free(undef); - jerry_value_free(r); - jerry_value_free(ASSET_BATCH_LOADED[i].promise); - ASSET_BATCH_LOADED_COUNT--; - if(i < ASSET_BATCH_LOADED_COUNT) { - ASSET_BATCH_LOADED[i] = - ASSET_BATCH_LOADED[ASSET_BATCH_LOADED_COUNT]; - } - } + jerry_value_t undef = jerry_undefined(); + uint32_t n = scriptPromisePendResolve( + ASSET_BATCH_PEND, &ASSET_BATCH_PEND_COUNT, batch, undef + ); + jerry_value_free(undef); + if(!n) return; + eventUnsubscribe(&batch->onLoaded, assetBatchOnLoadedFire); eventUnsubscribe(&batch->onError, assetBatchOnErrorFire); } -static void assetBatchOnErrorFire(void *params, void *user) { +void assetBatchOnErrorFire(void *params, void *user) { assetbatch_t *batch = (assetbatch_t *)user; - uint32_t i = 0; - while(i < ASSET_BATCH_LOADED_COUNT) { - if(ASSET_BATCH_LOADED[i].batch != batch) { i++; continue; } - jerry_value_t err = jerry_string_sz("Asset batch failed to load"); - jerry_value_t r = jerry_promise_reject( - ASSET_BATCH_LOADED[i].promise, err - ); - jerry_value_free(err); - jerry_value_free(r); - jerry_value_free(ASSET_BATCH_LOADED[i].promise); - ASSET_BATCH_LOADED_COUNT--; - if(i < ASSET_BATCH_LOADED_COUNT) { - ASSET_BATCH_LOADED[i] = - ASSET_BATCH_LOADED[ASSET_BATCH_LOADED_COUNT]; - } - } + jerry_value_t err = jerry_string_sz("Asset batch failed to load"); + uint32_t n = scriptPromisePendReject( + ASSET_BATCH_PEND, &ASSET_BATCH_PEND_COUNT, batch, err + ); + jerry_value_free(err); + if(!n) return; eventUnsubscribe(&batch->onLoaded, assetBatchOnLoadedFire); + eventUnsubscribe(&batch->onError, assetBatchOnErrorFire); } void moduleAssetBatchFree(void *ptr, jerry_object_native_info_t *info) { @@ -217,18 +191,14 @@ moduleBaseFunction(moduleAssetBatchLoaded) { return promise; } - if(ASSET_BATCH_LOADED_COUNT >= ASSET_BATCH_LOADED_MAX) { + if(ASSET_BATCH_PEND_COUNT >= ASSET_BATCH_PEND_MAX) { jerry_value_free(promise); return moduleBaseThrow("AssetBatch.loaded: too many pending"); } - bool_t subscribed = false; - for(uint32_t i = 0; i < ASSET_BATCH_LOADED_COUNT; i++) { - if(ASSET_BATCH_LOADED[i].batch == b->batch) { - subscribed = true; break; - } - } - if(!subscribed) { + if(!scriptPromisePendHas( + ASSET_BATCH_PEND, ASSET_BATCH_PEND_COUNT, b->batch + )) { eventSubscribe( &b->batch->onLoaded, assetBatchOnLoadedFire, b->batch ); @@ -237,10 +207,10 @@ moduleBaseFunction(moduleAssetBatchLoaded) { ); } - ASSET_BATCH_LOADED[ASSET_BATCH_LOADED_COUNT].batch = b->batch; - ASSET_BATCH_LOADED[ASSET_BATCH_LOADED_COUNT].promise = - jerry_value_copy(promise); - ASSET_BATCH_LOADED_COUNT++; + scriptPromisePendAdd( + ASSET_BATCH_PEND, &ASSET_BATCH_PEND_COUNT, + ASSET_BATCH_PEND_MAX, b->batch, promise + ); return promise; } @@ -339,7 +309,7 @@ moduleBaseFunction(moduleAssetBatchToString) { } void moduleAssetBatchInit(void) { - ASSET_BATCH_LOADED_COUNT = 0; + ASSET_BATCH_PEND_COUNT = 0; scriptProtoInit( &MODULE_ASSET_BATCH_PROTO, "AssetBatch", sizeof(jsassetbatch_t), moduleAssetBatchCtor @@ -400,25 +370,19 @@ void moduleAssetBatchInit(void) { } void moduleAssetBatchDispose(void) { - for(uint32_t i = 0; i < ASSET_BATCH_LOADED_COUNT; i++) { + for(uint32_t i = 0; i < ASSET_BATCH_PEND_COUNT; i++) { bool_t seen = false; for(uint32_t j = 0; j < i; j++) { - if(ASSET_BATCH_LOADED[j].batch == ASSET_BATCH_LOADED[i].batch) { + if(ASSET_BATCH_PEND[j].key == ASSET_BATCH_PEND[i].key) { seen = true; break; } } if(!seen) { - eventUnsubscribe( - &ASSET_BATCH_LOADED[i].batch->onLoaded, - assetBatchOnLoadedFire - ); - eventUnsubscribe( - &ASSET_BATCH_LOADED[i].batch->onError, - assetBatchOnErrorFire - ); + assetbatch_t *b = (assetbatch_t *)ASSET_BATCH_PEND[i].key; + eventUnsubscribe(&b->onLoaded, assetBatchOnLoadedFire); + eventUnsubscribe(&b->onError, assetBatchOnErrorFire); } - jerry_value_free(ASSET_BATCH_LOADED[i].promise); } - ASSET_BATCH_LOADED_COUNT = 0; + scriptPromisePendFreeAll(ASSET_BATCH_PEND, &ASSET_BATCH_PEND_COUNT); scriptProtoDispose(&MODULE_ASSET_BATCH_PROTO); } diff --git a/src/dusk/script/module/asset/moduleassetbatch.h b/src/dusk/script/module/asset/moduleassetbatch.h index c7d57ac2..4067594c 100644 --- a/src/dusk/script/module/asset/moduleassetbatch.h +++ b/src/dusk/script/module/asset/moduleassetbatch.h @@ -8,6 +8,7 @@ #pragma once #include "script/module/modulebase.h" #include "script/scriptproto.h" +#include "script/scriptpromisepend.h" #include "script/module/asset/moduleassetentry.h" #include "asset/assetbatch.h" #include "asset/asset.h" @@ -21,6 +22,24 @@ typedef struct { assetbatch_t *batch; } jsassetbatch_t; +/** + * Resolves all pending loaded() promises for this batch, then + * unsubscribes from both onLoaded and onError. + * + * @param params Unused. + * @param user The assetbatch_t pointer that finished loading. + */ +void assetBatchOnLoadedFire(void *params, void *user); + +/** + * Rejects all pending loaded() promises for this batch, then + * unsubscribes from both onLoaded and onError. + * + * @param params Unused. + * @param user The assetbatch_t pointer that errored. + */ +void assetBatchOnErrorFire(void *params, void *user); + /** * GC free callback - disposes and frees the batch when the JS object is * garbage collected. diff --git a/src/dusk/script/module/asset/moduleassetentry.c b/src/dusk/script/module/asset/moduleassetentry.c index 09b09564..0b228378 100644 --- a/src/dusk/script/module/asset/moduleassetentry.c +++ b/src/dusk/script/module/asset/moduleassetentry.c @@ -7,61 +7,35 @@ #include "moduleassetentry.h" -#define ASSET_ENTRY_LOADED_MAX 16 - -typedef struct { - assetentry_t *entry; - jerry_value_t promise; -} assetentryloadedpend_t; +#define ASSET_ENTRY_PEND_MAX 16 scriptproto_t MODULE_ASSET_ENTRY_PROTO; -static assetentryloadedpend_t ASSET_ENTRY_LOADED[ASSET_ENTRY_LOADED_MAX]; -static uint32_t ASSET_ENTRY_LOADED_COUNT = 0; +static scriptpromisepend_t ASSET_ENTRY_PEND[ASSET_ENTRY_PEND_MAX]; +static uint32_t ASSET_ENTRY_PEND_COUNT = 0; -static void assetEntryOnLoadedFire(void *params, void *user); -static void assetEntryOnErrorFire(void *params, void *user); - -static void assetEntryOnLoadedFire(void *params, void *user) { +void assetEntryOnLoadedFire(void *params, void *user) { assetentry_t *entry = (assetentry_t *)user; - uint32_t i = 0; - while(i < ASSET_ENTRY_LOADED_COUNT) { - if(ASSET_ENTRY_LOADED[i].entry != entry) { i++; continue; } - jerry_value_t undef = jerry_undefined(); - jerry_value_t r = jerry_promise_resolve( - ASSET_ENTRY_LOADED[i].promise, undef - ); - jerry_value_free(undef); - jerry_value_free(r); - jerry_value_free(ASSET_ENTRY_LOADED[i].promise); - ASSET_ENTRY_LOADED_COUNT--; - if(i < ASSET_ENTRY_LOADED_COUNT) { - ASSET_ENTRY_LOADED[i] = - ASSET_ENTRY_LOADED[ASSET_ENTRY_LOADED_COUNT]; - } - } + jerry_value_t undef = jerry_undefined(); + uint32_t n = scriptPromisePendResolve( + ASSET_ENTRY_PEND, &ASSET_ENTRY_PEND_COUNT, entry, undef + ); + jerry_value_free(undef); + if(!n) return; + eventUnsubscribe(&entry->onLoaded, assetEntryOnLoadedFire); eventUnsubscribe(&entry->onError, assetEntryOnErrorFire); } -static void assetEntryOnErrorFire(void *params, void *user) { +void assetEntryOnErrorFire(void *params, void *user) { assetentry_t *entry = (assetentry_t *)user; - uint32_t i = 0; - while(i < ASSET_ENTRY_LOADED_COUNT) { - if(ASSET_ENTRY_LOADED[i].entry != entry) { i++; continue; } - jerry_value_t err = jerry_string_sz("Asset failed to load"); - jerry_value_t r = jerry_promise_reject( - ASSET_ENTRY_LOADED[i].promise, err - ); - jerry_value_free(err); - jerry_value_free(r); - jerry_value_free(ASSET_ENTRY_LOADED[i].promise); - ASSET_ENTRY_LOADED_COUNT--; - if(i < ASSET_ENTRY_LOADED_COUNT) { - ASSET_ENTRY_LOADED[i] = - ASSET_ENTRY_LOADED[ASSET_ENTRY_LOADED_COUNT]; - } - } + jerry_value_t err = jerry_string_sz("Asset failed to load"); + uint32_t n = scriptPromisePendReject( + ASSET_ENTRY_PEND, &ASSET_ENTRY_PEND_COUNT, entry, err + ); + jerry_value_free(err); + if(!n) return; eventUnsubscribe(&entry->onLoaded, assetEntryOnLoadedFire); + eventUnsubscribe(&entry->onError, assetEntryOnErrorFire); } void moduleAssetEntryFree(void *ptr, jerry_object_native_info_t *info) { @@ -164,9 +138,7 @@ moduleBaseFunction(moduleAssetEntryLoaded) { if(!e || !e->entry) { return moduleBaseThrow("AssetEntry.loaded: invalid entry"); } - jerry_value_t promise = jerry_promise(); - if(e->entry->state == ASSET_ENTRY_STATE_LOADED) { jerry_value_t undef = jerry_undefined(); jerry_value_t r = jerry_promise_resolve(promise, undef); @@ -174,7 +146,6 @@ moduleBaseFunction(moduleAssetEntryLoaded) { jerry_value_free(r); return promise; } - if(e->entry->state == ASSET_ENTRY_STATE_ERROR) { jerry_value_t err = jerry_string_sz("Asset failed to load"); jerry_value_t r = jerry_promise_reject(promise, err); @@ -182,19 +153,13 @@ moduleBaseFunction(moduleAssetEntryLoaded) { jerry_value_free(r); return promise; } - - if(ASSET_ENTRY_LOADED_COUNT >= ASSET_ENTRY_LOADED_MAX) { + if(ASSET_ENTRY_PEND_COUNT >= ASSET_ENTRY_PEND_MAX) { jerry_value_free(promise); return moduleBaseThrow("AssetEntry.loaded: too many pending"); } - - bool_t subscribed = false; - for(uint32_t i = 0; i < ASSET_ENTRY_LOADED_COUNT; i++) { - if(ASSET_ENTRY_LOADED[i].entry == e->entry) { - subscribed = true; break; - } - } - if(!subscribed) { + if(!scriptPromisePendHas( + ASSET_ENTRY_PEND, ASSET_ENTRY_PEND_COUNT, e->entry + )) { eventSubscribe( &e->entry->onLoaded, assetEntryOnLoadedFire, e->entry ); @@ -202,11 +167,10 @@ moduleBaseFunction(moduleAssetEntryLoaded) { &e->entry->onError, assetEntryOnErrorFire, e->entry ); } - - ASSET_ENTRY_LOADED[ASSET_ENTRY_LOADED_COUNT].entry = e->entry; - ASSET_ENTRY_LOADED[ASSET_ENTRY_LOADED_COUNT].promise = - jerry_value_copy(promise); - ASSET_ENTRY_LOADED_COUNT++; + scriptPromisePendAdd( + ASSET_ENTRY_PEND, &ASSET_ENTRY_PEND_COUNT, + ASSET_ENTRY_PEND_MAX, e->entry, promise + ); return promise; } @@ -219,7 +183,7 @@ moduleBaseFunction(moduleAssetEntryToString) { } void moduleAssetEntryInit(void) { - ASSET_ENTRY_LOADED_COUNT = 0; + ASSET_ENTRY_PEND_COUNT = 0; scriptProtoInit( &MODULE_ASSET_ENTRY_PROTO, "AssetEntry", sizeof(jsassetentry_t), moduleAssetEntryCtor @@ -287,25 +251,19 @@ void moduleAssetEntryInit(void) { } void moduleAssetEntryDispose(void) { - for(uint32_t i = 0; i < ASSET_ENTRY_LOADED_COUNT; i++) { + for(uint32_t i = 0; i < ASSET_ENTRY_PEND_COUNT; i++) { bool_t seen = false; for(uint32_t j = 0; j < i; j++) { - if(ASSET_ENTRY_LOADED[j].entry == ASSET_ENTRY_LOADED[i].entry) { + if(ASSET_ENTRY_PEND[j].key == ASSET_ENTRY_PEND[i].key) { seen = true; break; } } if(!seen) { - eventUnsubscribe( - &ASSET_ENTRY_LOADED[i].entry->onLoaded, - assetEntryOnLoadedFire - ); - eventUnsubscribe( - &ASSET_ENTRY_LOADED[i].entry->onError, - assetEntryOnErrorFire - ); + assetentry_t *e = (assetentry_t *)ASSET_ENTRY_PEND[i].key; + eventUnsubscribe(&e->onLoaded, assetEntryOnLoadedFire); + eventUnsubscribe(&e->onError, assetEntryOnErrorFire); } - jerry_value_free(ASSET_ENTRY_LOADED[i].promise); } - ASSET_ENTRY_LOADED_COUNT = 0; + scriptPromisePendFreeAll(ASSET_ENTRY_PEND, &ASSET_ENTRY_PEND_COUNT); scriptProtoDispose(&MODULE_ASSET_ENTRY_PROTO); } diff --git a/src/dusk/script/module/asset/moduleassetentry.h b/src/dusk/script/module/asset/moduleassetentry.h index 3407a2fe..096fae60 100644 --- a/src/dusk/script/module/asset/moduleassetentry.h +++ b/src/dusk/script/module/asset/moduleassetentry.h @@ -8,6 +8,7 @@ #pragma once #include "script/module/modulebase.h" #include "script/scriptproto.h" +#include "script/scriptpromisepend.h" #include "script/module/display/moduletexture.h" #include "script/module/event/moduleevent.h" #include "asset/asset.h" @@ -22,6 +23,24 @@ typedef struct { assetentry_t *entry; } jsassetentry_t; +/** + * Resolves all pending loaded() promises for this entry, then + * unsubscribes from both onLoaded and onError. + * + * @param params Unused. + * @param user The assetentry_t pointer that finished loading. + */ +void assetEntryOnLoadedFire(void *params, void *user); + +/** + * Rejects all pending loaded() promises for this entry, then + * unsubscribes from both onLoaded and onError. + * + * @param params Unused. + * @param user The assetentry_t pointer that errored. + */ +void assetEntryOnErrorFire(void *params, void *user); + /** * GC free callback - releases the asset lock when the AssetEntry JS object * is garbage collected. diff --git a/src/dusk/script/module/event/moduleevent.c b/src/dusk/script/module/event/moduleevent.c index a1783e43..058bdfe4 100644 --- a/src/dusk/script/module/event/moduleevent.c +++ b/src/dusk/script/module/event/moduleevent.c @@ -11,14 +11,9 @@ #define MODULE_EVENT_PENDING_MAX 32 -typedef struct { - event_t *event; - jerry_value_t promise; -} moduleeventpending_t; - scriptproto_t MODULE_EVENT_PROTO; -static moduleeventpending_t MODULE_EVENT_PENDING[MODULE_EVENT_PENDING_MAX]; +static scriptpromisepend_t MODULE_EVENT_PENDING[MODULE_EVENT_PENDING_MAX]; static uint32_t MODULE_EVENT_PENDING_COUNT = 0; void moduleEventTrampoline0(void *params, void *user) { @@ -67,29 +62,6 @@ void moduleEventFree(void *ptr, jerry_object_native_info_t *info) { memoryFree(ptr); } -static void moduleEventWaitFire(void *params, void *user) { - event_t *event = (event_t *)user; - - uint32_t i = 0; - while(i < MODULE_EVENT_PENDING_COUNT) { - if(MODULE_EVENT_PENDING[i].event != event) { i++; continue; } - jerry_value_t undef = jerry_undefined(); - jerry_value_t r = jerry_promise_resolve( - MODULE_EVENT_PENDING[i].promise, undef - ); - jerry_value_free(undef); - jerry_value_free(r); - jerry_value_free(MODULE_EVENT_PENDING[i].promise); - MODULE_EVENT_PENDING_COUNT--; - if(i < MODULE_EVENT_PENDING_COUNT) { - MODULE_EVENT_PENDING[i] = - MODULE_EVENT_PENDING[MODULE_EVENT_PENDING_COUNT]; - } - } - - eventUnsubscribe(event, moduleEventWaitFire); -} - moduleBaseFunction(moduleEventOn) { moduleBaseRequireArgs(1); jsevent_t *ev = (jsevent_t *)scriptProtoGetValue( @@ -145,31 +117,24 @@ moduleBaseFunction(moduleEventWait) { 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"); } - - bool_t subscribed = false; - for(uint32_t i = 0; i < MODULE_EVENT_PENDING_COUNT; i++) { - if(MODULE_EVENT_PENDING[i].event == ev->event) { - subscribed = true; break; - } - } - if(!subscribed) { + 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, (void *)ev->event); + eventSubscribe(ev->event, moduleEventWaitFire, ev->event); } - jerry_value_t promise = jerry_promise(); - MODULE_EVENT_PENDING[MODULE_EVENT_PENDING_COUNT].event = ev->event; - MODULE_EVENT_PENDING[MODULE_EVENT_PENDING_COUNT].promise = - jerry_value_copy(promise); - MODULE_EVENT_PENDING_COUNT++; + scriptPromisePendAdd( + MODULE_EVENT_PENDING, &MODULE_EVENT_PENDING_COUNT, + MODULE_EVENT_PENDING_MAX, ev->event, promise + ); return promise; } @@ -203,6 +168,17 @@ jerry_value_t moduleEventGetOrCreate( 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( @@ -217,19 +193,20 @@ void moduleEventInit(void) { void moduleEventDispose(void) { for(uint32_t i = 0; i < MODULE_EVENT_PENDING_COUNT; i++) { - bool_t alreadyUnsub = false; + bool_t seen = false; for(uint32_t j = 0; j < i; j++) { - if(MODULE_EVENT_PENDING[j].event == MODULE_EVENT_PENDING[i].event) { - alreadyUnsub = true; break; + if(MODULE_EVENT_PENDING[j].key == MODULE_EVENT_PENDING[i].key) { + seen = true; break; } } - if(!alreadyUnsub) { + if(!seen) { eventUnsubscribe( - MODULE_EVENT_PENDING[i].event, moduleEventWaitFire + (event_t *)MODULE_EVENT_PENDING[i].key, moduleEventWaitFire ); } - jerry_value_free(MODULE_EVENT_PENDING[i].promise); } - MODULE_EVENT_PENDING_COUNT = 0; + scriptPromisePendFreeAll( + MODULE_EVENT_PENDING, &MODULE_EVENT_PENDING_COUNT + ); scriptProtoDispose(&MODULE_EVENT_PROTO); } diff --git a/src/dusk/script/module/event/moduleevent.h b/src/dusk/script/module/event/moduleevent.h index 31b3169a..c8012f6c 100644 --- a/src/dusk/script/module/event/moduleevent.h +++ b/src/dusk/script/module/event/moduleevent.h @@ -8,6 +8,7 @@ #pragma once #include "script/module/modulebase.h" #include "script/scriptproto.h" +#include "script/scriptpromisepend.h" #include "event/event.h" /** Maximum number of on() subscriber slots per Event. */ @@ -27,6 +28,15 @@ typedef struct { jerry_value_t fns[MODULE_EVENT_MAX_SLOTS]; } jsevent_t; +/** + * Event trampoline used by wait(). Resolves all pending wait() promises + * for the fired event and unsubscribes itself. + * + * @param params Unused. + * @param user The event_t pointer that fired. + */ +void moduleEventWaitFire(void *params, void *user); + /** * GC free callback - unsubscribes all on() slots and releases JS function * refs when the Event object is garbage collected. diff --git a/src/dusk/script/module/modulelist.c b/src/dusk/script/module/modulelist.c index cb0da584..1a686172 100644 --- a/src/dusk/script/module/modulelist.c +++ b/src/dusk/script/module/modulelist.c @@ -28,6 +28,7 @@ #include "script/module/scene/modulescene.h" #include "script/module/story/modulestory.h" #include "script/module/system/modulesystem.h" +#include "script/module/ui/modulefullbox.h" #include "script/module/ui/moduletextbox.h" @@ -55,6 +56,7 @@ void moduleListInit(void) { moduleSceneInit(); moduleStoryInit(); moduleSystemInit(); + moduleFullboxInit(); moduleTextboxInit(); } @@ -64,6 +66,7 @@ void moduleListUpdate(void) { } void moduleListDispose(void) { + moduleFullboxDispose(); moduleTextboxDispose(); moduleSystemDispose(); moduleOverworldDispose(); diff --git a/src/dusk/script/module/require/modulerequire.c b/src/dusk/script/module/require/modulerequire.c index 5652f8cf..36956cd5 100644 --- a/src/dusk/script/module/require/modulerequire.c +++ b/src/dusk/script/module/require/modulerequire.c @@ -6,18 +6,18 @@ */ #include "modulerequire.h" +#include "script/scriptpromisepend.h" #include "util/memory.h" #include "util/string.h" #include "assert/assert.h" -modulerequireasyncpending_t MODULE_REQUIRE_ASYNC_PENDING[ - MODULE_REQUIRE_ASYNC_MAX -]; -uint32_t MODULE_REQUIRE_ASYNC_PENDING_COUNT = 0; +#define MODULE_REQUIRE_ASYNC_MAX 16 + +static scriptpromisepend_t MODULE_REQUIRE_ASYNC_PEND[MODULE_REQUIRE_ASYNC_MAX]; +static uint32_t MODULE_REQUIRE_ASYNC_PEND_COUNT = 0; void moduleRequireAsyncOnLoaded(void *params, void *user) { - assetentry_t *entry = (assetentry_t *)params; - + assetentry_t *entry = (assetentry_t *)user; eventUnsubscribe(&entry->onLoaded, moduleRequireAsyncOnLoaded); eventUnsubscribe(&entry->onError, moduleRequireAsyncOnError); @@ -26,66 +26,26 @@ void moduleRequireAsyncOnLoaded(void *params, void *user) { jerry_undefined() : jerry_value_copy(entry->data.script.exports) ); - - uint32_t i = 0; - while(i < MODULE_REQUIRE_ASYNC_PENDING_COUNT) { - if(MODULE_REQUIRE_ASYNC_PENDING[i].entry != entry) { - i++; - continue; - } - - assetUnlockEntry(entry); - - jerry_value_t copy = jerry_value_copy(exports); - jerry_value_t ret = jerry_promise_resolve( - MODULE_REQUIRE_ASYNC_PENDING[i].promise, copy - ); - jerry_value_free(ret); - jerry_value_free(copy); - jerry_value_free(MODULE_REQUIRE_ASYNC_PENDING[i].promise); - - MODULE_REQUIRE_ASYNC_PENDING_COUNT--; - - if(i < MODULE_REQUIRE_ASYNC_PENDING_COUNT) { - MODULE_REQUIRE_ASYNC_PENDING[i] = ( - MODULE_REQUIRE_ASYNC_PENDING[MODULE_REQUIRE_ASYNC_PENDING_COUNT] - ); - } - } - + uint32_t n = scriptPromisePendResolve( + MODULE_REQUIRE_ASYNC_PEND, &MODULE_REQUIRE_ASYNC_PEND_COUNT, + entry, exports + ); jerry_value_free(exports); + for(uint32_t i = 0; i < n; i++) assetUnlockEntry(entry); } void moduleRequireAsyncOnError(void *params, void *user) { - assetentry_t *entry = (assetentry_t *)params; - + assetentry_t *entry = (assetentry_t *)user; eventUnsubscribe(&entry->onLoaded, moduleRequireAsyncOnLoaded); eventUnsubscribe(&entry->onError, moduleRequireAsyncOnError); jerry_value_t errStr = jerry_string_sz("Module load failed"); - - uint32_t i = 0; - while(i < MODULE_REQUIRE_ASYNC_PENDING_COUNT) { - if(MODULE_REQUIRE_ASYNC_PENDING[i].entry != entry) { i++; continue; } - assetUnlockEntry(entry); - - jerry_value_t copy = jerry_value_copy(errStr); - jerry_value_t ret = jerry_promise_reject( - MODULE_REQUIRE_ASYNC_PENDING[i].promise, copy - ); - jerry_value_free(ret); - jerry_value_free(copy); - jerry_value_free(MODULE_REQUIRE_ASYNC_PENDING[i].promise); - MODULE_REQUIRE_ASYNC_PENDING_COUNT--; - - if(i < MODULE_REQUIRE_ASYNC_PENDING_COUNT) { - MODULE_REQUIRE_ASYNC_PENDING[i] = ( - MODULE_REQUIRE_ASYNC_PENDING[MODULE_REQUIRE_ASYNC_PENDING_COUNT] - ); - } - } - + uint32_t n = scriptPromisePendReject( + MODULE_REQUIRE_ASYNC_PEND, &MODULE_REQUIRE_ASYNC_PEND_COUNT, + entry, errStr + ); jerry_value_free(errStr); + for(uint32_t i = 0; i < n; i++) assetUnlockEntry(entry); } jerry_value_t moduleRequireFunc( @@ -108,7 +68,9 @@ jerry_value_t moduleRequireFunc( assetloaderinput_t input; input.script.nothing = NULL; - assetentry_t *entry = assetLock(moduleName, ASSET_LOADER_TYPE_SCRIPT, &input); + assetentry_t *entry = assetLock( + moduleName, ASSET_LOADER_TYPE_SCRIPT, &input + ); errorret_t err = assetRequireLoaded(entry); if(errorIsNotOk(err)) { @@ -137,10 +99,12 @@ jerry_value_t moduleRequireAsyncFunc( if(argc < 1 || !jerry_value_is_string(args[0])) { return moduleBaseThrow("requireAsync expects a filename string."); } - if(jerry_string_size(args[0], JERRY_ENCODING_UTF8) >= ASSET_FILE_NAME_MAX) { + if( + jerry_string_size(args[0], JERRY_ENCODING_UTF8) >= ASSET_FILE_NAME_MAX + ) { return moduleBaseThrow("Module name too long."); } - if(MODULE_REQUIRE_ASYNC_PENDING_COUNT >= MODULE_REQUIRE_ASYNC_MAX) { + if(MODULE_REQUIRE_ASYNC_PEND_COUNT >= MODULE_REQUIRE_ASYNC_MAX) { return moduleBaseThrow("Too many pending requireAsync calls."); } @@ -149,11 +113,12 @@ jerry_value_t moduleRequireAsyncFunc( assetloaderinput_t input; input.script.nothing = NULL; - assetentry_t *entry = assetLock(moduleName, ASSET_LOADER_TYPE_SCRIPT, &input); + assetentry_t *entry = assetLock( + moduleName, ASSET_LOADER_TYPE_SCRIPT, &input + ); jerry_value_t promise = jerry_promise(); - // Already loaded - resolve immediately. if(entry->state == ASSET_ENTRY_STATE_LOADED) { jerry_value_t exports = ( jerry_value_is_undefined(entry->data.script.exports) ? @@ -167,7 +132,6 @@ jerry_value_t moduleRequireAsyncFunc( return promise; } - // Already errored - reject immediately. if(entry->state == ASSET_ENTRY_STATE_ERROR) { assetUnlockEntry(entry); jerry_value_t errStr = jerry_string_sz("Module load failed"); @@ -177,62 +141,56 @@ jerry_value_t moduleRequireAsyncFunc( return promise; } - // Subscribe to entry events only if this is the first pending await for it. - bool_t subscribed = false; - for(uint32_t i = 0; i < MODULE_REQUIRE_ASYNC_PENDING_COUNT; i++) { - if(MODULE_REQUIRE_ASYNC_PENDING[i].entry == entry) { - subscribed = true; - break; - } - } - - if(!subscribed) { + if(!scriptPromisePendHas( + MODULE_REQUIRE_ASYNC_PEND, MODULE_REQUIRE_ASYNC_PEND_COUNT, entry + )) { if(entry->onLoaded.count >= entry->onLoaded.size) { assetUnlockEntry(entry); jerry_value_free(promise); - return moduleBaseThrow("requireAsync: onLoaded event capacity exceeded."); + return moduleBaseThrow( + "requireAsync: onLoaded event capacity exceeded." + ); } if(entry->onError.count >= entry->onError.size) { assetUnlockEntry(entry); jerry_value_free(promise); - return moduleBaseThrow("requireAsync: onError event capacity exceeded."); + return moduleBaseThrow( + "requireAsync: onError event capacity exceeded." + ); } - eventSubscribe(&entry->onLoaded, moduleRequireAsyncOnLoaded, NULL); - eventSubscribe(&entry->onError, moduleRequireAsyncOnError, NULL); + eventSubscribe(&entry->onLoaded, moduleRequireAsyncOnLoaded, entry); + eventSubscribe(&entry->onError, moduleRequireAsyncOnError, entry); } - MODULE_REQUIRE_ASYNC_PENDING[MODULE_REQUIRE_ASYNC_PENDING_COUNT].entry = ( - entry + scriptPromisePendAdd( + MODULE_REQUIRE_ASYNC_PEND, &MODULE_REQUIRE_ASYNC_PEND_COUNT, + MODULE_REQUIRE_ASYNC_MAX, entry, promise ); - MODULE_REQUIRE_ASYNC_PENDING[MODULE_REQUIRE_ASYNC_PENDING_COUNT].promise = ( - jerry_value_copy(promise) - ); - MODULE_REQUIRE_ASYNC_PENDING_COUNT++; return promise; } void moduleRequireInit(void) { - MODULE_REQUIRE_ASYNC_PENDING_COUNT = 0; + MODULE_REQUIRE_ASYNC_PEND_COUNT = 0; moduleBaseDefineGlobalMethod("require", moduleRequireFunc); moduleBaseDefineGlobalMethod("requireAsync", moduleRequireAsyncFunc); } void moduleRequireDispose(void) { - for(uint32_t i = 0; i < MODULE_REQUIRE_ASYNC_PENDING_COUNT; i++) { - assetentry_t *entry = MODULE_REQUIRE_ASYNC_PENDING[i].entry; - bool_t alreadyUnsub = false; + for(uint32_t i = 0; i < MODULE_REQUIRE_ASYNC_PEND_COUNT; i++) { + assetentry_t *e = (assetentry_t *)MODULE_REQUIRE_ASYNC_PEND[i].key; + bool_t seen = false; for(uint32_t j = 0; j < i; j++) { - if(MODULE_REQUIRE_ASYNC_PENDING[j].entry == entry) { - alreadyUnsub = true; - break; + if(MODULE_REQUIRE_ASYNC_PEND[j].key == e) { + seen = true; break; } } - if(!alreadyUnsub) { - eventUnsubscribe(&entry->onLoaded, moduleRequireAsyncOnLoaded); - eventUnsubscribe(&entry->onError, moduleRequireAsyncOnError); + if(!seen) { + eventUnsubscribe(&e->onLoaded, moduleRequireAsyncOnLoaded); + eventUnsubscribe(&e->onError, moduleRequireAsyncOnError); } - assetUnlockEntry(entry); - jerry_value_free(MODULE_REQUIRE_ASYNC_PENDING[i].promise); + assetUnlockEntry(e); } - MODULE_REQUIRE_ASYNC_PENDING_COUNT = 0; + scriptPromisePendFreeAll( + MODULE_REQUIRE_ASYNC_PEND, &MODULE_REQUIRE_ASYNC_PEND_COUNT + ); } diff --git a/src/dusk/script/module/require/modulerequire.h b/src/dusk/script/module/require/modulerequire.h index ef3445d4..4bf5a576 100644 --- a/src/dusk/script/module/require/modulerequire.h +++ b/src/dusk/script/module/require/modulerequire.h @@ -9,19 +9,6 @@ #include "script/module/modulebase.h" #include "asset/asset.h" -#define MODULE_REQUIRE_ASYNC_MAX 16 - -typedef struct { - assetentry_t *entry; - jerry_value_t promise; -} modulerequireasyncpending_t; - -extern modulerequireasyncpending_t MODULE_REQUIRE_ASYNC_PENDING[ - MODULE_REQUIRE_ASYNC_MAX -]; - -extern uint32_t MODULE_REQUIRE_ASYNC_PENDING_COUNT; - /** * Success loaded callback for async module loading. * diff --git a/src/dusk/script/module/ui/CMakeLists.txt b/src/dusk/script/module/ui/CMakeLists.txt index a22320cb..d2884254 100644 --- a/src/dusk/script/module/ui/CMakeLists.txt +++ b/src/dusk/script/module/ui/CMakeLists.txt @@ -5,5 +5,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC + modulefullbox.c moduletextbox.c ) diff --git a/src/dusk/script/module/ui/modulefullbox.c b/src/dusk/script/module/ui/modulefullbox.c new file mode 100644 index 00000000..85d15bd1 --- /dev/null +++ b/src/dusk/script/module/ui/modulefullbox.c @@ -0,0 +1,150 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "modulefullbox.h" +#include "script/scriptpromisepend.h" +#include "script/module/display/modulecolor.h" +#include "animation/easing.h" + +#define MODULE_FULLBOX_PEND_MAX 8 + +typedef struct { + uifullbox_t *fullbox; +} jsuifullbox_t; + +scriptproto_t MODULE_FULLBOX_PROTO; + +static scriptpromisepend_t MODULE_FULLBOX_PEND[MODULE_FULLBOX_PEND_MAX]; +static uint32_t MODULE_FULLBOX_PEND_COUNT = 0; + +moduleBaseFunction(moduleFullboxSetColor) { + moduleBaseRequireArgs(1); + jsuifullbox_t *d = (jsuifullbox_t *)scriptProtoGetValue( + &MODULE_FULLBOX_PROTO, callInfo->this_value + ); + if(!d || !d->fullbox) return jerry_undefined(); + color_t *c = moduleColorFrom(args[0]); + if(!c) return jerry_undefined(); + moduleFullboxTransitionFire(NULL, d->fullbox); + uiFullboxTransition(d->fullbox, *c, *c, 0.0f, EASING_LINEAR); + jerry_value_t promise = jerry_promise(); + jerry_value_t undef = jerry_undefined(); + jerry_value_t r = jerry_promise_resolve(promise, undef); + jerry_value_free(undef); + jerry_value_free(r); + return promise; +} + +moduleBaseFunction(moduleFullboxTransition) { + moduleBaseRequireArgs(3); + jsuifullbox_t *d = (jsuifullbox_t *)scriptProtoGetValue( + &MODULE_FULLBOX_PROTO, callInfo->this_value + ); + if(!d || !d->fullbox) return jerry_undefined(); + color_t *from = moduleColorFrom(args[0]); + color_t *to = moduleColorFrom(args[1]); + if(!from || !to) return jerry_undefined(); + float_t duration = moduleBaseArgFloat(2); + easingtype_t easing = (easingtype_t)moduleBaseOptInt( + 3, (int32_t)EASING_LINEAR + ); + moduleFullboxTransitionFire(NULL, d->fullbox); + uiFullboxTransition(d->fullbox, *from, *to, duration, easing); + jerry_value_t promise = jerry_promise(); + if(duration <= 0.0f) { + jerry_value_t undef = jerry_undefined(); + jerry_value_t r = jerry_promise_resolve(promise, undef); + jerry_value_free(undef); + jerry_value_free(r); + return promise; + } + if(MODULE_FULLBOX_PEND_COUNT >= MODULE_FULLBOX_PEND_MAX) { + jerry_value_t errStr = jerry_string_sz( + "UIFullbox.transition: too many pending" + ); + jerry_value_t r = jerry_promise_reject(promise, errStr); + jerry_value_free(errStr); + jerry_value_free(r); + return promise; + } + if(!scriptPromisePendHas( + MODULE_FULLBOX_PEND, MODULE_FULLBOX_PEND_COUNT, d->fullbox + )) { + eventSubscribe( + &d->fullbox->onTransitionEnd, + moduleFullboxTransitionFire, + d->fullbox + ); + } + scriptPromisePendAdd( + MODULE_FULLBOX_PEND, &MODULE_FULLBOX_PEND_COUNT, + MODULE_FULLBOX_PEND_MAX, d->fullbox, promise + ); + return promise; +} + +void moduleFullboxTransitionFire(void *params, void *user) { + uifullbox_t *fullbox = (uifullbox_t *)user; + jerry_value_t undef = jerry_undefined(); + uint32_t n = scriptPromisePendResolve( + MODULE_FULLBOX_PEND, &MODULE_FULLBOX_PEND_COUNT, fullbox, undef + ); + jerry_value_free(undef); + if(!n) return; + eventUnsubscribe( + &fullbox->onTransitionEnd, moduleFullboxTransitionFire + ); +} + +void moduleFullboxInit(void) { + MODULE_FULLBOX_PEND_COUNT = 0; + scriptProtoInit( + &MODULE_FULLBOX_PROTO, NULL, sizeof(jsuifullbox_t), NULL + ); + + scriptProtoDefineFunc( + &MODULE_FULLBOX_PROTO, "setColor", moduleFullboxSetColor + ); + scriptProtoDefineFunc( + &MODULE_FULLBOX_PROTO, "transition", moduleFullboxTransition + ); + + jsuifullbox_t over = { .fullbox = &UI_FULLBOX_OVER }; + jerry_value_t overVal = scriptProtoCreateValue( + &MODULE_FULLBOX_PROTO, &over + ); + moduleBaseSetValue("UIFullboxOver", overVal); + jerry_value_free(overVal); + + jsuifullbox_t under = { .fullbox = &UI_FULLBOX_UNDER }; + jerry_value_t underVal = scriptProtoCreateValue( + &MODULE_FULLBOX_PROTO, &under + ); + moduleBaseSetValue("UIFullboxUnder", underVal); + jerry_value_free(underVal); +} + +void moduleFullboxDispose(void) { + for(uint32_t i = 0; i < MODULE_FULLBOX_PEND_COUNT; i++) { + bool_t seen = false; + for(uint32_t j = 0; j < i; j++) { + if(MODULE_FULLBOX_PEND[j].key == MODULE_FULLBOX_PEND[i].key) { + seen = true; break; + } + } + if(!seen) { + uifullbox_t *fb = (uifullbox_t *)MODULE_FULLBOX_PEND[i].key; + eventUnsubscribe( + &fb->onTransitionEnd, moduleFullboxTransitionFire + ); + } + } + scriptPromisePendFreeAll( + MODULE_FULLBOX_PEND, &MODULE_FULLBOX_PEND_COUNT + ); + scriptProtoDispose(&MODULE_FULLBOX_PROTO); +} diff --git a/src/dusk/script/module/ui/modulefullbox.h b/src/dusk/script/module/ui/modulefullbox.h new file mode 100644 index 00000000..dbd320c9 --- /dev/null +++ b/src/dusk/script/module/ui/modulefullbox.h @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/module/modulebase.h" +#include "script/scriptproto.h" +#include "ui/uifullbox.h" + +extern scriptproto_t MODULE_FULLBOX_PROTO; + +/** + * Event trampoline that resolves all pending transition promises for a + * fullbox and unsubscribes from onTransitionEnd. Also called directly + * to flush promises when a new transition interrupts a running one. + * + * @param params Unused (receives the uifullbox_t from eventInvoke). + * @param user The uifullbox_t pointer whose transition completed. + */ +void moduleFullboxTransitionFire(void *params, void *user); + +/** + * setColor(color) - instantly sets the fullbox to a solid color. + * Returns a Promise that resolves immediately. + * + * @param args[0] Color instance. + */ +moduleBaseFunction(moduleFullboxSetColor); + +/** + * transition(from, to, duration, easing?) - animates the fullbox from + * one color to another. Returns a Promise that resolves when the + * transition completes. + * + * @param args[0] Starting Color. + * @param args[1] Ending Color. + * @param args[2] Duration in seconds. + * @param args[3] Optional EASING_* constant (default: EASING_LINEAR). + */ +moduleBaseFunction(moduleFullboxTransition); + +/** + * Initializes the UIFullbox module, registering UIFullboxOver and + * UIFullboxUnder as global singleton objects. + */ +void moduleFullboxInit(void); + +/** + * Disposes the UIFullbox module. + */ +void moduleFullboxDispose(void); diff --git a/src/dusk/script/scriptpromisepend.c b/src/dusk/script/scriptpromisepend.c new file mode 100644 index 00000000..0559ca51 --- /dev/null +++ b/src/dusk/script/scriptpromisepend.c @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "scriptpromisepend.h" +#include "assert/assert.h" + +uint32_t scriptPromisePendResolve( + scriptpromisepend_t *pend, + uint32_t *count, + const void *key, + jerry_value_t value +) { + uint32_t resolved = 0; + uint32_t i = 0; + while(i < *count) { + if(pend[i].key != key) { i++; continue; } + jerry_value_t r = jerry_promise_resolve(pend[i].promise, value); + jerry_value_free(r); + jerry_value_free(pend[i].promise); + (*count)--; + if(i < *count) pend[i] = pend[*count]; + resolved++; + } + return resolved; +} + +uint32_t scriptPromisePendReject( + scriptpromisepend_t *pend, + uint32_t *count, + const void *key, + jerry_value_t error +) { + uint32_t rejected = 0; + uint32_t i = 0; + while(i < *count) { + if(pend[i].key != key) { i++; continue; } + jerry_value_t r = jerry_promise_reject(pend[i].promise, error); + jerry_value_free(r); + jerry_value_free(pend[i].promise); + (*count)--; + if(i < *count) pend[i] = pend[*count]; + rejected++; + } + return rejected; +} + +bool_t scriptPromisePendHas( + const scriptpromisepend_t *pend, + uint32_t count, + const void *key +) { + for(uint32_t i = 0; i < count; i++) { + if(pend[i].key == key) return true; + } + return false; +} + +void scriptPromisePendAdd( + scriptpromisepend_t *pend, + uint32_t *count, + uint32_t max, + const void *key, + jerry_value_t promise +) { + assertTrue(*count < max, "scriptPromisePendAdd: capacity exceeded"); + pend[*count].key = (void *)key; + pend[*count].promise = jerry_value_copy(promise); + (*count)++; +} + +void scriptPromisePendFreeAll( + scriptpromisepend_t *pend, + uint32_t *count +) { + for(uint32_t i = 0; i < *count; i++) { + jerry_value_free(pend[i].promise); + } + *count = 0; +} diff --git a/src/dusk/script/scriptpromisepend.h b/src/dusk/script/scriptpromisepend.h new file mode 100644 index 00000000..c6df046f --- /dev/null +++ b/src/dusk/script/scriptpromisepend.h @@ -0,0 +1,98 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" +#include + +/** + * One pending promise keyed to a pointer. + * Declare a static array of these in each script module that needs to + * wait on a C event before resolving a JS promise. + */ +typedef struct { + void *key; + jerry_value_t promise; +} scriptpromisepend_t; + +/** + * Resolves all entries whose key matches and swap-removes them. + * Returns the number of promises resolved. + * + * @param pend The pending array. + * @param count Current count (updated in place). + * @param key Key to match. + * @param value Value passed to jerry_promise_resolve. + * @return Number of promises resolved. + */ +uint32_t scriptPromisePendResolve( + scriptpromisepend_t *pend, + uint32_t *count, + const void *key, + jerry_value_t value +); + +/** + * Rejects all entries whose key matches and swap-removes them. + * Returns the number of promises rejected. + * + * @param pend The pending array. + * @param count Current count (updated in place). + * @param key Key to match. + * @param error Reason passed to jerry_promise_reject. + * @return Number of promises rejected. + */ +uint32_t scriptPromisePendReject( + scriptpromisepend_t *pend, + uint32_t *count, + const void *key, + jerry_value_t error +); + +/** + * Returns true if any entry has key equal to key. + * + * @param pend The pending array. + * @param count Current count. + * @param key Key to search for. + * @return true if found. + */ +bool_t scriptPromisePendHas( + const scriptpromisepend_t *pend, + uint32_t count, + const void *key +); + +/** + * Appends a new entry. Stores a copy of the promise; the caller keeps + * the original to return to script. Asserts that count < max. + * + * @param pend The pending array. + * @param count Current count (incremented in place). + * @param max Capacity of the array. + * @param key Key to associate with the promise. + * @param promise Promise to copy into the array. + */ +void scriptPromisePendAdd( + scriptpromisepend_t *pend, + uint32_t *count, + uint32_t max, + const void *key, + jerry_value_t promise +); + +/** + * Frees all stored promise references without resolving them, then + * zeros count. Call during module dispose before cleaning up events. + * + * @param pend The pending array. + * @param count Current count (set to 0). + */ +void scriptPromisePendFreeAll( + scriptpromisepend_t *pend, + uint32_t *count +); diff --git a/src/dusk/ui/ui.c b/src/dusk/ui/ui.c index 83416495..6310ee13 100644 --- a/src/dusk/ui/ui.c +++ b/src/dusk/ui/ui.c @@ -20,6 +20,8 @@ ui_t UI; errorret_t uiInit(void) { memoryZero(&UI, sizeof(ui_t)); + uiFullboxInit(&UI_FULLBOX_UNDER); + uiFullboxInit(&UI_FULLBOX_OVER); uiLoadingInit(); uielement_t *element = &UI_ELEMENTS[0]; diff --git a/src/dusk/ui/uielement.c b/src/dusk/ui/uielement.c index fc1266ef..a5c156d6 100644 --- a/src/dusk/ui/uielement.c +++ b/src/dusk/ui/uielement.c @@ -20,14 +20,17 @@ uielement_t UI_ELEMENTS[] = { // { .type = UI_ELEMENT_TYPE_SCRIPT, .script = { .script = "ui/test.js" } }, - { .type = UI_ELEMENT_TYPE_NATIVE, .draw = consoleDraw }, { .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiTextboxDraw }, // Fullbox over: above absolutely everything. { .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiFullboxOverDraw }, + + // These render above the fullbox overlay. + { .type = UI_ELEMENT_TYPE_NATIVE, .draw = consoleDraw }, { .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiFPSDraw }, { .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiLoadingDraw }, + { .type = UI_ELEMENT_TYPE_NULL }, }; diff --git a/types/index.d.ts b/types/index.d.ts index a5bd131f..e2d0edf4 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -51,6 +51,7 @@ /// // ui +/// /// // engine systems diff --git a/types/ui/fullbox.d.ts b/types/ui/fullbox.d.ts new file mode 100644 index 00000000..2082f9d4 --- /dev/null +++ b/types/ui/fullbox.d.ts @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** A full-screen color overlay. */ +interface UIFullboxInstance { + /** + * Sets the overlay to a solid color with no animation. + * Returns a Promise that resolves immediately. + * @param color - The color to apply. + */ + setColor(color: Color): Promise; + + /** + * Animates the overlay from one color to another. + * Returns a Promise that resolves when the transition completes. + * Starting a new transition while one is in progress resolves the + * old Promise immediately before beginning the new one. + * @param from - Starting color. + * @param to - Ending color. + * @param duration - Duration in seconds. + * @param easing - Optional EASING_* constant (default: EASING_LINEAR). + */ + transition( + from: Color, + to: Color, + duration: number, + easing?: number + ): Promise; +} + +/** Full-screen overlay drawn on top of all UI elements. */ +declare var UIFullboxOver: UIFullboxInstance; + +/** Full-screen overlay drawn below all UI elements. */ +declare var UIFullboxUnder: UIFullboxInstance;