From 2b3abbe13bb98379e365fec37763f439490eabf0 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Tue, 2 Jun 2026 16:46:39 -0500 Subject: [PATCH] ABout to try scene and script merger --- assets/init.js | 7 +- assets/testentity.js | 24 -- assets/testscene.js | 48 +++ src/dusk/asset/asset.c | 8 +- src/dusk/asset/assetbatch.c | 71 ++++- src/dusk/asset/assetbatch.h | 26 +- src/dusk/asset/loader/assetentry.c | 24 +- src/dusk/asset/loader/assetentry.h | 52 +++- .../asset/loader/script/assetscriptloader.c | 1 + .../asset/loader/script/assetscriptloader.h | 7 +- src/dusk/engine/engine.c | 1 - src/dusk/scene/CMakeLists.txt | 11 +- src/dusk/scene/initial/CMakeLists.txt | 10 - src/dusk/scene/initial/initialscene.c | 28 -- src/dusk/scene/initial/initialscene.h | 18 -- src/dusk/scene/scene.c | 61 +--- src/dusk/scene/scene.h | 58 +--- src/dusk/scene/scenelist.h | 14 - src/dusk/scene/test/testscene.c | 110 ------- src/dusk/scene/test/testscene.h | 17 - src/dusk/script/module/CMakeLists.txt | 3 + src/dusk/script/module/asset/moduleasset.h | 3 + .../script/module/asset/moduleassetbatch.h | 293 ++++++++++++++++++ .../script/module/asset/moduleassetentry.h | 33 ++ .../script/module/asset/moduleeventproxy.h | 245 +++++++++++++++ src/dusk/script/module/math/modulevec3.h | 20 +- src/dusk/script/module/modulelist.h | 3 + .../module/require}/CMakeLists.txt | 2 +- .../script/module/require/modulerequire.c | 34 ++ .../script/module/require/modulerequire.h | 54 ++++ src/dusk/script/module/scene/modulescene.h | 39 +-- types/asset/assetbatch.d.ts | 86 +++++ types/asset/assetentry.d.ts | 24 ++ types/index.d.ts | 4 + types/require.d.ts | 69 +++++ types/scene/scene.d.ts | 48 ++- 36 files changed, 1137 insertions(+), 419 deletions(-) delete mode 100644 assets/testentity.js create mode 100644 assets/testscene.js delete mode 100644 src/dusk/scene/initial/CMakeLists.txt delete mode 100644 src/dusk/scene/initial/initialscene.c delete mode 100644 src/dusk/scene/initial/initialscene.h delete mode 100644 src/dusk/scene/scenelist.h delete mode 100644 src/dusk/scene/test/testscene.c delete mode 100644 src/dusk/scene/test/testscene.h create mode 100644 src/dusk/script/module/asset/moduleassetbatch.h create mode 100644 src/dusk/script/module/asset/moduleeventproxy.h rename src/dusk/{scene/test => script/module/require}/CMakeLists.txt (90%) create mode 100644 src/dusk/script/module/require/modulerequire.c create mode 100644 src/dusk/script/module/require/modulerequire.h create mode 100644 types/asset/assetbatch.d.ts create mode 100644 types/require.d.ts diff --git a/assets/init.js b/assets/init.js index 52b524ff..3b3e7814 100644 --- a/assets/init.js +++ b/assets/init.js @@ -1,5 +1,3 @@ -Console.print('This is called from JavaScript'); - const platformNames = { [System.PLATFORM_LINUX]: 'Linux', [System.PLATFORM_KNULLI]: 'Knulli', @@ -8,5 +6,6 @@ const platformNames = { [System.PLATFORM_WII]: 'Wii', }; -const platformName = platformNames[System.platform] || 'Unknown'; -Console.print('Platform: ' + platformName); +Console.print('Platform: ' + (platformNames[System.platform] || 'Unknown')); + +// Scene.set('testscene.js'); diff --git a/assets/testentity.js b/assets/testentity.js deleted file mode 100644 index b0508048..00000000 --- a/assets/testentity.js +++ /dev/null @@ -1,24 +0,0 @@ -// Load rosa. -Console.print('Asset time'); -const entry = Asset.lock('test.png', Asset.TYPE_TEXTURE, Texture.FORMAT_RGBA); -Asset.requireLoaded(entry); -Console.print('Asset loaded'); - -// Camera at (3,3,3) looking at origin -const cam = Entity.create(); -const camPos = cam.add(Component.POSITION); -cam.add(Component.CAMERA); -camPos.localPosition = new Vec3(3, 3, 3); -camPos.lookAt(new Vec3(0, 0, 0)); - -// Test entity at origin -const testEntity = Entity.create(); -const testPos = testEntity.add(Component.POSITION); - -/** @type {RenderableSpritebatch} */ -const testRenderable = testEntity.add(Component.RENDERABLE); -testRenderable.texture = entry.texture; -testRenderable.sprites = [ - [0, 0, 1, 1, 0, 1, 1, 0] -]; -testPos.localPosition = new Vec3(0, 0, 0); diff --git a/assets/testscene.js b/assets/testscene.js new file mode 100644 index 00000000..875547fd --- /dev/null +++ b/assets/testscene.js @@ -0,0 +1,48 @@ +var scene = {}; + +scene.batch = AssetBatch([ + { path: 'test.png', type: Asset.TYPE_TEXTURE, format: Texture.FORMAT_RGBA } +]); + +var cam; +var camPos; +var testEntity; +var testPos; +var testRenderable; +var texEntry; + +scene.load = function() { + return scene.batch; +}; + +scene.init = function() { + texEntry = scene.batch.entry(0); + + // Camera at (3, 3, 3) looking at origin + cam = Entity.create(); + camPos = cam.add(Component.POSITION); + cam.add(Component.CAMERA); + camPos.localPosition = new Vec3(3, 3, 3); + camPos.lookAt(new Vec3(0, 0, 0)); + + // Test entity with textured quad at origin + testEntity = Entity.create(); + testPos = testEntity.add(Component.POSITION); + testRenderable = testEntity.add(Component.RENDERABLE); + + testRenderable.texture = texEntry.texture; + testRenderable.sprites = [ + [0, 0, 1, 1, 0, 1, 1, 0] + ]; + testPos.localPosition = new Vec3(0, 0, 0); +}; + +scene.dispose = function() { + Entity.dispose(cam); + Entity.dispose(testEntity); + + texEntry.unlock(); + scene.batch.unlock(); +}; + +module.exports = scene; diff --git a/src/dusk/asset/asset.c b/src/dusk/asset/asset.c index 424495b8..77c03d02 100644 --- a/src/dusk/asset/asset.c +++ b/src/dusk/asset/asset.c @@ -222,6 +222,8 @@ errorret_t assetUpdate(void) { loading->entry->state == ASSET_ENTRY_STATE_ERROR, "Loader did not set entry state to error on failed load." ); + } else if(loading->entry->state == ASSET_ENTRY_STATE_LOADED) { + eventInvoke(&loading->entry->onLoaded, loading->entry); } loading++; @@ -241,10 +243,14 @@ errorret_t assetUpdate(void) { loading++; break; - case ASSET_ENTRY_STATE_ERROR: + case ASSET_ENTRY_STATE_ERROR: { + assetentry_t *errEntry = loading->entry; + loading->entry = NULL; threadMutexUnlock(&loading->mutex); + eventInvoke(&errEntry->onError, errEntry); errorThrow("Failed to load asset asynchronously."); break; + } default: threadMutexUnlock(&loading->mutex); diff --git a/src/dusk/asset/assetbatch.c b/src/dusk/asset/assetbatch.c index 4bcb646d..1df3ab68 100644 --- a/src/dusk/asset/assetbatch.c +++ b/src/dusk/asset/assetbatch.c @@ -11,6 +11,38 @@ #include "util/memory.h" #include +/* ---- Per-entry event trampolines ----------------------------------------- */ + +static void assetBatchEntryOnLoadedCb(void *params, void *user) { + assetentry_t *entry = (assetentry_t *)params; + assetbatch_t *batch = (assetbatch_t *)user; + + batch->loadedCount++; + eventInvoke(&batch->onEntryLoaded, entry); + + if((uint16_t)(batch->loadedCount + batch->errorCount) >= batch->count) { + if(batch->errorCount == 0) { + eventInvoke(&batch->onLoaded, batch); + } else { + eventInvoke(&batch->onError, batch); + } + } +} + +static void assetBatchEntryOnErrorCb(void *params, void *user) { + assetentry_t *entry = (assetentry_t *)params; + assetbatch_t *batch = (assetbatch_t *)user; + + batch->errorCount++; + eventInvoke(&batch->onEntryError, entry); + + if((uint16_t)(batch->loadedCount + batch->errorCount) >= batch->count) { + eventInvoke(&batch->onError, batch); + } +} + +/* ---- Public API ---------------------------------------------------------- */ + void assetBatchInit( assetbatch_t *batch, const uint16_t count, @@ -24,10 +56,40 @@ void assetBatchInit( memoryZero(batch, sizeof(assetbatch_t)); batch->count = count; + eventInit( + &batch->onLoaded, + batch->onLoadedCallbacks, batch->onLoadedUsers, ASSET_BATCH_EVENT_MAX + ); + eventInit( + &batch->onEntryLoaded, + batch->onEntryLoadedCallbacks, batch->onEntryLoadedUsers, ASSET_BATCH_EVENT_MAX + ); + eventInit( + &batch->onError, + batch->onErrorCallbacks, batch->onErrorUsers, ASSET_BATCH_EVENT_MAX + ); + eventInit( + &batch->onEntryError, + batch->onEntryErrorCallbacks, batch->onEntryErrorUsers, ASSET_BATCH_EVENT_MAX + ); + for(uint16_t i = 0; i < count; i++) { - // Copy input into batch-owned storage so the descriptor need not persist. batch->inputs[i] = descs[i].input; batch->entries[i] = assetLock(descs[i].path, descs[i].type, &batch->inputs[i]); + + if(batch->entries[i]->state == ASSET_ENTRY_STATE_LOADED) { + /* Already loaded (cached) — count it now, no subscription needed. */ + batch->loadedCount++; + } else if(batch->entries[i]->state == ASSET_ENTRY_STATE_ERROR) { + batch->errorCount++; + } else { + eventSubscribe( + &batch->entries[i]->onLoaded, assetBatchEntryOnLoadedCb, batch + ); + eventSubscribe( + &batch->entries[i]->onError, assetBatchEntryOnErrorCb, batch + ); + } } } @@ -88,7 +150,12 @@ errorret_t assetBatchRequireLoaded(assetbatch_t *batch) { void assetBatchDispose(assetbatch_t *batch) { assertNotNull(batch, "Batch cannot be NULL."); for(uint16_t i = 0; i < batch->count; i++) { - assetUnlockEntry(batch->entries[i]); + if(batch->entries[i]) { + /* Unsubscribe while we still hold a lock so the entry is guaranteed live. */ + eventUnsubscribe(&batch->entries[i]->onLoaded, assetBatchEntryOnLoadedCb); + eventUnsubscribe(&batch->entries[i]->onError, assetBatchEntryOnErrorCb); + assetUnlockEntry(batch->entries[i]); + } } memoryZero(batch, sizeof(assetbatch_t)); } diff --git a/src/dusk/asset/assetbatch.h b/src/dusk/asset/assetbatch.h index db71a115..bc241fbb 100644 --- a/src/dusk/asset/assetbatch.h +++ b/src/dusk/asset/assetbatch.h @@ -8,8 +8,10 @@ #pragma once #include "asset/loader/assetentry.h" #include "asset/loader/assetloader.h" +#include "event/event.h" -#define ASSET_BATCH_COUNT_MAX 64 +#define ASSET_BATCH_COUNT_MAX 64 +#define ASSET_BATCH_EVENT_MAX 4 typedef struct { const char_t *path; @@ -21,6 +23,28 @@ typedef struct { assetentry_t *entries[ASSET_BATCH_COUNT_MAX]; assetloaderinput_t inputs[ASSET_BATCH_COUNT_MAX]; uint16_t count; + uint16_t loadedCount; + uint16_t errorCount; + + /** Fires once when every entry has loaded successfully. params = assetbatch_t * */ + event_t onLoaded; + eventcallback_t onLoadedCallbacks[ASSET_BATCH_EVENT_MAX]; + void *onLoadedUsers[ASSET_BATCH_EVENT_MAX]; + + /** Fires each time a single entry finishes loading. params = assetentry_t * */ + event_t onEntryLoaded; + eventcallback_t onEntryLoadedCallbacks[ASSET_BATCH_EVENT_MAX]; + void *onEntryLoadedUsers[ASSET_BATCH_EVENT_MAX]; + + /** Fires once when all entries have finished (any with errors). params = assetbatch_t * */ + event_t onError; + eventcallback_t onErrorCallbacks[ASSET_BATCH_EVENT_MAX]; + void *onErrorUsers[ASSET_BATCH_EVENT_MAX]; + + /** Fires each time a single entry errors. params = assetentry_t * */ + event_t onEntryError; + eventcallback_t onEntryErrorCallbacks[ASSET_BATCH_EVENT_MAX]; + void *onEntryErrorUsers[ASSET_BATCH_EVENT_MAX]; } assetbatch_t; /** diff --git a/src/dusk/asset/loader/assetentry.c b/src/dusk/asset/loader/assetentry.c index 7ceb7b1f..a2dbac8d 100644 --- a/src/dusk/asset/loader/assetentry.c +++ b/src/dusk/asset/loader/assetentry.c @@ -1,6 +1,6 @@ /** * Copyright (c) 2026 Dominic Masters - * + * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ @@ -31,6 +31,22 @@ void assetEntryInit( entry->input = &entry->inputData; } refInit(&entry->refs, entry, NULL, NULL, NULL); + + eventInit( + &entry->onLoaded, + entry->onLoadedCallbacks, entry->onLoadedUsers, + ASSET_ENTRY_EVENT_MAX + ); + eventInit( + &entry->onUnloaded, + entry->onUnloadedCallbacks, entry->onUnloadedUsers, + ASSET_ENTRY_EVENT_MAX + ); + eventInit( + &entry->onError, + entry->onErrorCallbacks, entry->onErrorUsers, + ASSET_ENTRY_EVENT_MAX + ); } void assetEntryLock(assetentry_t *entry) { @@ -67,11 +83,11 @@ void assetEntryStartLoading( errorret_t assetEntryDispose(assetentry_t *entry) { assertNotNull(entry, "Entry cannot be NULL"); - assertTrue(entry->type != ASSET_LOADER_TYPE_NULL, "Invalid loader type."); assertTrue(entry->type < ASSET_LOADER_TYPE_COUNT, "Invalid loader type."); - + + eventInvoke(&entry->onUnloaded, entry); errorChain(ASSET_LOADER_CALLBACKS[entry->type].dispose(entry)); memoryZero(entry, sizeof(assetentry_t)); errorOk(); -} \ No newline at end of file +} diff --git a/src/dusk/asset/loader/assetentry.h b/src/dusk/asset/loader/assetentry.h index 176379f2..ba8e2ed1 100644 --- a/src/dusk/asset/loader/assetentry.h +++ b/src/dusk/asset/loader/assetentry.h @@ -1,12 +1,13 @@ /** * Copyright (c) 2026 Dominic Masters - * + * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #pragma once #include "asset/loader/assetloading.h" +#include "event/event.h" #include "util/ref.h" typedef enum { @@ -19,7 +20,12 @@ typedef enum { ASSET_ENTRY_STATE_ERROR } assetentrystate_t; -typedef struct assetentry_s { +/** Maximum number of subscribers for each per-entry event. */ +#define ASSET_ENTRY_EVENT_MAX 2 + +typedef struct assetentry_s assetentry_t; + +struct assetentry_s { // Filename and cache key char_t name[ASSET_FILE_NAME_MAX]; // What type of asset is this? @@ -39,12 +45,37 @@ typedef struct assetentry_s { assetloaderinput_t inputData; // Pointer to inputData, or NULL if no input was provided. assetloaderinput_t *input; -} assetentry_t; + + /** + * Fired once when loading completes successfully (params = assetentry_t *). + * Always invoked on the main thread. + */ + event_t onLoaded; + eventcallback_t onLoadedCallbacks[ASSET_ENTRY_EVENT_MAX]; + void *onLoadedUsers[ASSET_ENTRY_EVENT_MAX]; + + /** + * Fired once when the entry is disposed/reaped (params = assetentry_t *). + * The asset data is still accessible when the callback runs. + * Always invoked on the main thread. + */ + event_t onUnloaded; + eventcallback_t onUnloadedCallbacks[ASSET_ENTRY_EVENT_MAX]; + void *onUnloadedUsers[ASSET_ENTRY_EVENT_MAX]; + + /** + * Fired once when loading fails (params = assetentry_t *). + * Always invoked on the main thread. + */ + event_t onError; + eventcallback_t onErrorCallbacks[ASSET_ENTRY_EVENT_MAX]; + void *onErrorUsers[ASSET_ENTRY_EVENT_MAX]; +}; /** * Initializes an asset entry with the given name and type. This does not load * the asset. - * + * * @param entry The asset entry to initialize. * @param name The name of the asset, used as a key for loading and caching. * @param type The type of asset this entry represents. @@ -59,14 +90,14 @@ void assetEntryInit( /** * Locks an asset entry, preventing it from being freed until it is unlocked. - * + * * @param entry The asset entry to lock. */ void assetEntryLock(assetentry_t *entry); /** * Unlocks an asset entry, allowing it to be freed if there are no more locks. - * + * * @param entry The asset entry to unlock. */ void assetEntryUnlock(assetentry_t *entry); @@ -75,9 +106,9 @@ void assetEntryUnlock(assetentry_t *entry); * Starts loading the given asset entry using an assetloading slot. This will * be called by the asset manager when it deems it's a good time to begin the * loading of this asset entry. - * + * * Currently we return the error but in future this will not be returned. - * + * * @param entry The asset entry to start loading. * @param loading The assetloading slot to use for loading this asset entry. * @return Any error that occurs during loading. @@ -86,8 +117,9 @@ void assetEntryStartLoading(assetentry_t *entry, assetloading_t *loading); /** * Disposes an asset entry, freeing any resources it holds. - * + * Fires the onUnloaded event before releasing asset data. + * * @param entry The asset entry to dispose. * @return Any error that occurs during disposal. */ -errorret_t assetEntryDispose(assetentry_t *entry); \ No newline at end of file +errorret_t assetEntryDispose(assetentry_t *entry); diff --git a/src/dusk/asset/loader/script/assetscriptloader.c b/src/dusk/asset/loader/script/assetscriptloader.c index 446d2801..93f2b837 100644 --- a/src/dusk/asset/loader/script/assetscriptloader.c +++ b/src/dusk/asset/loader/script/assetscriptloader.c @@ -9,6 +9,7 @@ #include "asset/loader/assetloading.h" #include "asset/loader/assetentry.h" #include "asset/loader/assetloader.h" +#include "script/module/require/modulerequire.h" #include "util/memory.h" #include "assert/assert.h" #include diff --git a/src/dusk/asset/loader/script/assetscriptloader.h b/src/dusk/asset/loader/script/assetscriptloader.h index ee006659..869789fa 100644 --- a/src/dusk/asset/loader/script/assetscriptloader.h +++ b/src/dusk/asset/loader/script/assetscriptloader.h @@ -13,7 +13,12 @@ typedef struct assetloading_s assetloading_t; typedef struct assetentry_s assetentry_t; -typedef struct { void *nothing; } assetscriptloaderinput_t; +/** + * Pass isModule = true to evaluate in isolated module scope. + * The loaded result (entry->data.script) will be module.exports rather than + * the script's return value. + */ +typedef struct { bool_t isModule; } assetscriptloaderinput_t; typedef uint32_t assetscriptoutput_t; typedef enum { diff --git a/src/dusk/engine/engine.c b/src/dusk/engine/engine.c index 8f4560e6..5618bba1 100644 --- a/src/dusk/engine/engine.c +++ b/src/dusk/engine/engine.c @@ -58,7 +58,6 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) { consolePrint("Engine initialized"); errorChain(scriptExecFile("init.js")); - sceneSet(SCENE_TYPE_INITIAL); errorOk(); } diff --git a/src/dusk/scene/CMakeLists.txt b/src/dusk/scene/CMakeLists.txt index c38e4e52..5329dd01 100644 --- a/src/dusk/scene/CMakeLists.txt +++ b/src/dusk/scene/CMakeLists.txt @@ -1,15 +1,10 @@ -# Copyright (c) 2025 Dominic Masters -# +# Copyright (c) 2026 Dominic Masters +# # This software is released under the MIT License. # https://opensource.org/licenses/MIT -# Sources target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC scene.c scenerenderpipeline.c -) - -# Subdirectories -add_subdirectory(initial) -add_subdirectory(test) \ No newline at end of file +) \ No newline at end of file diff --git a/src/dusk/scene/initial/CMakeLists.txt b/src/dusk/scene/initial/CMakeLists.txt deleted file mode 100644 index 81fdf7d5..00000000 --- a/src/dusk/scene/initial/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -# Sources -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - initialscene.c -) \ No newline at end of file diff --git a/src/dusk/scene/initial/initialscene.c b/src/dusk/scene/initial/initialscene.c deleted file mode 100644 index 6fe4a39c..00000000 --- a/src/dusk/scene/initial/initialscene.c +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "initialscene.h" -#include "console/console.h" -#include "scene/scene.h" -#include "script/script.h" -#include "entity/entitymanager.h" -#include "entity/entity.h" -#include "entity/component/display/entityposition.h" -#include "entity/component/display/entityrenderable.h" - -void initialSceneInit(void) { - consolePrint("Initial scene initialized"); - errorCatch(errorPrint(scriptExecFile("testentity.js"))); -} - -errorret_t initialSceneUpdate(void) { - errorOk(); -} - -void initialSceneDispose(void) { - entityDispose(SCENE.data.initial.cubeEntityId); -} diff --git a/src/dusk/scene/initial/initialscene.h b/src/dusk/scene/initial/initialscene.h deleted file mode 100644 index fb3d255d..00000000 --- a/src/dusk/scene/initial/initialscene.h +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "entity/entitybase.h" - -typedef struct { - entityid_t cubeEntityId; -} initialscene_t; - -void initialSceneInit(void); -errorret_t initialSceneUpdate(void); -void initialSceneDispose(void); diff --git a/src/dusk/scene/scene.c b/src/dusk/scene/scene.c index 001cad12..7a471da1 100644 --- a/src/dusk/scene/scene.c +++ b/src/dusk/scene/scene.c @@ -6,26 +6,15 @@ #include "scene.h" #include "assert/assert.h" #include "util/memory.h" -#include "log/log.h" #include "time/time.h" #include "display/screen/screen.h" #include "entity/entitymanager.h" #include "display/shader/shaderunlit.h" #include "display/display.h" -#include "console/console.h" -#include "util/string.h" #include "ui/ui.h" #include "scene/scenerenderpipeline.h" #include "entity/component.h" -scenefuncs_t SCENE_FUNCTIONS[SCENE_TYPE_COUNT] = { - { 0 }, - #define X(structName, varName, varNameUpper, initFunc, updateFunc, disposeFunc) \ - { initFunc, updateFunc, disposeFunc }, - #include "scene/scenelist.h" - #undef X -}; - scene_t SCENE; errorret_t sceneInit(void) { @@ -34,37 +23,6 @@ errorret_t sceneInit(void) { } errorret_t sceneUpdate(void) { - // Handle scene change - if(SCENE.nextType != SCENE_TYPE_NULL) { - // Dispose current scene. - if(SCENE.type != SCENE_TYPE_NULL) { - if(SCENE_FUNCTIONS[SCENE.type].dispose) { - SCENE_FUNCTIONS[SCENE.type].dispose(); - } - } - - // Init new scene - SCENE.type = SCENE.nextType; - SCENE.nextType = SCENE_TYPE_NULL; - - if(SCENE.type != SCENE_TYPE_NULL) { - if(SCENE_FUNCTIONS[SCENE.type].init) { - SCENE_FUNCTIONS[SCENE.type].init(); - } - } - } - - // Update scene - #ifdef DUSK_TIME_DYNAMIC - if(TIME.dynamicUpdate) { - errorOk(); - } - #endif - - if(SCENE.type != SCENE_TYPE_NULL && SCENE_FUNCTIONS[SCENE.type].update) { - errorChain(SCENE_FUNCTIONS[SCENE.type].update()); - } - errorOk(); } @@ -103,21 +61,14 @@ errorret_t sceneRender(void) { errorOk(); } -void sceneSet(const scenetype_t type) { - assertTrue( - type > SCENE_TYPE_NULL && type < SCENE_TYPE_COUNT, - "Invalid scene type" - ); - SCENE.nextType = type; +void sceneSet(void) { } errorret_t sceneDispose(void) { - if(SCENE.type != SCENE_TYPE_NULL) { - if(SCENE_FUNCTIONS[SCENE.type].dispose) { - SCENE_FUNCTIONS[SCENE.type].dispose(); - } - } - SCENE.type = SCENE_TYPE_NULL; - SCENE.nextType = SCENE_TYPE_NULL; + // if(SCENE.active) { + // scriptSceneDispose(); + // SCENE.active = false; + // } + // SCENE.transitioning = false; errorOk(); } diff --git a/src/dusk/scene/scene.h b/src/dusk/scene/scene.h index 911579bd..4db421b7 100644 --- a/src/dusk/scene/scene.h +++ b/src/dusk/scene/scene.h @@ -6,74 +6,38 @@ */ #pragma once -#include "asset/assetfile.h" -#include "scene/initial/initialscene.h" -#include "scene/test/testscene.h" - -#define SCENE_EVENT_UPDATE_MAX 16 - -typedef enum { - SCENE_TYPE_NULL, - #define X(structName, varName, varNameUpper, initFunc, updateFunc, disposeFunc) \ - SCENE_TYPE_##varNameUpper, - #include "scene/scenelist.h" - #undef X - SCENE_TYPE_COUNT -} scenetype_t; - -typedef union { - #define X(structName, varName, varNameUpper, initFunc, updateFunc, disposeFunc) \ - structName varName; - #include "scene/scenelist.h" - #undef X -} scenedata_t; +#include "error/error.h" typedef struct { - void (*init)(void); - errorret_t (*update)(void); - void (*dispose)(void); -} scenefuncs_t; - -typedef struct { - scenedata_t data; - scenetype_t type; - scenetype_t nextType; + void *nothing; } scene_t; -extern scenefuncs_t SCENE_FUNCTIONS[SCENE_TYPE_COUNT]; extern scene_t SCENE; /** - * Initializes the scene manager. - * - * @return Any error state that happened. + * Initialises the scene manager. */ errorret_t sceneInit(void); /** - * Ticks the scene manager; may call the scene's update method. - * - * @return Any error state that happened. + * Ticks the scene manager. Processes any pending scene transition, then + * calls scriptSceneUpdate on the active scene. */ errorret_t sceneUpdate(void); /** - * Renders the scene. - * - * @return Any error state that happened. + * Renders the current scene (entities, render pipeline, UI). */ errorret_t sceneRender(void); /** - * Requests a scene change on the next safe opportunity. - * - * @param type The type of scene to change to. + * Requests a scene transition. The pending script scene (set via + * scriptSceneSetPending) will be activated at the start of the next + * sceneUpdate call. */ -void sceneSet(const scenetype_t type); +// void sceneSet(void); /** - * Disposes of the current scene. - * - * @return Any error state that happened. + * Disposes the active scene immediately. */ errorret_t sceneDispose(void); diff --git a/src/dusk/scene/scenelist.h b/src/dusk/scene/scenelist.h deleted file mode 100644 index 4b5cb16d..00000000 --- a/src/dusk/scene/scenelist.h +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#ifndef X - #define X(structName, varName, varNameUpper, initFunc, updateFunc, disposeFunc) \ - ((void)) -#endif - -X(initialscene_t, initial, INITIAL, initialSceneInit, initialSceneUpdate, initialSceneDispose) -X(testscene_t, test, TEST, testSceneInit, testSceneUpdate, testSceneDispose) \ No newline at end of file diff --git a/src/dusk/scene/test/testscene.c b/src/dusk/scene/test/testscene.c deleted file mode 100644 index ba9d209f..00000000 --- a/src/dusk/scene/test/testscene.c +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "testscene.h" -#include "console/console.h" -#include "entity/entitymanager.h" -#include "input/input.h" - -#define TEST_SCENE_ENTITY_MAX (ENTITY_COUNT_MAX - 1) - -static entityid_t cameraEntityId; -static componentid_t cameraCompId; -static entityid_t testEntities[TEST_SCENE_ENTITY_MAX]; -static uint8_t testEntityCount = 0; - -static void testSceneSpawnTestEntity(void) { - if(testEntityCount >= TEST_SCENE_ENTITY_MAX) return; - - entityid_t entity = entityManagerAdd(); - componentid_t posComp = entityAddComponent(entity, COMPONENT_TYPE_POSITION); - entityAddComponent(entity, COMPONENT_TYPE_RENDERABLE); - - const int32_t cols = 20; - const float_t spacing = 1.5f; - int32_t col = testEntityCount % cols; - int32_t row = testEntityCount / cols; - vec3 pos = { - ((float_t)col - (cols - 1) * 0.5f) * spacing, - 0.0f, - (float_t)row * spacing - }; - entityPositionSetLocalPosition(entity, posComp, pos); - - testEntities[testEntityCount++] = entity; -} - -static void testSceneUpdateCamera(void) { - if(testEntityCount == 0) return; - - float_t minX = FLT_MAX, maxX = -FLT_MAX; - float_t minZ = FLT_MAX, maxZ = -FLT_MAX; - - for(entityid_t i = 0; i < testEntityCount; i++) { - componentid_t posComp = entityGetComponent( - testEntities[i], COMPONENT_TYPE_POSITION - ); - if(posComp == COMPONENT_ID_INVALID) continue; - vec3 pos; - entityPositionGetLocalPosition(testEntities[i], posComp, pos); - if(pos[0] < minX) minX = pos[0]; - if(pos[0] > maxX) maxX = pos[0]; - if(pos[2] < minZ) minZ = pos[2]; - if(pos[2] > maxZ) maxZ = pos[2]; - } - - float_t centerX = (minX + maxX) * 0.5f; - float_t centerZ = (minZ + maxZ) * 0.5f; - float_t extentX = (maxX - minX) * 0.5f + 0.5f; - float_t extentZ = (maxZ - minZ) * 0.5f + 0.5f; - float_t extent = extentX > extentZ ? extentX : extentZ; - float_t dist = extent * 1.5f + 2.0f; - - vec3 target = { centerX, 0.0f, centerZ }; - vec3 eye = { centerX + dist, dist, centerZ + dist }; - vec3 up = { 0.0f, 1.0f, 0.0f }; - entityPositionLookAt(cameraEntityId, cameraCompId, eye, target, up); -} - -void testSceneInit(void) { - consolePrint("Test scene initialized"); - testEntityCount = 0; - - cameraEntityId = entityManagerAdd(); - cameraCompId = entityAddComponent(cameraEntityId, COMPONENT_TYPE_POSITION); - entityAddComponent(cameraEntityId, COMPONENT_TYPE_CAMERA); - - vec3 eye, target, up; - glm_vec3_zero(target); - glm_vec3_copy((vec3){ 3.0f, 3.0f, 3.0f }, eye); - glm_vec3_copy((vec3){ 0.0f, 1.0f, 0.0f }, up); - entityPositionLookAt(cameraEntityId, cameraCompId, eye, target, up); - - for(int i = 0; i < 5; i++) testSceneSpawnTestEntity(); -} - -errorret_t testSceneUpdate(void) { - if(inputPressed(INPUT_ACTION_ACCEPT)) { - for(int i = 0; i < 5; i++) testSceneSpawnTestEntity(); - } - - if(inputPressed(INPUT_ACTION_CANCEL)) { - for(int i = 0; i < 5 && testEntityCount > 0; i++) { - entityDispose(testEntities[--testEntityCount]); - } - } - - testSceneUpdateCamera(); - - errorOk(); -} - -void testSceneDispose(void) { - testEntityCount = 0; - cameraEntityId = ENTITY_ID_INVALID; - cameraCompId = COMPONENT_ID_INVALID; -} diff --git a/src/dusk/scene/test/testscene.h b/src/dusk/scene/test/testscene.h deleted file mode 100644 index 7ef8f449..00000000 --- a/src/dusk/scene/test/testscene.h +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" - -typedef struct { - void *nothing; -} testscene_t; - -void testSceneInit(void); -errorret_t testSceneUpdate(void); -void testSceneDispose(void); diff --git a/src/dusk/script/module/CMakeLists.txt b/src/dusk/script/module/CMakeLists.txt index bbfc716d..b5fa4d0b 100644 --- a/src/dusk/script/module/CMakeLists.txt +++ b/src/dusk/script/module/CMakeLists.txt @@ -7,3 +7,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC modulebase.c ) + +# Subdirs +add_subdirectory(require) \ No newline at end of file diff --git a/src/dusk/script/module/asset/moduleasset.h b/src/dusk/script/module/asset/moduleasset.h index d83ff21f..8e1a4010 100644 --- a/src/dusk/script/module/asset/moduleasset.h +++ b/src/dusk/script/module/asset/moduleasset.h @@ -10,6 +10,7 @@ #include "script/scriptproto.h" #include "script/module/display/moduletexture.h" #include "script/module/asset/moduleassetentry.h" +#include "script/module/asset/moduleassetbatch.h" #include "asset/asset.h" #include "asset/loader/assetloader.h" @@ -89,6 +90,7 @@ moduleBaseFunction(moduleAssetUnlock) { static void moduleAssetInit(void) { moduleAssetEntryInit(); + moduleAssetBatchInit(); scriptProtoInit(&MODULE_ASSET_PROTO, "Asset", sizeof(uint8_t), NULL); scriptProtoDefineStaticFunc(&MODULE_ASSET_PROTO, "exists", moduleAssetExists); scriptProtoDefineStaticFunc(&MODULE_ASSET_PROTO, "lock", moduleAssetLock); @@ -134,5 +136,6 @@ static void moduleAssetInit(void) { static void moduleAssetDispose(void) { scriptProtoDispose(&MODULE_ASSET_PROTO); + moduleAssetBatchDispose(); moduleAssetEntryDispose(); } diff --git a/src/dusk/script/module/asset/moduleassetbatch.h b/src/dusk/script/module/asset/moduleassetbatch.h new file mode 100644 index 00000000..bcf0a4c1 --- /dev/null +++ b/src/dusk/script/module/asset/moduleassetbatch.h @@ -0,0 +1,293 @@ +/** + * 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 "script/module/asset/moduleassetentry.h" +#include "asset/assetbatch.h" +#include "asset/asset.h" +#include "asset/loader/assetloader.h" +#include "util/memory.h" + +static scriptproto_t MODULE_ASSET_BATCH_PROTO; + +typedef struct { + assetbatch_t *batch; +} jsassetbatch_t; + +static void moduleAssetBatchFree(void *ptr, jerry_object_native_info_t *info) { + (void)info; + jsassetbatch_t *b = (jsassetbatch_t *)ptr; + if(b && b->batch) { + assetBatchDispose(b->batch); + memoryFree(b->batch); + } + memoryFree(ptr); +} + +static inline jsassetbatch_t *moduleAssetBatchSelf( + const jerry_call_info_t *callInfo +) { + return (jsassetbatch_t *)scriptProtoGetValue( + &MODULE_ASSET_BATCH_PROTO, callInfo->this_value + ); +} + +/** + * AssetBatch(descriptors[]) + * + * Parses an array of {path, type, format?/input?/axis?} descriptor objects, + * locks all assets, and returns an AssetBatch JS object. Works with and + * without `new`. + */ +moduleBaseFunction(moduleAssetBatchCtor) { + if(argc < 1 || !jerry_value_is_array(args[0])) { + return moduleBaseThrow("AssetBatch: expected an array of descriptors"); + } + + uint32_t count = jerry_array_length(args[0]); + if(count == 0 || count > (uint32_t)ASSET_BATCH_COUNT_MAX) { + return moduleBaseThrow("AssetBatch: descriptor count out of range"); + } + + assetbatchdesc_t descs[ASSET_BATCH_COUNT_MAX]; + char_t paths[ASSET_BATCH_COUNT_MAX][ASSET_FILE_NAME_MAX]; + + for(uint32_t i = 0; i < count; i++) { + jerry_value_t desc = jerry_object_get_index(args[0], i); + if(!jerry_value_is_object(desc)) { + jerry_value_free(desc); + return moduleBaseThrow("AssetBatch: each descriptor must be an object"); + } + + /* path */ + jerry_value_t pathProp = moduleBaseGetProp(desc, "path"); + if(!jerry_value_is_string(pathProp)) { + jerry_value_free(pathProp); + jerry_value_free(desc); + return moduleBaseThrow("AssetBatch: descriptor.path must be a string"); + } + jerry_size_t pathLen = jerry_string_to_buffer( + pathProp, JERRY_ENCODING_UTF8, + (jerry_char_t *)paths[i], ASSET_FILE_NAME_MAX - 1 + ); + paths[i][pathLen] = '\0'; + jerry_value_free(pathProp); + descs[i].path = paths[i]; + + /* type */ + jerry_value_t typeProp = moduleBaseGetProp(desc, "type"); + if(!jerry_value_is_number(typeProp)) { + jerry_value_free(typeProp); + jerry_value_free(desc); + return moduleBaseThrow("AssetBatch: descriptor.type must be a number"); + } + descs[i].type = (assetloadertype_t)moduleBaseValueInt(typeProp); + jerry_value_free(typeProp); + + /* input: accept format, input, or axis — whichever is present */ + memoryZero(&descs[i].input, sizeof(assetloaderinput_t)); + jerry_value_t inputProp = moduleBaseGetProp(desc, "format"); + if(jerry_value_is_undefined(inputProp)) { + jerry_value_free(inputProp); + inputProp = moduleBaseGetProp(desc, "input"); + } + if(jerry_value_is_undefined(inputProp)) { + jerry_value_free(inputProp); + inputProp = moduleBaseGetProp(desc, "axis"); + } + if(jerry_value_is_number(inputProp)) { + int32_t v = moduleBaseValueInt(inputProp); + switch(descs[i].type) { + case ASSET_LOADER_TYPE_TEXTURE: + descs[i].input.texture = (textureformat_t)v; + break; + case ASSET_LOADER_TYPE_MESH: + descs[i].input.mesh = (assetmeshinputaxis_t)v; + break; + default: + break; + } + } + jerry_value_free(inputProp); + jerry_value_free(desc); + } + + assetbatch_t *batch = (assetbatch_t *)memoryAllocate(sizeof(assetbatch_t)); + assetBatchInit(batch, (uint16_t)count, descs); + + jsassetbatch_t init = { .batch = batch }; + return scriptProtoCreateValue(&MODULE_ASSET_BATCH_PROTO, &init); +} + +/* ---- Properties ---------------------------------------------------------- */ + +moduleBaseFunction(moduleAssetBatchGetCount) { + (void)args; (void)argc; + jsassetbatch_t *b = moduleAssetBatchSelf(callInfo); + if(!b || !b->batch) return jerry_number(0.0); + return jerry_number((double)b->batch->count); +} + +moduleBaseFunction(moduleAssetBatchGetIsLoaded) { + (void)args; (void)argc; + jsassetbatch_t *b = moduleAssetBatchSelf(callInfo); + if(!b || !b->batch) return jerry_boolean(false); + return jerry_boolean(assetBatchIsLoaded(b->batch)); +} + +moduleBaseFunction(moduleAssetBatchGetHasError) { + (void)args; (void)argc; + jsassetbatch_t *b = moduleAssetBatchSelf(callInfo); + if(!b || !b->batch) return jerry_boolean(false); + return jerry_boolean(assetBatchHasError(b->batch)); +} + +/* ---- Methods ------------------------------------------------------------- */ + +/* Blocks until every entry is loaded. Returns this for chaining. */ +moduleBaseFunction(moduleAssetBatchRequireLoaded) { + (void)args; (void)argc; + jsassetbatch_t *b = moduleAssetBatchSelf(callInfo); + if(!b || !b->batch) { + return moduleBaseThrow("AssetBatch.requireLoaded: batch already disposed"); + } + errorret_t err = assetBatchRequireLoaded(b->batch); + if(errorIsNotOk(err)) return moduleBaseThrowError(err); + return jerry_value_copy(callInfo->this_value); +} + +/* Acquires an additional lock on every entry. Returns this for chaining. */ +moduleBaseFunction(moduleAssetBatchLock) { + (void)args; (void)argc; + jsassetbatch_t *b = moduleAssetBatchSelf(callInfo); + if(!b || !b->batch) return jerry_undefined(); + assetBatchLock(b->batch); + return jerry_value_copy(callInfo->this_value); +} + +/* Releases all locks and clears the batch. The object becomes invalid. */ +moduleBaseFunction(moduleAssetBatchUnlock) { + (void)args; (void)argc; + jsassetbatch_t *b = moduleAssetBatchSelf(callInfo); + if(!b || !b->batch) return jerry_undefined(); + assetBatchDispose(b->batch); + memoryFree(b->batch); + b->batch = NULL; + return jerry_undefined(); +} + +/* Returns the AssetEntry at index i, adding an independent lock to it. */ +moduleBaseFunction(moduleAssetBatchEntry) { + moduleBaseRequireArgs(1); + jsassetbatch_t *b = moduleAssetBatchSelf(callInfo); + if(!b || !b->batch) return jerry_undefined(); + uint32_t idx = (uint32_t)moduleBaseArgInt(0); + if(idx >= (uint32_t)b->batch->count) return jerry_undefined(); + assetentry_t *entry = b->batch->entries[idx]; + if(!entry) return jerry_undefined(); + assetEntryLock(entry); + jsassetentry_t e = { .entry = entry }; + return scriptProtoCreateValue(&MODULE_ASSET_ENTRY_PROTO, &e); +} + +/* ---- Event proxies ------------------------------------------------------- */ + +moduleBaseFunction(moduleAssetBatchGetOnLoaded) { + jsassetbatch_t *b = moduleAssetBatchSelf(callInfo); + if(!b || !b->batch) return jerry_undefined(); + return moduleEventProxyGetOrCreate(callInfo, &b->batch->onLoaded, "_onLoaded"); +} + +moduleBaseFunction(moduleAssetBatchGetOnEntryLoaded) { + jsassetbatch_t *b = moduleAssetBatchSelf(callInfo); + if(!b || !b->batch) return jerry_undefined(); + return moduleEventProxyGetOrCreate(callInfo, &b->batch->onEntryLoaded, "_onEntryLoaded"); +} + +moduleBaseFunction(moduleAssetBatchGetOnError) { + jsassetbatch_t *b = moduleAssetBatchSelf(callInfo); + if(!b || !b->batch) return jerry_undefined(); + return moduleEventProxyGetOrCreate(callInfo, &b->batch->onError, "_onError"); +} + +moduleBaseFunction(moduleAssetBatchGetOnEntryError) { + jsassetbatch_t *b = moduleAssetBatchSelf(callInfo); + if(!b || !b->batch) return jerry_undefined(); + return moduleEventProxyGetOrCreate(callInfo, &b->batch->onEntryError, "_onEntryError"); +} + +moduleBaseFunction(moduleAssetBatchToString) { + jsassetbatch_t *b = moduleAssetBatchSelf(callInfo); + if(!b || !b->batch) return jerry_string_sz("AssetBatch:invalid"); + char_t buf[32]; + snprintf(buf, sizeof(buf), "AssetBatch(%u)", (unsigned)b->batch->count); + return jerry_string_sz(buf); +} + +/* ---- Init / Dispose ------------------------------------------------------ */ + +static void moduleAssetBatchInit(void) { + scriptProtoInit( + &MODULE_ASSET_BATCH_PROTO, "AssetBatch", + sizeof(jsassetbatch_t), moduleAssetBatchCtor + ); + MODULE_ASSET_BATCH_PROTO.info.free_cb = moduleAssetBatchFree; + + scriptProtoDefineProp( + &MODULE_ASSET_BATCH_PROTO, "count", + moduleAssetBatchGetCount, NULL + ); + scriptProtoDefineProp( + &MODULE_ASSET_BATCH_PROTO, "isLoaded", + moduleAssetBatchGetIsLoaded, NULL + ); + scriptProtoDefineProp( + &MODULE_ASSET_BATCH_PROTO, "hasError", + moduleAssetBatchGetHasError, NULL + ); + scriptProtoDefineFunc( + &MODULE_ASSET_BATCH_PROTO, "requireLoaded", + moduleAssetBatchRequireLoaded + ); + scriptProtoDefineFunc( + &MODULE_ASSET_BATCH_PROTO, "lock", + moduleAssetBatchLock + ); + scriptProtoDefineFunc( + &MODULE_ASSET_BATCH_PROTO, "unlock", + moduleAssetBatchUnlock + ); + scriptProtoDefineFunc( + &MODULE_ASSET_BATCH_PROTO, "entry", + moduleAssetBatchEntry + ); + scriptProtoDefineProp( + &MODULE_ASSET_BATCH_PROTO, "onLoaded", + moduleAssetBatchGetOnLoaded, NULL + ); + scriptProtoDefineProp( + &MODULE_ASSET_BATCH_PROTO, "onEntryLoaded", + moduleAssetBatchGetOnEntryLoaded, NULL + ); + scriptProtoDefineProp( + &MODULE_ASSET_BATCH_PROTO, "onError", + moduleAssetBatchGetOnError, NULL + ); + scriptProtoDefineProp( + &MODULE_ASSET_BATCH_PROTO, "onEntryError", + moduleAssetBatchGetOnEntryError, NULL + ); + scriptProtoDefineToString( + &MODULE_ASSET_BATCH_PROTO, moduleAssetBatchToString + ); +} + +static void moduleAssetBatchDispose(void) { + scriptProtoDispose(&MODULE_ASSET_BATCH_PROTO); +} diff --git a/src/dusk/script/module/asset/moduleassetentry.h b/src/dusk/script/module/asset/moduleassetentry.h index d61dfb5a..0d9f2db0 100644 --- a/src/dusk/script/module/asset/moduleassetentry.h +++ b/src/dusk/script/module/asset/moduleassetentry.h @@ -9,6 +9,7 @@ #include "script/module/modulebase.h" #include "script/scriptproto.h" #include "script/module/display/moduletexture.h" +#include "script/module/asset/moduleeventproxy.h" #include "asset/asset.h" #include "asset/loader/assetloader.h" #include "asset/loader/assetentry.h" @@ -106,6 +107,24 @@ moduleBaseFunction(moduleAssetEntryUnlock) { return jerry_undefined(); } +moduleBaseFunction(moduleAssetEntryGetOnLoaded) { + jsassetentry_t *e = moduleAssetEntrySelf(callInfo); + if(!e || !e->entry) return jerry_undefined(); + return moduleEventProxyGetOrCreate(callInfo, &e->entry->onLoaded, "_onLoaded"); +} + +moduleBaseFunction(moduleAssetEntryGetOnUnloaded) { + jsassetentry_t *e = moduleAssetEntrySelf(callInfo); + if(!e || !e->entry) return jerry_undefined(); + return moduleEventProxyGetOrCreate(callInfo, &e->entry->onUnloaded, "_onUnloaded"); +} + +moduleBaseFunction(moduleAssetEntryGetOnError) { + jsassetentry_t *e = moduleAssetEntrySelf(callInfo); + if(!e || !e->entry) return jerry_undefined(); + return moduleEventProxyGetOrCreate(callInfo, &e->entry->onError, "_onError"); +} + moduleBaseFunction(moduleAssetEntryToString) { jsassetentry_t *e = moduleAssetEntrySelf(callInfo); if(!e || !e->entry) return jerry_string_sz("AssetEntry:invalid"); @@ -115,6 +134,7 @@ moduleBaseFunction(moduleAssetEntryToString) { } static void moduleAssetEntryInit(void) { + moduleEventProxyInit(); scriptProtoInit( &MODULE_ASSET_ENTRY_PROTO, "AssetEntry", sizeof(jsassetentry_t), moduleAssetEntryCtor @@ -142,6 +162,18 @@ static void moduleAssetEntryInit(void) { scriptProtoDefineFunc( &MODULE_ASSET_ENTRY_PROTO, "unlock", moduleAssetEntryUnlock ); + scriptProtoDefineProp( + &MODULE_ASSET_ENTRY_PROTO, "onLoaded", + moduleAssetEntryGetOnLoaded, NULL + ); + scriptProtoDefineProp( + &MODULE_ASSET_ENTRY_PROTO, "onUnloaded", + moduleAssetEntryGetOnUnloaded, NULL + ); + scriptProtoDefineProp( + &MODULE_ASSET_ENTRY_PROTO, "onError", + moduleAssetEntryGetOnError, NULL + ); scriptProtoDefineToString( &MODULE_ASSET_ENTRY_PROTO, moduleAssetEntryToString ); @@ -168,4 +200,5 @@ static void moduleAssetEntryInit(void) { static void moduleAssetEntryDispose(void) { scriptProtoDispose(&MODULE_ASSET_ENTRY_PROTO); + moduleEventProxyDispose(); } diff --git a/src/dusk/script/module/asset/moduleeventproxy.h b/src/dusk/script/module/asset/moduleeventproxy.h new file mode 100644 index 00000000..9c9a8b31 --- /dev/null +++ b/src/dusk/script/module/asset/moduleeventproxy.h @@ -0,0 +1,245 @@ +/** + * 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 "event/event.h" + +/** + * Maximum number of JS subscriber slots per proxy. Must be >= the largest + * event capacity used (ASSET_ENTRY_EVENT_MAX, ASSET_BATCH_EVENT_MAX, etc.). + */ +#define MODULE_EVENT_PROXY_MAX_SLOTS 4 + +static scriptproto_t MODULE_EVENT_PROXY_PROTO; + +/** Native data stored on each EventProxy JS object. */ +typedef struct { + event_t *event; + jerry_value_t fns[MODULE_EVENT_PROXY_MAX_SLOTS]; +} jseventproxy_t; + +/* ---- C trampolines (one per slot index) ---------------------------------- */ + +static void moduleEventProxyTrampoline0(void *params, void *user) { + (void)params; + jerry_value_t fn = (jerry_value_t)(uintptr_t)user; + jerry_value_t ret = jerry_call(fn, jerry_undefined(), NULL, 0); + jerry_value_free(ret); +} + +static void moduleEventProxyTrampoline1(void *params, void *user) { + (void)params; + jerry_value_t fn = (jerry_value_t)(uintptr_t)user; + jerry_value_t ret = jerry_call(fn, jerry_undefined(), NULL, 0); + jerry_value_free(ret); +} + +static void moduleEventProxyTrampoline2(void *params, void *user) { + (void)params; + jerry_value_t fn = (jerry_value_t)(uintptr_t)user; + jerry_value_t ret = jerry_call(fn, jerry_undefined(), NULL, 0); + jerry_value_free(ret); +} + +static void moduleEventProxyTrampoline3(void *params, void *user) { + (void)params; + jerry_value_t fn = (jerry_value_t)(uintptr_t)user; + jerry_value_t ret = jerry_call(fn, jerry_undefined(), NULL, 0); + jerry_value_free(ret); +} + +static eventcallback_t MODULE_EVENT_PROXY_TRAMPOLINES[MODULE_EVENT_PROXY_MAX_SLOTS] = { + moduleEventProxyTrampoline0, + moduleEventProxyTrampoline1, + moduleEventProxyTrampoline2, + moduleEventProxyTrampoline3, +}; + +/* ---- GC free callback ---------------------------------------------------- */ + +static void moduleEventProxyFree(void *ptr, jerry_object_native_info_t *info) { + (void)info; + jseventproxy_t *ep = (jseventproxy_t *)ptr; + if(ep) { + for(uint32_t i = 0; i < MODULE_EVENT_PROXY_MAX_SLOTS; i++) { + if(jerry_value_is_function(ep->fns[i])) { + if(ep->event) eventUnsubscribe(ep->event, MODULE_EVENT_PROXY_TRAMPOLINES[i]); + jerry_value_free(ep->fns[i]); + } + } + } + memoryFree(ptr); +} + +/* ---- Self helper --------------------------------------------------------- */ + +static inline jseventproxy_t *moduleEventProxySelf( + const jerry_call_info_t *callInfo +) { + return (jseventproxy_t *)scriptProtoGetValue( + &MODULE_EVENT_PROXY_PROTO, callInfo->this_value + ); +} + +/* ---- Slot get/set helpers ------------------------------------------------ */ + +static inline jerry_value_t moduleEventProxyGetSlot( + const jerry_call_info_t *callInfo, + const uint32_t slot +) { + jseventproxy_t *ep = moduleEventProxySelf(callInfo); + if(!ep || !ep->event || slot >= ep->event->size) return jerry_null(); + return jerry_value_is_function(ep->fns[slot]) + ? jerry_value_copy(ep->fns[slot]) + : jerry_null(); +} + +static inline jerry_value_t moduleEventProxySetSlot( + const jerry_call_info_t *callInfo, + const jerry_value_t args[], + const jerry_length_t argc, + const uint32_t slot +) { + jseventproxy_t *ep = moduleEventProxySelf(callInfo); + if(!ep || !ep->event || slot >= ep->event->size) return jerry_undefined(); + + if(jerry_value_is_function(ep->fns[slot])) { + eventUnsubscribe(ep->event, MODULE_EVENT_PROXY_TRAMPOLINES[slot]); + jerry_value_free(ep->fns[slot]); + ep->fns[slot] = jerry_undefined(); + } + + jerry_value_t val = (argc > 0) ? args[0] : jerry_undefined(); + if(jerry_value_is_function(val)) { + ep->fns[slot] = jerry_value_copy(val); + eventSubscribe( + ep->event, + MODULE_EVENT_PROXY_TRAMPOLINES[slot], + (void *)(uintptr_t)ep->fns[slot] + ); + } + + return jerry_undefined(); +} + +/* ---- Per-slot getter/setter pairs ---------------------------------------- */ + +moduleBaseFunction(moduleEventProxyGet0) { + (void)args; (void)argc; + return moduleEventProxyGetSlot(callInfo, 0); +} +moduleBaseFunction(moduleEventProxySet0) { + return moduleEventProxySetSlot(callInfo, args, argc, 0); +} + +moduleBaseFunction(moduleEventProxyGet1) { + (void)args; (void)argc; + return moduleEventProxyGetSlot(callInfo, 1); +} +moduleBaseFunction(moduleEventProxySet1) { + return moduleEventProxySetSlot(callInfo, args, argc, 1); +} + +moduleBaseFunction(moduleEventProxyGet2) { + (void)args; (void)argc; + return moduleEventProxyGetSlot(callInfo, 2); +} +moduleBaseFunction(moduleEventProxySet2) { + return moduleEventProxySetSlot(callInfo, args, argc, 2); +} + +moduleBaseFunction(moduleEventProxyGet3) { + (void)args; (void)argc; + return moduleEventProxyGetSlot(callInfo, 3); +} +moduleBaseFunction(moduleEventProxySet3) { + return moduleEventProxySetSlot(callInfo, args, argc, 3); +} + +moduleBaseFunction(moduleEventProxyGetLength) { + (void)args; (void)argc; + jseventproxy_t *ep = moduleEventProxySelf(callInfo); + if(!ep || !ep->event) return jerry_number(0.0); + return jerry_number((double)ep->event->size); +} + +moduleBaseFunction(moduleEventProxyToString) { + (void)args; (void)argc; + return jerry_string_sz("EventProxy"); +} + +/* ---- Lazy-create helper (shared by all parent types) --------------------- */ + +/** + * Returns the event proxy pinned at pinKey on callInfo->this_value. + * Creates and pins it on first access. event must remain valid for the + * lifetime of the parent JS object (it is stored by pointer, not copied). + */ +static inline jerry_value_t moduleEventProxyGetOrCreate( + const jerry_call_info_t *callInfo, + event_t *event, + const char_t *pinKey +) { + jerry_value_t keyStr = jerry_string_sz(pinKey); + jerry_value_t existing = jerry_object_get(callInfo->this_value, keyStr); + if(!jerry_value_is_undefined(existing)) { + jerry_value_free(keyStr); + return existing; + } + jerry_value_free(existing); + + jseventproxy_t ep; + ep.event = event; + for(uint32_t i = 0; i < MODULE_EVENT_PROXY_MAX_SLOTS; i++) { + ep.fns[i] = jerry_undefined(); + } + + jerry_value_t proxy = scriptProtoCreateValue(&MODULE_EVENT_PROXY_PROTO, &ep); + jerry_object_set(callInfo->this_value, keyStr, proxy); + jerry_value_free(keyStr); + return proxy; +} + +/* ---- Init / Dispose ------------------------------------------------------ */ + +static void moduleEventProxyInit(void) { + scriptProtoInit( + &MODULE_EVENT_PROXY_PROTO, NULL, + sizeof(jseventproxy_t), NULL + ); + MODULE_EVENT_PROXY_PROTO.info.free_cb = moduleEventProxyFree; + + scriptProtoDefineProp( + &MODULE_EVENT_PROXY_PROTO, "0", + moduleEventProxyGet0, moduleEventProxySet0 + ); + scriptProtoDefineProp( + &MODULE_EVENT_PROXY_PROTO, "1", + moduleEventProxyGet1, moduleEventProxySet1 + ); + scriptProtoDefineProp( + &MODULE_EVENT_PROXY_PROTO, "2", + moduleEventProxyGet2, moduleEventProxySet2 + ); + scriptProtoDefineProp( + &MODULE_EVENT_PROXY_PROTO, "3", + moduleEventProxyGet3, moduleEventProxySet3 + ); + scriptProtoDefineProp( + &MODULE_EVENT_PROXY_PROTO, "length", + moduleEventProxyGetLength, NULL + ); + scriptProtoDefineToString( + &MODULE_EVENT_PROXY_PROTO, moduleEventProxyToString + ); +} + +static void moduleEventProxyDispose(void) { + scriptProtoDispose(&MODULE_EVENT_PROXY_PROTO); +} diff --git a/src/dusk/script/module/math/modulevec3.h b/src/dusk/script/module/math/modulevec3.h index e2695706..223d0020 100644 --- a/src/dusk/script/module/math/modulevec3.h +++ b/src/dusk/script/module/math/modulevec3.h @@ -13,31 +13,17 @@ static scriptproto_t MODULE_VEC3_PROTO; -/** - * Returns the native float[3] pointer from the Vec3 instance that owns - * the current call (this_value). Returns NULL if not a Vec3. - */ -static inline float_t *moduleVec3Get(const jerry_call_info_t *callInfo) { +float_t * moduleVec3Get(const jerry_call_info_t *callInfo) { return (float_t *)scriptProtoGetValue( &MODULE_VEC3_PROTO, callInfo->this_value ); } -/** - * Returns the native float[3] pointer from any jerry value. - * Returns NULL if the value is not a Vec3 instance. - */ -static inline float_t *moduleVec3From(const jerry_value_t val) { +float_t * moduleVec3From(const jerry_value_t val) { return (float_t *)scriptProtoGetValue(&MODULE_VEC3_PROTO, val); } -/** - * Creates a Vec3 JS object from a C vec3 array. - * - * @param v Source vec3 to copy. - * @return A new Vec3 JS instance owning a copy of the data. - */ -static inline jerry_value_t moduleVec3Push(const vec3 v) { +jerry_value_t moduleVec3Push(const vec3 v) { return scriptProtoCreateValue(&MODULE_VEC3_PROTO, v); } diff --git a/src/dusk/script/module/modulelist.h b/src/dusk/script/module/modulelist.h index 0ddb4b24..60dcd720 100644 --- a/src/dusk/script/module/modulelist.h +++ b/src/dusk/script/module/modulelist.h @@ -16,6 +16,7 @@ #include "script/module/entity/moduleentity.h" #include "script/module/input/moduleinput.h" #include "script/module/math/modulevec3.h" +#include "script/module/require/modulerequire.h" #include "script/module/scene/modulescene.h" #include "script/module/system/modulesystem.h" @@ -31,6 +32,7 @@ static void moduleListInit(void) { moduleEntityInit(); moduleComponentListInit(); moduleInputInit(); + moduleRequireInit(); moduleSceneInit(); moduleSystemInit(); } @@ -38,6 +40,7 @@ static void moduleListInit(void) { static void moduleListDispose(void) { moduleSystemDispose(); moduleSceneDispose(); + moduleRequireDispose(); moduleInputDispose(); moduleComponentListDispose(); moduleEntityDispose(); diff --git a/src/dusk/scene/test/CMakeLists.txt b/src/dusk/script/module/require/CMakeLists.txt similarity index 90% rename from src/dusk/scene/test/CMakeLists.txt rename to src/dusk/script/module/require/CMakeLists.txt index 92780e7e..c063ce82 100644 --- a/src/dusk/scene/test/CMakeLists.txt +++ b/src/dusk/script/module/require/CMakeLists.txt @@ -5,5 +5,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC - testscene.c + modulerequire.c ) diff --git a/src/dusk/script/module/require/modulerequire.c b/src/dusk/script/module/require/modulerequire.c new file mode 100644 index 00000000..90eae20c --- /dev/null +++ b/src/dusk/script/module/require/modulerequire.c @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "modulerequire.h" +#include "asset/asset.h" +#include "asset/assetfile.h" + +jerry_value_t moduleRequireFunc( + const jerry_call_info_t *callInfo, + const jerry_value_t args[], + const jerry_length_t argc +) { + return jerry_undefined(); +} + +jerry_value_t moduleRequireAsyncFunc( + const jerry_call_info_t *callInfo, + const jerry_value_t args[], + const jerry_length_t argc +) { + return jerry_undefined(); +} + +void moduleRequireInit(void) { + moduleBaseDefineGlobalMethod("require", moduleRequireFunc); + moduleBaseDefineGlobalMethod("requireAsync", moduleRequireAsyncFunc); +} + +void moduleRequireDispose(void) { +} diff --git a/src/dusk/script/module/require/modulerequire.h b/src/dusk/script/module/require/modulerequire.h new file mode 100644 index 00000000..85aa65c3 --- /dev/null +++ b/src/dusk/script/module/require/modulerequire.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" + +/** + * Initializes the require() module system: creates the module cache and + * registers the global require() and requireAsync() functions. + */ +void moduleRequireInit(void); + +/** + * Disposes of the require() module system, releasing the module cache. + */ +void moduleRequireDispose(void); + +/** + * Records the directory of the script about to be evaluated so that relative + * require() paths can be resolved correctly. Call before jerry_eval; pair each + * call with moduleRequirePopDir(). + * + * @param filePath Full asset path of the script (e.g. "scripts/Main.js"). + */ +void moduleRequirePushDir(const char_t *filePath); + +/** + * Pops the most recently pushed directory off the resolution stack. + */ +void moduleRequirePopDir(void); + +/** + * Evaluates a script source in an isolated module scope. + * + * Sets up fresh module/exports globals, pushes the directory for nested + * require() calls, evaluates the source, then restores everything. + * + * On success returns module.exports (caller owns the reference). + * On eval error returns the exception value (caller must check with + * jerry_value_is_exception and free appropriately). + * + * @param path Full asset path — used only for directory resolution. + * @param src Source bytes. + * @param size Byte count of src (not including any null terminator). + */ +jerry_value_t moduleRequireExecModule( + const char_t *path, + const jerry_char_t *src, + const size_t size +); diff --git a/src/dusk/script/module/scene/modulescene.h b/src/dusk/script/module/scene/modulescene.h index 20f576ce..668fd8ae 100644 --- a/src/dusk/script/module/scene/modulescene.h +++ b/src/dusk/script/module/scene/modulescene.h @@ -7,48 +7,17 @@ #pragma once #include "script/module/modulebase.h" +#include "script/module/asset/moduleassetbatch.h" #include "script/scriptproto.h" #include "scene/scene.h" +#include "asset/asset.h" +#include "asset/loader/assetloader.h" +#include "util/memory.h" static scriptproto_t MODULE_SCENE_PROTO; -moduleBaseFunction(moduleSceneGetCurrent) { - return jerry_number((double)SCENE.type); -} - -moduleBaseFunction(moduleSceneSet) { - moduleBaseRequireArgs(1); - moduleBaseRequireNumber(0); - const scenetype_t type = (scenetype_t)moduleBaseArgInt(0); - if(type <= SCENE_TYPE_NULL || type >= SCENE_TYPE_COUNT) { - return moduleBaseThrow("Scene.set: invalid scene type"); - } - sceneSet(type); - return jerry_undefined(); -} - static void moduleSceneInit(void) { scriptProtoInit(&MODULE_SCENE_PROTO, "Scene", sizeof(uint8_t), NULL); - - scriptProtoDefineStaticProp( - &MODULE_SCENE_PROTO, "current", moduleSceneGetCurrent, NULL - ); - scriptProtoDefineStaticFunc( - &MODULE_SCENE_PROTO, "set", moduleSceneSet - ); - - /* Scene.INITIAL, Scene.TEST, Scene.OVERWORLD, ... */ - jerry_value_t global = MODULE_SCENE_PROTO.prototype; - #define X(structName, varName, varNameUpper, initFunc, updateFunc, disposeFunc) \ - do { \ - jerry_value_t _key = jerry_string_sz(#varNameUpper); \ - jerry_value_t _val = jerry_number((double)SCENE_TYPE_##varNameUpper); \ - jerry_object_set(global, _key, _val); \ - jerry_value_free(_val); \ - jerry_value_free(_key); \ - } while(0); - #include "scene/scenelist.h" - #undef X } static void moduleSceneDispose(void) { diff --git a/types/asset/assetbatch.d.ts b/types/asset/assetbatch.d.ts new file mode 100644 index 00000000..144a9165 --- /dev/null +++ b/types/asset/assetbatch.d.ts @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** + * Descriptor for one entry in an `AssetBatch`. + * + * `format` — texture format constant (`Texture.FORMAT_*`). Alias for `input` + * when the type is `Asset.TYPE_TEXTURE`. + * `axis` — mesh axis constant (`Asset.MESH_AXIS_*`). Alias for `input` + * when the type is `Asset.TYPE_MESH`. + * `input` — generic numeric input for all other loader types. + */ +interface AssetBatchDescriptor { + path: string; + type: number; + format?: number; + axis?: number; + input?: number; +} + +/** A group of asset entries locked and queued for loading together. */ +interface AssetBatch { + /** Number of entries in the batch. */ + readonly count: number; + /** `true` when every entry has reached `LOADED`. */ + readonly isLoaded: boolean; + /** `true` if any entry is in an `ERROR` state. */ + readonly hasError: boolean; + /** + * Blocks until every entry is loaded. + * Returns `this` for chaining. + * @throws If any entry fails to load. + */ + requireLoaded(): this; + /** + * Acquires one additional lock on every entry. + * Returns `this` for chaining. + */ + lock(): this; + /** + * Releases all locks and clears the batch. + * After this call the object is invalid — do not use it again. + */ + unlock(): void; + /** + * Returns the `AssetEntry` at `index`, adding an independent lock. + * The returned entry must be unlocked separately when no longer needed. + * Returns `undefined` if `index` is out of range or the batch is disposed. + */ + entry(index: number): AssetEntry | undefined; + + /** + * Fires once when every entry has loaded successfully. + * Subscribe with `onLoaded[0] = () => { ... }`. + */ + readonly onLoaded: AssetEventProxy; + /** + * Fires each time a single entry finishes loading. + * Subscribe with `onEntryLoaded[0] = () => { ... }`. + */ + readonly onEntryLoaded: AssetEventProxy; + /** + * Fires once when all entries have finished but at least one errored. + * Subscribe with `onError[0] = () => { ... }`. + */ + readonly onError: AssetEventProxy; + /** + * Fires each time a single entry transitions to an error state. + * Subscribe with `onEntryError[0] = () => { ... }`. + */ + readonly onEntryError: AssetEventProxy; + + toString(): string; +} + +interface AssetBatchConstructor { + /** Creates a batch from an array of descriptors. Works with or without `new`. */ + (descriptors: AssetBatchDescriptor[]): AssetBatch; + new(descriptors: AssetBatchDescriptor[]): AssetBatch; +} + +declare var AssetBatch: AssetBatchConstructor; diff --git a/types/asset/assetentry.d.ts b/types/asset/assetentry.d.ts index 7d93918e..384d164e 100644 --- a/types/asset/assetentry.d.ts +++ b/types/asset/assetentry.d.ts @@ -5,6 +5,24 @@ * https://opensource.org/licenses/MIT */ +/** + * An array-like proxy over one of an asset entry's events. + * Assign a function to a numbered slot to subscribe; assign `null` to + * unsubscribe that slot. + * + * @example + * entry.onLoaded[0] = () => Console.print('loaded!'); + * entry.onLoaded[0] = null; // unsubscribe + */ +interface AssetEventProxy { + readonly length: number; + 0: (() => void) | null; + 1: (() => void) | null; + 2: (() => void) | null; + 3: (() => void) | null; + toString(): string; +} + /** * A live reference to an entry in the asset cache. * Holds a lock that keeps the entry alive; the lock is released automatically @@ -26,6 +44,12 @@ interface AssetEntry { * is not yet loaded. */ readonly texture: Texture | undefined; + /** Event proxy — subscribe up to 4 callbacks for when loading completes. */ + readonly onLoaded: AssetEventProxy; + /** Event proxy — subscribe up to 4 callbacks for when the entry is disposed. */ + readonly onUnloaded: AssetEventProxy; + /** Event proxy — subscribe up to 4 callbacks for when loading fails. */ + readonly onError: AssetEventProxy; /** * Blocks until the entry reaches `LOADED` (or `ERROR`). * Returns `this` for chaining. diff --git a/types/index.d.ts b/types/index.d.ts index 41942f41..9f50f77b 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -14,6 +14,9 @@ * { "compilerOptions": { "typeRoots": ["./types"] } } */ +// module system +/// + // math /// @@ -24,6 +27,7 @@ // asset /// +/// /// // engine systems diff --git a/types/require.d.ts b/types/require.d.ts new file mode 100644 index 00000000..d278f291 --- /dev/null +++ b/types/require.d.ts @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** + * CommonJS-style module loader. Accepts a single path or an array of paths. + * + * - Single string → returns that module's `exports`. + * - Array of strings → returns an array of `exports` in the same order. + * + * Modules are cached after their first load. Subsequent calls with the same + * resolved path return the cached exports without re-executing the file. + * + * Path rules: `"./foo"` / `"../foo"` resolve relative to the calling script's + * directory; any other string resolves from the archive root. `.js` is + * appended automatically when missing. + * + * @example + * const NPC = require('./entities/NPC'); + * const [NPC, Item] = require(['./entities/NPC', './entities/Item']); + */ +declare function require(path: string): any; +declare function require(paths: string[]): any[]; + +/** + * Asynchronous module loader. Accepts a single path or an array of paths. + * The asset file(s) are read in the background; once all are loaded and + * evaluated, `callback` is invoked. + * + * - Single string → `callback(exports)` — first argument is the module's + * `exports`, or `null` on load failure. + * - Array of strings → `callback(exportsArray)` — first argument is an array + * of `exports` values in the same order; failed entries are `null`. + * + * Cached modules resolve synchronously (callback fires on the same call). + * Path rules are identical to `require`. + * + * @example + * requireAsync('./entities/NPC', function(NPC) { + * if(NPC) NPC.init(); + * }); + * + * requireAsync(['./entities/NPC', './entities/Item'], function(mods) { + * const [NPC, Item] = mods; + * }); + */ +declare function requireAsync( + path: string, + callback: (exports: any) => void +): void; +declare function requireAsync( + paths: string[], + callback: (exports: any[]) => void +): void; + +/** + * The module object for the currently executing script. + * Assign `module.exports` to control what `require()` returns to callers. + */ +declare var module: { exports: any }; + +/** + * Shorthand for `module.exports`. Direct assignment (`exports = ...`) does + * NOT update `module.exports`; use `module.exports = ...` for that. + */ +declare var exports: any; diff --git a/types/scene/scene.d.ts b/types/scene/scene.d.ts index 1769b3a4..5a82f691 100644 --- a/types/scene/scene.d.ts +++ b/types/scene/scene.d.ts @@ -5,23 +5,49 @@ * https://opensource.org/licenses/MIT */ -/** Scene management — request scene transitions and query the active scene. */ +/** + * The object a JS scene module must export. + * All methods are optional. + * + * @example + * // assets/scenes/game.js + * var scene = {}; + * var batch = AssetBatch([{ path: 'tex/bg.png', type: Asset.TYPE_TEXTURE }]); + * + * scene.load = function() { return batch; }; + * scene.init = function() { Console.print('scene started'); }; + * scene.update = function() { /* per-frame logic *\/ }; + * scene.dispose = function() { batch.unlock(); }; + * + * module.exports = scene; + */ +interface SceneObject { + /** Return an AssetBatch to preload before init is called. Optional. */ + load?(): AssetBatch | undefined; + /** Called when this scene becomes active. Optional. */ + init?(): void; + /** Called every frame while this scene is active. Optional. */ + update?(): void; + /** Called when the scene is replaced or the engine shuts down. Optional. */ + dispose?(): void; +} + +/** Scene management. */ interface SceneNamespace { - /** Type constant of the currently active scene, or 0 if none. */ - readonly current: number; + /** `true` while a JS script scene is running. */ + readonly active: boolean; /** - * Requests a scene transition. The change takes effect at the start of the - * next safe update tick (current scene is disposed, new scene is initialized). + * Loads the JS module at `path`, waits for any `AssetBatch` returned by + * `scene.load()`, then activates the scene (calling `scene.init()`). + * The previous scene's `dispose()` is called just before `init()`. + * + * Returns immediately — the transition is asynchronous. * * @example - * Scene.set(Scene.OVERWORLD); + * Scene.set('assets/scenes/game.js'); */ - set(type: number): void; - - readonly INITIAL: number; - readonly TEST: number; - readonly OVERWORLD: number; + set(path: string): void; } declare var Scene: SceneNamespace;