Want to test this in PSP

This commit is contained in:
2026-06-08 11:32:59 -05:00
parent 2ca6780305
commit da3db50ca8
21 changed files with 645 additions and 309 deletions
+7
View File
@@ -1,3 +1,8 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
const platformNames = { const platformNames = {
[System.PLATFORM_LINUX]: 'Linux', [System.PLATFORM_LINUX]: 'Linux',
[System.PLATFORM_KNULLI]: 'Knulli', [System.PLATFORM_KNULLI]: 'Knulli',
@@ -8,6 +13,8 @@ const platformNames = {
Console.print('Platform: ' + (platformNames[System.platform] || 'Unknown')); Console.print('Platform: ' + (platformNames[System.platform] || 'Unknown'));
UIFullboxOver.setColor(Color.BLACK);
requireAsync('testscene.js').then(Scene.set).catch(err => { requireAsync('testscene.js').then(Scene.set).catch(err => {
Console.print('Error loading scene: ' + err); Console.print('Error loading scene: ' + err);
Engine.exit(); Engine.exit();
+4 -2
View File
@@ -18,7 +18,7 @@ scene.init = async function() {
var cam = scene.cam.add(Component.CAMERA); var cam = scene.cam.add(Component.CAMERA);
camPos.localPosition = new Vec3(3, 3, 3); camPos.localPosition = new Vec3(3, 3, 3);
camPos.lookAt(new Vec3(0, 0, 0)); camPos.lookAt(new Vec3(0, 0, 0));
// Floor - large flat slab, no texture needed. // Floor - large flat slab, no texture needed.
scene.floor = Entity.create(); scene.floor = Entity.create();
var floorPos = scene.floor.add(Component.POSITION); var floorPos = scene.floor.add(Component.POSITION);
@@ -27,6 +27,8 @@ scene.init = async function() {
floorR.color = Color.BLUE; floorR.color = Color.BLUE;
// floorPos.localScale = new Vec3(16, 0.2, 16); // floorPos.localScale = new Vec3(16, 0.2, 16);
// floorPos.localPosition = new Vec3(0, -0.1, 0); // floorPos.localPosition = new Vec3(0, -0.1, 0);
await UIFullboxOver.transition(Color.BLACK, Color.TRANSPARENT, 1.0);
}; };
scene.update = function() { scene.update = function() {
@@ -37,4 +39,4 @@ scene.dispose = function() {
Entity.dispose(scene.cam); Entity.dispose(scene.cam);
}; };
module.exports = scene; module.exports = scene;
+1
View File
@@ -7,6 +7,7 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME} target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC PUBLIC
script.c script.c
scriptpromisepend.c
scriptproto.c scriptproto.c
) )
+34 -70
View File
@@ -8,61 +8,35 @@
#include "moduleassetbatch.h" #include "moduleassetbatch.h"
#include "util/string.h" #include "util/string.h"
#define ASSET_BATCH_LOADED_MAX 8 #define ASSET_BATCH_PEND_MAX 8
typedef struct {
assetbatch_t *batch;
jerry_value_t promise;
} assetbatchloadedpend_t;
scriptproto_t MODULE_ASSET_BATCH_PROTO; scriptproto_t MODULE_ASSET_BATCH_PROTO;
static assetbatchloadedpend_t ASSET_BATCH_LOADED[ASSET_BATCH_LOADED_MAX]; static scriptpromisepend_t ASSET_BATCH_PEND[ASSET_BATCH_PEND_MAX];
static uint32_t ASSET_BATCH_LOADED_COUNT = 0; static uint32_t ASSET_BATCH_PEND_COUNT = 0;
static void assetBatchOnLoadedFire(void *params, void *user); void assetBatchOnLoadedFire(void *params, void *user) {
static void assetBatchOnErrorFire(void *params, void *user);
static void assetBatchOnLoadedFire(void *params, void *user) {
assetbatch_t *batch = (assetbatch_t *)user; assetbatch_t *batch = (assetbatch_t *)user;
uint32_t i = 0; jerry_value_t undef = jerry_undefined();
while(i < ASSET_BATCH_LOADED_COUNT) { uint32_t n = scriptPromisePendResolve(
if(ASSET_BATCH_LOADED[i].batch != batch) { i++; continue; } ASSET_BATCH_PEND, &ASSET_BATCH_PEND_COUNT, batch, undef
jerry_value_t undef = jerry_undefined(); );
jerry_value_t r = jerry_promise_resolve( jerry_value_free(undef);
ASSET_BATCH_LOADED[i].promise, undef if(!n) return;
); eventUnsubscribe(&batch->onLoaded, assetBatchOnLoadedFire);
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];
}
}
eventUnsubscribe(&batch->onError, assetBatchOnErrorFire); eventUnsubscribe(&batch->onError, assetBatchOnErrorFire);
} }
static void assetBatchOnErrorFire(void *params, void *user) { void assetBatchOnErrorFire(void *params, void *user) {
assetbatch_t *batch = (assetbatch_t *)user; assetbatch_t *batch = (assetbatch_t *)user;
uint32_t i = 0; jerry_value_t err = jerry_string_sz("Asset batch failed to load");
while(i < ASSET_BATCH_LOADED_COUNT) { uint32_t n = scriptPromisePendReject(
if(ASSET_BATCH_LOADED[i].batch != batch) { i++; continue; } ASSET_BATCH_PEND, &ASSET_BATCH_PEND_COUNT, batch, err
jerry_value_t err = jerry_string_sz("Asset batch failed to load"); );
jerry_value_t r = jerry_promise_reject( jerry_value_free(err);
ASSET_BATCH_LOADED[i].promise, err if(!n) return;
);
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];
}
}
eventUnsubscribe(&batch->onLoaded, assetBatchOnLoadedFire); eventUnsubscribe(&batch->onLoaded, assetBatchOnLoadedFire);
eventUnsubscribe(&batch->onError, assetBatchOnErrorFire);
} }
void moduleAssetBatchFree(void *ptr, jerry_object_native_info_t *info) { void moduleAssetBatchFree(void *ptr, jerry_object_native_info_t *info) {
@@ -217,18 +191,14 @@ moduleBaseFunction(moduleAssetBatchLoaded) {
return promise; return promise;
} }
if(ASSET_BATCH_LOADED_COUNT >= ASSET_BATCH_LOADED_MAX) { if(ASSET_BATCH_PEND_COUNT >= ASSET_BATCH_PEND_MAX) {
jerry_value_free(promise); jerry_value_free(promise);
return moduleBaseThrow("AssetBatch.loaded: too many pending"); return moduleBaseThrow("AssetBatch.loaded: too many pending");
} }
bool_t subscribed = false; if(!scriptPromisePendHas(
for(uint32_t i = 0; i < ASSET_BATCH_LOADED_COUNT; i++) { ASSET_BATCH_PEND, ASSET_BATCH_PEND_COUNT, b->batch
if(ASSET_BATCH_LOADED[i].batch == b->batch) { )) {
subscribed = true; break;
}
}
if(!subscribed) {
eventSubscribe( eventSubscribe(
&b->batch->onLoaded, assetBatchOnLoadedFire, b->batch &b->batch->onLoaded, assetBatchOnLoadedFire, b->batch
); );
@@ -237,10 +207,10 @@ moduleBaseFunction(moduleAssetBatchLoaded) {
); );
} }
ASSET_BATCH_LOADED[ASSET_BATCH_LOADED_COUNT].batch = b->batch; scriptPromisePendAdd(
ASSET_BATCH_LOADED[ASSET_BATCH_LOADED_COUNT].promise = ASSET_BATCH_PEND, &ASSET_BATCH_PEND_COUNT,
jerry_value_copy(promise); ASSET_BATCH_PEND_MAX, b->batch, promise
ASSET_BATCH_LOADED_COUNT++; );
return promise; return promise;
} }
@@ -339,7 +309,7 @@ moduleBaseFunction(moduleAssetBatchToString) {
} }
void moduleAssetBatchInit(void) { void moduleAssetBatchInit(void) {
ASSET_BATCH_LOADED_COUNT = 0; ASSET_BATCH_PEND_COUNT = 0;
scriptProtoInit( scriptProtoInit(
&MODULE_ASSET_BATCH_PROTO, "AssetBatch", &MODULE_ASSET_BATCH_PROTO, "AssetBatch",
sizeof(jsassetbatch_t), moduleAssetBatchCtor sizeof(jsassetbatch_t), moduleAssetBatchCtor
@@ -400,25 +370,19 @@ void moduleAssetBatchInit(void) {
} }
void moduleAssetBatchDispose(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; bool_t seen = false;
for(uint32_t j = 0; j < i; j++) { 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; seen = true; break;
} }
} }
if(!seen) { if(!seen) {
eventUnsubscribe( assetbatch_t *b = (assetbatch_t *)ASSET_BATCH_PEND[i].key;
&ASSET_BATCH_LOADED[i].batch->onLoaded, eventUnsubscribe(&b->onLoaded, assetBatchOnLoadedFire);
assetBatchOnLoadedFire eventUnsubscribe(&b->onError, assetBatchOnErrorFire);
);
eventUnsubscribe(
&ASSET_BATCH_LOADED[i].batch->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); scriptProtoDispose(&MODULE_ASSET_BATCH_PROTO);
} }
@@ -8,6 +8,7 @@
#pragma once #pragma once
#include "script/module/modulebase.h" #include "script/module/modulebase.h"
#include "script/scriptproto.h" #include "script/scriptproto.h"
#include "script/scriptpromisepend.h"
#include "script/module/asset/moduleassetentry.h" #include "script/module/asset/moduleassetentry.h"
#include "asset/assetbatch.h" #include "asset/assetbatch.h"
#include "asset/asset.h" #include "asset/asset.h"
@@ -21,6 +22,24 @@ typedef struct {
assetbatch_t *batch; assetbatch_t *batch;
} jsassetbatch_t; } 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 * GC free callback - disposes and frees the batch when the JS object is
* garbage collected. * garbage collected.
+34 -76
View File
@@ -7,61 +7,35 @@
#include "moduleassetentry.h" #include "moduleassetentry.h"
#define ASSET_ENTRY_LOADED_MAX 16 #define ASSET_ENTRY_PEND_MAX 16
typedef struct {
assetentry_t *entry;
jerry_value_t promise;
} assetentryloadedpend_t;
scriptproto_t MODULE_ASSET_ENTRY_PROTO; scriptproto_t MODULE_ASSET_ENTRY_PROTO;
static assetentryloadedpend_t ASSET_ENTRY_LOADED[ASSET_ENTRY_LOADED_MAX]; static scriptpromisepend_t ASSET_ENTRY_PEND[ASSET_ENTRY_PEND_MAX];
static uint32_t ASSET_ENTRY_LOADED_COUNT = 0; static uint32_t ASSET_ENTRY_PEND_COUNT = 0;
static void assetEntryOnLoadedFire(void *params, void *user); void assetEntryOnLoadedFire(void *params, void *user) {
static void assetEntryOnErrorFire(void *params, void *user);
static void assetEntryOnLoadedFire(void *params, void *user) {
assetentry_t *entry = (assetentry_t *)user; assetentry_t *entry = (assetentry_t *)user;
uint32_t i = 0; jerry_value_t undef = jerry_undefined();
while(i < ASSET_ENTRY_LOADED_COUNT) { uint32_t n = scriptPromisePendResolve(
if(ASSET_ENTRY_LOADED[i].entry != entry) { i++; continue; } ASSET_ENTRY_PEND, &ASSET_ENTRY_PEND_COUNT, entry, undef
jerry_value_t undef = jerry_undefined(); );
jerry_value_t r = jerry_promise_resolve( jerry_value_free(undef);
ASSET_ENTRY_LOADED[i].promise, undef if(!n) return;
); eventUnsubscribe(&entry->onLoaded, assetEntryOnLoadedFire);
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];
}
}
eventUnsubscribe(&entry->onError, assetEntryOnErrorFire); eventUnsubscribe(&entry->onError, assetEntryOnErrorFire);
} }
static void assetEntryOnErrorFire(void *params, void *user) { void assetEntryOnErrorFire(void *params, void *user) {
assetentry_t *entry = (assetentry_t *)user; assetentry_t *entry = (assetentry_t *)user;
uint32_t i = 0; jerry_value_t err = jerry_string_sz("Asset failed to load");
while(i < ASSET_ENTRY_LOADED_COUNT) { uint32_t n = scriptPromisePendReject(
if(ASSET_ENTRY_LOADED[i].entry != entry) { i++; continue; } ASSET_ENTRY_PEND, &ASSET_ENTRY_PEND_COUNT, entry, err
jerry_value_t err = jerry_string_sz("Asset failed to load"); );
jerry_value_t r = jerry_promise_reject( jerry_value_free(err);
ASSET_ENTRY_LOADED[i].promise, err if(!n) return;
);
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];
}
}
eventUnsubscribe(&entry->onLoaded, assetEntryOnLoadedFire); eventUnsubscribe(&entry->onLoaded, assetEntryOnLoadedFire);
eventUnsubscribe(&entry->onError, assetEntryOnErrorFire);
} }
void moduleAssetEntryFree(void *ptr, jerry_object_native_info_t *info) { void moduleAssetEntryFree(void *ptr, jerry_object_native_info_t *info) {
@@ -164,9 +138,7 @@ moduleBaseFunction(moduleAssetEntryLoaded) {
if(!e || !e->entry) { if(!e || !e->entry) {
return moduleBaseThrow("AssetEntry.loaded: invalid entry"); return moduleBaseThrow("AssetEntry.loaded: invalid entry");
} }
jerry_value_t promise = jerry_promise(); jerry_value_t promise = jerry_promise();
if(e->entry->state == ASSET_ENTRY_STATE_LOADED) { if(e->entry->state == ASSET_ENTRY_STATE_LOADED) {
jerry_value_t undef = jerry_undefined(); jerry_value_t undef = jerry_undefined();
jerry_value_t r = jerry_promise_resolve(promise, undef); jerry_value_t r = jerry_promise_resolve(promise, undef);
@@ -174,7 +146,6 @@ moduleBaseFunction(moduleAssetEntryLoaded) {
jerry_value_free(r); jerry_value_free(r);
return promise; return promise;
} }
if(e->entry->state == ASSET_ENTRY_STATE_ERROR) { if(e->entry->state == ASSET_ENTRY_STATE_ERROR) {
jerry_value_t err = jerry_string_sz("Asset failed to load"); jerry_value_t err = jerry_string_sz("Asset failed to load");
jerry_value_t r = jerry_promise_reject(promise, err); jerry_value_t r = jerry_promise_reject(promise, err);
@@ -182,19 +153,13 @@ moduleBaseFunction(moduleAssetEntryLoaded) {
jerry_value_free(r); jerry_value_free(r);
return promise; return promise;
} }
if(ASSET_ENTRY_PEND_COUNT >= ASSET_ENTRY_PEND_MAX) {
if(ASSET_ENTRY_LOADED_COUNT >= ASSET_ENTRY_LOADED_MAX) {
jerry_value_free(promise); jerry_value_free(promise);
return moduleBaseThrow("AssetEntry.loaded: too many pending"); return moduleBaseThrow("AssetEntry.loaded: too many pending");
} }
if(!scriptPromisePendHas(
bool_t subscribed = false; ASSET_ENTRY_PEND, ASSET_ENTRY_PEND_COUNT, e->entry
for(uint32_t i = 0; i < ASSET_ENTRY_LOADED_COUNT; i++) { )) {
if(ASSET_ENTRY_LOADED[i].entry == e->entry) {
subscribed = true; break;
}
}
if(!subscribed) {
eventSubscribe( eventSubscribe(
&e->entry->onLoaded, assetEntryOnLoadedFire, e->entry &e->entry->onLoaded, assetEntryOnLoadedFire, e->entry
); );
@@ -202,11 +167,10 @@ moduleBaseFunction(moduleAssetEntryLoaded) {
&e->entry->onError, assetEntryOnErrorFire, e->entry &e->entry->onError, assetEntryOnErrorFire, e->entry
); );
} }
scriptPromisePendAdd(
ASSET_ENTRY_LOADED[ASSET_ENTRY_LOADED_COUNT].entry = e->entry; ASSET_ENTRY_PEND, &ASSET_ENTRY_PEND_COUNT,
ASSET_ENTRY_LOADED[ASSET_ENTRY_LOADED_COUNT].promise = ASSET_ENTRY_PEND_MAX, e->entry, promise
jerry_value_copy(promise); );
ASSET_ENTRY_LOADED_COUNT++;
return promise; return promise;
} }
@@ -219,7 +183,7 @@ moduleBaseFunction(moduleAssetEntryToString) {
} }
void moduleAssetEntryInit(void) { void moduleAssetEntryInit(void) {
ASSET_ENTRY_LOADED_COUNT = 0; ASSET_ENTRY_PEND_COUNT = 0;
scriptProtoInit( scriptProtoInit(
&MODULE_ASSET_ENTRY_PROTO, "AssetEntry", &MODULE_ASSET_ENTRY_PROTO, "AssetEntry",
sizeof(jsassetentry_t), moduleAssetEntryCtor sizeof(jsassetentry_t), moduleAssetEntryCtor
@@ -287,25 +251,19 @@ void moduleAssetEntryInit(void) {
} }
void moduleAssetEntryDispose(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; bool_t seen = false;
for(uint32_t j = 0; j < i; j++) { 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; seen = true; break;
} }
} }
if(!seen) { if(!seen) {
eventUnsubscribe( assetentry_t *e = (assetentry_t *)ASSET_ENTRY_PEND[i].key;
&ASSET_ENTRY_LOADED[i].entry->onLoaded, eventUnsubscribe(&e->onLoaded, assetEntryOnLoadedFire);
assetEntryOnLoadedFire eventUnsubscribe(&e->onError, assetEntryOnErrorFire);
);
eventUnsubscribe(
&ASSET_ENTRY_LOADED[i].entry->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); scriptProtoDispose(&MODULE_ASSET_ENTRY_PROTO);
} }
@@ -8,6 +8,7 @@
#pragma once #pragma once
#include "script/module/modulebase.h" #include "script/module/modulebase.h"
#include "script/scriptproto.h" #include "script/scriptproto.h"
#include "script/scriptpromisepend.h"
#include "script/module/display/moduletexture.h" #include "script/module/display/moduletexture.h"
#include "script/module/event/moduleevent.h" #include "script/module/event/moduleevent.h"
#include "asset/asset.h" #include "asset/asset.h"
@@ -22,6 +23,24 @@ typedef struct {
assetentry_t *entry; assetentry_t *entry;
} jsassetentry_t; } 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 * GC free callback - releases the asset lock when the AssetEntry JS object
* is garbage collected. * is garbage collected.
+28 -51
View File
@@ -11,14 +11,9 @@
#define MODULE_EVENT_PENDING_MAX 32 #define MODULE_EVENT_PENDING_MAX 32
typedef struct {
event_t *event;
jerry_value_t promise;
} moduleeventpending_t;
scriptproto_t MODULE_EVENT_PROTO; 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; static uint32_t MODULE_EVENT_PENDING_COUNT = 0;
void moduleEventTrampoline0(void *params, void *user) { void moduleEventTrampoline0(void *params, void *user) {
@@ -67,29 +62,6 @@ void moduleEventFree(void *ptr, jerry_object_native_info_t *info) {
memoryFree(ptr); 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) { moduleBaseFunction(moduleEventOn) {
moduleBaseRequireArgs(1); moduleBaseRequireArgs(1);
jsevent_t *ev = (jsevent_t *)scriptProtoGetValue( jsevent_t *ev = (jsevent_t *)scriptProtoGetValue(
@@ -145,31 +117,24 @@ moduleBaseFunction(moduleEventWait) {
if(!ev || !ev->event) { if(!ev || !ev->event) {
return moduleBaseThrow("Event.wait: invalid event"); return moduleBaseThrow("Event.wait: invalid event");
} }
if(MODULE_EVENT_PENDING_COUNT >= MODULE_EVENT_PENDING_MAX) { if(MODULE_EVENT_PENDING_COUNT >= MODULE_EVENT_PENDING_MAX) {
return moduleBaseThrow("Event.wait: too many pending awaits"); return moduleBaseThrow("Event.wait: too many pending awaits");
} }
if(!scriptPromisePendHas(
bool_t subscribed = false; MODULE_EVENT_PENDING, MODULE_EVENT_PENDING_COUNT, ev->event
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(ev->event->count >= ev->event->size) { if(ev->event->count >= ev->event->size) {
return moduleBaseThrow( return moduleBaseThrow(
"Event.wait: event subscriber capacity exceeded" "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(); jerry_value_t promise = jerry_promise();
MODULE_EVENT_PENDING[MODULE_EVENT_PENDING_COUNT].event = ev->event; scriptPromisePendAdd(
MODULE_EVENT_PENDING[MODULE_EVENT_PENDING_COUNT].promise = MODULE_EVENT_PENDING, &MODULE_EVENT_PENDING_COUNT,
jerry_value_copy(promise); MODULE_EVENT_PENDING_MAX, ev->event, promise
MODULE_EVENT_PENDING_COUNT++; );
return promise; return promise;
} }
@@ -203,6 +168,17 @@ jerry_value_t moduleEventGetOrCreate(
return ev; 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) { void moduleEventInit(void) {
MODULE_EVENT_PENDING_COUNT = 0; MODULE_EVENT_PENDING_COUNT = 0;
scriptProtoInit( scriptProtoInit(
@@ -217,19 +193,20 @@ void moduleEventInit(void) {
void moduleEventDispose(void) { void moduleEventDispose(void) {
for(uint32_t i = 0; i < MODULE_EVENT_PENDING_COUNT; i++) { 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++) { for(uint32_t j = 0; j < i; j++) {
if(MODULE_EVENT_PENDING[j].event == MODULE_EVENT_PENDING[i].event) { if(MODULE_EVENT_PENDING[j].key == MODULE_EVENT_PENDING[i].key) {
alreadyUnsub = true; break; seen = true; break;
} }
} }
if(!alreadyUnsub) { if(!seen) {
eventUnsubscribe( 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); scriptProtoDispose(&MODULE_EVENT_PROTO);
} }
@@ -8,6 +8,7 @@
#pragma once #pragma once
#include "script/module/modulebase.h" #include "script/module/modulebase.h"
#include "script/scriptproto.h" #include "script/scriptproto.h"
#include "script/scriptpromisepend.h"
#include "event/event.h" #include "event/event.h"
/** Maximum number of on() subscriber slots per Event. */ /** Maximum number of on() subscriber slots per Event. */
@@ -27,6 +28,15 @@ typedef struct {
jerry_value_t fns[MODULE_EVENT_MAX_SLOTS]; jerry_value_t fns[MODULE_EVENT_MAX_SLOTS];
} jsevent_t; } 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 * GC free callback - unsubscribes all on() slots and releases JS function
* refs when the Event object is garbage collected. * refs when the Event object is garbage collected.
+3
View File
@@ -28,6 +28,7 @@
#include "script/module/scene/modulescene.h" #include "script/module/scene/modulescene.h"
#include "script/module/story/modulestory.h" #include "script/module/story/modulestory.h"
#include "script/module/system/modulesystem.h" #include "script/module/system/modulesystem.h"
#include "script/module/ui/modulefullbox.h"
#include "script/module/ui/moduletextbox.h" #include "script/module/ui/moduletextbox.h"
@@ -55,6 +56,7 @@ void moduleListInit(void) {
moduleSceneInit(); moduleSceneInit();
moduleStoryInit(); moduleStoryInit();
moduleSystemInit(); moduleSystemInit();
moduleFullboxInit();
moduleTextboxInit(); moduleTextboxInit();
} }
@@ -64,6 +66,7 @@ void moduleListUpdate(void) {
} }
void moduleListDispose(void) { void moduleListDispose(void) {
moduleFullboxDispose();
moduleTextboxDispose(); moduleTextboxDispose();
moduleSystemDispose(); moduleSystemDispose();
moduleOverworldDispose(); moduleOverworldDispose();
+54 -96
View File
@@ -6,18 +6,18 @@
*/ */
#include "modulerequire.h" #include "modulerequire.h"
#include "script/scriptpromisepend.h"
#include "util/memory.h" #include "util/memory.h"
#include "util/string.h" #include "util/string.h"
#include "assert/assert.h" #include "assert/assert.h"
modulerequireasyncpending_t MODULE_REQUIRE_ASYNC_PENDING[ #define MODULE_REQUIRE_ASYNC_MAX 16
MODULE_REQUIRE_ASYNC_MAX
]; static scriptpromisepend_t MODULE_REQUIRE_ASYNC_PEND[MODULE_REQUIRE_ASYNC_MAX];
uint32_t MODULE_REQUIRE_ASYNC_PENDING_COUNT = 0; static uint32_t MODULE_REQUIRE_ASYNC_PEND_COUNT = 0;
void moduleRequireAsyncOnLoaded(void *params, void *user) { void moduleRequireAsyncOnLoaded(void *params, void *user) {
assetentry_t *entry = (assetentry_t *)params; assetentry_t *entry = (assetentry_t *)user;
eventUnsubscribe(&entry->onLoaded, moduleRequireAsyncOnLoaded); eventUnsubscribe(&entry->onLoaded, moduleRequireAsyncOnLoaded);
eventUnsubscribe(&entry->onError, moduleRequireAsyncOnError); eventUnsubscribe(&entry->onError, moduleRequireAsyncOnError);
@@ -26,66 +26,26 @@ void moduleRequireAsyncOnLoaded(void *params, void *user) {
jerry_undefined() : jerry_undefined() :
jerry_value_copy(entry->data.script.exports) jerry_value_copy(entry->data.script.exports)
); );
uint32_t n = scriptPromisePendResolve(
uint32_t i = 0; MODULE_REQUIRE_ASYNC_PEND, &MODULE_REQUIRE_ASYNC_PEND_COUNT,
while(i < MODULE_REQUIRE_ASYNC_PENDING_COUNT) { entry, exports
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]
);
}
}
jerry_value_free(exports); jerry_value_free(exports);
for(uint32_t i = 0; i < n; i++) assetUnlockEntry(entry);
} }
void moduleRequireAsyncOnError(void *params, void *user) { void moduleRequireAsyncOnError(void *params, void *user) {
assetentry_t *entry = (assetentry_t *)params; assetentry_t *entry = (assetentry_t *)user;
eventUnsubscribe(&entry->onLoaded, moduleRequireAsyncOnLoaded); eventUnsubscribe(&entry->onLoaded, moduleRequireAsyncOnLoaded);
eventUnsubscribe(&entry->onError, moduleRequireAsyncOnError); eventUnsubscribe(&entry->onError, moduleRequireAsyncOnError);
jerry_value_t errStr = jerry_string_sz("Module load failed"); jerry_value_t errStr = jerry_string_sz("Module load failed");
uint32_t n = scriptPromisePendReject(
uint32_t i = 0; MODULE_REQUIRE_ASYNC_PEND, &MODULE_REQUIRE_ASYNC_PEND_COUNT,
while(i < MODULE_REQUIRE_ASYNC_PENDING_COUNT) { entry, errStr
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]
);
}
}
jerry_value_free(errStr); jerry_value_free(errStr);
for(uint32_t i = 0; i < n; i++) assetUnlockEntry(entry);
} }
jerry_value_t moduleRequireFunc( jerry_value_t moduleRequireFunc(
@@ -108,7 +68,9 @@ jerry_value_t moduleRequireFunc(
assetloaderinput_t input; assetloaderinput_t input;
input.script.nothing = NULL; 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); errorret_t err = assetRequireLoaded(entry);
if(errorIsNotOk(err)) { if(errorIsNotOk(err)) {
@@ -137,10 +99,12 @@ jerry_value_t moduleRequireAsyncFunc(
if(argc < 1 || !jerry_value_is_string(args[0])) { if(argc < 1 || !jerry_value_is_string(args[0])) {
return moduleBaseThrow("requireAsync expects a filename string."); 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."); 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."); return moduleBaseThrow("Too many pending requireAsync calls.");
} }
@@ -149,11 +113,12 @@ jerry_value_t moduleRequireAsyncFunc(
assetloaderinput_t input; assetloaderinput_t input;
input.script.nothing = NULL; 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(); jerry_value_t promise = jerry_promise();
// Already loaded - resolve immediately.
if(entry->state == ASSET_ENTRY_STATE_LOADED) { if(entry->state == ASSET_ENTRY_STATE_LOADED) {
jerry_value_t exports = ( jerry_value_t exports = (
jerry_value_is_undefined(entry->data.script.exports) ? jerry_value_is_undefined(entry->data.script.exports) ?
@@ -167,7 +132,6 @@ jerry_value_t moduleRequireAsyncFunc(
return promise; return promise;
} }
// Already errored - reject immediately.
if(entry->state == ASSET_ENTRY_STATE_ERROR) { if(entry->state == ASSET_ENTRY_STATE_ERROR) {
assetUnlockEntry(entry); assetUnlockEntry(entry);
jerry_value_t errStr = jerry_string_sz("Module load failed"); jerry_value_t errStr = jerry_string_sz("Module load failed");
@@ -177,62 +141,56 @@ jerry_value_t moduleRequireAsyncFunc(
return promise; return promise;
} }
// Subscribe to entry events only if this is the first pending await for it. if(!scriptPromisePendHas(
bool_t subscribed = false; MODULE_REQUIRE_ASYNC_PEND, MODULE_REQUIRE_ASYNC_PEND_COUNT, entry
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(entry->onLoaded.count >= entry->onLoaded.size) { if(entry->onLoaded.count >= entry->onLoaded.size) {
assetUnlockEntry(entry); assetUnlockEntry(entry);
jerry_value_free(promise); 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) { if(entry->onError.count >= entry->onError.size) {
assetUnlockEntry(entry); assetUnlockEntry(entry);
jerry_value_free(promise); 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->onLoaded, moduleRequireAsyncOnLoaded, entry);
eventSubscribe(&entry->onError, moduleRequireAsyncOnError, NULL); eventSubscribe(&entry->onError, moduleRequireAsyncOnError, entry);
} }
MODULE_REQUIRE_ASYNC_PENDING[MODULE_REQUIRE_ASYNC_PENDING_COUNT].entry = ( scriptPromisePendAdd(
entry 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; return promise;
} }
void moduleRequireInit(void) { void moduleRequireInit(void) {
MODULE_REQUIRE_ASYNC_PENDING_COUNT = 0; MODULE_REQUIRE_ASYNC_PEND_COUNT = 0;
moduleBaseDefineGlobalMethod("require", moduleRequireFunc); moduleBaseDefineGlobalMethod("require", moduleRequireFunc);
moduleBaseDefineGlobalMethod("requireAsync", moduleRequireAsyncFunc); moduleBaseDefineGlobalMethod("requireAsync", moduleRequireAsyncFunc);
} }
void moduleRequireDispose(void) { void moduleRequireDispose(void) {
for(uint32_t i = 0; i < MODULE_REQUIRE_ASYNC_PENDING_COUNT; i++) { for(uint32_t i = 0; i < MODULE_REQUIRE_ASYNC_PEND_COUNT; i++) {
assetentry_t *entry = MODULE_REQUIRE_ASYNC_PENDING[i].entry; assetentry_t *e = (assetentry_t *)MODULE_REQUIRE_ASYNC_PEND[i].key;
bool_t alreadyUnsub = false; bool_t seen = false;
for(uint32_t j = 0; j < i; j++) { for(uint32_t j = 0; j < i; j++) {
if(MODULE_REQUIRE_ASYNC_PENDING[j].entry == entry) { if(MODULE_REQUIRE_ASYNC_PEND[j].key == e) {
alreadyUnsub = true; seen = true; break;
break;
} }
} }
if(!alreadyUnsub) { if(!seen) {
eventUnsubscribe(&entry->onLoaded, moduleRequireAsyncOnLoaded); eventUnsubscribe(&e->onLoaded, moduleRequireAsyncOnLoaded);
eventUnsubscribe(&entry->onError, moduleRequireAsyncOnError); eventUnsubscribe(&e->onError, moduleRequireAsyncOnError);
} }
assetUnlockEntry(entry); assetUnlockEntry(e);
jerry_value_free(MODULE_REQUIRE_ASYNC_PENDING[i].promise);
} }
MODULE_REQUIRE_ASYNC_PENDING_COUNT = 0; scriptPromisePendFreeAll(
MODULE_REQUIRE_ASYNC_PEND, &MODULE_REQUIRE_ASYNC_PEND_COUNT
);
} }
@@ -9,19 +9,6 @@
#include "script/module/modulebase.h" #include "script/module/modulebase.h"
#include "asset/asset.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. * Success loaded callback for async module loading.
* *
+1
View File
@@ -5,5 +5,6 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME} target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC PUBLIC
modulefullbox.c
moduletextbox.c moduletextbox.c
) )
+150
View File
@@ -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);
}
+54
View File
@@ -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);
+83
View File
@@ -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;
}
+98
View File
@@ -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 <jerryscript.h>
/**
* 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
);
+2
View File
@@ -20,6 +20,8 @@ ui_t UI;
errorret_t uiInit(void) { errorret_t uiInit(void) {
memoryZero(&UI, sizeof(ui_t)); memoryZero(&UI, sizeof(ui_t));
uiFullboxInit(&UI_FULLBOX_UNDER);
uiFullboxInit(&UI_FULLBOX_OVER);
uiLoadingInit(); uiLoadingInit();
uielement_t *element = &UI_ELEMENTS[0]; uielement_t *element = &UI_ELEMENTS[0];
+4 -1
View File
@@ -20,14 +20,17 @@ uielement_t UI_ELEMENTS[] = {
// { .type = UI_ELEMENT_TYPE_SCRIPT, .script = { .script = "ui/test.js" } }, // { .type = UI_ELEMENT_TYPE_SCRIPT, .script = { .script = "ui/test.js" } },
{ .type = UI_ELEMENT_TYPE_NATIVE, .draw = consoleDraw },
{ .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiTextboxDraw }, { .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiTextboxDraw },
// Fullbox over: above absolutely everything. // Fullbox over: above absolutely everything.
{ .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiFullboxOverDraw }, { .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiFullboxOverDraw },
// These render above the fullbox overlay. // 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 = uiFPSDraw },
{ .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiLoadingDraw }, { .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiLoadingDraw },
{ .type = UI_ELEMENT_TYPE_NULL }, { .type = UI_ELEMENT_TYPE_NULL },
}; };
+1
View File
@@ -51,6 +51,7 @@
/// <reference path="./save/save.d.ts" /> /// <reference path="./save/save.d.ts" />
// ui // ui
/// <reference path="./ui/fullbox.d.ts" />
/// <reference path="./ui/textbox.d.ts" /> /// <reference path="./ui/textbox.d.ts" />
// engine systems // engine systems
+39
View File
@@ -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<void>;
/**
* 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<void>;
}
/** 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;