diff --git a/assets/init.js b/assets/init.js index ec80478f..16b0aadd 100644 --- a/assets/init.js +++ b/assets/init.js @@ -8,4 +8,7 @@ const platformNames = { Console.print('Platform: ' + (platformNames[System.platform] || 'Unknown')); -requireAsync('testscene.js').then(Scene.set).catch(Engine.exit); \ No newline at end of file +requireAsync('testscene.js').then(Scene.set).catch(err => { + Console.print('Error loading scene: ' + err); + Engine.exit(); +}); \ No newline at end of file diff --git a/assets/testscene.js b/assets/testscene.js index 6626a54c..fa10809b 100644 --- a/assets/testscene.js +++ b/assets/testscene.js @@ -4,14 +4,17 @@ var scene = {}; // { path: 'test.png', type: Asset.TYPE_TEXTURE, format: Texture.FORMAT_RGBA } // ]); -// var cam; -// var camPos; -// var testEntity; -// var testPos; -// var testRenderable; -// var texEntry; +var cam; +var camPos; +var testEntity; +var testPos; +var testRenderable; +var texEntry; scene.init = function() { + // assets.lock(); + // await assets.loaded(); + Console.print('Scene Init'); // texEntry = assets.entry(0); @@ -28,11 +31,11 @@ scene.init = function() { testRenderable = testEntity.add(Component.RENDERABLE); // testRenderable.texture = texEntry.texture; - testRenderable.type = Renderable.SPRITEBATCH; - testRenderable.sprites = [ - [0, 0, 1, 1, 0, 1, 1, 0] - ]; - testPos.localPosition = new Vec3(0, 0, 0); + // testRenderable.type = Renderable.SPRITEBATCH; + // testRenderable.sprites = [ + // [0, 0, 1, 1, 0, 1, 1, 0] + // ]; + // testPos.localPosition = new Vec3(0, 0, 0); } scene.dispose = function() { diff --git a/src/dusk/script/module/CMakeLists.txt b/src/dusk/script/module/CMakeLists.txt index 5f82f17f..a81eeaa9 100644 --- a/src/dusk/script/module/CMakeLists.txt +++ b/src/dusk/script/module/CMakeLists.txt @@ -10,5 +10,14 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} ) # Subdirs +add_subdirectory(asset) +add_subdirectory(console) +add_subdirectory(display) +add_subdirectory(engine) +add_subdirectory(entity) add_subdirectory(event) -add_subdirectory(require) \ No newline at end of file +add_subdirectory(input) +add_subdirectory(math) +add_subdirectory(require) +add_subdirectory(scene) +add_subdirectory(system) \ No newline at end of file diff --git a/src/dusk/script/module/asset/CMakeLists.txt b/src/dusk/script/module/asset/CMakeLists.txt new file mode 100644 index 00000000..801d99ea --- /dev/null +++ b/src/dusk/script/module/asset/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + moduleassetentry.c + moduleassetbatch.c + moduleasset.c +) diff --git a/src/dusk/script/module/asset/moduleasset.c b/src/dusk/script/module/asset/moduleasset.c new file mode 100644 index 00000000..58fe8f62 --- /dev/null +++ b/src/dusk/script/module/asset/moduleasset.c @@ -0,0 +1,132 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "moduleasset.h" + +scriptproto_t MODULE_ASSET_PROTO; + +moduleBaseFunction(moduleAssetExists) { + moduleBaseRequireArgs(1); + moduleBaseRequireString(0); + char_t buf[256]; + moduleBaseToString(args[0], buf, sizeof(buf)); + return jerry_boolean(assetFileExists(buf)); +} + +moduleBaseFunction(moduleAssetLock) { + moduleBaseRequireArgs(2); + moduleBaseRequireString(0); + moduleBaseRequireNumber(1); + + char_t buf[256]; + moduleBaseToString(args[0], buf, sizeof(buf)); + assetloadertype_t type = (assetloadertype_t)moduleBaseArgInt(1); + + assetloaderinput_t input; + assetloaderinput_t *inputPtr = NULL; + + if(argc >= 3 && jerry_value_is_number(args[2])) { + int32_t inputVal = moduleBaseArgInt(2); + switch(type) { + case ASSET_LOADER_TYPE_TEXTURE: + input.texture = (textureformat_t)inputVal; + inputPtr = &input; + break; + case ASSET_LOADER_TYPE_MESH: + input.mesh = (assetmeshinputaxis_t)inputVal; + inputPtr = &input; + break; + default: + break; + } + } + + assetentry_t *entry = assetLock(buf, type, inputPtr); + if(!entry) return moduleBaseThrow("Asset.lock: failed to lock asset"); + jsassetentry_t e = { .entry = entry }; + return scriptProtoCreateValue(&MODULE_ASSET_ENTRY_PROTO, &e); +} + +moduleBaseFunction(moduleAssetRequireLoaded) { + moduleBaseRequireArgs(1); + jsassetentry_t *e = (jsassetentry_t *)scriptProtoGetValue( + &MODULE_ASSET_ENTRY_PROTO, args[0] + ); + if(!e || !e->entry) { + return moduleBaseThrow("Asset.requireLoaded: expected AssetEntry"); + } + errorret_t err = assetRequireLoaded(e->entry); + if(errorIsNotOk(err)) return moduleBaseThrowError(err); + return jerry_value_copy(args[0]); +} + +moduleBaseFunction(moduleAssetUnlock) { + moduleBaseRequireArgs(1); + moduleBaseRequireString(0); + char_t buf[256]; + moduleBaseToString(args[0], buf, sizeof(buf)); + assetUnlock(buf); + return jerry_undefined(); +} + +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 + ); + scriptProtoDefineStaticFunc( + &MODULE_ASSET_PROTO, "unlock", moduleAssetUnlock + ); + scriptProtoDefineStaticFunc( + &MODULE_ASSET_PROTO, "requireLoaded", moduleAssetRequireLoaded + ); + + jerry_value_t global = MODULE_ASSET_PROTO.prototype; + + struct { const char_t *name; int val; } types[] = { + { "TYPE_MESH", ASSET_LOADER_TYPE_MESH }, + { "TYPE_TEXTURE", ASSET_LOADER_TYPE_TEXTURE }, + { "TYPE_TILESET", ASSET_LOADER_TYPE_TILESET }, + { "TYPE_LOCALE", ASSET_LOADER_TYPE_LOCALE }, + { "TYPE_JSON", ASSET_LOADER_TYPE_JSON }, + { "TYPE_SCRIPT", ASSET_LOADER_TYPE_SCRIPT }, + }; + for(int i = 0; i < 6; i++) { + jerry_value_t k = jerry_string_sz(types[i].name); + jerry_value_t v = jerry_number((double)types[i].val); + jerry_object_set(global, k, v); + jerry_value_free(v); + jerry_value_free(k); + } + + struct { const char_t *name; int val; } axes[] = { + { "MESH_AXIS_Y_UP", MESH_INPUT_AXIS_Y_UP }, + { "MESH_AXIS_Z_UP", MESH_INPUT_AXIS_Z_UP }, + { "MESH_AXIS_X_UP", MESH_INPUT_AXIS_X_UP }, + { "MESH_AXIS_Y_DOWN", MESH_INPUT_AXIS_Y_DOWN }, + { "MESH_AXIS_Z_DOWN", MESH_INPUT_AXIS_Z_DOWN }, + { "MESH_AXIS_X_DOWN", MESH_INPUT_AXIS_X_DOWN }, + }; + for(int i = 0; i < 6; i++) { + jerry_value_t k = jerry_string_sz(axes[i].name); + jerry_value_t v = jerry_number((double)axes[i].val); + jerry_object_set(global, k, v); + jerry_value_free(v); + jerry_value_free(k); + } +} + +void moduleAssetDispose(void) { + scriptProtoDispose(&MODULE_ASSET_PROTO); + moduleAssetBatchDispose(); + moduleAssetEntryDispose(); +} diff --git a/src/dusk/script/module/asset/moduleasset.h b/src/dusk/script/module/asset/moduleasset.h index 8e1a4010..6ec679e1 100644 --- a/src/dusk/script/module/asset/moduleasset.h +++ b/src/dusk/script/module/asset/moduleasset.h @@ -14,128 +14,48 @@ #include "asset/asset.h" #include "asset/loader/assetloader.h" -static scriptproto_t MODULE_ASSET_PROTO; +extern scriptproto_t MODULE_ASSET_PROTO; -moduleBaseFunction(moduleAssetExists) { - moduleBaseRequireArgs(1); - moduleBaseRequireString(0); +/** + * Asset.exists(path) — returns true if the path exists in the asset archive. + * @param args[0] Archive-relative path string. + */ +moduleBaseFunction(moduleAssetExists); - char_t buf[256]; - moduleBaseToString(args[0], buf, sizeof(buf)); +/** + * Asset.lock(path, type, input?) — locks an asset entry and returns an + * AssetEntry. The entry begins loading in the background. + * + * @param args[0] Path string. + * @param args[1] Loader type constant (Asset.TYPE_*). + * @param args[2] Optional loader input (Texture.FORMAT_* or Asset.MESH_AXIS_*). + */ +moduleBaseFunction(moduleAssetLock); - return jerry_boolean(assetFileExists(buf)); -} +/** + * Asset.requireLoaded(entry) — blocks until the entry is fully loaded. + * @param args[0] An AssetEntry object. + * @return The same AssetEntry for chaining. + * @throws If the load fails. + */ +moduleBaseFunction(moduleAssetRequireLoaded); -moduleBaseFunction(moduleAssetLock) { - moduleBaseRequireArgs(2); - moduleBaseRequireString(0); - moduleBaseRequireNumber(1); +/** + * Asset.unlock(path) — releases the asset lock for the given path. + * Prefer entry.unlock() on the AssetEntry object directly. + * @param args[0] Path string originally passed to Asset.lock(). + */ +moduleBaseFunction(moduleAssetUnlock); - char_t buf[256]; - moduleBaseToString(args[0], buf, sizeof(buf)); - assetloadertype_t type = (assetloadertype_t)moduleBaseArgInt(1); +/** + * Initializes the Asset module. Also calls moduleAssetEntryInit() and + * moduleAssetBatchInit(), then registers the global Asset object with + * exists/lock/unlock/requireLoaded and all TYPE_* / MESH_AXIS_* constants. + */ +void moduleAssetInit(void); - assetloaderinput_t input; - assetloaderinput_t *inputPtr = NULL; - - if(argc >= 3 && jerry_value_is_number(args[2])) { - int32_t inputVal = moduleBaseArgInt(2); - switch(type) { - case ASSET_LOADER_TYPE_TEXTURE: - input.texture = (textureformat_t)inputVal; - inputPtr = &input; - break; - - case ASSET_LOADER_TYPE_MESH: - input.mesh = (assetmeshinputaxis_t)inputVal; - inputPtr = &input; - break; - - default: - break; - } - } - - assetentry_t *entry = assetLock(buf, type, inputPtr); - if(!entry) return moduleBaseThrow("Asset.lock: failed to lock asset"); - jsassetentry_t e = { .entry = entry }; - - return scriptProtoCreateValue(&MODULE_ASSET_ENTRY_PROTO, &e); -} - -moduleBaseFunction(moduleAssetRequireLoaded) { - moduleBaseRequireArgs(1); - - jsassetentry_t *e = (jsassetentry_t *)scriptProtoGetValue( - &MODULE_ASSET_ENTRY_PROTO, args[0] - ); - if(!e || !e->entry) { - return moduleBaseThrow("Asset.requireLoaded: expected AssetEntry"); - } - errorret_t err = assetRequireLoaded(e->entry); - if(errorIsNotOk(err)) return moduleBaseThrowError(err); - jerry_value_t ref = jerry_value_copy(args[0]); - - return ref; -} - -moduleBaseFunction(moduleAssetUnlock) { - moduleBaseRequireArgs(1); - moduleBaseRequireString(0); - char_t buf[256]; - moduleBaseToString(args[0], buf, sizeof(buf)); - assetUnlock(buf); - return jerry_undefined(); -} - -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); - scriptProtoDefineStaticFunc(&MODULE_ASSET_PROTO, "unlock", moduleAssetUnlock); - scriptProtoDefineStaticFunc(&MODULE_ASSET_PROTO, "requireLoaded", moduleAssetRequireLoaded); - - jerry_value_t global = MODULE_ASSET_PROTO.prototype; - - /* Asset.TYPE_* loader type constants */ - struct { const char_t *name; int val; } types[] = { - { "TYPE_MESH", ASSET_LOADER_TYPE_MESH }, - { "TYPE_TEXTURE", ASSET_LOADER_TYPE_TEXTURE }, - { "TYPE_TILESET", ASSET_LOADER_TYPE_TILESET }, - { "TYPE_LOCALE", ASSET_LOADER_TYPE_LOCALE }, - { "TYPE_JSON", ASSET_LOADER_TYPE_JSON }, - { "TYPE_SCRIPT", ASSET_LOADER_TYPE_SCRIPT }, - }; - for(int i = 0; i < 6; i++) { - jerry_value_t k = jerry_string_sz(types[i].name); - jerry_value_t v = jerry_number((double)types[i].val); - jerry_object_set(global, k, v); - jerry_value_free(v); - jerry_value_free(k); - } - - /* Asset.MESH_AXIS_* input constants for TYPE_MESH */ - struct { const char_t *name; int val; } axes[] = { - { "MESH_AXIS_Y_UP", MESH_INPUT_AXIS_Y_UP }, - { "MESH_AXIS_Z_UP", MESH_INPUT_AXIS_Z_UP }, - { "MESH_AXIS_X_UP", MESH_INPUT_AXIS_X_UP }, - { "MESH_AXIS_Y_DOWN", MESH_INPUT_AXIS_Y_DOWN }, - { "MESH_AXIS_Z_DOWN", MESH_INPUT_AXIS_Z_DOWN }, - { "MESH_AXIS_X_DOWN", MESH_INPUT_AXIS_X_DOWN }, - }; - for(int i = 0; i < 6; i++) { - jerry_value_t k = jerry_string_sz(axes[i].name); - jerry_value_t v = jerry_number((double)axes[i].val); - jerry_object_set(global, k, v); - jerry_value_free(v); - jerry_value_free(k); - } -} - -static void moduleAssetDispose(void) { - scriptProtoDispose(&MODULE_ASSET_PROTO); - moduleAssetBatchDispose(); - moduleAssetEntryDispose(); -} +/** + * Disposes the Asset module and calls moduleAssetBatchDispose() and + * moduleAssetEntryDispose(). + */ +void moduleAssetDispose(void); diff --git a/src/dusk/script/module/asset/moduleassetbatch.c b/src/dusk/script/module/asset/moduleassetbatch.c new file mode 100644 index 00000000..82573e54 --- /dev/null +++ b/src/dusk/script/module/asset/moduleassetbatch.c @@ -0,0 +1,394 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "moduleassetbatch.h" + +#define ASSET_BATCH_LOADED_MAX 8 + +typedef struct { + assetbatch_t *batch; + jerry_value_t promise; +} assetbatchloadedpend_t; + +scriptproto_t MODULE_ASSET_BATCH_PROTO; + +static assetbatchloadedpend_t ASSET_BATCH_LOADED[ASSET_BATCH_LOADED_MAX]; +static uint32_t ASSET_BATCH_LOADED_COUNT = 0; + +static 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; + uint32_t i = 0; + while(i < ASSET_BATCH_LOADED_COUNT) { + if(ASSET_BATCH_LOADED[i].batch != batch) { i++; continue; } + jerry_value_t undef = jerry_undefined(); + jerry_value_t r = jerry_promise_resolve( + ASSET_BATCH_LOADED[i].promise, undef + ); + jerry_value_free(undef); + jerry_value_free(r); + jerry_value_free(ASSET_BATCH_LOADED[i].promise); + ASSET_BATCH_LOADED_COUNT--; + if(i < ASSET_BATCH_LOADED_COUNT) { + ASSET_BATCH_LOADED[i] = + ASSET_BATCH_LOADED[ASSET_BATCH_LOADED_COUNT]; + } + } + eventUnsubscribe(&batch->onError, assetBatchOnErrorFire); +} + +static void assetBatchOnErrorFire(void *params, void *user) { + assetbatch_t *batch = (assetbatch_t *)user; + uint32_t i = 0; + while(i < ASSET_BATCH_LOADED_COUNT) { + if(ASSET_BATCH_LOADED[i].batch != batch) { i++; continue; } + jerry_value_t err = jerry_string_sz("Asset batch failed to load"); + jerry_value_t r = jerry_promise_reject( + ASSET_BATCH_LOADED[i].promise, err + ); + jerry_value_free(err); + jerry_value_free(r); + jerry_value_free(ASSET_BATCH_LOADED[i].promise); + ASSET_BATCH_LOADED_COUNT--; + if(i < ASSET_BATCH_LOADED_COUNT) { + ASSET_BATCH_LOADED[i] = + ASSET_BATCH_LOADED[ASSET_BATCH_LOADED_COUNT]; + } + } + eventUnsubscribe(&batch->onLoaded, assetBatchOnLoadedFire); +} + +void moduleAssetBatchFree(void *ptr, jerry_object_native_info_t *info) { + jsassetbatch_t *b = (jsassetbatch_t *)ptr; + if(b && b->batch) { + assetBatchDispose(b->batch); + memoryFree(b->batch); + } + memoryFree(ptr); +} + +jsassetbatch_t *moduleAssetBatchSelf(const jerry_call_info_t *callInfo) { + return (jsassetbatch_t *)scriptProtoGetValue( + &MODULE_ASSET_BATCH_PROTO, callInfo->this_value + ); +} + +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" + ); + } + + 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]; + + 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); + + 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); +} + +moduleBaseFunction(moduleAssetBatchGetCount) { + jsassetbatch_t *b = moduleAssetBatchSelf(callInfo); + if(!b || !b->batch) return jerry_number(0.0); + return jerry_number((double)b->batch->count); +} + +moduleBaseFunction(moduleAssetBatchGetIsLoaded) { + jsassetbatch_t *b = moduleAssetBatchSelf(callInfo); + if(!b || !b->batch) return jerry_boolean(false); + return jerry_boolean(assetBatchIsLoaded(b->batch)); +} + +moduleBaseFunction(moduleAssetBatchGetHasError) { + jsassetbatch_t *b = moduleAssetBatchSelf(callInfo); + if(!b || !b->batch) return jerry_boolean(false); + return jerry_boolean(assetBatchHasError(b->batch)); +} + +moduleBaseFunction(moduleAssetBatchRequireLoaded) { + 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); +} + +moduleBaseFunction(moduleAssetBatchLoaded) { + jsassetbatch_t *b = moduleAssetBatchSelf(callInfo); + if(!b || !b->batch) { + return moduleBaseThrow("AssetBatch.loaded: batch disposed"); + } + + jerry_value_t promise = jerry_promise(); + + if(assetBatchIsLoaded(b->batch)) { + 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(assetBatchHasError(b->batch)) { + jerry_value_t err = jerry_string_sz("Asset batch failed to load"); + jerry_value_t r = jerry_promise_reject(promise, err); + jerry_value_free(err); + jerry_value_free(r); + return promise; + } + + if(ASSET_BATCH_LOADED_COUNT >= ASSET_BATCH_LOADED_MAX) { + jerry_value_free(promise); + return moduleBaseThrow("AssetBatch.loaded: too many pending"); + } + + bool_t subscribed = false; + for(uint32_t i = 0; i < ASSET_BATCH_LOADED_COUNT; i++) { + if(ASSET_BATCH_LOADED[i].batch == b->batch) { + subscribed = true; break; + } + } + if(!subscribed) { + eventSubscribe( + &b->batch->onLoaded, assetBatchOnLoadedFire, b->batch + ); + eventSubscribe( + &b->batch->onError, assetBatchOnErrorFire, b->batch + ); + } + + ASSET_BATCH_LOADED[ASSET_BATCH_LOADED_COUNT].batch = b->batch; + ASSET_BATCH_LOADED[ASSET_BATCH_LOADED_COUNT].promise = + jerry_value_copy(promise); + ASSET_BATCH_LOADED_COUNT++; + return promise; +} + +moduleBaseFunction(moduleAssetBatchLock) { + jsassetbatch_t *b = moduleAssetBatchSelf(callInfo); + if(!b || !b->batch) return jerry_undefined(); + assetBatchLock(b->batch); + return jerry_value_copy(callInfo->this_value); +} + +moduleBaseFunction(moduleAssetBatchUnlock) { + jsassetbatch_t *b = moduleAssetBatchSelf(callInfo); + if(!b || !b->batch) return jerry_undefined(); + assetBatchDispose(b->batch); + memoryFree(b->batch); + b->batch = NULL; + return jerry_undefined(); +} + +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); +} + +moduleBaseFunction(moduleAssetBatchGetOnLoaded) { + jsassetbatch_t *b = moduleAssetBatchSelf(callInfo); + if(!b || !b->batch) return jerry_undefined(); + return moduleEventGetOrCreate( + callInfo, &b->batch->onLoaded, "_onLoaded" + ); +} + +moduleBaseFunction(moduleAssetBatchGetOnEntryLoaded) { + jsassetbatch_t *b = moduleAssetBatchSelf(callInfo); + if(!b || !b->batch) return jerry_undefined(); + return moduleEventGetOrCreate( + callInfo, &b->batch->onEntryLoaded, "_onEntryLoaded" + ); +} + +moduleBaseFunction(moduleAssetBatchGetOnError) { + jsassetbatch_t *b = moduleAssetBatchSelf(callInfo); + if(!b || !b->batch) return jerry_undefined(); + return moduleEventGetOrCreate( + callInfo, &b->batch->onError, "_onError" + ); +} + +moduleBaseFunction(moduleAssetBatchGetOnEntryError) { + jsassetbatch_t *b = moduleAssetBatchSelf(callInfo); + if(!b || !b->batch) return jerry_undefined(); + return moduleEventGetOrCreate( + 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); +} + +void moduleAssetBatchInit(void) { + ASSET_BATCH_LOADED_COUNT = 0; + 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, "loaded", moduleAssetBatchLoaded + ); + 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 + ); +} + +void moduleAssetBatchDispose(void) { + for(uint32_t i = 0; i < ASSET_BATCH_LOADED_COUNT; i++) { + bool_t seen = false; + for(uint32_t j = 0; j < i; j++) { + if(ASSET_BATCH_LOADED[j].batch == ASSET_BATCH_LOADED[i].batch) { + seen = true; break; + } + } + if(!seen) { + eventUnsubscribe( + &ASSET_BATCH_LOADED[i].batch->onLoaded, + assetBatchOnLoadedFire + ); + eventUnsubscribe( + &ASSET_BATCH_LOADED[i].batch->onError, + assetBatchOnErrorFire + ); + } + jerry_value_free(ASSET_BATCH_LOADED[i].promise); + } + ASSET_BATCH_LOADED_COUNT = 0; + scriptProtoDispose(&MODULE_ASSET_BATCH_PROTO); +} diff --git a/src/dusk/script/module/asset/moduleassetbatch.h b/src/dusk/script/module/asset/moduleassetbatch.h index bcf0a4c1..7dcb8efe 100644 --- a/src/dusk/script/module/asset/moduleassetbatch.h +++ b/src/dusk/script/module/asset/moduleassetbatch.h @@ -14,280 +14,101 @@ #include "asset/loader/assetloader.h" #include "util/memory.h" -static scriptproto_t MODULE_ASSET_BATCH_PROTO; +extern scriptproto_t MODULE_ASSET_BATCH_PROTO; +/** C struct wrapped by every AssetBatch JS instance. */ 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 - ); -} +/** + * GC free callback — disposes and frees the batch when the JS object is + * garbage collected. + * + * @param ptr Native jsassetbatch_t pointer. + * @param info Native info (unused). + */ +void moduleAssetBatchFree(void *ptr, jerry_object_native_info_t *info); /** - * AssetBatch(descriptors[]) + * Returns the jsassetbatch_t pointer from the current this_value. * - * 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`. + * @param callInfo The call info. + * @return Pointer to the jsassetbatch_t, or NULL if invalid. */ -moduleBaseFunction(moduleAssetBatchCtor) { - if(argc < 1 || !jerry_value_is_array(args[0])) { - return moduleBaseThrow("AssetBatch: expected an array of descriptors"); - } +jsassetbatch_t *moduleAssetBatchSelf(const jerry_call_info_t *callInfo); - 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"); - } +/** + * AssetBatch(descriptors[]) constructor. 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`. + * + * @param args[0] Array of AssetBatchDescriptor objects. + */ +moduleBaseFunction(moduleAssetBatchCtor); - assetbatchdesc_t descs[ASSET_BATCH_COUNT_MAX]; - char_t paths[ASSET_BATCH_COUNT_MAX][ASSET_FILE_NAME_MAX]; +/** @return Number of entries in the batch. */ +moduleBaseFunction(moduleAssetBatchGetCount); - 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"); - } +/** @return True when every entry has reached LOADED. */ +moduleBaseFunction(moduleAssetBatchGetIsLoaded); - /* 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]; +/** @return True if any entry is in an ERROR state. */ +moduleBaseFunction(moduleAssetBatchGetHasError); - /* 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); +/** + * requireLoaded() — blocks until every entry is loaded. + * @return this for chaining. + * @throws If any entry fails to load. + */ +moduleBaseFunction(moduleAssetBatchRequireLoaded); - /* 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); - } +/** + * loaded() — returns a Promise that resolves when all entries load, or + * rejects if any entry errors. Resolves immediately if already loaded. + */ +moduleBaseFunction(moduleAssetBatchLoaded); - assetbatch_t *batch = (assetbatch_t *)memoryAllocate(sizeof(assetbatch_t)); - assetBatchInit(batch, (uint16_t)count, descs); +/** + * lock() — acquires one additional lock on every entry. + * @return this for chaining. + */ +moduleBaseFunction(moduleAssetBatchLock); - jsassetbatch_t init = { .batch = batch }; - return scriptProtoCreateValue(&MODULE_ASSET_BATCH_PROTO, &init); -} +/** + * unlock() — releases all locks and disposes the batch. The object is + * invalid after this call. + */ +moduleBaseFunction(moduleAssetBatchUnlock); -/* ---- Properties ---------------------------------------------------------- */ +/** + * entry(index) — returns the AssetEntry at index, adding an independent + * lock. + * @param args[0] Index (number). + * @return AssetEntry, or undefined if out of range. + */ +moduleBaseFunction(moduleAssetBatchEntry); -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); -} +/** @return The onLoaded Event (fires once when all entries load). */ +moduleBaseFunction(moduleAssetBatchGetOnLoaded); -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)); -} +/** @return The onEntryLoaded Event (fires per entry). */ +moduleBaseFunction(moduleAssetBatchGetOnEntryLoaded); -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)); -} +/** @return The onError Event (fires once when any entry errors). */ +moduleBaseFunction(moduleAssetBatchGetOnError); -/* ---- Methods ------------------------------------------------------------- */ +/** @return The onEntryError Event (fires per errored entry). */ +moduleBaseFunction(moduleAssetBatchGetOnEntryError); -/* 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); -} +/** @return "AssetBatch(n)" string. */ +moduleBaseFunction(moduleAssetBatchToString); -/* 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); -} +/** + * Initializes the AssetBatch module and registers the global AssetBatch + * class. + */ +void moduleAssetBatchInit(void); -/* 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); -} +/** Disposes the AssetBatch module. */ +void moduleAssetBatchDispose(void); diff --git a/src/dusk/script/module/asset/moduleassetentry.c b/src/dusk/script/module/asset/moduleassetentry.c new file mode 100644 index 00000000..09b09564 --- /dev/null +++ b/src/dusk/script/module/asset/moduleassetentry.c @@ -0,0 +1,311 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "moduleassetentry.h" + +#define ASSET_ENTRY_LOADED_MAX 16 + +typedef struct { + assetentry_t *entry; + jerry_value_t promise; +} assetentryloadedpend_t; + +scriptproto_t MODULE_ASSET_ENTRY_PROTO; + +static assetentryloadedpend_t ASSET_ENTRY_LOADED[ASSET_ENTRY_LOADED_MAX]; +static uint32_t ASSET_ENTRY_LOADED_COUNT = 0; + +static 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; + uint32_t i = 0; + while(i < ASSET_ENTRY_LOADED_COUNT) { + if(ASSET_ENTRY_LOADED[i].entry != entry) { i++; continue; } + jerry_value_t undef = jerry_undefined(); + jerry_value_t r = jerry_promise_resolve( + ASSET_ENTRY_LOADED[i].promise, undef + ); + jerry_value_free(undef); + jerry_value_free(r); + jerry_value_free(ASSET_ENTRY_LOADED[i].promise); + ASSET_ENTRY_LOADED_COUNT--; + if(i < ASSET_ENTRY_LOADED_COUNT) { + ASSET_ENTRY_LOADED[i] = + ASSET_ENTRY_LOADED[ASSET_ENTRY_LOADED_COUNT]; + } + } + eventUnsubscribe(&entry->onError, assetEntryOnErrorFire); +} + +static void assetEntryOnErrorFire(void *params, void *user) { + assetentry_t *entry = (assetentry_t *)user; + uint32_t i = 0; + while(i < ASSET_ENTRY_LOADED_COUNT) { + if(ASSET_ENTRY_LOADED[i].entry != entry) { i++; continue; } + jerry_value_t err = jerry_string_sz("Asset failed to load"); + jerry_value_t r = jerry_promise_reject( + ASSET_ENTRY_LOADED[i].promise, err + ); + jerry_value_free(err); + jerry_value_free(r); + jerry_value_free(ASSET_ENTRY_LOADED[i].promise); + ASSET_ENTRY_LOADED_COUNT--; + if(i < ASSET_ENTRY_LOADED_COUNT) { + ASSET_ENTRY_LOADED[i] = + ASSET_ENTRY_LOADED[ASSET_ENTRY_LOADED_COUNT]; + } + } + eventUnsubscribe(&entry->onLoaded, assetEntryOnLoadedFire); +} + +void moduleAssetEntryFree(void *ptr, jerry_object_native_info_t *info) { + jsassetentry_t *e = (jsassetentry_t *)ptr; + if(e && e->entry) { + assetUnlockEntry(e->entry); + e->entry = NULL; + } + memoryFree(ptr); +} + +jsassetentry_t *moduleAssetEntrySelf(const jerry_call_info_t *callInfo) { + return (jsassetentry_t *)scriptProtoGetValue( + &MODULE_ASSET_ENTRY_PROTO, callInfo->this_value + ); +} + +moduleBaseFunction(moduleAssetEntryCtor) { + return moduleBaseThrow("AssetEntry cannot be instantiated with new"); +} + +moduleBaseFunction(moduleAssetEntryGetName) { + jsassetentry_t *e = moduleAssetEntrySelf(callInfo); + if(!e || !e->entry) return jerry_undefined(); + return jerry_string_sz(e->entry->name); +} + +moduleBaseFunction(moduleAssetEntryGetState) { + jsassetentry_t *e = moduleAssetEntrySelf(callInfo); + if(!e || !e->entry) return jerry_undefined(); + return jerry_number((double)e->entry->state); +} + +moduleBaseFunction(moduleAssetEntryGetType) { + jsassetentry_t *e = moduleAssetEntrySelf(callInfo); + if(!e || !e->entry) return jerry_undefined(); + return jerry_number((double)e->entry->type); +} + +moduleBaseFunction(moduleAssetEntryGetIsLoaded) { + jsassetentry_t *e = moduleAssetEntrySelf(callInfo); + if(!e || !e->entry) return jerry_boolean(false); + return jerry_boolean(e->entry->state == ASSET_ENTRY_STATE_LOADED); +} + +moduleBaseFunction(moduleAssetEntryGetTexture) { + jsassetentry_t *e = moduleAssetEntrySelf(callInfo); + if(!e || !e->entry) return jerry_undefined(); + if(e->entry->type != ASSET_LOADER_TYPE_TEXTURE) return jerry_undefined(); + if(e->entry->state != ASSET_ENTRY_STATE_LOADED) return jerry_undefined(); + assetEntryLock(e->entry); + jstexture_t tex = { .entry = e->entry }; + return scriptProtoCreateValue(&MODULE_TEXTURE_PROTO, &tex); +} + +moduleBaseFunction(moduleAssetEntryRequireLoaded) { + jsassetentry_t *e = moduleAssetEntrySelf(callInfo); + if(!e || !e->entry) { + return moduleBaseThrow("AssetEntry.requireLoaded: invalid entry"); + } + errorret_t err = assetRequireLoaded(e->entry); + if(errorIsNotOk(err)) return moduleBaseThrowError(err); + return jerry_value_copy(callInfo->this_value); +} + +moduleBaseFunction(moduleAssetEntryUnlock) { + jsassetentry_t *e = moduleAssetEntrySelf(callInfo); + if(!e || !e->entry) return jerry_undefined(); + assetUnlockEntry(e->entry); + e->entry = NULL; + return jerry_undefined(); +} + +moduleBaseFunction(moduleAssetEntryGetOnLoaded) { + jsassetentry_t *e = moduleAssetEntrySelf(callInfo); + if(!e || !e->entry) return jerry_undefined(); + return moduleEventGetOrCreate( + callInfo, &e->entry->onLoaded, "_onLoaded" + ); +} + +moduleBaseFunction(moduleAssetEntryGetOnUnloaded) { + jsassetentry_t *e = moduleAssetEntrySelf(callInfo); + if(!e || !e->entry) return jerry_undefined(); + return moduleEventGetOrCreate( + callInfo, &e->entry->onUnloaded, "_onUnloaded" + ); +} + +moduleBaseFunction(moduleAssetEntryGetOnError) { + jsassetentry_t *e = moduleAssetEntrySelf(callInfo); + if(!e || !e->entry) return jerry_undefined(); + return moduleEventGetOrCreate( + callInfo, &e->entry->onError, "_onError" + ); +} + +moduleBaseFunction(moduleAssetEntryLoaded) { + jsassetentry_t *e = moduleAssetEntrySelf(callInfo); + if(!e || !e->entry) { + return moduleBaseThrow("AssetEntry.loaded: invalid entry"); + } + + jerry_value_t promise = jerry_promise(); + + if(e->entry->state == ASSET_ENTRY_STATE_LOADED) { + jerry_value_t undef = jerry_undefined(); + jerry_value_t r = jerry_promise_resolve(promise, undef); + jerry_value_free(undef); + jerry_value_free(r); + return promise; + } + + if(e->entry->state == ASSET_ENTRY_STATE_ERROR) { + jerry_value_t err = jerry_string_sz("Asset failed to load"); + jerry_value_t r = jerry_promise_reject(promise, err); + jerry_value_free(err); + jerry_value_free(r); + return promise; + } + + if(ASSET_ENTRY_LOADED_COUNT >= ASSET_ENTRY_LOADED_MAX) { + jerry_value_free(promise); + return moduleBaseThrow("AssetEntry.loaded: too many pending"); + } + + bool_t subscribed = false; + for(uint32_t i = 0; i < ASSET_ENTRY_LOADED_COUNT; i++) { + if(ASSET_ENTRY_LOADED[i].entry == e->entry) { + subscribed = true; break; + } + } + if(!subscribed) { + eventSubscribe( + &e->entry->onLoaded, assetEntryOnLoadedFire, e->entry + ); + eventSubscribe( + &e->entry->onError, assetEntryOnErrorFire, e->entry + ); + } + + ASSET_ENTRY_LOADED[ASSET_ENTRY_LOADED_COUNT].entry = e->entry; + ASSET_ENTRY_LOADED[ASSET_ENTRY_LOADED_COUNT].promise = + jerry_value_copy(promise); + ASSET_ENTRY_LOADED_COUNT++; + return promise; +} + +moduleBaseFunction(moduleAssetEntryToString) { + jsassetentry_t *e = moduleAssetEntrySelf(callInfo); + if(!e || !e->entry) return jerry_string_sz("AssetEntry:invalid"); + char_t buf[64]; + snprintf(buf, sizeof(buf), "AssetEntry(%s)", e->entry->name); + return jerry_string_sz(buf); +} + +void moduleAssetEntryInit(void) { + ASSET_ENTRY_LOADED_COUNT = 0; + scriptProtoInit( + &MODULE_ASSET_ENTRY_PROTO, "AssetEntry", + sizeof(jsassetentry_t), moduleAssetEntryCtor + ); + MODULE_ASSET_ENTRY_PROTO.info.free_cb = moduleAssetEntryFree; + + scriptProtoDefineProp( + &MODULE_ASSET_ENTRY_PROTO, "name", moduleAssetEntryGetName, NULL + ); + scriptProtoDefineProp( + &MODULE_ASSET_ENTRY_PROTO, "state", moduleAssetEntryGetState, NULL + ); + scriptProtoDefineProp( + &MODULE_ASSET_ENTRY_PROTO, "type", moduleAssetEntryGetType, NULL + ); + scriptProtoDefineProp( + &MODULE_ASSET_ENTRY_PROTO, "isLoaded", + moduleAssetEntryGetIsLoaded, NULL + ); + scriptProtoDefineProp( + &MODULE_ASSET_ENTRY_PROTO, "texture", + moduleAssetEntryGetTexture, NULL + ); + scriptProtoDefineFunc( + &MODULE_ASSET_ENTRY_PROTO, "requireLoaded", + moduleAssetEntryRequireLoaded + ); + 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 + ); + scriptProtoDefineFunc( + &MODULE_ASSET_ENTRY_PROTO, "loaded", moduleAssetEntryLoaded + ); + scriptProtoDefineToString( + &MODULE_ASSET_ENTRY_PROTO, moduleAssetEntryToString + ); + + jerry_value_t ctor = MODULE_ASSET_ENTRY_PROTO.constructor; + struct { const char_t *name; int val; } states[] = { + { "NOT_STARTED", ASSET_ENTRY_STATE_NOT_STARTED }, + { "PENDING", ASSET_ENTRY_STATE_PENDING_ASYNC }, + { "LOADING", ASSET_ENTRY_STATE_LOADING_ASYNC }, + { "LOADED", ASSET_ENTRY_STATE_LOADED }, + { "ERROR", ASSET_ENTRY_STATE_ERROR }, + }; + for(int i = 0; i < 5; i++) { + jerry_value_t k = jerry_string_sz(states[i].name); + jerry_value_t v = jerry_number((double)states[i].val); + jerry_object_set(ctor, k, v); + jerry_value_free(v); + jerry_value_free(k); + } +} + +void moduleAssetEntryDispose(void) { + for(uint32_t i = 0; i < ASSET_ENTRY_LOADED_COUNT; i++) { + bool_t seen = false; + for(uint32_t j = 0; j < i; j++) { + if(ASSET_ENTRY_LOADED[j].entry == ASSET_ENTRY_LOADED[i].entry) { + seen = true; break; + } + } + if(!seen) { + eventUnsubscribe( + &ASSET_ENTRY_LOADED[i].entry->onLoaded, + assetEntryOnLoadedFire + ); + eventUnsubscribe( + &ASSET_ENTRY_LOADED[i].entry->onError, + assetEntryOnErrorFire + ); + } + jerry_value_free(ASSET_ENTRY_LOADED[i].promise); + } + ASSET_ENTRY_LOADED_COUNT = 0; + scriptProtoDispose(&MODULE_ASSET_ENTRY_PROTO); +} diff --git a/src/dusk/script/module/asset/moduleassetentry.h b/src/dusk/script/module/asset/moduleassetentry.h index 0d9f2db0..c7f78c5a 100644 --- a/src/dusk/script/module/asset/moduleassetentry.h +++ b/src/dusk/script/module/asset/moduleassetentry.h @@ -9,196 +9,88 @@ #include "script/module/modulebase.h" #include "script/scriptproto.h" #include "script/module/display/moduletexture.h" -#include "script/module/asset/moduleeventproxy.h" +#include "script/module/event/moduleevent.h" #include "asset/asset.h" #include "asset/loader/assetloader.h" #include "asset/loader/assetentry.h" #include "util/memory.h" -static scriptproto_t MODULE_ASSET_ENTRY_PROTO; +extern scriptproto_t MODULE_ASSET_ENTRY_PROTO; +/** C struct wrapped by every AssetEntry JS instance. */ typedef struct { assetentry_t *entry; } jsassetentry_t; -/** Releases the asset lock when the JS object is GC'd. */ -static void moduleAssetEntryFree( - void *ptr, - jerry_object_native_info_t *info -) { - (void)info; - jsassetentry_t *e = (jsassetentry_t *)ptr; - if(e && e->entry) { - assetUnlockEntry(e->entry); - e->entry = NULL; - } - memoryFree(ptr); -} - -moduleBaseFunction(moduleAssetEntryCtor) { - (void)callInfo; (void)args; (void)argc; - return moduleBaseThrow("AssetEntry cannot be instantiated with new"); -} - -static inline jsassetentry_t *moduleAssetEntrySelf( - const jerry_call_info_t *callInfo -) { - return (jsassetentry_t *)scriptProtoGetValue( - &MODULE_ASSET_ENTRY_PROTO, callInfo->this_value - ); -} - -moduleBaseFunction(moduleAssetEntryGetName) { - jsassetentry_t *e = moduleAssetEntrySelf(callInfo); - if(!e || !e->entry) return jerry_undefined(); - return jerry_string_sz(e->entry->name); -} - -moduleBaseFunction(moduleAssetEntryGetState) { - jsassetentry_t *e = moduleAssetEntrySelf(callInfo); - if(!e || !e->entry) return jerry_undefined(); - return jerry_number((double)e->entry->state); -} - -moduleBaseFunction(moduleAssetEntryGetType) { - jsassetentry_t *e = moduleAssetEntrySelf(callInfo); - if(!e || !e->entry) return jerry_undefined(); - return jerry_number((double)e->entry->type); -} - -moduleBaseFunction(moduleAssetEntryGetIsLoaded) { - jsassetentry_t *e = moduleAssetEntrySelf(callInfo); - if(!e || !e->entry) return jerry_boolean(false); - return jerry_boolean(e->entry->state == ASSET_ENTRY_STATE_LOADED); -} - -/* requireLoaded() — blocks until fully loaded, returns this for chaining. */ -moduleBaseFunction(moduleAssetEntryRequireLoaded) { - jsassetentry_t *e = moduleAssetEntrySelf(callInfo); - if(!e || !e->entry) return moduleBaseThrow("AssetEntry.requireLoaded: invalid entry"); - errorret_t err = assetRequireLoaded(e->entry); - if(errorIsNotOk(err)) return moduleBaseThrowError(err); - jerry_value_t self = jerry_value_copy(callInfo->this_value); - return self; -} - -/* - * texture — returns a Texture wrapping this entry's loaded texture data. - * Returns undefined if the entry is not a texture or not yet loaded. - * Locks the entry a second time so the Texture holds its own independent - * reference; the lock is released when the Texture is GC'd. +/** + * GC free callback — releases the asset lock when the AssetEntry JS object + * is garbage collected. + * + * @param ptr Native jsassetentry_t pointer. + * @param info Native info (unused). */ -moduleBaseFunction(moduleAssetEntryGetTexture) { - jsassetentry_t *e = moduleAssetEntrySelf(callInfo); - if(!e || !e->entry) return jerry_undefined(); - if(e->entry->type != ASSET_LOADER_TYPE_TEXTURE) return jerry_undefined(); - if(e->entry->state != ASSET_ENTRY_STATE_LOADED) return jerry_undefined(); - assetEntryLock(e->entry); - jstexture_t tex = { .entry = e->entry }; - return scriptProtoCreateValue(&MODULE_TEXTURE_PROTO, &tex); -} +void moduleAssetEntryFree(void *ptr, jerry_object_native_info_t *info); -/* unlock() — releases the lock early; subsequent access is undefined. */ -moduleBaseFunction(moduleAssetEntryUnlock) { - jsassetentry_t *e = moduleAssetEntrySelf(callInfo); - if(!e || !e->entry) return jerry_undefined(); - assetUnlockEntry(e->entry); - e->entry = NULL; - return jerry_undefined(); -} +/** + * Returns the jsassetentry_t pointer from the current this_value. + * + * @param callInfo The call info. + * @return Pointer to the jsassetentry_t, or NULL if invalid. + */ +jsassetentry_t *moduleAssetEntrySelf(const jerry_call_info_t *callInfo); -moduleBaseFunction(moduleAssetEntryGetOnLoaded) { - jsassetentry_t *e = moduleAssetEntrySelf(callInfo); - if(!e || !e->entry) return jerry_undefined(); - return moduleEventProxyGetOrCreate(callInfo, &e->entry->onLoaded, "_onLoaded"); -} +/** AssetEntry() constructor — always throws; not directly instantiable. */ +moduleBaseFunction(moduleAssetEntryCtor); -moduleBaseFunction(moduleAssetEntryGetOnUnloaded) { - jsassetentry_t *e = moduleAssetEntrySelf(callInfo); - if(!e || !e->entry) return jerry_undefined(); - return moduleEventProxyGetOrCreate(callInfo, &e->entry->onUnloaded, "_onUnloaded"); -} +/** @return Archive-relative path used as the cache key. */ +moduleBaseFunction(moduleAssetEntryGetName); -moduleBaseFunction(moduleAssetEntryGetOnError) { - jsassetentry_t *e = moduleAssetEntrySelf(callInfo); - if(!e || !e->entry) return jerry_undefined(); - return moduleEventProxyGetOrCreate(callInfo, &e->entry->onError, "_onError"); -} +/** @return Current loading state as a number (AssetEntry.* constants). */ +moduleBaseFunction(moduleAssetEntryGetState); -moduleBaseFunction(moduleAssetEntryToString) { - jsassetentry_t *e = moduleAssetEntrySelf(callInfo); - if(!e || !e->entry) return jerry_string_sz("AssetEntry:invalid"); - char_t buf[64]; - snprintf(buf, sizeof(buf), "AssetEntry(%s)", e->entry->name); - return jerry_string_sz(buf); -} +/** @return Loader type constant. */ +moduleBaseFunction(moduleAssetEntryGetType); -static void moduleAssetEntryInit(void) { - moduleEventProxyInit(); - scriptProtoInit( - &MODULE_ASSET_ENTRY_PROTO, "AssetEntry", - sizeof(jsassetentry_t), moduleAssetEntryCtor - ); - MODULE_ASSET_ENTRY_PROTO.info.free_cb = moduleAssetEntryFree; +/** @return True when the entry has fully loaded. */ +moduleBaseFunction(moduleAssetEntryGetIsLoaded); - scriptProtoDefineProp( - &MODULE_ASSET_ENTRY_PROTO, "name", moduleAssetEntryGetName, NULL - ); - scriptProtoDefineProp( - &MODULE_ASSET_ENTRY_PROTO, "state", moduleAssetEntryGetState, NULL - ); - scriptProtoDefineProp( - &MODULE_ASSET_ENTRY_PROTO, "type", moduleAssetEntryGetType, NULL - ); - scriptProtoDefineProp( - &MODULE_ASSET_ENTRY_PROTO, "isLoaded", moduleAssetEntryGetIsLoaded, NULL - ); - scriptProtoDefineProp( - &MODULE_ASSET_ENTRY_PROTO, "texture", moduleAssetEntryGetTexture, NULL - ); - scriptProtoDefineFunc( - &MODULE_ASSET_ENTRY_PROTO, "requireLoaded", moduleAssetEntryRequireLoaded - ); - 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 - ); +/** + * Returns a Texture wrapping this entry's texture data. The Texture holds + * its own asset lock independently of this AssetEntry. + * @return A Texture JS object, or undefined if not a loaded texture. + */ +moduleBaseFunction(moduleAssetEntryGetTexture); - /* State constants */ - jerry_value_t ctor = MODULE_ASSET_ENTRY_PROTO.constructor; - struct { const char_t *name; int val; } states[] = { - { "NOT_STARTED", ASSET_ENTRY_STATE_NOT_STARTED }, - { "PENDING", ASSET_ENTRY_STATE_PENDING_ASYNC }, - { "LOADING", ASSET_ENTRY_STATE_LOADING_ASYNC }, - { "LOADED", ASSET_ENTRY_STATE_LOADED }, - { "ERROR", ASSET_ENTRY_STATE_ERROR }, - }; - for(int i = 0; i < 5; i++) { - jerry_value_t k = jerry_string_sz(states[i].name); - jerry_value_t v = jerry_number((double)states[i].val); - jerry_object_set(ctor, k, v); - jerry_value_free(v); - jerry_value_free(k); - } +/** + * requireLoaded() — blocks until the entry is LOADED or ERROR. + * @return this for chaining. + * @throws If the load fails. + */ +moduleBaseFunction(moduleAssetEntryRequireLoaded); +/** unlock() — releases the asset lock immediately. */ +moduleBaseFunction(moduleAssetEntryUnlock); -} +/** @return The onLoaded Event for this entry. */ +moduleBaseFunction(moduleAssetEntryGetOnLoaded); -static void moduleAssetEntryDispose(void) { - scriptProtoDispose(&MODULE_ASSET_ENTRY_PROTO); - moduleEventProxyDispose(); -} +/** @return The onUnloaded Event for this entry. */ +moduleBaseFunction(moduleAssetEntryGetOnUnloaded); + +/** @return The onError Event for this entry. */ +moduleBaseFunction(moduleAssetEntryGetOnError); + +/** + * loaded() — returns a Promise that resolves when the entry loads, or + * rejects on error. Resolves immediately if already loaded. + */ +moduleBaseFunction(moduleAssetEntryLoaded); + +/** @return "AssetEntry(name)" string. */ +moduleBaseFunction(moduleAssetEntryToString); + +/** Initializes the AssetEntry module. */ +void moduleAssetEntryInit(void); + +/** Disposes the AssetEntry module. */ +void moduleAssetEntryDispose(void); diff --git a/src/dusk/script/module/asset/moduleeventproxy.c b/src/dusk/script/module/asset/moduleeventproxy.c new file mode 100644 index 00000000..fdfc965d --- /dev/null +++ b/src/dusk/script/module/asset/moduleeventproxy.c @@ -0,0 +1,202 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "moduleeventproxy.h" +#include "util/memory.h" + +scriptproto_t MODULE_EVENT_PROXY_PROTO; + +void moduleEventProxyTrampoline0(void *params, void *user) { + jerry_value_t fn = (jerry_value_t)(uintptr_t)user; + jerry_value_t ret = jerry_call(fn, jerry_undefined(), NULL, 0); + jerry_value_free(ret); +} + +void moduleEventProxyTrampoline1(void *params, void *user) { + jerry_value_t fn = (jerry_value_t)(uintptr_t)user; + jerry_value_t ret = jerry_call(fn, jerry_undefined(), NULL, 0); + jerry_value_free(ret); +} + +void moduleEventProxyTrampoline2(void *params, void *user) { + jerry_value_t fn = (jerry_value_t)(uintptr_t)user; + jerry_value_t ret = jerry_call(fn, jerry_undefined(), NULL, 0); + jerry_value_free(ret); +} + +void moduleEventProxyTrampoline3(void *params, void *user) { + jerry_value_t fn = (jerry_value_t)(uintptr_t)user; + jerry_value_t ret = jerry_call(fn, jerry_undefined(), NULL, 0); + jerry_value_free(ret); +} + +eventcallback_t MODULE_EVENT_PROXY_TRAMPOLINES[MODULE_EVENT_PROXY_MAX_SLOTS] = { + moduleEventProxyTrampoline0, + moduleEventProxyTrampoline1, + moduleEventProxyTrampoline2, + moduleEventProxyTrampoline3, +}; + +void moduleEventProxyFree(void *ptr, jerry_object_native_info_t *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); +} + +jseventproxy_t *moduleEventProxySelf(const jerry_call_info_t *callInfo) { + return (jseventproxy_t *)scriptProtoGetValue( + &MODULE_EVENT_PROXY_PROTO, callInfo->this_value + ); +} + +static 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 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(); +} + +moduleBaseFunction(moduleEventProxyGet0) { + return moduleEventProxyGetSlot(callInfo, 0); +} +moduleBaseFunction(moduleEventProxySet0) { + return moduleEventProxySetSlot(callInfo, args, argc, 0); +} + +moduleBaseFunction(moduleEventProxyGet1) { + return moduleEventProxyGetSlot(callInfo, 1); +} +moduleBaseFunction(moduleEventProxySet1) { + return moduleEventProxySetSlot(callInfo, args, argc, 1); +} + +moduleBaseFunction(moduleEventProxyGet2) { + return moduleEventProxyGetSlot(callInfo, 2); +} +moduleBaseFunction(moduleEventProxySet2) { + return moduleEventProxySetSlot(callInfo, args, argc, 2); +} + +moduleBaseFunction(moduleEventProxyGet3) { + return moduleEventProxyGetSlot(callInfo, 3); +} +moduleBaseFunction(moduleEventProxySet3) { + return moduleEventProxySetSlot(callInfo, args, argc, 3); +} + +moduleBaseFunction(moduleEventProxyGetLength) { + jseventproxy_t *ep = moduleEventProxySelf(callInfo); + if(!ep || !ep->event) return jerry_number(0.0); + return jerry_number((double)ep->event->size); +} + +moduleBaseFunction(moduleEventProxyToString) { + return jerry_string_sz("EventProxy"); +} + +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; +} + +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 + ); +} + +void moduleEventProxyDispose(void) { + scriptProtoDispose(&MODULE_EVENT_PROXY_PROTO); +} diff --git a/src/dusk/script/module/asset/moduleeventproxy.h b/src/dusk/script/module/asset/moduleeventproxy.h index 9c9a8b31..f1c785b2 100644 --- a/src/dusk/script/module/asset/moduleeventproxy.h +++ b/src/dusk/script/module/asset/moduleeventproxy.h @@ -5,241 +5,6 @@ * https://opensource.org/licenses/MIT */ +/* Merged into script/module/event/moduleevent.h */ #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); -} +#include "script/module/event/moduleevent.h" diff --git a/src/dusk/script/module/console/CMakeLists.txt b/src/dusk/script/module/console/CMakeLists.txt new file mode 100644 index 00000000..9867bfde --- /dev/null +++ b/src/dusk/script/module/console/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + moduleconsole.c +) diff --git a/src/dusk/script/module/console/moduleconsole.c b/src/dusk/script/module/console/moduleconsole.c new file mode 100644 index 00000000..15fd17ed --- /dev/null +++ b/src/dusk/script/module/console/moduleconsole.c @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "moduleconsole.h" +#include "util/string.h" +#include + +scriptproto_t MODULE_CONSOLE_PROTO; + +moduleBaseFunction(moduleConsolePrint) { + char_t buf[512]; + char_t msg[4096]; + size_t msgLen = 0; + + for(jerry_length_t i = 0; i < argc; ++i) { + jerry_value_t strVal = jerry_value_to_string(args[i]); + moduleBaseToString(strVal, buf, sizeof(buf)); + jerry_value_free(strVal); + + size_t partLen = strlen(buf); + if(msgLen + partLen + 1 < sizeof(msg)) { + stringCopy(msg + msgLen, buf, sizeof(msg) - msgLen); + msgLen += partLen; + } + + if(i + 1 < argc && msgLen + 1 < sizeof(msg)) { + msg[msgLen++] = '\t'; + msg[msgLen] = '\0'; + } + } + + consolePrint("%s", msg); + return jerry_undefined(); +} + +moduleBaseFunction(moduleConsoleGetVisible) { + return jerry_boolean(CONSOLE.visible); +} + +moduleBaseFunction(moduleConsoleSetVisible) { + moduleBaseRequireArgs(1); + CONSOLE.visible = moduleBaseArgBool(0); + return jerry_undefined(); +} + +void moduleConsoleInit(void) { + scriptProtoInit( + &MODULE_CONSOLE_PROTO, "Console", + sizeof(uint8_t), NULL + ); + scriptProtoDefineStaticFunc( + &MODULE_CONSOLE_PROTO, "print", moduleConsolePrint + ); + scriptProtoDefineStaticProp( + &MODULE_CONSOLE_PROTO, "visible", + moduleConsoleGetVisible, moduleConsoleSetVisible + ); +} + +void moduleConsoleDispose(void) { + scriptProtoDispose(&MODULE_CONSOLE_PROTO); +} diff --git a/src/dusk/script/module/console/moduleconsole.h b/src/dusk/script/module/console/moduleconsole.h index 29f3b114..395c93a8 100644 --- a/src/dusk/script/module/console/moduleconsole.h +++ b/src/dusk/script/module/console/moduleconsole.h @@ -10,59 +10,27 @@ #include "script/scriptproto.h" #include "console/console.h" -static scriptproto_t MODULE_CONSOLE_PROTO; +extern scriptproto_t MODULE_CONSOLE_PROTO; -moduleBaseFunction(moduleConsolePrint) { - char_t buf[512]; - char_t msg[4096]; - size_t msgLen = 0; +/** + * Console.print(...args) — concatenates all arguments tab-separated and prints + * to the engine console. + */ +moduleBaseFunction(moduleConsolePrint); - for(jerry_length_t i = 0; i < argc; ++i) { - jerry_value_t strVal = jerry_value_to_string(args[i]); - moduleBaseToString(strVal, buf, sizeof(buf)); - jerry_value_free(strVal); +/** @return Whether the console overlay is currently visible. */ +moduleBaseFunction(moduleConsoleGetVisible); - size_t partLen = strlen(buf); - if(msgLen + partLen + 1 < sizeof(msg)) { - stringCopy(msg + msgLen, buf, sizeof(msg) - msgLen); - msgLen += partLen; - } +/** Sets console visibility. @param args[0] Boolean visible state. */ +moduleBaseFunction(moduleConsoleSetVisible); - if(i + 1 < argc && msgLen + 1 < sizeof(msg)) { - msg[msgLen++] = '\t'; - msg[msgLen] = '\0'; - } - } +/** + * Initializes the Console module and registers the global Console object with + * print() and the visible property. + */ +void moduleConsoleInit(void); - consolePrint("%s", msg); - return jerry_undefined(); -} - -moduleBaseFunction(moduleConsoleGetVisible) { - return jerry_boolean(CONSOLE.visible); -} - -moduleBaseFunction(moduleConsoleSetVisible) { - moduleBaseRequireArgs(1); - CONSOLE.visible = moduleBaseArgBool(0); - return jerry_undefined(); -} - -static void moduleConsoleInit(void) { - scriptProtoInit( - &MODULE_CONSOLE_PROTO, "Console", - sizeof(uint8_t), NULL - ); - - scriptProtoDefineStaticFunc( - &MODULE_CONSOLE_PROTO, "print", moduleConsolePrint - ); - scriptProtoDefineStaticProp( - &MODULE_CONSOLE_PROTO, "visible", - moduleConsoleGetVisible, moduleConsoleSetVisible - ); -} - -static void moduleConsoleDispose(void) { - scriptProtoDispose(&MODULE_CONSOLE_PROTO); -} +/** + * Disposes the Console module. + */ +void moduleConsoleDispose(void); diff --git a/src/dusk/script/module/display/CMakeLists.txt b/src/dusk/script/module/display/CMakeLists.txt new file mode 100644 index 00000000..f009d77d --- /dev/null +++ b/src/dusk/script/module/display/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + modulecolor.c + modulescreen.c + moduletexture.c +) diff --git a/src/dusk/script/module/display/modulecolor.c b/src/dusk/script/module/display/modulecolor.c new file mode 100644 index 00000000..09d77127 --- /dev/null +++ b/src/dusk/script/module/display/modulecolor.c @@ -0,0 +1,149 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "modulecolor.h" + +scriptproto_t MODULE_COLOR_PROTO; + +color_t *moduleColorFrom(const jerry_value_t val) { + return (color_t *)scriptProtoGetValue(&MODULE_COLOR_PROTO, val); +} + +jerry_value_t moduleColorPush(const color_t c) { + return scriptProtoCreateValue(&MODULE_COLOR_PROTO, &c); +} + +moduleBaseFunction(moduleColorConstructor) { + color_t *ptr = (color_t *)memoryAllocate(sizeof(color_t)); + ptr->r = (uint8_t)moduleBaseOptInt(0, 0); + ptr->g = (uint8_t)moduleBaseOptInt(1, 0); + ptr->b = (uint8_t)moduleBaseOptInt(2, 0); + ptr->a = (uint8_t)moduleBaseOptInt(3, 255); + jerry_object_set_native_ptr( + callInfo->this_value, &MODULE_COLOR_PROTO.info, ptr + ); + return jerry_undefined(); +} + +moduleBaseFunction(moduleColorGetR) { + color_t *c = moduleColorFrom(callInfo->this_value); + if(!c) return jerry_undefined(); + return jerry_number((double)c->r); +} + +moduleBaseFunction(moduleColorSetR) { + moduleBaseRequireArgs(1); + color_t *c = moduleColorFrom(callInfo->this_value); + if(!c) return jerry_undefined(); + c->r = (uint8_t)moduleBaseArgInt(0); + return jerry_undefined(); +} + +moduleBaseFunction(moduleColorGetG) { + color_t *c = moduleColorFrom(callInfo->this_value); + if(!c) return jerry_undefined(); + return jerry_number((double)c->g); +} + +moduleBaseFunction(moduleColorSetG) { + moduleBaseRequireArgs(1); + color_t *c = moduleColorFrom(callInfo->this_value); + if(!c) return jerry_undefined(); + c->g = (uint8_t)moduleBaseArgInt(0); + return jerry_undefined(); +} + +moduleBaseFunction(moduleColorGetB) { + color_t *c = moduleColorFrom(callInfo->this_value); + if(!c) return jerry_undefined(); + return jerry_number((double)c->b); +} + +moduleBaseFunction(moduleColorSetB) { + moduleBaseRequireArgs(1); + color_t *c = moduleColorFrom(callInfo->this_value); + if(!c) return jerry_undefined(); + c->b = (uint8_t)moduleBaseArgInt(0); + return jerry_undefined(); +} + +moduleBaseFunction(moduleColorGetA) { + color_t *c = moduleColorFrom(callInfo->this_value); + if(!c) return jerry_undefined(); + return jerry_number((double)c->a); +} + +moduleBaseFunction(moduleColorSetA) { + moduleBaseRequireArgs(1); + color_t *c = moduleColorFrom(callInfo->this_value); + if(!c) return jerry_undefined(); + c->a = (uint8_t)moduleBaseArgInt(0); + return jerry_undefined(); +} + +moduleBaseFunction(moduleColorToString) { + color_t *c = moduleColorFrom(callInfo->this_value); + if(!c) return jerry_string_sz("Color:invalid"); + char_t buf[32]; + snprintf(buf, sizeof(buf), "Color(%u,%u,%u,%u)", + (unsigned)c->r, (unsigned)c->g, + (unsigned)c->b, (unsigned)c->a + ); + return jerry_string_sz(buf); +} + +void moduleColorInit(void) { + scriptProtoInit( + &MODULE_COLOR_PROTO, "Color", + sizeof(color_t), moduleColorConstructor + ); + scriptProtoDefineProp( + &MODULE_COLOR_PROTO, "r", moduleColorGetR, moduleColorSetR + ); + scriptProtoDefineProp( + &MODULE_COLOR_PROTO, "g", moduleColorGetG, moduleColorSetG + ); + scriptProtoDefineProp( + &MODULE_COLOR_PROTO, "b", moduleColorGetB, moduleColorSetB + ); + scriptProtoDefineProp( + &MODULE_COLOR_PROTO, "a", moduleColorGetA, moduleColorSetA + ); + scriptProtoDefineToString(&MODULE_COLOR_PROTO, moduleColorToString); + + struct { const char_t *name; color_t val; } constants[] = { + { "WHITE", COLOR_WHITE }, + { "BLACK", COLOR_BLACK }, + { "RED", COLOR_RED }, + { "GREEN", COLOR_GREEN }, + { "BLUE", COLOR_BLUE }, + { "YELLOW", COLOR_YELLOW }, + { "CYAN", COLOR_CYAN }, + { "MAGENTA", COLOR_MAGENTA }, + { "TRANSPARENT", COLOR_TRANSPARENT }, + { "GRAY", COLOR_GRAY }, + { "LIGHT_GRAY", COLOR_LIGHT_GRAY }, + { "DARK_GRAY", COLOR_DARK_GRAY }, + { "ORANGE", COLOR_ORANGE }, + { "PURPLE", COLOR_PURPLE }, + { "PINK", COLOR_PINK }, + { "TEAL", COLOR_TEAL }, + { "CORNFLOWER_BLUE", COLOR_CORNFLOWER_BLUE }, + }; + jerry_value_t ctor = MODULE_COLOR_PROTO.constructor; + for(int i = 0; i < (int)(sizeof(constants)/sizeof(constants[0])); i++) { + jerry_value_t k = jerry_string_sz(constants[i].name); + jerry_value_t v = moduleColorPush(constants[i].val); + jerry_object_set(ctor, k, v); + jerry_value_free(v); + jerry_value_free(k); + } +} + +void moduleColorDispose(void) { + scriptProtoDispose(&MODULE_COLOR_PROTO); +} diff --git a/src/dusk/script/module/display/modulecolor.h b/src/dusk/script/module/display/modulecolor.h index f62ddbc4..df92f23e 100644 --- a/src/dusk/script/module/display/modulecolor.h +++ b/src/dusk/script/module/display/modulecolor.h @@ -11,144 +11,57 @@ #include "util/memory.h" #include "display/color.h" -static scriptproto_t MODULE_COLOR_PROTO; +extern scriptproto_t MODULE_COLOR_PROTO; /** - * Returns the native color_t pointer from a Color JS instance. - * Returns NULL if the value is not a Color. + * Returns the native color_t pointer from a Color JS value. + * + * @param val The JS value to extract from. + * @return Pointer to the color data, or NULL if not a Color. */ -static inline color_t *moduleColorFrom(const jerry_value_t val) { - return (color_t *)scriptProtoGetValue(&MODULE_COLOR_PROTO, val); -} +color_t *moduleColorFrom(const jerry_value_t val); /** - * Creates a Color JS object from a C color_t value. + * Creates a Color JS object wrapping a copy of a C color_t. + * + * @param c The source color. + * @return A new Color JS object. */ -static inline jerry_value_t moduleColorPush(const color_t c) { - return scriptProtoCreateValue(&MODULE_COLOR_PROTO, &c); -} +jerry_value_t moduleColorPush(const color_t c); -moduleBaseFunction(moduleColorConstructor) { - color_t *ptr = (color_t *)memoryAllocate(sizeof(color_t)); - ptr->r = (uint8_t)moduleBaseOptInt(0, 0); - ptr->g = (uint8_t)moduleBaseOptInt(1, 0); - ptr->b = (uint8_t)moduleBaseOptInt(2, 0); - ptr->a = (uint8_t)moduleBaseOptInt(3, 255); - jerry_object_set_native_ptr( - callInfo->this_value, &MODULE_COLOR_PROTO.info, ptr - ); - return jerry_undefined(); -} +/** Color(r?, g?, b?, a?) constructor. */ +moduleBaseFunction(moduleColorConstructor); -moduleBaseFunction(moduleColorGetR) { - color_t *c = moduleColorFrom(callInfo->this_value); - if(!c) return jerry_undefined(); - return jerry_number((double)c->r); -} +/** @return The red channel as a number. */ +moduleBaseFunction(moduleColorGetR); +/** Sets the red channel. @param args[0] New r value (0-255). */ +moduleBaseFunction(moduleColorSetR); -moduleBaseFunction(moduleColorSetR) { - moduleBaseRequireArgs(1); - color_t *c = moduleColorFrom(callInfo->this_value); - if(!c) return jerry_undefined(); - c->r = (uint8_t)moduleBaseArgInt(0); - return jerry_undefined(); -} +/** @return The green channel as a number. */ +moduleBaseFunction(moduleColorGetG); +/** Sets the green channel. @param args[0] New g value (0-255). */ +moduleBaseFunction(moduleColorSetG); -moduleBaseFunction(moduleColorGetG) { - color_t *c = moduleColorFrom(callInfo->this_value); - if(!c) return jerry_undefined(); - return jerry_number((double)c->g); -} +/** @return The blue channel as a number. */ +moduleBaseFunction(moduleColorGetB); +/** Sets the blue channel. @param args[0] New b value (0-255). */ +moduleBaseFunction(moduleColorSetB); -moduleBaseFunction(moduleColorSetG) { - moduleBaseRequireArgs(1); - color_t *c = moduleColorFrom(callInfo->this_value); - if(!c) return jerry_undefined(); - c->g = (uint8_t)moduleBaseArgInt(0); - return jerry_undefined(); -} +/** @return The alpha channel as a number. */ +moduleBaseFunction(moduleColorGetA); +/** Sets the alpha channel. @param args[0] New a value (0-255). */ +moduleBaseFunction(moduleColorSetA); -moduleBaseFunction(moduleColorGetB) { - color_t *c = moduleColorFrom(callInfo->this_value); - if(!c) return jerry_undefined(); - return jerry_number((double)c->b); -} +/** @return "Color(r,g,b,a)" string. */ +moduleBaseFunction(moduleColorToString); -moduleBaseFunction(moduleColorSetB) { - moduleBaseRequireArgs(1); - color_t *c = moduleColorFrom(callInfo->this_value); - if(!c) return jerry_undefined(); - c->b = (uint8_t)moduleBaseArgInt(0); - return jerry_undefined(); -} +/** + * Initializes the Color module and registers the global Color class with named + * color constants (Color.WHITE, Color.RED, etc.). + */ +void moduleColorInit(void); -moduleBaseFunction(moduleColorGetA) { - color_t *c = moduleColorFrom(callInfo->this_value); - if(!c) return jerry_undefined(); - return jerry_number((double)c->a); -} - -moduleBaseFunction(moduleColorSetA) { - moduleBaseRequireArgs(1); - color_t *c = moduleColorFrom(callInfo->this_value); - if(!c) return jerry_undefined(); - c->a = (uint8_t)moduleBaseArgInt(0); - return jerry_undefined(); -} - -moduleBaseFunction(moduleColorToString) { - color_t *c = moduleColorFrom(callInfo->this_value); - if(!c) return jerry_string_sz("Color:invalid"); - char_t buf[32]; - snprintf(buf, sizeof(buf), "Color(%u,%u,%u,%u)", - (unsigned)c->r, (unsigned)c->g, - (unsigned)c->b, (unsigned)c->a - ); - return jerry_string_sz(buf); -} - -static void moduleColorInit(void) { - scriptProtoInit( - &MODULE_COLOR_PROTO, "Color", - sizeof(color_t), moduleColorConstructor - ); - - scriptProtoDefineProp(&MODULE_COLOR_PROTO, "r", moduleColorGetR, moduleColorSetR); - scriptProtoDefineProp(&MODULE_COLOR_PROTO, "g", moduleColorGetG, moduleColorSetG); - scriptProtoDefineProp(&MODULE_COLOR_PROTO, "b", moduleColorGetB, moduleColorSetB); - scriptProtoDefineProp(&MODULE_COLOR_PROTO, "a", moduleColorGetA, moduleColorSetA); - scriptProtoDefineToString(&MODULE_COLOR_PROTO, moduleColorToString); - - /* Static named color constants on the constructor. */ - struct { const char_t *name; color_t val; } constants[] = { - { "WHITE", COLOR_WHITE }, - { "BLACK", COLOR_BLACK }, - { "RED", COLOR_RED }, - { "GREEN", COLOR_GREEN }, - { "BLUE", COLOR_BLUE }, - { "YELLOW", COLOR_YELLOW }, - { "CYAN", COLOR_CYAN }, - { "MAGENTA", COLOR_MAGENTA }, - { "TRANSPARENT", COLOR_TRANSPARENT }, - { "GRAY", COLOR_GRAY }, - { "LIGHT_GRAY", COLOR_LIGHT_GRAY }, - { "DARK_GRAY", COLOR_DARK_GRAY }, - { "ORANGE", COLOR_ORANGE }, - { "PURPLE", COLOR_PURPLE }, - { "PINK", COLOR_PINK }, - { "TEAL", COLOR_TEAL }, - { "CORNFLOWER_BLUE", COLOR_CORNFLOWER_BLUE }, - }; - jerry_value_t ctor = MODULE_COLOR_PROTO.constructor; - for(int i = 0; i < (int)(sizeof(constants)/sizeof(constants[0])); i++) { - jerry_value_t k = jerry_string_sz(constants[i].name); - jerry_value_t v = moduleColorPush(constants[i].val); - jerry_object_set(ctor, k, v); - jerry_value_free(v); - jerry_value_free(k); - } -} - -static void moduleColorDispose(void) { - scriptProtoDispose(&MODULE_COLOR_PROTO); -} +/** + * Disposes the Color module. + */ +void moduleColorDispose(void); diff --git a/src/dusk/script/module/display/modulescreen.c b/src/dusk/script/module/display/modulescreen.c new file mode 100644 index 00000000..71842da7 --- /dev/null +++ b/src/dusk/script/module/display/modulescreen.c @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "modulescreen.h" + +scriptproto_t MODULE_SCREEN_PROTO; + +moduleBaseFunction(moduleScreenGetWidth) { + return jerry_number((double)SCREEN.width); +} + +moduleBaseFunction(moduleScreenGetHeight) { + return jerry_number((double)SCREEN.height); +} + +moduleBaseFunction(moduleScreenGetAspect) { + return jerry_number((double)SCREEN.aspect); +} + +void moduleScreenInit(void) { + scriptProtoInit(&MODULE_SCREEN_PROTO, "Screen", sizeof(uint8_t), NULL); + scriptProtoDefineStaticProp( + &MODULE_SCREEN_PROTO, "width", moduleScreenGetWidth, NULL + ); + scriptProtoDefineStaticProp( + &MODULE_SCREEN_PROTO, "height", moduleScreenGetHeight, NULL + ); + scriptProtoDefineStaticProp( + &MODULE_SCREEN_PROTO, "aspect", moduleScreenGetAspect, NULL + ); +} + +void moduleScreenDispose(void) { + scriptProtoDispose(&MODULE_SCREEN_PROTO); +} diff --git a/src/dusk/script/module/display/modulescreen.h b/src/dusk/script/module/display/modulescreen.h index c2bc3204..e15a3c03 100644 --- a/src/dusk/script/module/display/modulescreen.h +++ b/src/dusk/script/module/display/modulescreen.h @@ -10,37 +10,24 @@ #include "script/scriptproto.h" #include "display/screen/screen.h" -static scriptproto_t MODULE_SCREEN_PROTO; +extern scriptproto_t MODULE_SCREEN_PROTO; -moduleBaseFunction(moduleScreenGetWidth) { - return jerry_number((double)SCREEN.width); -} +/** @return Current screen width in pixels. */ +moduleBaseFunction(moduleScreenGetWidth); -moduleBaseFunction(moduleScreenGetHeight) { - return jerry_number((double)SCREEN.height); -} +/** @return Current screen height in pixels. */ +moduleBaseFunction(moduleScreenGetHeight); -moduleBaseFunction(moduleScreenGetAspect) { - return jerry_number((double)SCREEN.aspect); -} +/** @return Current screen aspect ratio (width / height). */ +moduleBaseFunction(moduleScreenGetAspect); -static void moduleScreenInit(void) { - scriptProtoInit(&MODULE_SCREEN_PROTO, "Screen", sizeof(uint8_t), NULL); +/** + * Initializes the Screen module and registers read-only width/height/aspect + * properties on the global Screen object. + */ +void moduleScreenInit(void); - scriptProtoDefineStaticProp( - &MODULE_SCREEN_PROTO, "width", - moduleScreenGetWidth, NULL - ); - scriptProtoDefineStaticProp( - &MODULE_SCREEN_PROTO, "height", - moduleScreenGetHeight, NULL - ); - scriptProtoDefineStaticProp( - &MODULE_SCREEN_PROTO, "aspect", - moduleScreenGetAspect, NULL - ); -} - -static void moduleScreenDispose(void) { - scriptProtoDispose(&MODULE_SCREEN_PROTO); -} +/** + * Disposes the Screen module. + */ +void moduleScreenDispose(void); diff --git a/src/dusk/script/module/display/moduletexture.c b/src/dusk/script/module/display/moduletexture.c new file mode 100644 index 00000000..e787e103 --- /dev/null +++ b/src/dusk/script/module/display/moduletexture.c @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "moduletexture.h" + +scriptproto_t MODULE_TEXTURE_PROTO; + +void moduleTextureFree(void *ptr, jerry_object_native_info_t *info) { + jstexture_t *tex = (jstexture_t *)ptr; + if(tex && tex->entry) { + assetUnlockEntry(tex->entry); + tex->entry = NULL; + } + memoryFree(ptr); +} + +jstexture_t *moduleTextureSelf(const jerry_call_info_t *callInfo) { + return (jstexture_t *)scriptProtoGetValue( + &MODULE_TEXTURE_PROTO, callInfo->this_value + ); +} + +moduleBaseFunction(moduleTextureCtor) { + return moduleBaseThrow("Texture cannot be instantiated with new"); +} + +moduleBaseFunction(moduleTextureGetWidth) { + jstexture_t *t = moduleTextureSelf(callInfo); + if(!t || !t->entry) return jerry_undefined(); + return jerry_number((double)t->entry->data.texture.width); +} + +moduleBaseFunction(moduleTextureGetHeight) { + jstexture_t *t = moduleTextureSelf(callInfo); + if(!t || !t->entry) return jerry_undefined(); + return jerry_number((double)t->entry->data.texture.height); +} + +moduleBaseFunction(moduleTextureToString) { + jstexture_t *t = moduleTextureSelf(callInfo); + if(!t || !t->entry) return jerry_string_sz("Texture:invalid"); + char_t buf[64]; + snprintf(buf, sizeof(buf), "Texture(%dx%d)", + t->entry->data.texture.width, + t->entry->data.texture.height + ); + return jerry_string_sz(buf); +} + +void moduleTextureInit(void) { + scriptProtoInit( + &MODULE_TEXTURE_PROTO, "Texture", + sizeof(jstexture_t), moduleTextureCtor + ); + MODULE_TEXTURE_PROTO.info.free_cb = moduleTextureFree; + + scriptProtoDefineProp( + &MODULE_TEXTURE_PROTO, "width", moduleTextureGetWidth, NULL + ); + scriptProtoDefineProp( + &MODULE_TEXTURE_PROTO, "height", moduleTextureGetHeight, NULL + ); + scriptProtoDefineToString(&MODULE_TEXTURE_PROTO, moduleTextureToString); + + jerry_value_t ctor = MODULE_TEXTURE_PROTO.constructor; + struct { const char_t *name; int val; } formats[] = { + { "FORMAT_RGBA", TEXTURE_FORMAT_RGBA }, + { "FORMAT_PALETTE", TEXTURE_FORMAT_PALETTE }, + }; + for(int i = 0; i < 2; i++) { + jerry_value_t k = jerry_string_sz(formats[i].name); + jerry_value_t v = jerry_number((double)formats[i].val); + jerry_object_set(ctor, k, v); + jerry_value_free(v); + jerry_value_free(k); + } +} + +void moduleTextureDispose(void) { + scriptProtoDispose(&MODULE_TEXTURE_PROTO); +} diff --git a/src/dusk/script/module/display/moduletexture.h b/src/dusk/script/module/display/moduletexture.h index da66fbc3..392e2d53 100644 --- a/src/dusk/script/module/display/moduletexture.h +++ b/src/dusk/script/module/display/moduletexture.h @@ -13,96 +13,49 @@ #include "display/texture/texture.h" #include "util/memory.h" -static scriptproto_t MODULE_TEXTURE_PROTO; +extern scriptproto_t MODULE_TEXTURE_PROTO; +/** C struct wrapped by every Texture JS instance. */ typedef struct { assetentry_t *entry; } jstexture_t; /** - * Custom free callback — unlocks the asset entry so it can be reclaimed - * once the JS Texture object is garbage collected. + * GC free callback — unlocks the asset entry when the Texture JS object is + * garbage collected. + * + * @param ptr Native jstexture_t pointer. + * @param info Native info (unused). */ -static void moduleTextureFree( - void *ptr, - jerry_object_native_info_t *info -) { - (void)info; - jstexture_t *tex = (jstexture_t *)ptr; - if(tex && tex->entry) { - assetUnlockEntry(tex->entry); - tex->entry = NULL; - } - memoryFree(ptr); -} +void moduleTextureFree(void *ptr, jerry_object_native_info_t *info); -moduleBaseFunction(moduleTextureCtor) { - (void)callInfo; (void)args; (void)argc; - return moduleBaseThrow("Texture cannot be instantiated with new"); -} +/** + * Returns the jstexture_t pointer from the current this_value. + * + * @param callInfo The call info. + * @return Pointer to the jstexture_t, or NULL if invalid. + */ +jstexture_t *moduleTextureSelf(const jerry_call_info_t *callInfo); -static inline jstexture_t *moduleTextureSelf( - const jerry_call_info_t *callInfo -) { - return (jstexture_t *)scriptProtoGetValue( - &MODULE_TEXTURE_PROTO, callInfo->this_value - ); -} +/** Texture() constructor — always throws; not directly instantiable. */ +moduleBaseFunction(moduleTextureCtor); -moduleBaseFunction(moduleTextureGetWidth) { - jstexture_t *t = moduleTextureSelf(callInfo); - if(!t || !t->entry) return jerry_undefined(); - return jerry_number((double)t->entry->data.texture.width); -} +/** @return Texture width in pixels, or undefined if not loaded. */ +moduleBaseFunction(moduleTextureGetWidth); -moduleBaseFunction(moduleTextureGetHeight) { - jstexture_t *t = moduleTextureSelf(callInfo); - if(!t || !t->entry) return jerry_undefined(); - return jerry_number((double)t->entry->data.texture.height); -} +/** @return Texture height in pixels, or undefined if not loaded. */ +moduleBaseFunction(moduleTextureGetHeight); -moduleBaseFunction(moduleTextureToString) { - jstexture_t *t = moduleTextureSelf(callInfo); - if(!t || !t->entry) return jerry_string_sz("Texture:invalid"); - char_t buf[64]; - snprintf(buf, sizeof(buf), "Texture(%dx%d)", - t->entry->data.texture.width, - t->entry->data.texture.height - ); - return jerry_string_sz(buf); -} +/** @return "Texture(WxH)" string. */ +moduleBaseFunction(moduleTextureToString); -static void moduleTextureInit(void) { - scriptProtoInit( - &MODULE_TEXTURE_PROTO, "Texture", - sizeof(jstexture_t), moduleTextureCtor - ); - /* Override the default free callback so the asset lock is released on GC. */ - MODULE_TEXTURE_PROTO.info.free_cb = moduleTextureFree; +/** + * Initializes the Texture module and registers the global Texture class with + * FORMAT_RGBA and FORMAT_PALETTE constants. + */ +void moduleTextureInit(void); - scriptProtoDefineProp( - &MODULE_TEXTURE_PROTO, "width", moduleTextureGetWidth, NULL - ); - scriptProtoDefineProp( - &MODULE_TEXTURE_PROTO, "height", moduleTextureGetHeight, NULL - ); - scriptProtoDefineToString(&MODULE_TEXTURE_PROTO, moduleTextureToString); - - /* Texture.FORMAT_* constants */ - jerry_value_t ctor = MODULE_TEXTURE_PROTO.constructor; - struct { const char_t *name; int val; } formats[] = { - { "FORMAT_RGBA", TEXTURE_FORMAT_RGBA }, - { "FORMAT_PALETTE", TEXTURE_FORMAT_PALETTE }, - }; - for(int i = 0; i < 2; i++) { - jerry_value_t k = jerry_string_sz(formats[i].name); - jerry_value_t v = jerry_number((double)formats[i].val); - jerry_object_set(ctor, k, v); - jerry_value_free(v); - jerry_value_free(k); - } -} - -static void moduleTextureDispose(void) { - scriptProtoDispose(&MODULE_TEXTURE_PROTO); -} +/** + * Disposes the Texture module. + */ +void moduleTextureDispose(void); diff --git a/src/dusk/script/module/engine/CMakeLists.txt b/src/dusk/script/module/engine/CMakeLists.txt new file mode 100644 index 00000000..87bf0ea6 --- /dev/null +++ b/src/dusk/script/module/engine/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + moduleengine.c + moduleframe.c + moduletimeout.c +) diff --git a/src/dusk/script/module/engine/moduleengine.c b/src/dusk/script/module/engine/moduleengine.c new file mode 100644 index 00000000..9cccc1c7 --- /dev/null +++ b/src/dusk/script/module/engine/moduleengine.c @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "moduleengine.h" + +scriptproto_t MODULE_ENGINE_PROTO; + +moduleBaseFunction(moduleEngineGetRunning) { + return jerry_boolean(ENGINE.running); +} + +moduleBaseFunction(moduleEngineExit) { + ENGINE.running = false; + return jerry_undefined(); +} + +void moduleEngineInit(void) { + scriptProtoInit(&MODULE_ENGINE_PROTO, "Engine", sizeof(uint8_t), NULL); + scriptProtoDefineStaticProp( + &MODULE_ENGINE_PROTO, "running", + moduleEngineGetRunning, NULL + ); + scriptProtoDefineStaticFunc( + &MODULE_ENGINE_PROTO, "exit", moduleEngineExit + ); +} + +void moduleEngineDispose(void) { + scriptProtoDispose(&MODULE_ENGINE_PROTO); +} diff --git a/src/dusk/script/module/engine/moduleengine.h b/src/dusk/script/module/engine/moduleengine.h index 693ce978..11b7aa3d 100644 --- a/src/dusk/script/module/engine/moduleengine.h +++ b/src/dusk/script/module/engine/moduleengine.h @@ -10,29 +10,21 @@ #include "script/scriptproto.h" #include "engine/engine.h" -static scriptproto_t MODULE_ENGINE_PROTO; +extern scriptproto_t MODULE_ENGINE_PROTO; -moduleBaseFunction(moduleEngineGetRunning) { - return jerry_boolean(ENGINE.running); -} +/** @return True if the engine main loop is still running. */ +moduleBaseFunction(moduleEngineGetRunning); -moduleBaseFunction(moduleEngineExit) { - ENGINE.running = false; - return jerry_undefined(); -} +/** Signals the engine to stop on the next frame. */ +moduleBaseFunction(moduleEngineExit); -static void moduleEngineInit(void) { - scriptProtoInit(&MODULE_ENGINE_PROTO, "Engine", sizeof(uint8_t), NULL); +/** + * Initializes the Engine module and registers the global Engine object with the + * running property and exit() method. + */ +void moduleEngineInit(void); - scriptProtoDefineStaticProp( - &MODULE_ENGINE_PROTO, "running", - moduleEngineGetRunning, NULL - ); - scriptProtoDefineStaticFunc( - &MODULE_ENGINE_PROTO, "exit", moduleEngineExit - ); -} - -static void moduleEngineDispose(void) { - scriptProtoDispose(&MODULE_ENGINE_PROTO); -} +/** + * Disposes the Engine module. + */ +void moduleEngineDispose(void); diff --git a/src/dusk/script/module/engine/moduleframe.c b/src/dusk/script/module/engine/moduleframe.c new file mode 100644 index 00000000..5ab00613 --- /dev/null +++ b/src/dusk/script/module/engine/moduleframe.c @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "moduleframe.h" + +jerry_value_t MODULE_FRAME_PENDING[MODULE_FRAME_PENDING_MAX]; +uint32_t MODULE_FRAME_PENDING_COUNT = 0; + +moduleBaseFunction(moduleFrameFrame) { + if(MODULE_FRAME_PENDING_COUNT >= MODULE_FRAME_PENDING_MAX) { + return moduleBaseThrow("Too many pending frame() calls"); + } + jerry_value_t promise = jerry_promise(); + MODULE_FRAME_PENDING[MODULE_FRAME_PENDING_COUNT++] = + jerry_value_copy(promise); + return promise; +} + +void moduleFrameFlush(void) { + uint32_t count = MODULE_FRAME_PENDING_COUNT; + MODULE_FRAME_PENDING_COUNT = 0; + for(uint32_t i = 0; i < count; i++) { + jerry_value_t ret = jerry_promise_resolve( + MODULE_FRAME_PENDING[i], jerry_undefined() + ); + jerry_value_free(ret); + jerry_value_free(MODULE_FRAME_PENDING[i]); + } +} + +void moduleFrameInit(void) { + MODULE_FRAME_PENDING_COUNT = 0; + moduleBaseDefineGlobalMethod("frame", moduleFrameFrame); +} + +void moduleFrameDispose(void) { + for(uint32_t i = 0; i < MODULE_FRAME_PENDING_COUNT; i++) { + jerry_value_free(MODULE_FRAME_PENDING[i]); + } + MODULE_FRAME_PENDING_COUNT = 0; +} diff --git a/src/dusk/script/module/engine/moduleframe.h b/src/dusk/script/module/engine/moduleframe.h index 167ae09f..f7fff5d3 100644 --- a/src/dusk/script/module/engine/moduleframe.h +++ b/src/dusk/script/module/engine/moduleframe.h @@ -8,38 +8,30 @@ #pragma once #include "script/module/modulebase.h" +/** Maximum number of concurrent frame() awaits. */ #define MODULE_FRAME_PENDING_MAX 64 -static jerry_value_t MODULE_FRAME_PENDING[MODULE_FRAME_PENDING_MAX]; -static uint32_t MODULE_FRAME_PENDING_COUNT = 0; +extern jerry_value_t MODULE_FRAME_PENDING[MODULE_FRAME_PENDING_MAX]; +extern uint32_t MODULE_FRAME_PENDING_COUNT; -moduleBaseFunction(moduleFrameFrame) { - if(MODULE_FRAME_PENDING_COUNT >= MODULE_FRAME_PENDING_MAX) { - return moduleBaseThrow("Too many pending frame() calls"); - } - jerry_value_t promise = jerry_promise(); - MODULE_FRAME_PENDING[MODULE_FRAME_PENDING_COUNT++] = jerry_value_copy(promise); - return promise; -} +/** + * frame() — returns a Promise that resolves at the start of the next frame. + * Used as `await frame()` inside an async script loop. + */ +moduleBaseFunction(moduleFrameFrame); -static void moduleFrameFlush(void) { - uint32_t count = MODULE_FRAME_PENDING_COUNT; - MODULE_FRAME_PENDING_COUNT = 0; - for(uint32_t i = 0; i < count; i++) { - jerry_value_t ret = jerry_promise_resolve(MODULE_FRAME_PENDING[i], jerry_undefined()); - jerry_value_free(ret); - jerry_value_free(MODULE_FRAME_PENDING[i]); - } -} +/** + * Resolves all pending frame() promises. Must be called once per frame before + * jerry_run_jobs() so that awaiting scripts resume in the same tick. + */ +void moduleFrameFlush(void); -static void moduleFrameInit(void) { - MODULE_FRAME_PENDING_COUNT = 0; - moduleBaseDefineGlobalMethod("frame", moduleFrameFrame); -} +/** + * Initializes the frame module and registers the global frame() function. + */ +void moduleFrameInit(void); -static void moduleFrameDispose(void) { - for(uint32_t i = 0; i < MODULE_FRAME_PENDING_COUNT; i++) { - jerry_value_free(MODULE_FRAME_PENDING[i]); - } - MODULE_FRAME_PENDING_COUNT = 0; -} +/** + * Disposes the frame module, releasing any unresolved pending promises. + */ +void moduleFrameDispose(void); diff --git a/src/dusk/script/module/engine/moduletimeout.c b/src/dusk/script/module/engine/moduletimeout.c new file mode 100644 index 00000000..7417e408 --- /dev/null +++ b/src/dusk/script/module/engine/moduletimeout.c @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "moduletimeout.h" + +moduletimeoutentry_t MODULE_TIMEOUT_PENDING[MODULE_TIMEOUT_PENDING_MAX]; +uint32_t MODULE_TIMEOUT_PENDING_COUNT = 0; + +moduleBaseFunction(moduleTimeoutTimeout) { + moduleBaseRequireArgs(1); + moduleBaseRequireNumber(0); + + if(MODULE_TIMEOUT_PENDING_COUNT >= MODULE_TIMEOUT_PENDING_MAX) { + return moduleBaseThrow("Too many pending timeout() calls"); + } + + float_t ms = moduleBaseArgFloat(0); + jerry_value_t promise = jerry_promise(); + MODULE_TIMEOUT_PENDING[MODULE_TIMEOUT_PENDING_COUNT].promise = + jerry_value_copy(promise); + MODULE_TIMEOUT_PENDING[MODULE_TIMEOUT_PENDING_COUNT].targetTime = + TIME.time + ms / 1000.0f; + MODULE_TIMEOUT_PENDING_COUNT++; + return promise; +} + +void moduleTimeoutFlush(void) { + uint32_t i = 0; + while(i < MODULE_TIMEOUT_PENDING_COUNT) { + if(TIME.time >= MODULE_TIMEOUT_PENDING[i].targetTime) { + jerry_value_t ret = jerry_promise_resolve( + MODULE_TIMEOUT_PENDING[i].promise, jerry_undefined() + ); + jerry_value_free(ret); + jerry_value_free(MODULE_TIMEOUT_PENDING[i].promise); + MODULE_TIMEOUT_PENDING_COUNT--; + if(i < MODULE_TIMEOUT_PENDING_COUNT) { + MODULE_TIMEOUT_PENDING[i] = + MODULE_TIMEOUT_PENDING[MODULE_TIMEOUT_PENDING_COUNT]; + } + } else { + i++; + } + } +} + +void moduleTimeoutInit(void) { + MODULE_TIMEOUT_PENDING_COUNT = 0; + moduleBaseDefineGlobalMethod("timeout", moduleTimeoutTimeout); +} + +void moduleTimeoutDispose(void) { + for(uint32_t i = 0; i < MODULE_TIMEOUT_PENDING_COUNT; i++) { + jerry_value_free(MODULE_TIMEOUT_PENDING[i].promise); + } + MODULE_TIMEOUT_PENDING_COUNT = 0; +} diff --git a/src/dusk/script/module/engine/moduletimeout.h b/src/dusk/script/module/engine/moduletimeout.h index 78575b98..b8433b4b 100644 --- a/src/dusk/script/module/engine/moduletimeout.h +++ b/src/dusk/script/module/engine/moduletimeout.h @@ -9,59 +9,38 @@ #include "script/module/modulebase.h" #include "time/time.h" +/** Maximum number of concurrent timeout() awaits. */ #define MODULE_TIMEOUT_PENDING_MAX 64 +/** Entry tracking one pending timeout() promise. */ typedef struct { jerry_value_t promise; float_t targetTime; } moduletimeoutentry_t; -static moduletimeoutentry_t MODULE_TIMEOUT_PENDING[MODULE_TIMEOUT_PENDING_MAX]; -static uint32_t MODULE_TIMEOUT_PENDING_COUNT = 0; +extern moduletimeoutentry_t MODULE_TIMEOUT_PENDING[MODULE_TIMEOUT_PENDING_MAX]; +extern uint32_t MODULE_TIMEOUT_PENDING_COUNT; -moduleBaseFunction(moduleTimeoutTimeout) { - moduleBaseRequireArgs(1); - moduleBaseRequireNumber(0); +/** + * timeout(ms) — returns a Promise that resolves after at least `ms` + * milliseconds have elapsed. Used as `await timeout(500)`. + * + * @param args[0] Delay in milliseconds (number). + */ +moduleBaseFunction(moduleTimeoutTimeout); - if(MODULE_TIMEOUT_PENDING_COUNT >= MODULE_TIMEOUT_PENDING_MAX) { - return moduleBaseThrow("Too many pending timeout() calls"); - } +/** + * Resolves any pending timeout() promises whose target time has passed. + * Must be called once per frame before jerry_run_jobs(). + */ +void moduleTimeoutFlush(void); - float_t ms = moduleBaseArgFloat(0); - jerry_value_t promise = jerry_promise(); - MODULE_TIMEOUT_PENDING[MODULE_TIMEOUT_PENDING_COUNT].promise = jerry_value_copy(promise); - MODULE_TIMEOUT_PENDING[MODULE_TIMEOUT_PENDING_COUNT].targetTime = TIME.time + ms / 1000.0f; - MODULE_TIMEOUT_PENDING_COUNT++; - return promise; -} +/** + * Initializes the timeout module and registers the global timeout() function. + */ +void moduleTimeoutInit(void); -static void moduleTimeoutFlush(void) { - uint32_t i = 0; - while(i < MODULE_TIMEOUT_PENDING_COUNT) { - if(TIME.time >= MODULE_TIMEOUT_PENDING[i].targetTime) { - jerry_value_t ret = jerry_promise_resolve( - MODULE_TIMEOUT_PENDING[i].promise, jerry_undefined() - ); - jerry_value_free(ret); - jerry_value_free(MODULE_TIMEOUT_PENDING[i].promise); - MODULE_TIMEOUT_PENDING_COUNT--; - if(i < MODULE_TIMEOUT_PENDING_COUNT) { - MODULE_TIMEOUT_PENDING[i] = MODULE_TIMEOUT_PENDING[MODULE_TIMEOUT_PENDING_COUNT]; - } - } else { - i++; - } - } -} - -static void moduleTimeoutInit(void) { - MODULE_TIMEOUT_PENDING_COUNT = 0; - moduleBaseDefineGlobalMethod("timeout", moduleTimeoutTimeout); -} - -static void moduleTimeoutDispose(void) { - for(uint32_t i = 0; i < MODULE_TIMEOUT_PENDING_COUNT; i++) { - jerry_value_free(MODULE_TIMEOUT_PENDING[i].promise); - } - MODULE_TIMEOUT_PENDING_COUNT = 0; -} +/** + * Disposes the timeout module, releasing any unresolved pending promises. + */ +void moduleTimeoutDispose(void); diff --git a/src/dusk/script/module/entity/CMakeLists.txt b/src/dusk/script/module/entity/CMakeLists.txt new file mode 100644 index 00000000..a5b59035 --- /dev/null +++ b/src/dusk/script/module/entity/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + modulecomponent.c + moduleentity.c +) + +# Subdirs +add_subdirectory(component) diff --git a/src/dusk/script/module/entity/component/CMakeLists.txt b/src/dusk/script/module/entity/component/CMakeLists.txt new file mode 100644 index 00000000..18ba1045 --- /dev/null +++ b/src/dusk/script/module/entity/component/CMakeLists.txt @@ -0,0 +1,14 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + modulecomponentlist.c +) + +# Subdirs +add_subdirectory(display) +add_subdirectory(physics) +add_subdirectory(trigger) diff --git a/src/dusk/script/module/entity/component/display/CMakeLists.txt b/src/dusk/script/module/entity/component/display/CMakeLists.txt new file mode 100644 index 00000000..76c71939 --- /dev/null +++ b/src/dusk/script/module/entity/component/display/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + modulecamera.c + moduleposition.c + modulerenderable.c +) diff --git a/src/dusk/script/module/entity/component/display/modulecamera.c b/src/dusk/script/module/entity/component/display/modulecamera.c new file mode 100644 index 00000000..df143525 --- /dev/null +++ b/src/dusk/script/module/entity/component/display/modulecamera.c @@ -0,0 +1,169 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "modulecamera.h" + +scriptproto_t MODULE_CAMERA_PROTO; + +jscomponent_t *moduleCameraSelf(const jerry_call_info_t *callInfo) { + return (jscomponent_t *)scriptProtoGetValue( + &MODULE_CAMERA_PROTO, callInfo->this_value + ); +} + +entitycamera_t *moduleCameraData(const jscomponent_t *c) { + return (entitycamera_t *)componentGetData( + c->entityId, c->componentId, COMPONENT_TYPE_CAMERA + ); +} + +moduleBaseFunction(moduleCameraCtor) { + return moduleBaseThrow("Camera cannot be instantiated with new"); +} + +moduleBaseFunction(moduleCameraGetEntity) { + jscomponent_t *c = moduleCameraSelf(callInfo); + if(!c) return jerry_undefined(); + return jerry_number((double)c->entityId); +} + +moduleBaseFunction(moduleCameraGetId) { + jscomponent_t *c = moduleCameraSelf(callInfo); + if(!c) return jerry_undefined(); + return jerry_number((double)c->componentId); +} + +moduleBaseFunction(moduleCameraGetFov) { + jscomponent_t *c = moduleCameraSelf(callInfo); + if(!c) return jerry_undefined(); + entitycamera_t *cam = moduleCameraData(c); + if(!cam) return jerry_undefined(); + return jerry_number((double)cam->perspective.fov); +} + +moduleBaseFunction(moduleCameraSetFov) { + moduleBaseRequireArgs(1); + jscomponent_t *c = moduleCameraSelf(callInfo); + if(!c) return jerry_undefined(); + entitycamera_t *cam = moduleCameraData(c); + if(!cam) return jerry_undefined(); + cam->perspective.fov = moduleBaseArgFloat(0); + return jerry_undefined(); +} + +moduleBaseFunction(moduleCameraGetNearClip) { + jscomponent_t *c = moduleCameraSelf(callInfo); + if(!c) return jerry_undefined(); + entitycamera_t *cam = moduleCameraData(c); + if(!cam) return jerry_undefined(); + return jerry_number((double)cam->nearClip); +} + +moduleBaseFunction(moduleCameraSetNearClip) { + moduleBaseRequireArgs(1); + jscomponent_t *c = moduleCameraSelf(callInfo); + if(!c) return jerry_undefined(); + entitycamera_t *cam = moduleCameraData(c); + if(!cam) return jerry_undefined(); + cam->nearClip = moduleBaseArgFloat(0); + return jerry_undefined(); +} + +moduleBaseFunction(moduleCameraGetFarClip) { + jscomponent_t *c = moduleCameraSelf(callInfo); + if(!c) return jerry_undefined(); + entitycamera_t *cam = moduleCameraData(c); + if(!cam) return jerry_undefined(); + return jerry_number((double)cam->farClip); +} + +moduleBaseFunction(moduleCameraSetFarClip) { + moduleBaseRequireArgs(1); + jscomponent_t *c = moduleCameraSelf(callInfo); + if(!c) return jerry_undefined(); + entitycamera_t *cam = moduleCameraData(c); + if(!cam) return jerry_undefined(); + cam->farClip = moduleBaseArgFloat(0); + return jerry_undefined(); +} + +moduleBaseFunction(moduleCameraGetProjType) { + jscomponent_t *c = moduleCameraSelf(callInfo); + if(!c) return jerry_undefined(); + entitycamera_t *cam = moduleCameraData(c); + if(!cam) return jerry_undefined(); + return jerry_number((double)cam->projType); +} + +moduleBaseFunction(moduleCameraSetProjType) { + moduleBaseRequireArgs(1); + jscomponent_t *c = moduleCameraSelf(callInfo); + if(!c) return jerry_undefined(); + entitycamera_t *cam = moduleCameraData(c); + if(!cam) return jerry_undefined(); + cam->projType = (entitycameraprojectiontype_t)moduleBaseArgInt(0); + return jerry_undefined(); +} + +moduleBaseFunction(moduleCameraToString) { + jscomponent_t *c = moduleCameraSelf(callInfo); + if(!c) return jerry_string_sz("Camera:invalid"); + char_t buf[32]; + snprintf(buf, sizeof(buf), "Camera(%u)", (unsigned)c->componentId); + return jerry_string_sz(buf); +} + +void moduleCameraInit(void) { + scriptProtoInit( + &MODULE_CAMERA_PROTO, "Camera", + sizeof(jscomponent_t), moduleCameraCtor + ); + + scriptProtoDefineProp( + &MODULE_CAMERA_PROTO, "entity", moduleCameraGetEntity, NULL + ); + scriptProtoDefineProp( + &MODULE_CAMERA_PROTO, "id", moduleCameraGetId, NULL + ); + scriptProtoDefineProp( + &MODULE_CAMERA_PROTO, "fov", moduleCameraGetFov, moduleCameraSetFov + ); + scriptProtoDefineProp( + &MODULE_CAMERA_PROTO, "nearClip", + moduleCameraGetNearClip, moduleCameraSetNearClip + ); + scriptProtoDefineProp( + &MODULE_CAMERA_PROTO, "farClip", + moduleCameraGetFarClip, moduleCameraSetFarClip + ); + scriptProtoDefineProp( + &MODULE_CAMERA_PROTO, "projType", + moduleCameraGetProjType, moduleCameraSetProjType + ); + scriptProtoDefineToString(&MODULE_CAMERA_PROTO, moduleCameraToString); + + jerry_value_t ctor = MODULE_CAMERA_PROTO.constructor; + struct { const char_t *name; int val; } projtypes[] = { + { "PERSPECTIVE", ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE }, + { + "PERSPECTIVE_FLIPPED", + ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED + }, + { "ORTHOGRAPHIC", ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC }, + }; + for(int i = 0; i < 3; i++) { + jerry_value_t k = jerry_string_sz(projtypes[i].name); + jerry_value_t v = jerry_number((double)projtypes[i].val); + jerry_object_set(ctor, k, v); + jerry_value_free(v); + jerry_value_free(k); + } +} + +void moduleCameraDispose(void) { + scriptProtoDispose(&MODULE_CAMERA_PROTO); +} diff --git a/src/dusk/script/module/entity/component/display/modulecamera.h b/src/dusk/script/module/entity/component/display/modulecamera.h index d12f0dc6..49495bc2 100644 --- a/src/dusk/script/module/entity/component/display/modulecamera.h +++ b/src/dusk/script/module/entity/component/display/modulecamera.h @@ -11,164 +11,52 @@ #include "script/module/entity/modulecomponent.h" #include "entity/component/display/entitycamera.h" -static scriptproto_t MODULE_CAMERA_PROTO; +extern scriptproto_t MODULE_CAMERA_PROTO; -moduleBaseFunction(moduleCameraCtor) { - (void)callInfo; (void)args; (void)argc; - return moduleBaseThrow("Camera cannot be instantiated with new"); -} +/** Camera() constructor — always throws; not directly instantiable. */ +moduleBaseFunction(moduleCameraCtor); -static inline jscomponent_t *moduleCameraSelf( - const jerry_call_info_t *callInfo -) { - return (jscomponent_t *)scriptProtoGetValue( - &MODULE_CAMERA_PROTO, callInfo->this_value - ); -} +/** @return Entity ID that owns this camera component. */ +moduleBaseFunction(moduleCameraGetEntity); -static inline entitycamera_t *moduleCameraData(const jscomponent_t *c) { - return (entitycamera_t *)componentGetData( - c->entityId, c->componentId, COMPONENT_TYPE_CAMERA - ); -} +/** @return This component's ID. */ +moduleBaseFunction(moduleCameraGetId); -moduleBaseFunction(moduleCameraGetEntity) { - jscomponent_t *c = moduleCameraSelf(callInfo); - if(!c) return jerry_undefined(); - return jerry_number((double)c->entityId); -} +/** @return Field of view in degrees. */ +moduleBaseFunction(moduleCameraGetFov); -moduleBaseFunction(moduleCameraGetId) { - jscomponent_t *c = moduleCameraSelf(callInfo); - if(!c) return jerry_undefined(); - return jerry_number((double)c->componentId); -} +/** Sets field of view. @param args[0] FOV in degrees. */ +moduleBaseFunction(moduleCameraSetFov); -moduleBaseFunction(moduleCameraGetFov) { - jscomponent_t *c = moduleCameraSelf(callInfo); - if(!c) return jerry_undefined(); - entitycamera_t *cam = moduleCameraData(c); - if(!cam) return jerry_undefined(); - return jerry_number((double)cam->perspective.fov); -} +/** @return Near clip plane distance. */ +moduleBaseFunction(moduleCameraGetNearClip); -moduleBaseFunction(moduleCameraSetFov) { - moduleBaseRequireArgs(1); - jscomponent_t *c = moduleCameraSelf(callInfo); - if(!c) return jerry_undefined(); - entitycamera_t *cam = moduleCameraData(c); - if(!cam) return jerry_undefined(); - cam->perspective.fov = moduleBaseArgFloat(0); - return jerry_undefined(); -} +/** Sets near clip plane. @param args[0] Near clip distance. */ +moduleBaseFunction(moduleCameraSetNearClip); -moduleBaseFunction(moduleCameraGetNearClip) { - jscomponent_t *c = moduleCameraSelf(callInfo); - if(!c) return jerry_undefined(); - entitycamera_t *cam = moduleCameraData(c); - if(!cam) return jerry_undefined(); - return jerry_number((double)cam->nearClip); -} +/** @return Far clip plane distance. */ +moduleBaseFunction(moduleCameraGetFarClip); -moduleBaseFunction(moduleCameraSetNearClip) { - moduleBaseRequireArgs(1); - jscomponent_t *c = moduleCameraSelf(callInfo); - if(!c) return jerry_undefined(); - entitycamera_t *cam = moduleCameraData(c); - if(!cam) return jerry_undefined(); - cam->nearClip = moduleBaseArgFloat(0); - return jerry_undefined(); -} +/** Sets far clip plane. @param args[0] Far clip distance. */ +moduleBaseFunction(moduleCameraSetFarClip); -moduleBaseFunction(moduleCameraGetFarClip) { - jscomponent_t *c = moduleCameraSelf(callInfo); - if(!c) return jerry_undefined(); - entitycamera_t *cam = moduleCameraData(c); - if(!cam) return jerry_undefined(); - return jerry_number((double)cam->farClip); -} +/** @return Projection type constant (Camera.PERSPECTIVE etc.). */ +moduleBaseFunction(moduleCameraGetProjType); -moduleBaseFunction(moduleCameraSetFarClip) { - moduleBaseRequireArgs(1); - jscomponent_t *c = moduleCameraSelf(callInfo); - if(!c) return jerry_undefined(); - entitycamera_t *cam = moduleCameraData(c); - if(!cam) return jerry_undefined(); - cam->farClip = moduleBaseArgFloat(0); - return jerry_undefined(); -} +/** Sets projection type. @param args[0] entitycameraprojectiontype_t value. */ +moduleBaseFunction(moduleCameraSetProjType); -moduleBaseFunction(moduleCameraGetProjType) { - jscomponent_t *c = moduleCameraSelf(callInfo); - if(!c) return jerry_undefined(); - entitycamera_t *cam = moduleCameraData(c); - if(!cam) return jerry_undefined(); - return jerry_number((double)cam->projType); -} +/** @return "Camera(id)" string. */ +moduleBaseFunction(moduleCameraToString); -moduleBaseFunction(moduleCameraSetProjType) { - moduleBaseRequireArgs(1); - jscomponent_t *c = moduleCameraSelf(callInfo); - if(!c) return jerry_undefined(); - entitycamera_t *cam = moduleCameraData(c); - if(!cam) return jerry_undefined(); - cam->projType = (entitycameraprojectiontype_t)moduleBaseArgInt(0); - return jerry_undefined(); -} +/** + * Initializes the Camera module and registers the global Camera class with + * fov/nearClip/farClip/projType properties and PERSPECTIVE/ORTHOGRAPHIC + * constants. + */ +void moduleCameraInit(void); -moduleBaseFunction(moduleCameraToString) { - jscomponent_t *c = moduleCameraSelf(callInfo); - if(!c) return jerry_string_sz("Camera:invalid"); - char_t buf[32]; - snprintf(buf, sizeof(buf), "Camera(%u)", (unsigned)c->componentId); - return jerry_string_sz(buf); -} - -static void moduleCameraInit(void) { - scriptProtoInit( - &MODULE_CAMERA_PROTO, "Camera", - sizeof(jscomponent_t), moduleCameraCtor - ); - - scriptProtoDefineProp( - &MODULE_CAMERA_PROTO, "entity", moduleCameraGetEntity, NULL - ); - scriptProtoDefineProp( - &MODULE_CAMERA_PROTO, "id", moduleCameraGetId, NULL - ); - scriptProtoDefineProp( - &MODULE_CAMERA_PROTO, "fov", moduleCameraGetFov, moduleCameraSetFov - ); - scriptProtoDefineProp( - &MODULE_CAMERA_PROTO, "nearClip", - moduleCameraGetNearClip, moduleCameraSetNearClip - ); - scriptProtoDefineProp( - &MODULE_CAMERA_PROTO, "farClip", - moduleCameraGetFarClip, moduleCameraSetFarClip - ); - scriptProtoDefineProp( - &MODULE_CAMERA_PROTO, "projType", - moduleCameraGetProjType, moduleCameraSetProjType - ); - scriptProtoDefineToString(&MODULE_CAMERA_PROTO, moduleCameraToString); - - /* Camera.PERSPECTIVE, Camera.PERSPECTIVE_FLIPPED, Camera.ORTHOGRAPHIC */ - jerry_value_t ctor = MODULE_CAMERA_PROTO.constructor; - struct { const char_t *name; int val; } projtypes[] = { - { "PERSPECTIVE", ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE }, - { "PERSPECTIVE_FLIPPED", ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED }, - { "ORTHOGRAPHIC", ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC }, - }; - for(int i = 0; i < 3; i++) { - jerry_value_t k = jerry_string_sz(projtypes[i].name); - jerry_value_t v = jerry_number((double)projtypes[i].val); - jerry_object_set(ctor, k, v); - jerry_value_free(v); - jerry_value_free(k); - } -} - -static void moduleCameraDispose(void) { - scriptProtoDispose(&MODULE_CAMERA_PROTO); -} +/** + * Disposes the Camera module. + */ +void moduleCameraDispose(void); diff --git a/src/dusk/script/module/entity/component/display/moduleposition.c b/src/dusk/script/module/entity/component/display/moduleposition.c new file mode 100644 index 00000000..ccdfb1a0 --- /dev/null +++ b/src/dusk/script/module/entity/component/display/moduleposition.c @@ -0,0 +1,244 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "moduleposition.h" + +scriptproto_t MODULE_POSITION_PROTO; + +jscomponent_t *modulePositionSelf(const jerry_call_info_t *callInfo) { + return (jscomponent_t *)scriptProtoGetValue( + &MODULE_POSITION_PROTO, callInfo->this_value + ); +} + +moduleBaseFunction(modulePositionCtor) { + return moduleBaseThrow("Position cannot be instantiated with new"); +} + +moduleBaseFunction(modulePositionGetEntity) { + jscomponent_t *c = modulePositionSelf(callInfo); + if(!c) return jerry_undefined(); + return jerry_number((double)c->entityId); +} + +moduleBaseFunction(modulePositionGetId) { + jscomponent_t *c = modulePositionSelf(callInfo); + if(!c) return jerry_undefined(); + return jerry_number((double)c->componentId); +} + +moduleBaseFunction(modulePositionGetLocalPos) { + jscomponent_t *c = modulePositionSelf(callInfo); + if(!c) return jerry_undefined(); + vec3 v; + entityPositionGetLocalPosition(c->entityId, c->componentId, v); + return moduleVec3Push(v); +} + +moduleBaseFunction(modulePositionSetLocalPos) { + moduleBaseRequireArgs(1); + jscomponent_t *c = modulePositionSelf(callInfo); + if(!c) return jerry_undefined(); + float_t *v = moduleVec3From(args[0]); + if(!v) return moduleBaseThrow("Position.localPosition: expected Vec3"); + entityPositionSetLocalPosition(c->entityId, c->componentId, v); + return jerry_undefined(); +} + +moduleBaseFunction(modulePositionGetWorldPos) { + jscomponent_t *c = modulePositionSelf(callInfo); + if(!c) return jerry_undefined(); + vec3 v; + entityPositionGetWorldPosition(c->entityId, c->componentId, v); + return moduleVec3Push(v); +} + +moduleBaseFunction(modulePositionSetWorldPos) { + moduleBaseRequireArgs(1); + jscomponent_t *c = modulePositionSelf(callInfo); + if(!c) return jerry_undefined(); + float_t *v = moduleVec3From(args[0]); + if(!v) return moduleBaseThrow("Position.worldPosition: expected Vec3"); + entityPositionSetWorldPosition(c->entityId, c->componentId, v); + return jerry_undefined(); +} + +moduleBaseFunction(modulePositionGetLocalRot) { + jscomponent_t *c = modulePositionSelf(callInfo); + if(!c) return jerry_undefined(); + vec3 v; + entityPositionGetLocalRotation(c->entityId, c->componentId, v); + return moduleVec3Push(v); +} + +moduleBaseFunction(modulePositionSetLocalRot) { + moduleBaseRequireArgs(1); + jscomponent_t *c = modulePositionSelf(callInfo); + if(!c) return jerry_undefined(); + float_t *v = moduleVec3From(args[0]); + if(!v) return moduleBaseThrow("Position.localRotation: expected Vec3"); + entityPositionSetLocalRotation(c->entityId, c->componentId, v); + return jerry_undefined(); +} + +moduleBaseFunction(modulePositionGetWorldRot) { + jscomponent_t *c = modulePositionSelf(callInfo); + if(!c) return jerry_undefined(); + vec3 v; + entityPositionGetWorldRotation(c->entityId, c->componentId, v); + return moduleVec3Push(v); +} + +moduleBaseFunction(modulePositionSetWorldRot) { + moduleBaseRequireArgs(1); + jscomponent_t *c = modulePositionSelf(callInfo); + if(!c) return jerry_undefined(); + float_t *v = moduleVec3From(args[0]); + if(!v) return moduleBaseThrow("Position.worldRotation: expected Vec3"); + entityPositionSetWorldRotation(c->entityId, c->componentId, v); + return jerry_undefined(); +} + +moduleBaseFunction(modulePositionGetLocalScale) { + jscomponent_t *c = modulePositionSelf(callInfo); + if(!c) return jerry_undefined(); + vec3 v; + entityPositionGetLocalScale(c->entityId, c->componentId, v); + return moduleVec3Push(v); +} + +moduleBaseFunction(modulePositionSetLocalScale) { + moduleBaseRequireArgs(1); + jscomponent_t *c = modulePositionSelf(callInfo); + if(!c) return jerry_undefined(); + float_t *v = moduleVec3From(args[0]); + if(!v) return moduleBaseThrow("Position.localScale: expected Vec3"); + entityPositionSetLocalScale(c->entityId, c->componentId, v); + return jerry_undefined(); +} + +moduleBaseFunction(modulePositionGetWorldScale) { + jscomponent_t *c = modulePositionSelf(callInfo); + if(!c) return jerry_undefined(); + vec3 v; + entityPositionGetWorldScale(c->entityId, c->componentId, v); + return moduleVec3Push(v); +} + +moduleBaseFunction(modulePositionSetWorldScale) { + moduleBaseRequireArgs(1); + jscomponent_t *c = modulePositionSelf(callInfo); + if(!c) return jerry_undefined(); + float_t *v = moduleVec3From(args[0]); + if(!v) return moduleBaseThrow("Position.worldScale: expected Vec3"); + entityPositionSetWorldScale(c->entityId, c->componentId, v); + return jerry_undefined(); +} + +moduleBaseFunction(modulePositionLookAt) { + jscomponent_t *c = modulePositionSelf(callInfo); + if(!c) return jerry_undefined(); + moduleBaseRequireArgs(1); + float_t *target = moduleVec3From(args[0]); + if(!target) return moduleBaseThrow("Position.lookAt: expected Vec3 target"); + + vec3 eye; + entityPositionGetLocalPosition(c->entityId, c->componentId, eye); + + vec3 up = { 0.0f, 1.0f, 0.0f }; + if(argc >= 2) { + float_t *upArg = moduleVec3From(args[1]); + if(upArg) glm_vec3_copy(upArg, up); + } + + entityPositionLookAt(c->entityId, c->componentId, eye, target, up); + return jerry_undefined(); +} + +moduleBaseFunction(modulePositionSetParent) { + jscomponent_t *c = modulePositionSelf(callInfo); + if(!c) return jerry_undefined(); + if(argc == 0 || + jerry_value_is_null(args[0]) || + jerry_value_is_undefined(args[0])) { + entityPositionSetParent( + c->entityId, c->componentId, + ENTITY_ID_INVALID, COMPONENT_ID_INVALID + ); + return jerry_undefined(); + } + jscomponent_t *parent = (jscomponent_t *)scriptProtoGetValue( + &MODULE_POSITION_PROTO, args[0] + ); + if(!parent) { + return moduleBaseThrow( + "Position.setParent: expected Position or null" + ); + } + entityPositionSetParent( + c->entityId, c->componentId, + parent->entityId, parent->componentId + ); + return jerry_undefined(); +} + +moduleBaseFunction(modulePositionToString) { + jscomponent_t *c = modulePositionSelf(callInfo); + if(!c) return jerry_string_sz("Position:invalid"); + char_t buf[32]; + snprintf(buf, sizeof(buf), "Position(%u)", (unsigned)c->componentId); + return jerry_string_sz(buf); +} + +void modulePositionInit(void) { + scriptProtoInit( + &MODULE_POSITION_PROTO, "Position", + sizeof(jscomponent_t), modulePositionCtor + ); + + scriptProtoDefineProp( + &MODULE_POSITION_PROTO, "entity", modulePositionGetEntity, NULL + ); + scriptProtoDefineProp( + &MODULE_POSITION_PROTO, "id", modulePositionGetId, NULL + ); + scriptProtoDefineProp( + &MODULE_POSITION_PROTO, "localPosition", + modulePositionGetLocalPos, modulePositionSetLocalPos + ); + scriptProtoDefineProp( + &MODULE_POSITION_PROTO, "worldPosition", + modulePositionGetWorldPos, modulePositionSetWorldPos + ); + scriptProtoDefineProp( + &MODULE_POSITION_PROTO, "localRotation", + modulePositionGetLocalRot, modulePositionSetLocalRot + ); + scriptProtoDefineProp( + &MODULE_POSITION_PROTO, "worldRotation", + modulePositionGetWorldRot, modulePositionSetWorldRot + ); + scriptProtoDefineProp( + &MODULE_POSITION_PROTO, "localScale", + modulePositionGetLocalScale, modulePositionSetLocalScale + ); + scriptProtoDefineProp( + &MODULE_POSITION_PROTO, "worldScale", + modulePositionGetWorldScale, modulePositionSetWorldScale + ); + scriptProtoDefineFunc( + &MODULE_POSITION_PROTO, "lookAt", modulePositionLookAt + ); + scriptProtoDefineFunc( + &MODULE_POSITION_PROTO, "setParent", modulePositionSetParent + ); + scriptProtoDefineToString(&MODULE_POSITION_PROTO, modulePositionToString); +} + +void modulePositionDispose(void) { + scriptProtoDispose(&MODULE_POSITION_PROTO); +} diff --git a/src/dusk/script/module/entity/component/display/moduleposition.h b/src/dusk/script/module/entity/component/display/moduleposition.h index db9a89b3..3ff63925 100644 --- a/src/dusk/script/module/entity/component/display/moduleposition.h +++ b/src/dusk/script/module/entity/component/display/moduleposition.h @@ -12,237 +12,78 @@ #include "script/module/entity/modulecomponent.h" #include "entity/component/display/entityposition.h" -static scriptproto_t MODULE_POSITION_PROTO; +extern scriptproto_t MODULE_POSITION_PROTO; -moduleBaseFunction(modulePositionCtor) { - (void)callInfo; (void)args; (void)argc; - return moduleBaseThrow("Position cannot be instantiated with new"); -} +/** Position() constructor — always throws; not directly instantiable. */ +moduleBaseFunction(modulePositionCtor); -static inline jscomponent_t *modulePositionSelf( - const jerry_call_info_t *callInfo -) { - return (jscomponent_t *)scriptProtoGetValue( - &MODULE_POSITION_PROTO, callInfo->this_value - ); -} +/** @return Entity ID that owns this position component. */ +moduleBaseFunction(modulePositionGetEntity); -moduleBaseFunction(modulePositionGetEntity) { - jscomponent_t *c = modulePositionSelf(callInfo); - if(!c) return jerry_undefined(); - return jerry_number((double)c->entityId); -} +/** @return This component's ID. */ +moduleBaseFunction(modulePositionGetId); -moduleBaseFunction(modulePositionGetId) { - jscomponent_t *c = modulePositionSelf(callInfo); - if(!c) return jerry_undefined(); - return jerry_number((double)c->componentId); -} +/** @return Local position as a Vec3. */ +moduleBaseFunction(modulePositionGetLocalPos); -moduleBaseFunction(modulePositionGetLocalPos) { - jscomponent_t *c = modulePositionSelf(callInfo); - if(!c) return jerry_undefined(); - vec3 v; - entityPositionGetLocalPosition(c->entityId, c->componentId, v); - return moduleVec3Push(v); -} +/** Sets local position. @param args[0] Vec3. */ +moduleBaseFunction(modulePositionSetLocalPos); -moduleBaseFunction(modulePositionSetLocalPos) { - moduleBaseRequireArgs(1); - jscomponent_t *c = modulePositionSelf(callInfo); - if(!c) return jerry_undefined(); - float_t *v = moduleVec3From(args[0]); - if(!v) return moduleBaseThrow("Position.localPosition: expected Vec3"); - entityPositionSetLocalPosition(c->entityId, c->componentId, v); - return jerry_undefined(); -} +/** @return World position as a Vec3. */ +moduleBaseFunction(modulePositionGetWorldPos); -moduleBaseFunction(modulePositionGetWorldPos) { - jscomponent_t *c = modulePositionSelf(callInfo); - if(!c) return jerry_undefined(); - vec3 v; - entityPositionGetWorldPosition(c->entityId, c->componentId, v); - return moduleVec3Push(v); -} +/** Sets world position. @param args[0] Vec3. */ +moduleBaseFunction(modulePositionSetWorldPos); -moduleBaseFunction(modulePositionSetWorldPos) { - moduleBaseRequireArgs(1); - jscomponent_t *c = modulePositionSelf(callInfo); - if(!c) return jerry_undefined(); - float_t *v = moduleVec3From(args[0]); - if(!v) return moduleBaseThrow("Position.worldPosition: expected Vec3"); - entityPositionSetWorldPosition(c->entityId, c->componentId, v); - return jerry_undefined(); -} +/** @return Local rotation as a Vec3 (Euler angles). */ +moduleBaseFunction(modulePositionGetLocalRot); -moduleBaseFunction(modulePositionGetLocalRot) { - jscomponent_t *c = modulePositionSelf(callInfo); - if(!c) return jerry_undefined(); - vec3 v; - entityPositionGetLocalRotation(c->entityId, c->componentId, v); - return moduleVec3Push(v); -} +/** Sets local rotation. @param args[0] Vec3. */ +moduleBaseFunction(modulePositionSetLocalRot); -moduleBaseFunction(modulePositionSetLocalRot) { - moduleBaseRequireArgs(1); - jscomponent_t *c = modulePositionSelf(callInfo); - if(!c) return jerry_undefined(); - float_t *v = moduleVec3From(args[0]); - if(!v) return moduleBaseThrow("Position.localRotation: expected Vec3"); - entityPositionSetLocalRotation(c->entityId, c->componentId, v); - return jerry_undefined(); -} +/** @return World rotation as a Vec3 (Euler angles). */ +moduleBaseFunction(modulePositionGetWorldRot); -moduleBaseFunction(modulePositionGetWorldRot) { - jscomponent_t *c = modulePositionSelf(callInfo); - if(!c) return jerry_undefined(); - vec3 v; - entityPositionGetWorldRotation(c->entityId, c->componentId, v); - return moduleVec3Push(v); -} +/** Sets world rotation. @param args[0] Vec3. */ +moduleBaseFunction(modulePositionSetWorldRot); -moduleBaseFunction(modulePositionSetWorldRot) { - moduleBaseRequireArgs(1); - jscomponent_t *c = modulePositionSelf(callInfo); - if(!c) return jerry_undefined(); - float_t *v = moduleVec3From(args[0]); - if(!v) return moduleBaseThrow("Position.worldRotation: expected Vec3"); - entityPositionSetWorldRotation(c->entityId, c->componentId, v); - return jerry_undefined(); -} +/** @return Local scale as a Vec3. */ +moduleBaseFunction(modulePositionGetLocalScale); -moduleBaseFunction(modulePositionGetLocalScale) { - jscomponent_t *c = modulePositionSelf(callInfo); - if(!c) return jerry_undefined(); - vec3 v; - entityPositionGetLocalScale(c->entityId, c->componentId, v); - return moduleVec3Push(v); -} +/** Sets local scale. @param args[0] Vec3. */ +moduleBaseFunction(modulePositionSetLocalScale); -moduleBaseFunction(modulePositionSetLocalScale) { - moduleBaseRequireArgs(1); - jscomponent_t *c = modulePositionSelf(callInfo); - if(!c) return jerry_undefined(); - float_t *v = moduleVec3From(args[0]); - if(!v) return moduleBaseThrow("Position.localScale: expected Vec3"); - entityPositionSetLocalScale(c->entityId, c->componentId, v); - return jerry_undefined(); -} +/** @return World scale as a Vec3. */ +moduleBaseFunction(modulePositionGetWorldScale); -moduleBaseFunction(modulePositionGetWorldScale) { - jscomponent_t *c = modulePositionSelf(callInfo); - if(!c) return jerry_undefined(); - vec3 v; - entityPositionGetWorldScale(c->entityId, c->componentId, v); - return moduleVec3Push(v); -} +/** Sets world scale. @param args[0] Vec3. */ +moduleBaseFunction(modulePositionSetWorldScale); -moduleBaseFunction(modulePositionSetWorldScale) { - moduleBaseRequireArgs(1); - jscomponent_t *c = modulePositionSelf(callInfo); - if(!c) return jerry_undefined(); - float_t *v = moduleVec3From(args[0]); - if(!v) return moduleBaseThrow("Position.worldScale: expected Vec3"); - entityPositionSetWorldScale(c->entityId, c->componentId, v); - return jerry_undefined(); -} +/** + * lookAt(target, up?) — orients the component toward a target point. + * @param args[0] Vec3 target position. + * @param args[1] Optional Vec3 up vector (defaults to world up). + */ +moduleBaseFunction(modulePositionLookAt); -moduleBaseFunction(modulePositionLookAt) { - jscomponent_t *c = modulePositionSelf(callInfo); - if(!c) return jerry_undefined(); - moduleBaseRequireArgs(1); - float_t *target = moduleVec3From(args[0]); - if(!target) return moduleBaseThrow("Position.lookAt: expected Vec3 target"); +/** + * setParent(position?) — parents this component to another Position, or + * unparents when called with null/undefined. + * @param args[0] Position component or null/undefined. + */ +moduleBaseFunction(modulePositionSetParent); - vec3 eye; - entityPositionGetLocalPosition(c->entityId, c->componentId, eye); +/** @return "Position(id)" string. */ +moduleBaseFunction(modulePositionToString); - vec3 up = { 0.0f, 1.0f, 0.0f }; - if(argc >= 2) { - float_t *upArg = moduleVec3From(args[1]); - if(upArg) glm_vec3_copy(upArg, up); - } +/** + * Initializes the Position module and registers the global Position class with + * localPosition/worldPosition/localRotation/worldRotation/localScale/worldScale + * properties and lookAt/setParent methods. + */ +void modulePositionInit(void); - entityPositionLookAt(c->entityId, c->componentId, eye, target, up); - return jerry_undefined(); -} - -moduleBaseFunction(modulePositionSetParent) { - jscomponent_t *c = modulePositionSelf(callInfo); - if(!c) return jerry_undefined(); - if(argc == 0 || - jerry_value_is_null(args[0]) || - jerry_value_is_undefined(args[0])) { - entityPositionSetParent( - c->entityId, c->componentId, - ENTITY_ID_INVALID, COMPONENT_ID_INVALID - ); - return jerry_undefined(); - } - jscomponent_t *parent = (jscomponent_t *)scriptProtoGetValue( - &MODULE_POSITION_PROTO, args[0] - ); - if(!parent) return moduleBaseThrow("Position.setParent: expected Position or null"); - entityPositionSetParent( - c->entityId, c->componentId, - parent->entityId, parent->componentId - ); - return jerry_undefined(); -} - -moduleBaseFunction(modulePositionToString) { - jscomponent_t *c = modulePositionSelf(callInfo); - if(!c) return jerry_string_sz("Position:invalid"); - char_t buf[32]; - snprintf(buf, sizeof(buf), "Position(%u)", (unsigned)c->componentId); - return jerry_string_sz(buf); -} - -static void modulePositionInit(void) { - scriptProtoInit( - &MODULE_POSITION_PROTO, "Position", - sizeof(jscomponent_t), modulePositionCtor - ); - - scriptProtoDefineProp( - &MODULE_POSITION_PROTO, "entity", modulePositionGetEntity, NULL - ); - scriptProtoDefineProp( - &MODULE_POSITION_PROTO, "id", modulePositionGetId, NULL - ); - scriptProtoDefineProp( - &MODULE_POSITION_PROTO, "localPosition", - modulePositionGetLocalPos, modulePositionSetLocalPos - ); - scriptProtoDefineProp( - &MODULE_POSITION_PROTO, "worldPosition", - modulePositionGetWorldPos, modulePositionSetWorldPos - ); - scriptProtoDefineProp( - &MODULE_POSITION_PROTO, "localRotation", - modulePositionGetLocalRot, modulePositionSetLocalRot - ); - scriptProtoDefineProp( - &MODULE_POSITION_PROTO, "worldRotation", - modulePositionGetWorldRot, modulePositionSetWorldRot - ); - scriptProtoDefineProp( - &MODULE_POSITION_PROTO, "localScale", - modulePositionGetLocalScale, modulePositionSetLocalScale - ); - scriptProtoDefineProp( - &MODULE_POSITION_PROTO, "worldScale", - modulePositionGetWorldScale, modulePositionSetWorldScale - ); - scriptProtoDefineFunc( - &MODULE_POSITION_PROTO, "lookAt", modulePositionLookAt - ); - scriptProtoDefineFunc( - &MODULE_POSITION_PROTO, "setParent", modulePositionSetParent - ); - scriptProtoDefineToString(&MODULE_POSITION_PROTO, modulePositionToString); -} - -static void modulePositionDispose(void) { - scriptProtoDispose(&MODULE_POSITION_PROTO); -} +/** + * Disposes the Position module. + */ +void modulePositionDispose(void); diff --git a/src/dusk/script/module/entity/component/display/modulerenderable.c b/src/dusk/script/module/entity/component/display/modulerenderable.c new file mode 100644 index 00000000..a0e23a8c --- /dev/null +++ b/src/dusk/script/module/entity/component/display/modulerenderable.c @@ -0,0 +1,278 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "modulerenderable.h" + +scriptproto_t MODULE_RENDERABLE_PROTO; + +jscomponent_t *moduleRenderableSelf(const jerry_call_info_t *callInfo) { + return (jscomponent_t *)scriptProtoGetValue( + &MODULE_RENDERABLE_PROTO, callInfo->this_value + ); +} + +entityrenderable_t *moduleRenderableData(const jscomponent_t *c) { + return (entityrenderable_t *)componentGetData( + c->entityId, c->componentId, COMPONENT_TYPE_RENDERABLE + ); +} + +float_t moduleRenderableArrayFloat( + const jerry_value_t arr, + const uint32_t idx, + const float_t def +) { + if(idx >= jerry_array_length(arr)) return def; + jerry_value_t v = jerry_object_get_index(arr, idx); + float_t f = jerry_value_is_number(v) + ? (float_t)jerry_value_as_number(v) : def; + jerry_value_free(v); + return f; +} + +moduleBaseFunction(moduleRenderableCtor) { + return moduleBaseThrow("Renderable cannot be instantiated with new"); +} + +moduleBaseFunction(moduleRenderableGetEntity) { + jscomponent_t *c = moduleRenderableSelf(callInfo); + if(!c) return jerry_undefined(); + return jerry_number((double)c->entityId); +} + +moduleBaseFunction(moduleRenderableGetId) { + jscomponent_t *c = moduleRenderableSelf(callInfo); + if(!c) return jerry_undefined(); + return jerry_number((double)c->componentId); +} + +moduleBaseFunction(moduleRenderableGetType) { + jscomponent_t *c = moduleRenderableSelf(callInfo); + if(!c) return jerry_undefined(); + entityrenderable_t *r = moduleRenderableData(c); + if(!r) return jerry_undefined(); + return jerry_number((double)r->type); +} + +moduleBaseFunction(moduleRenderableSetType) { + moduleBaseRequireArgs(1); + jscomponent_t *c = moduleRenderableSelf(callInfo); + if(!c) return jerry_undefined(); + entityRenderableSetType( + c->entityId, c->componentId, + (entityrenderabletype_t)moduleBaseArgInt(0) + ); + return jerry_undefined(); +} + +moduleBaseFunction(moduleRenderableGetPriority) { + jscomponent_t *c = moduleRenderableSelf(callInfo); + if(!c) return jerry_undefined(); + entityrenderable_t *r = moduleRenderableData(c); + if(!r) return jerry_undefined(); + return jerry_number((double)r->priority); +} + +moduleBaseFunction(moduleRenderableSetPriority) { + moduleBaseRequireArgs(1); + jscomponent_t *c = moduleRenderableSelf(callInfo); + if(!c) return jerry_undefined(); + entityRenderableSetPriority( + c->entityId, c->componentId, + (int8_t)moduleBaseArgInt(0) + ); + return jerry_undefined(); +} + +moduleBaseFunction(moduleRenderableGetColor) { + jscomponent_t *c = moduleRenderableSelf(callInfo); + if(!c) return jerry_undefined(); + entityrenderable_t *r = moduleRenderableData(c); + if(!r) return jerry_undefined(); + return moduleColorPush(r->data.material.material.unlit.color); +} + +moduleBaseFunction(moduleRenderableSetColor) { + moduleBaseRequireArgs(1); + jscomponent_t *c = moduleRenderableSelf(callInfo); + if(!c) return jerry_undefined(); + entityrenderable_t *r = moduleRenderableData(c); + if(!r) return jerry_undefined(); + color_t *col = moduleColorFrom(args[0]); + if(!col) return moduleBaseThrow("Renderable.color: expected Color"); + r->data.material.material.unlit.color = *col; + return jerry_undefined(); +} + +moduleBaseFunction(moduleRenderableGetTexture) { + jerry_value_t key = jerry_string_sz("_tex"); + jerry_value_t val = jerry_object_get(callInfo->this_value, key); + jerry_value_free(key); + return val; +} + +moduleBaseFunction(moduleRenderableSetTexture) { + moduleBaseRequireArgs(1); + jscomponent_t *c = moduleRenderableSelf(callInfo); + if(!c) return jerry_undefined(); + entityrenderable_t *r = moduleRenderableData(c); + if(!r) return jerry_undefined(); + jstexture_t *tex = (jstexture_t *)scriptProtoGetValue( + &MODULE_TEXTURE_PROTO, args[0] + ); + if(!tex || !tex->entry) { + return moduleBaseThrow("Renderable.texture: expected Texture"); + } + r->type = ENTITY_RENDERABLE_TYPE_SPRITEBATCH; + r->data.spritebatch.texture = &tex->entry->data.texture; + jerry_value_t pinKey = jerry_string_sz("_tex"); + jerry_object_set(callInfo->this_value, pinKey, args[0]); + jerry_value_free(pinKey); + return jerry_undefined(); +} + +moduleBaseFunction(moduleRenderableGetSprites) { + jscomponent_t *c = moduleRenderableSelf(callInfo); + if(!c) return jerry_undefined(); + entityrenderable_t *r = moduleRenderableData(c); + if(!r) return jerry_undefined(); + const entityrenderablespritebatch_t *sb = &r->data.spritebatch; + + jerry_value_t arr = jerry_array((uint32_t)sb->spriteCount); + for(uint32_t i = 0; i < (uint32_t)sb->spriteCount; i++) { + const spritebatchsprite_t *s = &sb->sprites[i]; + float_t vals[10] = { + s->min[0], s->min[1], s->min[2], + s->max[0], s->max[1], s->max[2], + s->uvMin[0], s->uvMin[1], + s->uvMax[0], s->uvMax[1], + }; + jerry_value_t sprite = jerry_array(10); + for(uint32_t j = 0; j < 10; j++) { + jerry_value_t num = jerry_number((double)vals[j]); + jerry_object_set_index(sprite, j, num); + jerry_value_free(num); + } + jerry_object_set_index(arr, i, sprite); + jerry_value_free(sprite); + } + return arr; +} + +moduleBaseFunction(moduleRenderableSetSprites) { + moduleBaseRequireArgs(1); + jscomponent_t *c = moduleRenderableSelf(callInfo); + if(!c) return jerry_undefined(); + entityrenderable_t *r = moduleRenderableData(c); + if(!r) return jerry_undefined(); + if(!jerry_value_is_array(args[0])) { + return moduleBaseThrow("Renderable.sprites: expected Array"); + } + entityrenderablespritebatch_t *sb = &r->data.spritebatch; + uint32_t count = jerry_array_length(args[0]); + if(count > ENTITY_RENDERABLE_SPRITEBATCH_SPRITES_MAX) { + return moduleBaseThrow("Renderable.sprites: exceeds sprite capacity"); + } + sb->spriteCount = 0; + for(uint32_t i = 0; i < count; i++) { + jerry_value_t elem = jerry_object_get_index(args[0], i); + if(!jerry_value_is_array(elem)) { + jerry_value_free(elem); + return moduleBaseThrow( + "Renderable.sprites: each element must be an Array" + ); + } + spritebatchsprite_t s; + if(jerry_array_length(elem) >= 10) { + s.min[0] = moduleRenderableArrayFloat(elem, 0, 0.0f); + s.min[1] = moduleRenderableArrayFloat(elem, 1, 0.0f); + s.min[2] = moduleRenderableArrayFloat(elem, 2, 0.0f); + s.max[0] = moduleRenderableArrayFloat(elem, 3, 0.0f); + s.max[1] = moduleRenderableArrayFloat(elem, 4, 0.0f); + s.max[2] = moduleRenderableArrayFloat(elem, 5, 0.0f); + s.uvMin[0] = moduleRenderableArrayFloat(elem, 6, 0.0f); + s.uvMin[1] = moduleRenderableArrayFloat(elem, 7, 0.0f); + s.uvMax[0] = moduleRenderableArrayFloat(elem, 8, 1.0f); + s.uvMax[1] = moduleRenderableArrayFloat(elem, 9, 1.0f); + } else { + s.min[0] = moduleRenderableArrayFloat(elem, 0, 0.0f); + s.min[1] = moduleRenderableArrayFloat(elem, 1, 0.0f); + s.min[2] = 0.0f; + s.max[0] = moduleRenderableArrayFloat(elem, 2, 0.0f); + s.max[1] = moduleRenderableArrayFloat(elem, 3, 0.0f); + s.max[2] = 0.0f; + s.uvMin[0] = moduleRenderableArrayFloat(elem, 4, 0.0f); + s.uvMin[1] = moduleRenderableArrayFloat(elem, 5, 0.0f); + s.uvMax[0] = moduleRenderableArrayFloat(elem, 6, 1.0f); + s.uvMax[1] = moduleRenderableArrayFloat(elem, 7, 1.0f); + } + jerry_value_free(elem); + sb->sprites[sb->spriteCount++] = s; + } + return jerry_undefined(); +} + +moduleBaseFunction(moduleRenderableToString) { + jscomponent_t *c = moduleRenderableSelf(callInfo); + if(!c) return jerry_string_sz("Renderable:invalid"); + char_t buf[32]; + snprintf(buf, sizeof(buf), "Renderable(%u)", (unsigned)c->componentId); + return jerry_string_sz(buf); +} + +void moduleRenderableInit(void) { + scriptProtoInit( + &MODULE_RENDERABLE_PROTO, "Renderable", + sizeof(jscomponent_t), moduleRenderableCtor + ); + + scriptProtoDefineProp( + &MODULE_RENDERABLE_PROTO, "entity", moduleRenderableGetEntity, NULL + ); + scriptProtoDefineProp( + &MODULE_RENDERABLE_PROTO, "id", moduleRenderableGetId, NULL + ); + scriptProtoDefineProp( + &MODULE_RENDERABLE_PROTO, "type", + moduleRenderableGetType, moduleRenderableSetType + ); + scriptProtoDefineProp( + &MODULE_RENDERABLE_PROTO, "priority", + moduleRenderableGetPriority, moduleRenderableSetPriority + ); + scriptProtoDefineProp( + &MODULE_RENDERABLE_PROTO, "color", + moduleRenderableGetColor, moduleRenderableSetColor + ); + scriptProtoDefineProp( + &MODULE_RENDERABLE_PROTO, "texture", + moduleRenderableGetTexture, moduleRenderableSetTexture + ); + scriptProtoDefineProp( + &MODULE_RENDERABLE_PROTO, "sprites", + moduleRenderableGetSprites, moduleRenderableSetSprites + ); + scriptProtoDefineToString(&MODULE_RENDERABLE_PROTO, moduleRenderableToString); + + jerry_value_t ctor = MODULE_RENDERABLE_PROTO.constructor; + struct { const char_t *name; int val; } types[] = { + { "SHADER_MATERIAL", ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL }, + { "SPRITEBATCH", ENTITY_RENDERABLE_TYPE_SPRITEBATCH }, + { "CUSTOM", ENTITY_RENDERABLE_TYPE_CUSTOM }, + }; + for(int i = 0; i < 3; i++) { + jerry_value_t k = jerry_string_sz(types[i].name); + jerry_value_t v = jerry_number((double)types[i].val); + jerry_object_set(ctor, k, v); + jerry_value_free(v); + jerry_value_free(k); + } +} + +void moduleRenderableDispose(void) { + scriptProtoDispose(&MODULE_RENDERABLE_PROTO); +} diff --git a/src/dusk/script/module/entity/component/display/modulerenderable.h b/src/dusk/script/module/entity/component/display/modulerenderable.h index 51a8723c..82a4109c 100644 --- a/src/dusk/script/module/entity/component/display/modulerenderable.h +++ b/src/dusk/script/module/entity/component/display/modulerenderable.h @@ -13,291 +13,69 @@ #include "script/scriptproto.h" #include "entity/component/display/entityrenderable.h" -static scriptproto_t MODULE_RENDERABLE_PROTO; +extern scriptproto_t MODULE_RENDERABLE_PROTO; -moduleBaseFunction(moduleRenderableCtor) { - (void)callInfo; (void)args; (void)argc; - return moduleBaseThrow("Renderable cannot be instantiated with new"); -} +/** Renderable() constructor — always throws; not directly instantiable. */ +moduleBaseFunction(moduleRenderableCtor); -static inline jscomponent_t *moduleRenderableSelf( - const jerry_call_info_t *callInfo -) { - return (jscomponent_t *)scriptProtoGetValue( - &MODULE_RENDERABLE_PROTO, callInfo->this_value - ); -} +/** @return Entity ID that owns this renderable component. */ +moduleBaseFunction(moduleRenderableGetEntity); -static inline entityrenderable_t *moduleRenderableData(const jscomponent_t *c) { - return (entityrenderable_t *)componentGetData( - c->entityId, c->componentId, COMPONENT_TYPE_RENDERABLE - ); -} +/** @return This component's ID. */ +moduleBaseFunction(moduleRenderableGetId); -/** Read a float from a JS array at index, returning def if out of range. */ -static inline float_t moduleRenderableArrayFloat( - const jerry_value_t arr, - const uint32_t idx, - const float_t def -) { - if(idx >= jerry_array_length(arr)) return def; - jerry_value_t v = jerry_object_get_index(arr, idx); - float_t f = jerry_value_is_number(v) ? (float_t)jerry_value_as_number(v) : def; - jerry_value_free(v); - return f; -} +/** @return Render type constant (Renderable.SPRITEBATCH etc.). */ +moduleBaseFunction(moduleRenderableGetType); -moduleBaseFunction(moduleRenderableGetEntity) { - jscomponent_t *c = moduleRenderableSelf(callInfo); - if(!c) return jerry_undefined(); - return jerry_number((double)c->entityId); -} +/** Sets render type. @param args[0] entityrenderabletype_t value. */ +moduleBaseFunction(moduleRenderableSetType); -moduleBaseFunction(moduleRenderableGetId) { - jscomponent_t *c = moduleRenderableSelf(callInfo); - if(!c) return jerry_undefined(); - return jerry_number((double)c->componentId); -} +/** @return Render priority (signed integer). */ +moduleBaseFunction(moduleRenderableGetPriority); -moduleBaseFunction(moduleRenderableGetType) { - jscomponent_t *c = moduleRenderableSelf(callInfo); - if(!c) return jerry_undefined(); - entityrenderable_t *r = moduleRenderableData(c); - if(!r) return jerry_undefined(); - return jerry_number((double)r->type); -} +/** Sets render priority. @param args[0] Priority integer. */ +moduleBaseFunction(moduleRenderableSetPriority); -moduleBaseFunction(moduleRenderableSetType) { - moduleBaseRequireArgs(1); - jscomponent_t *c = moduleRenderableSelf(callInfo); - if(!c) return jerry_undefined(); - entityRenderableSetType( - c->entityId, c->componentId, - (entityrenderabletype_t)moduleBaseArgInt(0) - ); - return jerry_undefined(); -} +/** @return Material color as a Color object. */ +moduleBaseFunction(moduleRenderableGetColor); -moduleBaseFunction(moduleRenderableGetPriority) { - jscomponent_t *c = moduleRenderableSelf(callInfo); - if(!c) return jerry_undefined(); - entityrenderable_t *r = moduleRenderableData(c); - if(!r) return jerry_undefined(); - return jerry_number((double)r->priority); -} +/** Sets material color. @param args[0] Color object. */ +moduleBaseFunction(moduleRenderableSetColor); -moduleBaseFunction(moduleRenderableSetPriority) { - moduleBaseRequireArgs(1); - jscomponent_t *c = moduleRenderableSelf(callInfo); - if(!c) return jerry_undefined(); - entityRenderableSetPriority( - c->entityId, c->componentId, - (int8_t)moduleBaseArgInt(0) - ); - return jerry_undefined(); -} +/** @return The pinned Texture JS instance, or undefined if none set. */ +moduleBaseFunction(moduleRenderableGetTexture); -moduleBaseFunction(moduleRenderableGetColor) { - jscomponent_t *c = moduleRenderableSelf(callInfo); - if(!c) return jerry_undefined(); - entityrenderable_t *r = moduleRenderableData(c); - if(!r) return jerry_undefined(); - return moduleColorPush(r->data.material.material.unlit.color); -} - -moduleBaseFunction(moduleRenderableSetColor) { - moduleBaseRequireArgs(1); - jscomponent_t *c = moduleRenderableSelf(callInfo); - if(!c) return jerry_undefined(); - entityrenderable_t *r = moduleRenderableData(c); - if(!r) return jerry_undefined(); - color_t *col = moduleColorFrom(args[0]); - if(!col) return moduleBaseThrow("Renderable.color: expected Color"); - r->data.material.material.unlit.color = *col; - return jerry_undefined(); -} - -/* - * texture getter — returns the pinned Texture instance, or undefined if none. +/** + * Sets the texture for SPRITEBATCH rendering. Also switches the render type to + * SPRITEBATCH and pins the JS Texture object to prevent GC. + * @param args[0] Texture JS object. */ -moduleBaseFunction(moduleRenderableGetTexture) { - jscomponent_t *c = moduleRenderableSelf(callInfo); - if(!c) return jerry_undefined(); - jerry_value_t key = jerry_string_sz("_tex"); - jerry_value_t val = jerry_object_get(callInfo->this_value, key); - jerry_value_free(key); - return val; -} +moduleBaseFunction(moduleRenderableSetTexture); -/* - * texture setter — switches to SPRITEBATCH, binds the texture, and pins the - * Texture JS object so GC won't free the asset while the pointer is live. +/** + * @return JS array of sprite sub-arrays, each with 10 numbers: + * [x1,y1,z1, x2,y2,z2, u1,v1, u2,v2]. */ -moduleBaseFunction(moduleRenderableSetTexture) { - moduleBaseRequireArgs(1); - jscomponent_t *c = moduleRenderableSelf(callInfo); - if(!c) return jerry_undefined(); - entityrenderable_t *r = moduleRenderableData(c); - if(!r) return jerry_undefined(); - jstexture_t *tex = (jstexture_t *)scriptProtoGetValue( - &MODULE_TEXTURE_PROTO, args[0] - ); - if(!tex || !tex->entry) { - return moduleBaseThrow("Renderable.texture: expected Texture"); - } - r->type = ENTITY_RENDERABLE_TYPE_SPRITEBATCH; - r->data.spritebatch.texture = &tex->entry->data.texture; - jerry_value_t pinKey = jerry_string_sz("_tex"); - jerry_object_set(callInfo->this_value, pinKey, args[0]); - jerry_value_free(pinKey); - return jerry_undefined(); -} +moduleBaseFunction(moduleRenderableGetSprites); -/* - * sprites getter — returns a JS array of sprite sub-arrays. - * Each element is [x1,y1,z1, x2,y2,z2, u1,v1, u2,v2] (10 numbers). +/** + * Sets sprite data. Accepts an array of sub-arrays — 10 elements (3D) or + * 8 elements (2D, z defaults to 0). + * @param args[0] Array of sprite sub-arrays. */ -moduleBaseFunction(moduleRenderableGetSprites) { - jscomponent_t *c = moduleRenderableSelf(callInfo); - if(!c) return jerry_undefined(); - entityrenderable_t *r = moduleRenderableData(c); - if(!r) return jerry_undefined(); - const entityrenderablespritebatch_t *sb = &r->data.spritebatch; +moduleBaseFunction(moduleRenderableSetSprites); - jerry_value_t arr = jerry_array((uint32_t)sb->spriteCount); - for(uint32_t i = 0; i < (uint32_t)sb->spriteCount; i++) { - const spritebatchsprite_t *s = &sb->sprites[i]; - float_t vals[10] = { - s->min[0], s->min[1], s->min[2], - s->max[0], s->max[1], s->max[2], - s->uvMin[0], s->uvMin[1], - s->uvMax[0], s->uvMax[1], - }; - jerry_value_t sprite = jerry_array(10); - for(uint32_t j = 0; j < 10; j++) { - jerry_value_t num = jerry_number((double)vals[j]); - jerry_object_set_index(sprite, j, num); - jerry_value_free(num); - } - jerry_object_set_index(arr, i, sprite); - jerry_value_free(sprite); - } - return arr; -} +/** @return "Renderable(id)" string. */ +moduleBaseFunction(moduleRenderableToString); -/* - * sprites setter — accepts an array of sub-arrays. - * Each element: 10 numbers (3D) or 8 numbers (2D, z defaults to 0). +/** + * Initializes the Renderable module and registers the global Renderable class + * with type/priority/color/texture/sprites properties and SPRITEBATCH/ + * SHADER_MATERIAL/CUSTOM constants. */ -moduleBaseFunction(moduleRenderableSetSprites) { - moduleBaseRequireArgs(1); - jscomponent_t *c = moduleRenderableSelf(callInfo); - if(!c) return jerry_undefined(); - entityrenderable_t *r = moduleRenderableData(c); - if(!r) return jerry_undefined(); - if(!jerry_value_is_array(args[0])) { - return moduleBaseThrow("Renderable.sprites: expected Array"); - } - entityrenderablespritebatch_t *sb = &r->data.spritebatch; - uint32_t count = jerry_array_length(args[0]); - if(count > ENTITY_RENDERABLE_SPRITEBATCH_SPRITES_MAX) { - return moduleBaseThrow("Renderable.sprites: exceeds sprite capacity"); - } - sb->spriteCount = 0; - for(uint32_t i = 0; i < count; i++) { - jerry_value_t elem = jerry_object_get_index(args[0], i); - if(!jerry_value_is_array(elem)) { - jerry_value_free(elem); - return moduleBaseThrow("Renderable.sprites: each element must be an Array"); - } - spritebatchsprite_t s; - if(jerry_array_length(elem) >= 10) { - s.min[0] = moduleRenderableArrayFloat(elem, 0, 0.0f); - s.min[1] = moduleRenderableArrayFloat(elem, 1, 0.0f); - s.min[2] = moduleRenderableArrayFloat(elem, 2, 0.0f); - s.max[0] = moduleRenderableArrayFloat(elem, 3, 0.0f); - s.max[1] = moduleRenderableArrayFloat(elem, 4, 0.0f); - s.max[2] = moduleRenderableArrayFloat(elem, 5, 0.0f); - s.uvMin[0] = moduleRenderableArrayFloat(elem, 6, 0.0f); - s.uvMin[1] = moduleRenderableArrayFloat(elem, 7, 0.0f); - s.uvMax[0] = moduleRenderableArrayFloat(elem, 8, 1.0f); - s.uvMax[1] = moduleRenderableArrayFloat(elem, 9, 1.0f); - } else { - s.min[0] = moduleRenderableArrayFloat(elem, 0, 0.0f); - s.min[1] = moduleRenderableArrayFloat(elem, 1, 0.0f); - s.min[2] = 0.0f; - s.max[0] = moduleRenderableArrayFloat(elem, 2, 0.0f); - s.max[1] = moduleRenderableArrayFloat(elem, 3, 0.0f); - s.max[2] = 0.0f; - s.uvMin[0] = moduleRenderableArrayFloat(elem, 4, 0.0f); - s.uvMin[1] = moduleRenderableArrayFloat(elem, 5, 0.0f); - s.uvMax[0] = moduleRenderableArrayFloat(elem, 6, 1.0f); - s.uvMax[1] = moduleRenderableArrayFloat(elem, 7, 1.0f); - } - jerry_value_free(elem); - sb->sprites[sb->spriteCount++] = s; - } - return jerry_undefined(); -} +void moduleRenderableInit(void); -moduleBaseFunction(moduleRenderableToString) { - jscomponent_t *c = moduleRenderableSelf(callInfo); - if(!c) return jerry_string_sz("Renderable:invalid"); - char_t buf[32]; - snprintf(buf, sizeof(buf), "Renderable(%u)", (unsigned)c->componentId); - return jerry_string_sz(buf); -} - -static void moduleRenderableInit(void) { - scriptProtoInit( - &MODULE_RENDERABLE_PROTO, "Renderable", - sizeof(jscomponent_t), moduleRenderableCtor - ); - - scriptProtoDefineProp( - &MODULE_RENDERABLE_PROTO, "entity", moduleRenderableGetEntity, NULL - ); - scriptProtoDefineProp( - &MODULE_RENDERABLE_PROTO, "id", moduleRenderableGetId, NULL - ); - scriptProtoDefineProp( - &MODULE_RENDERABLE_PROTO, "type", - moduleRenderableGetType, moduleRenderableSetType - ); - scriptProtoDefineProp( - &MODULE_RENDERABLE_PROTO, "priority", - moduleRenderableGetPriority, moduleRenderableSetPriority - ); - scriptProtoDefineProp( - &MODULE_RENDERABLE_PROTO, "color", - moduleRenderableGetColor, moduleRenderableSetColor - ); - scriptProtoDefineProp( - &MODULE_RENDERABLE_PROTO, "texture", - moduleRenderableGetTexture, moduleRenderableSetTexture - ); - scriptProtoDefineProp( - &MODULE_RENDERABLE_PROTO, "sprites", - moduleRenderableGetSprites, moduleRenderableSetSprites - ); - scriptProtoDefineToString(&MODULE_RENDERABLE_PROTO, moduleRenderableToString); - - /* Renderable.SHADER_MATERIAL, .SPRITEBATCH, .CUSTOM */ - jerry_value_t ctor = MODULE_RENDERABLE_PROTO.constructor; - struct { const char_t *name; int val; } types[] = { - { "SHADER_MATERIAL", ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL }, - { "SPRITEBATCH", ENTITY_RENDERABLE_TYPE_SPRITEBATCH }, - { "CUSTOM", ENTITY_RENDERABLE_TYPE_CUSTOM }, - }; - for(int i = 0; i < 3; i++) { - jerry_value_t k = jerry_string_sz(types[i].name); - jerry_value_t v = jerry_number((double)types[i].val); - jerry_object_set(ctor, k, v); - jerry_value_free(v); - jerry_value_free(k); - } -} - -static void moduleRenderableDispose(void) { - scriptProtoDispose(&MODULE_RENDERABLE_PROTO); -} +/** + * Disposes the Renderable module. + */ +void moduleRenderableDispose(void); diff --git a/src/dusk/script/module/entity/component/modulecomponentlist.c b/src/dusk/script/module/entity/component/modulecomponentlist.c new file mode 100644 index 00000000..472b3605 --- /dev/null +++ b/src/dusk/script/module/entity/component/modulecomponentlist.c @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "modulecomponentlist.h" + +jerry_value_t moduleComponentListCreateInstance( + const componenttype_t type, + const jscomponent_t *comp +) { + switch(type) { + case COMPONENT_TYPE_CAMERA: + return scriptProtoCreateValue(&MODULE_CAMERA_PROTO, comp); + case COMPONENT_TYPE_PHYSICS: + return scriptProtoCreateValue(&MODULE_PHYSICS_PROTO, comp); + case COMPONENT_TYPE_POSITION: + return scriptProtoCreateValue(&MODULE_POSITION_PROTO, comp); + case COMPONENT_TYPE_RENDERABLE: + return scriptProtoCreateValue(&MODULE_RENDERABLE_PROTO, comp); + case COMPONENT_TYPE_TRIGGER: + return scriptProtoCreateValue(&MODULE_TRIGGER_PROTO, comp); + default: + return scriptProtoCreateValue(&MODULE_COMPONENT_PROTO, comp); + } +} + +void moduleComponentListInit(void) { + moduleCameraInit(); + modulePhysicsInit(); + modulePositionInit(); + moduleRenderableInit(); + moduleTriggerInit(); +} + +void moduleComponentListDispose(void) { + moduleTriggerDispose(); + moduleRenderableDispose(); + modulePositionDispose(); + modulePhysicsDispose(); + moduleCameraDispose(); +} diff --git a/src/dusk/script/module/entity/component/modulecomponentlist.h b/src/dusk/script/module/entity/component/modulecomponentlist.h index b674172f..0399ebb6 100644 --- a/src/dusk/script/module/entity/component/modulecomponentlist.h +++ b/src/dusk/script/module/entity/component/modulecomponentlist.h @@ -16,39 +16,23 @@ /** * Returns a typed JS instance for a newly-added component. Falls back to the * generic Component proto for types that have no specific module yet. + * + * @param type Component type constant. + * @param comp Initialized jscomponent_t with entityId and componentId. + * @return A jerry_value_t JS object of the appropriate subtype. */ -static jerry_value_t moduleComponentListCreateInstance( +jerry_value_t moduleComponentListCreateInstance( const componenttype_t type, const jscomponent_t *comp -) { - switch(type) { - case COMPONENT_TYPE_CAMERA: - return scriptProtoCreateValue(&MODULE_CAMERA_PROTO, comp); - case COMPONENT_TYPE_PHYSICS: - return scriptProtoCreateValue(&MODULE_PHYSICS_PROTO, comp); - case COMPONENT_TYPE_POSITION: - return scriptProtoCreateValue(&MODULE_POSITION_PROTO, comp); - case COMPONENT_TYPE_RENDERABLE: - return scriptProtoCreateValue(&MODULE_RENDERABLE_PROTO, comp); - case COMPONENT_TYPE_TRIGGER: - return scriptProtoCreateValue(&MODULE_TRIGGER_PROTO, comp); - default: - return scriptProtoCreateValue(&MODULE_COMPONENT_PROTO, comp); - } -} +); -static void moduleComponentListInit(void) { - moduleCameraInit(); - modulePhysicsInit(); - modulePositionInit(); - moduleRenderableInit(); - moduleTriggerInit(); -} +/** + * Initializes all component sub-modules (camera, physics, position, renderable, + * trigger). + */ +void moduleComponentListInit(void); -static void moduleComponentListDispose(void) { - moduleTriggerDispose(); - moduleRenderableDispose(); - modulePositionDispose(); - modulePhysicsDispose(); - moduleCameraDispose(); -} +/** + * Disposes all component sub-modules in reverse init order. + */ +void moduleComponentListDispose(void); diff --git a/src/dusk/script/module/entity/component/physics/CMakeLists.txt b/src/dusk/script/module/entity/component/physics/CMakeLists.txt new file mode 100644 index 00000000..4bb31417 --- /dev/null +++ b/src/dusk/script/module/entity/component/physics/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + modulephysics.c +) diff --git a/src/dusk/script/module/entity/component/physics/modulephysics.c b/src/dusk/script/module/entity/component/physics/modulephysics.c new file mode 100644 index 00000000..0aa7db8e --- /dev/null +++ b/src/dusk/script/module/entity/component/physics/modulephysics.c @@ -0,0 +1,200 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "modulephysics.h" + +scriptproto_t MODULE_PHYSICS_PROTO; + +jscomponent_t *modulePhysicsSelf(const jerry_call_info_t *callInfo) { + return (jscomponent_t *)scriptProtoGetValue( + &MODULE_PHYSICS_PROTO, callInfo->this_value + ); +} + +moduleBaseFunction(modulePhysicsCtor) { + return moduleBaseThrow("Physics cannot be instantiated with new"); +} + +moduleBaseFunction(modulePhysicsGetEntity) { + jscomponent_t *c = modulePhysicsSelf(callInfo); + if(!c) return jerry_undefined(); + return jerry_number((double)c->entityId); +} + +moduleBaseFunction(modulePhysicsGetId) { + jscomponent_t *c = modulePhysicsSelf(callInfo); + if(!c) return jerry_undefined(); + return jerry_number((double)c->componentId); +} + +moduleBaseFunction(modulePhysicsGetBodyType) { + jscomponent_t *c = modulePhysicsSelf(callInfo); + if(!c) return jerry_undefined(); + return jerry_number( + (double)entityPhysicsGetBodyType(c->entityId, c->componentId) + ); +} + +moduleBaseFunction(modulePhysicsSetBodyType) { + moduleBaseRequireArgs(1); + jscomponent_t *c = modulePhysicsSelf(callInfo); + if(!c) return jerry_undefined(); + entityPhysicsSetBodyType( + c->entityId, c->componentId, + (physicsbodytype_t)moduleBaseArgInt(0) + ); + return jerry_undefined(); +} + +moduleBaseFunction(modulePhysicsGetShape) { + jscomponent_t *c = modulePhysicsSelf(callInfo); + if(!c) return jerry_undefined(); + return jerry_number( + (double)entityPhysicsGetShape( + c->entityId, c->componentId + ).type + ); +} + +moduleBaseFunction(modulePhysicsSetShape) { + moduleBaseRequireArgs(1); + jscomponent_t *c = modulePhysicsSelf(callInfo); + if(!c) return jerry_undefined(); + physicsshape_t shape = entityPhysicsGetShape(c->entityId, c->componentId); + shape.type = (physicshapetype_t)moduleBaseArgInt(0); + entityPhysicsSetShape(c->entityId, c->componentId, shape); + return jerry_undefined(); +} + +moduleBaseFunction(modulePhysicsGetVelocity) { + jscomponent_t *c = modulePhysicsSelf(callInfo); + if(!c) return jerry_undefined(); + vec3 v; + entityPhysicsGetVelocity(c->entityId, c->componentId, v); + return moduleVec3Push(v); +} + +moduleBaseFunction(modulePhysicsSetVelocity) { + moduleBaseRequireArgs(1); + jscomponent_t *c = modulePhysicsSelf(callInfo); + if(!c) return jerry_undefined(); + float_t *v = moduleVec3From(args[0]); + if(!v) return moduleBaseThrow("Physics.velocity: expected Vec3"); + entityPhysicsSetVelocity(c->entityId, c->componentId, v); + return jerry_undefined(); +} + +moduleBaseFunction(modulePhysicsGetGravityScale) { + jscomponent_t *c = modulePhysicsSelf(callInfo); + if(!c) return jerry_undefined(); + entityphysics_t *p = entityPhysicsGet(c->entityId, c->componentId); + if(!p) return jerry_undefined(); + return jerry_number((double)p->gravityScale); +} + +moduleBaseFunction(modulePhysicsSetGravityScale) { + moduleBaseRequireArgs(1); + jscomponent_t *c = modulePhysicsSelf(callInfo); + if(!c) return jerry_undefined(); + entityphysics_t *p = entityPhysicsGet(c->entityId, c->componentId); + if(!p) return jerry_undefined(); + p->gravityScale = moduleBaseArgFloat(0); + return jerry_undefined(); +} + +moduleBaseFunction(modulePhysicsGetOnGround) { + jscomponent_t *c = modulePhysicsSelf(callInfo); + if(!c) return jerry_undefined(); + return jerry_boolean(entityPhysicsIsOnGround(c->entityId, c->componentId)); +} + +moduleBaseFunction(modulePhysicsApplyImpulse) { + moduleBaseRequireArgs(1); + jscomponent_t *c = modulePhysicsSelf(callInfo); + if(!c) return jerry_undefined(); + float_t *v = moduleVec3From(args[0]); + if(!v) return moduleBaseThrow("Physics.applyImpulse: expected Vec3"); + entityPhysicsApplyImpulse(c->entityId, c->componentId, v); + return jerry_undefined(); +} + +moduleBaseFunction(modulePhysicsToString) { + jscomponent_t *c = modulePhysicsSelf(callInfo); + if(!c) return jerry_string_sz("Physics:invalid"); + char_t buf[32]; + snprintf(buf, sizeof(buf), "Physics(%u)", (unsigned)c->componentId); + return jerry_string_sz(buf); +} + +void modulePhysicsInit(void) { + scriptProtoInit( + &MODULE_PHYSICS_PROTO, "Physics", + sizeof(jscomponent_t), modulePhysicsCtor + ); + + scriptProtoDefineProp( + &MODULE_PHYSICS_PROTO, "entity", modulePhysicsGetEntity, NULL + ); + scriptProtoDefineProp( + &MODULE_PHYSICS_PROTO, "id", modulePhysicsGetId, NULL + ); + scriptProtoDefineProp( + &MODULE_PHYSICS_PROTO, "bodyType", + modulePhysicsGetBodyType, modulePhysicsSetBodyType + ); + scriptProtoDefineProp( + &MODULE_PHYSICS_PROTO, "shape", + modulePhysicsGetShape, modulePhysicsSetShape + ); + scriptProtoDefineProp( + &MODULE_PHYSICS_PROTO, "velocity", + modulePhysicsGetVelocity, modulePhysicsSetVelocity + ); + scriptProtoDefineProp( + &MODULE_PHYSICS_PROTO, "gravityScale", + modulePhysicsGetGravityScale, modulePhysicsSetGravityScale + ); + scriptProtoDefineProp( + &MODULE_PHYSICS_PROTO, "onGround", modulePhysicsGetOnGround, NULL + ); + scriptProtoDefineFunc( + &MODULE_PHYSICS_PROTO, "applyImpulse", modulePhysicsApplyImpulse + ); + scriptProtoDefineToString(&MODULE_PHYSICS_PROTO, modulePhysicsToString); + + jerry_value_t ctor = MODULE_PHYSICS_PROTO.constructor; + struct { const char_t *name; int val; } bodyTypes[] = { + { "STATIC", PHYSICS_BODY_STATIC }, + { "DYNAMIC", PHYSICS_BODY_DYNAMIC }, + { "KINEMATIC", PHYSICS_BODY_KINEMATIC }, + }; + for(int i = 0; i < 3; i++) { + jerry_value_t k = jerry_string_sz(bodyTypes[i].name); + jerry_value_t v = jerry_number((double)bodyTypes[i].val); + jerry_object_set(ctor, k, v); + jerry_value_free(v); + jerry_value_free(k); + } + + struct { const char_t *name; int val; } shapes[] = { + { "SHAPE_CUBE", PHYSICS_SHAPE_CUBE }, + { "SHAPE_SPHERE", PHYSICS_SHAPE_SPHERE }, + { "SHAPE_CAPSULE", PHYSICS_SHAPE_CAPSULE }, + { "SHAPE_PLANE", PHYSICS_SHAPE_PLANE }, + }; + for(int i = 0; i < 4; i++) { + jerry_value_t k = jerry_string_sz(shapes[i].name); + jerry_value_t v = jerry_number((double)shapes[i].val); + jerry_object_set(ctor, k, v); + jerry_value_free(v); + jerry_value_free(k); + } +} + +void modulePhysicsDispose(void) { + scriptProtoDispose(&MODULE_PHYSICS_PROTO); +} diff --git a/src/dusk/script/module/entity/component/physics/modulephysics.h b/src/dusk/script/module/entity/component/physics/modulephysics.h index 8e7cffd6..a3dfce0c 100644 --- a/src/dusk/script/module/entity/component/physics/modulephysics.h +++ b/src/dusk/script/module/entity/component/physics/modulephysics.h @@ -12,193 +12,61 @@ #include "script/module/entity/modulecomponent.h" #include "entity/component/physics/entityphysics.h" -static scriptproto_t MODULE_PHYSICS_PROTO; +extern scriptproto_t MODULE_PHYSICS_PROTO; -moduleBaseFunction(modulePhysicsCtor) { - (void)callInfo; (void)args; (void)argc; - return moduleBaseThrow("Physics cannot be instantiated with new"); -} +/** Physics() constructor — always throws; not directly instantiable. */ +moduleBaseFunction(modulePhysicsCtor); -static inline jscomponent_t *modulePhysicsSelf( - const jerry_call_info_t *callInfo -) { - return (jscomponent_t *)scriptProtoGetValue( - &MODULE_PHYSICS_PROTO, callInfo->this_value - ); -} +/** @return Entity ID that owns this physics component. */ +moduleBaseFunction(modulePhysicsGetEntity); -moduleBaseFunction(modulePhysicsGetEntity) { - jscomponent_t *c = modulePhysicsSelf(callInfo); - if(!c) return jerry_undefined(); - return jerry_number((double)c->entityId); -} +/** @return This component's ID. */ +moduleBaseFunction(modulePhysicsGetId); -moduleBaseFunction(modulePhysicsGetId) { - jscomponent_t *c = modulePhysicsSelf(callInfo); - if(!c) return jerry_undefined(); - return jerry_number((double)c->componentId); -} +/** @return Body type constant (Physics.STATIC, .DYNAMIC, .KINEMATIC). */ +moduleBaseFunction(modulePhysicsGetBodyType); -moduleBaseFunction(modulePhysicsGetBodyType) { - jscomponent_t *c = modulePhysicsSelf(callInfo); - if(!c) return jerry_undefined(); - return jerry_number((double)entityPhysicsGetBodyType(c->entityId, c->componentId)); -} +/** Sets body type. @param args[0] physicsbodytype_t value. */ +moduleBaseFunction(modulePhysicsSetBodyType); -moduleBaseFunction(modulePhysicsSetBodyType) { - moduleBaseRequireArgs(1); - jscomponent_t *c = modulePhysicsSelf(callInfo); - if(!c) return jerry_undefined(); - entityPhysicsSetBodyType( - c->entityId, c->componentId, - (physicsbodytype_t)moduleBaseArgInt(0) - ); - return jerry_undefined(); -} +/** @return Collision shape type constant (Physics.SHAPE_*). */ +moduleBaseFunction(modulePhysicsGetShape); -moduleBaseFunction(modulePhysicsGetShape) { - jscomponent_t *c = modulePhysicsSelf(callInfo); - if(!c) return jerry_undefined(); - return jerry_number((double)entityPhysicsGetShape(c->entityId, c->componentId).type); -} +/** Sets collision shape type. @param args[0] physicshapetype_t value. */ +moduleBaseFunction(modulePhysicsSetShape); -moduleBaseFunction(modulePhysicsSetShape) { - moduleBaseRequireArgs(1); - jscomponent_t *c = modulePhysicsSelf(callInfo); - if(!c) return jerry_undefined(); - physicsshape_t shape = entityPhysicsGetShape(c->entityId, c->componentId); - shape.type = (physicshapetype_t)moduleBaseArgInt(0); - entityPhysicsSetShape(c->entityId, c->componentId, shape); - return jerry_undefined(); -} +/** @return Linear velocity as a Vec3. */ +moduleBaseFunction(modulePhysicsGetVelocity); -moduleBaseFunction(modulePhysicsGetVelocity) { - jscomponent_t *c = modulePhysicsSelf(callInfo); - if(!c) return jerry_undefined(); - vec3 v; - entityPhysicsGetVelocity(c->entityId, c->componentId, v); - return moduleVec3Push(v); -} +/** Sets linear velocity. @param args[0] Vec3. */ +moduleBaseFunction(modulePhysicsSetVelocity); -moduleBaseFunction(modulePhysicsSetVelocity) { - moduleBaseRequireArgs(1); - jscomponent_t *c = modulePhysicsSelf(callInfo); - if(!c) return jerry_undefined(); - float_t *v = moduleVec3From(args[0]); - if(!v) return moduleBaseThrow("Physics.velocity: expected Vec3"); - entityPhysicsSetVelocity(c->entityId, c->componentId, v); - return jerry_undefined(); -} +/** @return Gravity scale multiplier (float). */ +moduleBaseFunction(modulePhysicsGetGravityScale); -moduleBaseFunction(modulePhysicsGetGravityScale) { - jscomponent_t *c = modulePhysicsSelf(callInfo); - if(!c) return jerry_undefined(); - entityphysics_t *p = entityPhysicsGet(c->entityId, c->componentId); - if(!p) return jerry_undefined(); - return jerry_number((double)p->gravityScale); -} +/** Sets gravity scale. @param args[0] Float multiplier. */ +moduleBaseFunction(modulePhysicsSetGravityScale); -moduleBaseFunction(modulePhysicsSetGravityScale) { - moduleBaseRequireArgs(1); - jscomponent_t *c = modulePhysicsSelf(callInfo); - if(!c) return jerry_undefined(); - entityphysics_t *p = entityPhysicsGet(c->entityId, c->componentId); - if(!p) return jerry_undefined(); - p->gravityScale = moduleBaseArgFloat(0); - return jerry_undefined(); -} +/** @return True when the body is resting on a surface. */ +moduleBaseFunction(modulePhysicsGetOnGround); -moduleBaseFunction(modulePhysicsGetOnGround) { - jscomponent_t *c = modulePhysicsSelf(callInfo); - if(!c) return jerry_undefined(); - return jerry_boolean(entityPhysicsIsOnGround(c->entityId, c->componentId)); -} +/** + * applyImpulse(impulse) — applies an instantaneous force to the body. + * @param args[0] Vec3 impulse vector. + */ +moduleBaseFunction(modulePhysicsApplyImpulse); -moduleBaseFunction(modulePhysicsApplyImpulse) { - moduleBaseRequireArgs(1); - jscomponent_t *c = modulePhysicsSelf(callInfo); - if(!c) return jerry_undefined(); - float_t *v = moduleVec3From(args[0]); - if(!v) return moduleBaseThrow("Physics.applyImpulse: expected Vec3"); - entityPhysicsApplyImpulse(c->entityId, c->componentId, v); - return jerry_undefined(); -} +/** @return "Physics(id)" string. */ +moduleBaseFunction(modulePhysicsToString); -moduleBaseFunction(modulePhysicsToString) { - jscomponent_t *c = modulePhysicsSelf(callInfo); - if(!c) return jerry_string_sz("Physics:invalid"); - char_t buf[32]; - snprintf(buf, sizeof(buf), "Physics(%u)", (unsigned)c->componentId); - return jerry_string_sz(buf); -} +/** + * Initializes the Physics module and registers the global Physics class with + * bodyType/shape/velocity/gravityScale/onGround properties, applyImpulse + * method, and STATIC/DYNAMIC/KINEMATIC and SHAPE_* constants. + */ +void modulePhysicsInit(void); -static void modulePhysicsInit(void) { - scriptProtoInit( - &MODULE_PHYSICS_PROTO, "Physics", - sizeof(jscomponent_t), modulePhysicsCtor - ); - - scriptProtoDefineProp( - &MODULE_PHYSICS_PROTO, "entity", modulePhysicsGetEntity, NULL - ); - scriptProtoDefineProp( - &MODULE_PHYSICS_PROTO, "id", modulePhysicsGetId, NULL - ); - scriptProtoDefineProp( - &MODULE_PHYSICS_PROTO, "bodyType", - modulePhysicsGetBodyType, modulePhysicsSetBodyType - ); - scriptProtoDefineProp( - &MODULE_PHYSICS_PROTO, "shape", - modulePhysicsGetShape, modulePhysicsSetShape - ); - scriptProtoDefineProp( - &MODULE_PHYSICS_PROTO, "velocity", - modulePhysicsGetVelocity, modulePhysicsSetVelocity - ); - scriptProtoDefineProp( - &MODULE_PHYSICS_PROTO, "gravityScale", - modulePhysicsGetGravityScale, modulePhysicsSetGravityScale - ); - scriptProtoDefineProp( - &MODULE_PHYSICS_PROTO, "onGround", modulePhysicsGetOnGround, NULL - ); - scriptProtoDefineFunc( - &MODULE_PHYSICS_PROTO, "applyImpulse", modulePhysicsApplyImpulse - ); - scriptProtoDefineToString(&MODULE_PHYSICS_PROTO, modulePhysicsToString); - - /* Body type constants */ - jerry_value_t ctor = MODULE_PHYSICS_PROTO.constructor; - struct { const char_t *name; int val; } bodyTypes[] = { - { "STATIC", PHYSICS_BODY_STATIC }, - { "DYNAMIC", PHYSICS_BODY_DYNAMIC }, - { "KINEMATIC", PHYSICS_BODY_KINEMATIC }, - }; - for(int i = 0; i < 3; i++) { - jerry_value_t k = jerry_string_sz(bodyTypes[i].name); - jerry_value_t v = jerry_number((double)bodyTypes[i].val); - jerry_object_set(ctor, k, v); - jerry_value_free(v); - jerry_value_free(k); - } - - /* Shape type constants */ - struct { const char_t *name; int val; } shapes[] = { - { "SHAPE_CUBE", PHYSICS_SHAPE_CUBE }, - { "SHAPE_SPHERE", PHYSICS_SHAPE_SPHERE }, - { "SHAPE_CAPSULE", PHYSICS_SHAPE_CAPSULE }, - { "SHAPE_PLANE", PHYSICS_SHAPE_PLANE }, - }; - for(int i = 0; i < 4; i++) { - jerry_value_t k = jerry_string_sz(shapes[i].name); - jerry_value_t v = jerry_number((double)shapes[i].val); - jerry_object_set(ctor, k, v); - jerry_value_free(v); - jerry_value_free(k); - } -} - -static void modulePhysicsDispose(void) { - scriptProtoDispose(&MODULE_PHYSICS_PROTO); -} +/** + * Disposes the Physics module. + */ +void modulePhysicsDispose(void); diff --git a/src/dusk/script/module/entity/component/trigger/CMakeLists.txt b/src/dusk/script/module/entity/component/trigger/CMakeLists.txt new file mode 100644 index 00000000..915db5ac --- /dev/null +++ b/src/dusk/script/module/entity/component/trigger/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + moduletrigger.c +) diff --git a/src/dusk/script/module/entity/component/trigger/moduletrigger.c b/src/dusk/script/module/entity/component/trigger/moduletrigger.c new file mode 100644 index 00000000..1cf5c5b6 --- /dev/null +++ b/src/dusk/script/module/entity/component/trigger/moduletrigger.c @@ -0,0 +1,132 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "moduletrigger.h" + +scriptproto_t MODULE_TRIGGER_PROTO; + +jscomponent_t *moduleTriggerSelf(const jerry_call_info_t *callInfo) { + return (jscomponent_t *)scriptProtoGetValue( + &MODULE_TRIGGER_PROTO, callInfo->this_value + ); +} + +moduleBaseFunction(moduleTriggerCtor) { + return moduleBaseThrow("Trigger cannot be instantiated with new"); +} + +moduleBaseFunction(moduleTriggerGetEntity) { + jscomponent_t *c = moduleTriggerSelf(callInfo); + if(!c) return jerry_undefined(); + return jerry_number((double)c->entityId); +} + +moduleBaseFunction(moduleTriggerGetId) { + jscomponent_t *c = moduleTriggerSelf(callInfo); + if(!c) return jerry_undefined(); + return jerry_number((double)c->componentId); +} + +moduleBaseFunction(moduleTriggerGetMin) { + jscomponent_t *c = moduleTriggerSelf(callInfo); + if(!c) return jerry_undefined(); + entitytrigger_t *t = entityTriggerGet(c->entityId, c->componentId); + if(!t) return jerry_undefined(); + return moduleVec3Push(t->min); +} + +moduleBaseFunction(moduleTriggerSetMin) { + moduleBaseRequireArgs(1); + jscomponent_t *c = moduleTriggerSelf(callInfo); + if(!c) return jerry_undefined(); + float_t *v = moduleVec3From(args[0]); + if(!v) return moduleBaseThrow("Trigger.min: expected Vec3"); + entitytrigger_t *t = entityTriggerGet(c->entityId, c->componentId); + if(!t) return jerry_undefined(); + glm_vec3_copy(v, t->min); + return jerry_undefined(); +} + +moduleBaseFunction(moduleTriggerGetMax) { + jscomponent_t *c = moduleTriggerSelf(callInfo); + if(!c) return jerry_undefined(); + entitytrigger_t *t = entityTriggerGet(c->entityId, c->componentId); + if(!t) return jerry_undefined(); + return moduleVec3Push(t->max); +} + +moduleBaseFunction(moduleTriggerSetMax) { + moduleBaseRequireArgs(1); + jscomponent_t *c = moduleTriggerSelf(callInfo); + if(!c) return jerry_undefined(); + float_t *v = moduleVec3From(args[0]); + if(!v) return moduleBaseThrow("Trigger.max: expected Vec3"); + entitytrigger_t *t = entityTriggerGet(c->entityId, c->componentId); + if(!t) return jerry_undefined(); + glm_vec3_copy(v, t->max); + return jerry_undefined(); +} + +moduleBaseFunction(moduleTriggerSetBounds) { + moduleBaseRequireArgs(2); + jscomponent_t *c = moduleTriggerSelf(callInfo); + if(!c) return jerry_undefined(); + float_t *minV = moduleVec3From(args[0]); + float_t *maxV = moduleVec3From(args[1]); + if(!minV) return moduleBaseThrow("Trigger.setBounds: expected Vec3 for min"); + if(!maxV) return moduleBaseThrow("Trigger.setBounds: expected Vec3 for max"); + entityTriggerSetBounds(c->entityId, c->componentId, minV, maxV); + return jerry_undefined(); +} + +moduleBaseFunction(moduleTriggerContains) { + moduleBaseRequireArgs(1); + jscomponent_t *c = moduleTriggerSelf(callInfo); + if(!c) return jerry_undefined(); + float_t *v = moduleVec3From(args[0]); + if(!v) return moduleBaseThrow("Trigger.contains: expected Vec3"); + return jerry_boolean(entityTriggerContains(c->entityId, c->componentId, v)); +} + +moduleBaseFunction(moduleTriggerToString) { + jscomponent_t *c = moduleTriggerSelf(callInfo); + if(!c) return jerry_string_sz("Trigger:invalid"); + char_t buf[32]; + snprintf(buf, sizeof(buf), "Trigger(%u)", (unsigned)c->componentId); + return jerry_string_sz(buf); +} + +void moduleTriggerInit(void) { + scriptProtoInit( + &MODULE_TRIGGER_PROTO, "Trigger", + sizeof(jscomponent_t), moduleTriggerCtor + ); + + scriptProtoDefineProp( + &MODULE_TRIGGER_PROTO, "entity", moduleTriggerGetEntity, NULL + ); + scriptProtoDefineProp( + &MODULE_TRIGGER_PROTO, "id", moduleTriggerGetId, NULL + ); + scriptProtoDefineProp( + &MODULE_TRIGGER_PROTO, "min", moduleTriggerGetMin, moduleTriggerSetMin + ); + scriptProtoDefineProp( + &MODULE_TRIGGER_PROTO, "max", moduleTriggerGetMax, moduleTriggerSetMax + ); + scriptProtoDefineFunc( + &MODULE_TRIGGER_PROTO, "setBounds", moduleTriggerSetBounds + ); + scriptProtoDefineFunc( + &MODULE_TRIGGER_PROTO, "contains", moduleTriggerContains + ); + scriptProtoDefineToString(&MODULE_TRIGGER_PROTO, moduleTriggerToString); +} + +void moduleTriggerDispose(void) { + scriptProtoDispose(&MODULE_TRIGGER_PROTO); +} diff --git a/src/dusk/script/module/entity/component/trigger/moduletrigger.h b/src/dusk/script/module/entity/component/trigger/moduletrigger.h index 0039aad0..60580166 100644 --- a/src/dusk/script/module/entity/component/trigger/moduletrigger.h +++ b/src/dusk/script/module/entity/component/trigger/moduletrigger.h @@ -12,129 +12,53 @@ #include "script/module/entity/modulecomponent.h" #include "entity/component/trigger/entitytrigger.h" -static scriptproto_t MODULE_TRIGGER_PROTO; +extern scriptproto_t MODULE_TRIGGER_PROTO; -moduleBaseFunction(moduleTriggerCtor) { - (void)callInfo; (void)args; (void)argc; - return moduleBaseThrow("Trigger cannot be instantiated with new"); -} +/** Trigger() constructor — always throws; not directly instantiable. */ +moduleBaseFunction(moduleTriggerCtor); -static inline jscomponent_t *moduleTriggerSelf( - const jerry_call_info_t *callInfo -) { - return (jscomponent_t *)scriptProtoGetValue( - &MODULE_TRIGGER_PROTO, callInfo->this_value - ); -} +/** @return Entity ID that owns this trigger component. */ +moduleBaseFunction(moduleTriggerGetEntity); -moduleBaseFunction(moduleTriggerGetEntity) { - jscomponent_t *c = moduleTriggerSelf(callInfo); - if(!c) return jerry_undefined(); - return jerry_number((double)c->entityId); -} +/** @return This component's ID. */ +moduleBaseFunction(moduleTriggerGetId); -moduleBaseFunction(moduleTriggerGetId) { - jscomponent_t *c = moduleTriggerSelf(callInfo); - if(!c) return jerry_undefined(); - return jerry_number((double)c->componentId); -} +/** @return AABB minimum corner as a Vec3. */ +moduleBaseFunction(moduleTriggerGetMin); -moduleBaseFunction(moduleTriggerGetMin) { - jscomponent_t *c = moduleTriggerSelf(callInfo); - if(!c) return jerry_undefined(); - entitytrigger_t *t = entityTriggerGet(c->entityId, c->componentId); - if(!t) return jerry_undefined(); - return moduleVec3Push(t->min); -} +/** Sets AABB minimum corner. @param args[0] Vec3. */ +moduleBaseFunction(moduleTriggerSetMin); -moduleBaseFunction(moduleTriggerSetMin) { - moduleBaseRequireArgs(1); - jscomponent_t *c = moduleTriggerSelf(callInfo); - if(!c) return jerry_undefined(); - float_t *v = moduleVec3From(args[0]); - if(!v) return moduleBaseThrow("Trigger.min: expected Vec3"); - entitytrigger_t *t = entityTriggerGet(c->entityId, c->componentId); - if(!t) return jerry_undefined(); - glm_vec3_copy(v, t->min); - return jerry_undefined(); -} +/** @return AABB maximum corner as a Vec3. */ +moduleBaseFunction(moduleTriggerGetMax); -moduleBaseFunction(moduleTriggerGetMax) { - jscomponent_t *c = moduleTriggerSelf(callInfo); - if(!c) return jerry_undefined(); - entitytrigger_t *t = entityTriggerGet(c->entityId, c->componentId); - if(!t) return jerry_undefined(); - return moduleVec3Push(t->max); -} +/** Sets AABB maximum corner. @param args[0] Vec3. */ +moduleBaseFunction(moduleTriggerSetMax); -moduleBaseFunction(moduleTriggerSetMax) { - moduleBaseRequireArgs(1); - jscomponent_t *c = moduleTriggerSelf(callInfo); - if(!c) return jerry_undefined(); - float_t *v = moduleVec3From(args[0]); - if(!v) return moduleBaseThrow("Trigger.max: expected Vec3"); - entitytrigger_t *t = entityTriggerGet(c->entityId, c->componentId); - if(!t) return jerry_undefined(); - glm_vec3_copy(v, t->max); - return jerry_undefined(); -} +/** + * setBounds(min, max) — sets both AABB corners at once. + * @param args[0] Vec3 minimum corner. + * @param args[1] Vec3 maximum corner. + */ +moduleBaseFunction(moduleTriggerSetBounds); -moduleBaseFunction(moduleTriggerSetBounds) { - moduleBaseRequireArgs(2); - jscomponent_t *c = moduleTriggerSelf(callInfo); - if(!c) return jerry_undefined(); - float_t *minV = moduleVec3From(args[0]); - float_t *maxV = moduleVec3From(args[1]); - if(!minV) return moduleBaseThrow("Trigger.setBounds: expected Vec3 for min"); - if(!maxV) return moduleBaseThrow("Trigger.setBounds: expected Vec3 for max"); - entityTriggerSetBounds(c->entityId, c->componentId, minV, maxV); - return jerry_undefined(); -} +/** + * contains(point) — tests whether a world-space point is inside the AABB. + * @param args[0] Vec3 point. + * @return true if the point is inside the trigger volume. + */ +moduleBaseFunction(moduleTriggerContains); -moduleBaseFunction(moduleTriggerContains) { - moduleBaseRequireArgs(1); - jscomponent_t *c = moduleTriggerSelf(callInfo); - if(!c) return jerry_undefined(); - float_t *v = moduleVec3From(args[0]); - if(!v) return moduleBaseThrow("Trigger.contains: expected Vec3"); - return jerry_boolean(entityTriggerContains(c->entityId, c->componentId, v)); -} +/** @return "Trigger(id)" string. */ +moduleBaseFunction(moduleTriggerToString); -moduleBaseFunction(moduleTriggerToString) { - jscomponent_t *c = moduleTriggerSelf(callInfo); - if(!c) return jerry_string_sz("Trigger:invalid"); - char_t buf[32]; - snprintf(buf, sizeof(buf), "Trigger(%u)", (unsigned)c->componentId); - return jerry_string_sz(buf); -} +/** + * Initializes the Trigger module and registers the global Trigger class with + * min/max properties and setBounds/contains methods. + */ +void moduleTriggerInit(void); -static void moduleTriggerInit(void) { - scriptProtoInit( - &MODULE_TRIGGER_PROTO, "Trigger", - sizeof(jscomponent_t), moduleTriggerCtor - ); - - scriptProtoDefineProp( - &MODULE_TRIGGER_PROTO, "entity", moduleTriggerGetEntity, NULL - ); - scriptProtoDefineProp( - &MODULE_TRIGGER_PROTO, "id", moduleTriggerGetId, NULL - ); - scriptProtoDefineProp( - &MODULE_TRIGGER_PROTO, "min", moduleTriggerGetMin, moduleTriggerSetMin - ); - scriptProtoDefineProp( - &MODULE_TRIGGER_PROTO, "max", moduleTriggerGetMax, moduleTriggerSetMax - ); - scriptProtoDefineFunc( - &MODULE_TRIGGER_PROTO, "setBounds", moduleTriggerSetBounds - ); - scriptProtoDefineFunc( - &MODULE_TRIGGER_PROTO, "contains", moduleTriggerContains - ); - scriptProtoDefineToString(&MODULE_TRIGGER_PROTO, moduleTriggerToString); -} - -static void moduleTriggerDispose(void) { - scriptProtoDispose(&MODULE_TRIGGER_PROTO); -} +/** + * Disposes the Trigger module. + */ +void moduleTriggerDispose(void); diff --git a/src/dusk/script/module/entity/modulecomponent.c b/src/dusk/script/module/entity/modulecomponent.c new file mode 100644 index 00000000..3326c949 --- /dev/null +++ b/src/dusk/script/module/entity/modulecomponent.c @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "modulecomponent.h" + +scriptproto_t MODULE_COMPONENT_PROTO; + +moduleBaseFunction(moduleComponentCtor) { + return moduleBaseThrow("Component cannot be instantiated with new"); +} + +moduleBaseFunction(moduleComponentGetEntity) { + jscomponent_t *c = (jscomponent_t *)scriptProtoGetValue( + &MODULE_COMPONENT_PROTO, callInfo->this_value + ); + if(!c) return jerry_undefined(); + return jerry_number((double)c->entityId); +} + +moduleBaseFunction(moduleComponentGetId) { + jscomponent_t *c = (jscomponent_t *)scriptProtoGetValue( + &MODULE_COMPONENT_PROTO, callInfo->this_value + ); + if(!c) return jerry_undefined(); + return jerry_number((double)c->componentId); +} + +moduleBaseFunction(moduleComponentToString) { + jscomponent_t *c = (jscomponent_t *)scriptProtoGetValue( + &MODULE_COMPONENT_PROTO, callInfo->this_value + ); + if(!c) return jerry_string_sz("Component:invalid"); + char_t buf[32]; + snprintf(buf, sizeof(buf), "Component(%u)", (unsigned)c->componentId); + return jerry_string_sz(buf); +} + +void moduleComponentInit(void) { + scriptProtoInit( + &MODULE_COMPONENT_PROTO, "Component", + sizeof(jscomponent_t), moduleComponentCtor + ); + scriptProtoDefineProp( + &MODULE_COMPONENT_PROTO, "entity", moduleComponentGetEntity, NULL + ); + scriptProtoDefineProp( + &MODULE_COMPONENT_PROTO, "id", moduleComponentGetId, NULL + ); + scriptProtoDefineToString(&MODULE_COMPONENT_PROTO, moduleComponentToString); + + jerry_value_t ctor = MODULE_COMPONENT_PROTO.constructor; + #define X(enumName, type, field, init, dispose, render) { \ + jerry_value_t _k = jerry_string_sz(#enumName); \ + jerry_value_t _v = jerry_number((double)COMPONENT_TYPE_##enumName); \ + jerry_object_set(ctor, _k, _v); \ + jerry_value_free(_v); \ + jerry_value_free(_k); \ + } + #include "entity/componentlist.h" + #undef X +} + +void moduleComponentDispose(void) { + scriptProtoDispose(&MODULE_COMPONENT_PROTO); +} diff --git a/src/dusk/script/module/entity/modulecomponent.h b/src/dusk/script/module/entity/modulecomponent.h index dc036b00..fbee1596 100644 --- a/src/dusk/script/module/entity/modulecomponent.h +++ b/src/dusk/script/module/entity/modulecomponent.h @@ -10,84 +10,33 @@ #include "script/scriptproto.h" #include "entity/component.h" +extern scriptproto_t MODULE_COMPONENT_PROTO; + /** C struct wrapped by every Component JS instance. */ typedef struct { entityid_t entityId; componentid_t componentId; } jscomponent_t; -static scriptproto_t MODULE_COMPONENT_PROTO; +/** Component() constructor — always throws; not directly instantiable. */ +moduleBaseFunction(moduleComponentCtor); -moduleBaseFunction(moduleComponentCtor) { - (void)callInfo; (void)args; (void)argc; - return moduleBaseThrow("Component cannot be instantiated with new"); -} +/** @return The entity ID that owns this component. */ +moduleBaseFunction(moduleComponentGetEntity); -moduleBaseFunction(moduleComponentGetEntity) { - jscomponent_t *comp = scriptProtoGetValue( - &MODULE_COMPONENT_PROTO, callInfo->this_value - ); - if(!comp) return jerry_undefined(); - return jerry_number((double)comp->entityId); -} +/** @return This component's ID. */ +moduleBaseFunction(moduleComponentGetId); -moduleBaseFunction(moduleComponentGetId) { - jscomponent_t *comp = scriptProtoGetValue( - &MODULE_COMPONENT_PROTO, callInfo->this_value - ); - if(!comp) return jerry_undefined(); - return jerry_number((double)comp->componentId); -} +/** @return Component ID as a string. */ +moduleBaseFunction(moduleComponentToString); -moduleBaseFunction(moduleComponentToString) { - jscomponent_t *comp = scriptProtoGetValue( - &MODULE_COMPONENT_PROTO, callInfo->this_value - ); - if(!comp) return jerry_string_sz("Component:invalid"); - jerry_value_t num = jerry_number((double)comp->componentId); - jerry_value_t str = jerry_value_to_string(num); - jerry_value_free(num); - return str; -} +/** + * Initializes the Component module, registers the global Component class with + * entity/id properties and TYPE_* / INVALID constants. + */ +void moduleComponentInit(void); -static void moduleComponentInit(void) { - scriptProtoInit( - &MODULE_COMPONENT_PROTO, "Component", - sizeof(jscomponent_t), moduleComponentCtor - ); - - /* Instance properties */ - scriptProtoDefineProp( - &MODULE_COMPONENT_PROTO, "entity", - moduleComponentGetEntity, NULL - ); - scriptProtoDefineProp( - &MODULE_COMPONENT_PROTO, "id", - moduleComponentGetId, NULL - ); - scriptProtoDefineToString(&MODULE_COMPONENT_PROTO, moduleComponentToString); - - /* Component.POSITION, Component.CAMERA, etc. from componentlist.h */ - jerry_value_t ctor = MODULE_COMPONENT_PROTO.constructor; -#define X(enumName, type, field, init, dispose, render) \ - do { \ - jerry_value_t _key = jerry_string_sz(#enumName); \ - jerry_value_t _val = jerry_number((double)COMPONENT_TYPE_##enumName); \ - jerry_object_set(ctor, _key, _val); \ - jerry_value_free(_val); \ - jerry_value_free(_key); \ - } while(0); -#include "entity/componentlist.h" -#undef X - - /* Component.INVALID */ - jerry_value_t _key = jerry_string_sz("INVALID"); - jerry_value_t _val = jerry_number((double)COMPONENT_ID_INVALID); - jerry_object_set(ctor, _key, _val); - jerry_value_free(_val); - jerry_value_free(_key); -} - -static void moduleComponentDispose(void) { - scriptProtoDispose(&MODULE_COMPONENT_PROTO); -} +/** + * Disposes the Component module. + */ +void moduleComponentDispose(void); diff --git a/src/dusk/script/module/entity/moduleentity.c b/src/dusk/script/module/entity/moduleentity.c new file mode 100644 index 00000000..7897af05 --- /dev/null +++ b/src/dusk/script/module/entity/moduleentity.c @@ -0,0 +1,97 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "moduleentity.h" + +scriptproto_t MODULE_ENTITY_PROTO; + +moduleBaseFunction(moduleEntityCtor) { + return moduleBaseThrow("Entity cannot be instantiated with new"); +} + +moduleBaseFunction(moduleEntityCreate) { + entityid_t id = entityManagerAdd(); + if(id == ENTITY_ID_INVALID) { + return moduleBaseThrow("Entity.create: no entity slots available"); + } + jsentity_t ent = { .id = id }; + return scriptProtoCreateValue(&MODULE_ENTITY_PROTO, &ent); +} + +moduleBaseFunction(moduleEntityDisposeEntity) { + moduleBaseRequireArgs(1); + jsentity_t *ent = scriptProtoGetValue(&MODULE_ENTITY_PROTO, args[0]); + if(!ent) return moduleBaseThrow("Entity.dispose: expected Entity object"); + entityDispose(ent->id); + return jerry_undefined(); +} + +moduleBaseFunction(moduleEntityAdd) { + jsentity_t *ent = scriptProtoGetValue( + &MODULE_ENTITY_PROTO, callInfo->this_value + ); + if(!ent) return moduleBaseThrow("Entity.add: invalid this"); + moduleBaseRequireArgs(1); + moduleBaseRequireNumber(0); + + const componenttype_t type = (componenttype_t)moduleBaseArgInt(0); + if(type <= COMPONENT_TYPE_NULL || type >= COMPONENT_TYPE_COUNT) { + return moduleBaseThrow("Entity.add: invalid component type"); + } + + componentid_t cid = entityAddComponent(ent->id, type); + if(cid == COMPONENT_ID_INVALID) { + return moduleBaseThrow("Entity.add: failed to add component"); + } + + jscomponent_t comp = { .entityId = ent->id, .componentId = cid }; + return moduleComponentListCreateInstance(type, &comp); +} + +moduleBaseFunction(moduleEntityToString) { + jsentity_t *ent = scriptProtoGetValue( + &MODULE_ENTITY_PROTO, callInfo->this_value + ); + if(!ent) return jerry_string_sz("Entity:invalid"); + jerry_value_t num = jerry_number((double)ent->id); + jerry_value_t str = jerry_value_to_string(num); + jerry_value_free(num); + return str; +} + +void moduleEntityInit(void) { + moduleComponentInit(); + moduleComponentListInit(); + + scriptProtoInit( + &MODULE_ENTITY_PROTO, "Entity", + sizeof(jsentity_t), moduleEntityCtor + ); + + scriptProtoDefineStaticFunc( + &MODULE_ENTITY_PROTO, "create", moduleEntityCreate + ); + scriptProtoDefineStaticFunc( + &MODULE_ENTITY_PROTO, "dispose", moduleEntityDisposeEntity + ); + + jerry_value_t ctor = MODULE_ENTITY_PROTO.constructor; + jerry_value_t _key = jerry_string_sz("INVALID"); + jerry_value_t _val = jerry_number((double)ENTITY_ID_INVALID); + jerry_object_set(ctor, _key, _val); + jerry_value_free(_val); + jerry_value_free(_key); + + scriptProtoDefineFunc(&MODULE_ENTITY_PROTO, "add", moduleEntityAdd); + scriptProtoDefineToString(&MODULE_ENTITY_PROTO, moduleEntityToString); +} + +void moduleEntityDispose(void) { + scriptProtoDispose(&MODULE_ENTITY_PROTO); + moduleComponentListDispose(); + moduleComponentDispose(); +} diff --git a/src/dusk/script/module/entity/moduleentity.h b/src/dusk/script/module/entity/moduleentity.h index 990c9146..4df4abc3 100644 --- a/src/dusk/script/module/entity/moduleentity.h +++ b/src/dusk/script/module/entity/moduleentity.h @@ -12,95 +12,50 @@ #include "script/module/entity/component/modulecomponentlist.h" #include "entity/entitymanager.h" +extern scriptproto_t MODULE_ENTITY_PROTO; + /** C struct wrapped by every Entity JS instance. */ typedef struct { entityid_t id; } jsentity_t; -static scriptproto_t MODULE_ENTITY_PROTO; +/** Entity() constructor — always throws; use Entity.create() instead. */ +moduleBaseFunction(moduleEntityCtor); -moduleBaseFunction(moduleEntityCtor) { - (void)callInfo; (void)args; (void)argc; - return moduleBaseThrow("Entity cannot be instantiated with new"); -} +/** + * Entity.create() — allocates a new entity slot and returns an + * Entity JS object. + * @return Entity JS object. + * @throws If no entity slots are available. + */ +moduleBaseFunction(moduleEntityCreate); -moduleBaseFunction(moduleEntityCreate) { - entityid_t id = entityManagerAdd(); - if(id == ENTITY_ID_INVALID) { - return moduleBaseThrow("Entity.create: no entity slots available"); - } - jsentity_t ent = { .id = id }; - return scriptProtoCreateValue(&MODULE_ENTITY_PROTO, &ent); -} +/** + * Entity.dispose(entity) — disposes an entity and all its components. + * @param args[0] Entity JS object. + */ +moduleBaseFunction(moduleEntityDisposeEntity); -moduleBaseFunction(moduleEntityDisposeEntity) { - moduleBaseRequireArgs(1); - jsentity_t *ent = scriptProtoGetValue(&MODULE_ENTITY_PROTO, args[0]); - if(!ent) return moduleBaseThrow("Entity.dispose: expected Entity object"); - entityDispose(ent->id); - return jerry_undefined(); -} +/** + * entity.add(type) — adds a component of the given type to this entity. + * @param args[0] Component type constant (e.g. COMPONENT_TYPE_POSITION). + * @return Typed component JS object (Position, Camera, etc.). + * @throws If the type is invalid or no component slots are available. + */ +moduleBaseFunction(moduleEntityAdd); -moduleBaseFunction(moduleEntityAdd) { - jsentity_t *ent = scriptProtoGetValue( - &MODULE_ENTITY_PROTO, callInfo->this_value - ); - if(!ent) return moduleBaseThrow("Entity.add: invalid this"); - moduleBaseRequireArgs(1); - moduleBaseRequireNumber(0); +/** @return Entity ID as a string. */ +moduleBaseFunction(moduleEntityToString); - const componenttype_t type = (componenttype_t)moduleBaseArgInt(0); - if(type <= COMPONENT_TYPE_NULL || type >= COMPONENT_TYPE_COUNT) { - return moduleBaseThrow("Entity.add: invalid component type"); - } +/** + * Initializes the Entity module and registers the global Entity class with + * create/dispose static methods, add instance method, and INVALID constant. + * Also calls moduleComponentInit() and moduleComponentListInit(). + */ +void moduleEntityInit(void); - componentid_t cid = entityAddComponent(ent->id, type); - if(cid == COMPONENT_ID_INVALID) { - return moduleBaseThrow("Entity.add: failed to add component"); - } - - jscomponent_t comp = { .entityId = ent->id, .componentId = cid }; - return moduleComponentListCreateInstance(type, &comp); -} - -moduleBaseFunction(moduleEntityToString) { - jsentity_t *ent = scriptProtoGetValue( - &MODULE_ENTITY_PROTO, callInfo->this_value - ); - if(!ent) return jerry_string_sz("Entity:invalid"); - jerry_value_t num = jerry_number((double)ent->id); - jerry_value_t str = jerry_value_to_string(num); - jerry_value_free(num); - return str; -} - -static void moduleEntityInit(void) { - scriptProtoInit( - &MODULE_ENTITY_PROTO, "Entity", - sizeof(jsentity_t), moduleEntityCtor - ); - - /* Static methods */ - scriptProtoDefineStaticFunc( - &MODULE_ENTITY_PROTO, "create", moduleEntityCreate - ); - scriptProtoDefineStaticFunc( - &MODULE_ENTITY_PROTO, "dispose", moduleEntityDisposeEntity - ); - - /* Entity.INVALID */ - jerry_value_t ctor = MODULE_ENTITY_PROTO.constructor; - jerry_value_t _key = jerry_string_sz("INVALID"); - jerry_value_t _val = jerry_number((double)ENTITY_ID_INVALID); - jerry_object_set(ctor, _key, _val); - jerry_value_free(_val); - jerry_value_free(_key); - - /* Instance methods */ - scriptProtoDefineFunc(&MODULE_ENTITY_PROTO, "add", moduleEntityAdd); - scriptProtoDefineToString(&MODULE_ENTITY_PROTO, moduleEntityToString); -} - -static void moduleEntityDispose(void) { - scriptProtoDispose(&MODULE_ENTITY_PROTO); -} +/** + * Disposes the Entity module and calls moduleComponentListDispose() and + * moduleComponentDispose(). + */ +void moduleEntityDispose(void); diff --git a/src/dusk/script/module/event/moduleevent.c b/src/dusk/script/module/event/moduleevent.c index 23608390..a1783e43 100644 --- a/src/dusk/script/module/event/moduleevent.c +++ b/src/dusk/script/module/event/moduleevent.c @@ -21,80 +21,201 @@ scriptproto_t MODULE_EVENT_PROTO; static moduleeventpending_t MODULE_EVENT_PENDING[MODULE_EVENT_PENDING_MAX]; static uint32_t MODULE_EVENT_PENDING_COUNT = 0; -/** - * Single shared C callback subscribed to any event that has JS awaits. - * Resolves all pending promises for the fired event, then unsubscribes. - * The user pointer is the event_t * so we can look up and unsubscribe. - */ -static void moduleEventFireCallback(void *params, void *user) { - (void)params; +void moduleEventTrampoline0(void *params, void *user) { + jerry_value_t fn = (jerry_value_t)(uintptr_t)user; + jerry_value_t ret = jerry_call(fn, jerry_undefined(), NULL, 0); + jerry_value_free(ret); +} + +void moduleEventTrampoline1(void *params, void *user) { + jerry_value_t fn = (jerry_value_t)(uintptr_t)user; + jerry_value_t ret = jerry_call(fn, jerry_undefined(), NULL, 0); + jerry_value_free(ret); +} + +void moduleEventTrampoline2(void *params, void *user) { + jerry_value_t fn = (jerry_value_t)(uintptr_t)user; + jerry_value_t ret = jerry_call(fn, jerry_undefined(), NULL, 0); + jerry_value_free(ret); +} + +void moduleEventTrampoline3(void *params, void *user) { + jerry_value_t fn = (jerry_value_t)(uintptr_t)user; + jerry_value_t ret = jerry_call(fn, jerry_undefined(), NULL, 0); + jerry_value_free(ret); +} + +eventcallback_t MODULE_EVENT_TRAMPOLINES[MODULE_EVENT_MAX_SLOTS] = { + moduleEventTrampoline0, + moduleEventTrampoline1, + moduleEventTrampoline2, + moduleEventTrampoline3, +}; + +void moduleEventFree(void *ptr, jerry_object_native_info_t *info) { + jsevent_t *ev = (jsevent_t *)ptr; + if(ev) { + for(uint32_t i = 0; i < MODULE_EVENT_MAX_SLOTS; i++) { + if(jerry_value_is_function(ev->fns[i])) { + if(ev->event) { + eventUnsubscribe(ev->event, MODULE_EVENT_TRAMPOLINES[i]); + } + jerry_value_free(ev->fns[i]); + } + } + } + memoryFree(ptr); +} + +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 ret = jerry_promise_resolve( - MODULE_EVENT_PENDING[i].promise, jerry_undefined() + jerry_value_t undef = jerry_undefined(); + jerry_value_t r = jerry_promise_resolve( + MODULE_EVENT_PENDING[i].promise, undef ); - jerry_value_free(ret); + 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]; + MODULE_EVENT_PENDING[i] = + MODULE_EVENT_PENDING[MODULE_EVENT_PENDING_COUNT]; } } - eventUnsubscribe(event, moduleEventFireCallback); + eventUnsubscribe(event, moduleEventWaitFire); } -static jerry_value_t moduleEventWait( - const jerry_call_info_t *callInfo, - const jerry_value_t args[], - const jerry_length_t argc -) { - (void)args; (void)argc; +moduleBaseFunction(moduleEventOn) { + moduleBaseRequireArgs(1); + jsevent_t *ev = (jsevent_t *)scriptProtoGetValue( + &MODULE_EVENT_PROTO, callInfo->this_value + ); + if(!ev || !ev->event) { + return moduleBaseThrow("Event.on: invalid event"); + } + if(!jerry_value_is_function(args[0])) { + return moduleBaseThrow("Event.on: expected function"); + } + for(uint32_t i = 0; i < MODULE_EVENT_MAX_SLOTS; i++) { + if(!jerry_value_is_function(ev->fns[i])) { + ev->fns[i] = jerry_value_copy(args[0]); + eventSubscribe( + ev->event, + MODULE_EVENT_TRAMPOLINES[i], + (void *)(uintptr_t)ev->fns[i] + ); + return jerry_value_copy(callInfo->this_value); + } + } + return moduleBaseThrow("Event.on: no available subscriber slots"); +} - jsevent_t *ev = scriptProtoGetValue(&MODULE_EVENT_PROTO, callInfo->this_value); - if(!ev) return moduleBaseThrow("Event.wait: invalid this"); +moduleBaseFunction(moduleEventOff) { + moduleBaseRequireArgs(1); + jsevent_t *ev = (jsevent_t *)scriptProtoGetValue( + &MODULE_EVENT_PROTO, callInfo->this_value + ); + if(!ev || !ev->event) return jerry_value_copy(callInfo->this_value); + for(uint32_t i = 0; i < MODULE_EVENT_MAX_SLOTS; i++) { + if(!jerry_value_is_function(ev->fns[i])) continue; + jerry_value_t eq = jerry_binary_op( + JERRY_BIN_OP_STRICT_EQUAL, ev->fns[i], args[0] + ); + bool_t match = jerry_value_is_true(eq); + jerry_value_free(eq); + if(match) { + eventUnsubscribe(ev->event, MODULE_EVENT_TRAMPOLINES[i]); + jerry_value_free(ev->fns[i]); + ev->fns[i] = jerry_undefined(); + return jerry_value_copy(callInfo->this_value); + } + } + return jerry_value_copy(callInfo->this_value); +} + +moduleBaseFunction(moduleEventWait) { + jsevent_t *ev = (jsevent_t *)scriptProtoGetValue( + &MODULE_EVENT_PROTO, callInfo->this_value + ); + if(!ev || !ev->event) { + return moduleBaseThrow("Event.wait: invalid event"); + } if(MODULE_EVENT_PENDING_COUNT >= MODULE_EVENT_PENDING_MAX) { return moduleBaseThrow("Event.wait: too many pending awaits"); } - // Only subscribe once per event — check if we already have a pending await bool_t subscribed = false; for(uint32_t i = 0; i < MODULE_EVENT_PENDING_COUNT; i++) { - if(MODULE_EVENT_PENDING[i].event == ev->event) { subscribed = true; break; } + if(MODULE_EVENT_PENDING[i].event == ev->event) { + subscribed = true; break; + } } - if(!subscribed) { if(ev->event->count >= ev->event->size) { - return moduleBaseThrow("Event.wait: event subscriber capacity exceeded"); + return moduleBaseThrow( + "Event.wait: event subscriber capacity exceeded" + ); } - eventSubscribe(ev->event, moduleEventFireCallback, (void *)ev->event); + eventSubscribe(ev->event, moduleEventWaitFire, (void *)ev->event); } jerry_value_t promise = jerry_promise(); MODULE_EVENT_PENDING[MODULE_EVENT_PENDING_COUNT].event = ev->event; - MODULE_EVENT_PENDING[MODULE_EVENT_PENDING_COUNT].promise = jerry_value_copy(promise); + MODULE_EVENT_PENDING[MODULE_EVENT_PENDING_COUNT].promise = + jerry_value_copy(promise); MODULE_EVENT_PENDING_COUNT++; return promise; } jerry_value_t moduleEventCreate(event_t *event) { assertNotNull(event, "moduleEventCreate: event must not be NULL"); - jsevent_t ev = { .event = event }; + jsevent_t ev; + ev.event = event; + for(uint32_t i = 0; i < MODULE_EVENT_MAX_SLOTS; i++) { + ev.fns[i] = jerry_undefined(); + } return scriptProtoCreateValue(&MODULE_EVENT_PROTO, &ev); } +jerry_value_t moduleEventGetOrCreate( + const jerry_call_info_t *callInfo, + event_t *event, + const char_t *pinKey +) { + jerry_value_t keyStr = jerry_string_sz(pinKey); + jerry_value_t existing = jerry_object_get( + callInfo->this_value, keyStr + ); + if(!jerry_value_is_undefined(existing)) { + jerry_value_free(keyStr); + return existing; + } + jerry_value_free(existing); + jerry_value_t ev = moduleEventCreate(event); + jerry_object_set(callInfo->this_value, keyStr, ev); + jerry_value_free(keyStr); + return ev; +} + void moduleEventInit(void) { MODULE_EVENT_PENDING_COUNT = 0; - scriptProtoInit(&MODULE_EVENT_PROTO, NULL, sizeof(jsevent_t), NULL); + scriptProtoInit( + &MODULE_EVENT_PROTO, NULL, + sizeof(jsevent_t), NULL + ); + MODULE_EVENT_PROTO.info.free_cb = moduleEventFree; + scriptProtoDefineFunc(&MODULE_EVENT_PROTO, "on", moduleEventOn); + scriptProtoDefineFunc(&MODULE_EVENT_PROTO, "off", moduleEventOff); scriptProtoDefineFunc(&MODULE_EVENT_PROTO, "wait", moduleEventWait); } void moduleEventDispose(void) { - // Unsubscribe from each distinct event still in the pending list for(uint32_t i = 0; i < MODULE_EVENT_PENDING_COUNT; i++) { bool_t alreadyUnsub = false; for(uint32_t j = 0; j < i; j++) { @@ -103,7 +224,9 @@ void moduleEventDispose(void) { } } if(!alreadyUnsub) { - eventUnsubscribe(MODULE_EVENT_PENDING[i].event, moduleEventFireCallback); + eventUnsubscribe( + MODULE_EVENT_PENDING[i].event, moduleEventWaitFire + ); } jerry_value_free(MODULE_EVENT_PENDING[i].promise); } diff --git a/src/dusk/script/module/event/moduleevent.h b/src/dusk/script/module/event/moduleevent.h index 18fd6e2d..e3cd8836 100644 --- a/src/dusk/script/module/event/moduleevent.h +++ b/src/dusk/script/module/event/moduleevent.h @@ -10,15 +10,51 @@ #include "script/scriptproto.h" #include "event/event.h" -/** C struct wrapped by every Event JS instance. */ -typedef struct { - event_t *event; -} jsevent_t; +/** Maximum number of on() subscriber slots per Event. */ +#define MODULE_EVENT_MAX_SLOTS 4 extern scriptproto_t MODULE_EVENT_PROTO; /** - * Wraps a C event_t pointer in a JS Event object. + * Array of C trampolines for on() subscriptions. + * Slot i uses MODULE_EVENT_TRAMPOLINES[i]. + */ +extern eventcallback_t MODULE_EVENT_TRAMPOLINES[MODULE_EVENT_MAX_SLOTS]; + +/** Native data stored on each Event JS object. */ +typedef struct { + event_t *event; + jerry_value_t fns[MODULE_EVENT_MAX_SLOTS]; +} jsevent_t; + +/** + * GC free callback — unsubscribes all on() slots and releases JS function + * refs when the Event object is garbage collected. + * + * @param ptr Native jsevent_t pointer. + * @param info Native info (unused). + */ +void moduleEventFree(void *ptr, jerry_object_native_info_t *info); + +/** + * on(fn) — subscribes fn to the event. Returns this for chaining. + * @param args[0] Function to call when the event fires. + */ +moduleBaseFunction(moduleEventOn); + +/** + * off(fn) — removes a previously subscribed fn. Returns this for chaining. + * @param args[0] The same function reference passed to on(). + */ +moduleBaseFunction(moduleEventOff); + +/** + * wait() — returns a Promise that resolves the next time the event fires. + */ +moduleBaseFunction(moduleEventWait); + +/** + * Wraps a C event_t pointer in a new JS Event object. * * @param event The event to wrap. Must outlive the returned JS value. * @return A new JS Event instance. @@ -26,11 +62,25 @@ extern scriptproto_t MODULE_EVENT_PROTO; jerry_value_t moduleEventCreate(event_t *event); /** - * Initializes the Event module and registers the global Event prototype. + * Returns the Event object pinned at pinKey on the parent JS object, + * creating and pinning a new one on first access. + * + * @param callInfo The call info — this_value is the parent object. + * @param event The C event_t to wrap. Must outlive the parent object. + * @param pinKey Property name used to cache the Event on the parent. + * @return A JS Event value (caller must free). */ +jerry_value_t moduleEventGetOrCreate( + const jerry_call_info_t *callInfo, + event_t *event, + const char_t *pinKey +); + +/** Initializes the Event module and registers the global Event prototype. */ void moduleEventInit(void); /** - * Disposes the Event module, rejecting any pending awaits and cleaning up. + * Disposes the Event module, rejecting any pending wait() promises and + * cleaning up. */ void moduleEventDispose(void); diff --git a/src/dusk/script/module/input/CMakeLists.txt b/src/dusk/script/module/input/CMakeLists.txt new file mode 100644 index 00000000..fde1ea90 --- /dev/null +++ b/src/dusk/script/module/input/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + moduleinput.c +) diff --git a/src/dusk/script/module/input/moduleinput.c b/src/dusk/script/module/input/moduleinput.c new file mode 100644 index 00000000..f927e330 --- /dev/null +++ b/src/dusk/script/module/input/moduleinput.c @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "moduleinput.h" +#include + +scriptproto_t MODULE_INPUT_PROTO; + +moduleBaseFunction(moduleInputIsDown) { + moduleInputRequireAction(0, "Input.isDown"); + return jerry_boolean(inputIsDown((inputaction_t)moduleBaseArgInt(0))); +} + +moduleBaseFunction(moduleInputWasDown) { + moduleInputRequireAction(0, "Input.wasDown"); + return jerry_boolean(inputWasDown((inputaction_t)moduleBaseArgInt(0))); +} + +moduleBaseFunction(moduleInputPressed) { + moduleInputRequireAction(0, "Input.pressed"); + return jerry_boolean(inputPressed((inputaction_t)moduleBaseArgInt(0))); +} + +moduleBaseFunction(moduleInputReleased) { + moduleInputRequireAction(0, "Input.released"); + return jerry_boolean(inputReleased((inputaction_t)moduleBaseArgInt(0))); +} + +moduleBaseFunction(moduleInputGetValue) { + moduleInputRequireAction(0, "Input.getValue"); + return jerry_number( + inputGetCurrentValue((inputaction_t)moduleBaseArgInt(0)) + ); +} + +moduleBaseFunction(moduleInputAxis) { + moduleInputRequireAction(0, "Input.axis"); + moduleInputRequireAction(1, "Input.axis"); + return jerry_number(inputAxis( + (inputaction_t)moduleBaseArgInt(0), + (inputaction_t)moduleBaseArgInt(1) + )); +} + +moduleBaseFunction(moduleInputBind) { + moduleBaseRequireArgs(2); + moduleBaseRequireString(0); + if(!jerry_value_is_number(args[1])) { + return moduleBaseThrow("Input.bind: action must be a number"); + } + const inputaction_t action = (inputaction_t)moduleBaseArgInt(1); + if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) { + return moduleBaseThrow("Input.bind: invalid action"); + } + + char_t name[128]; + moduleBaseToString(args[0], name, sizeof(name)); + if(name[0] == '\0') { + return moduleBaseThrow("Input.bind: button name cannot be empty"); + } + + inputbutton_t btn = inputButtonGetByName(name); + if(btn.type == INPUT_BUTTON_TYPE_NONE) { + return moduleBaseThrow("Input.bind: unknown button name"); + } + + inputBind(btn, action); + return jerry_undefined(); +} + +void moduleInputInit(void) { + scriptProtoInit(&MODULE_INPUT_PROTO, "Input", sizeof(uint8_t), NULL); + + scriptProtoDefineStaticFunc( + &MODULE_INPUT_PROTO, "isDown", moduleInputIsDown + ); + scriptProtoDefineStaticFunc( + &MODULE_INPUT_PROTO, "wasDown", moduleInputWasDown + ); + scriptProtoDefineStaticFunc( + &MODULE_INPUT_PROTO, "pressed", moduleInputPressed + ); + scriptProtoDefineStaticFunc( + &MODULE_INPUT_PROTO, "released", moduleInputReleased + ); + scriptProtoDefineStaticFunc( + &MODULE_INPUT_PROTO, "getValue", moduleInputGetValue + ); + scriptProtoDefineStaticFunc(&MODULE_INPUT_PROTO, "axis", moduleInputAxis); + scriptProtoDefineStaticFunc(&MODULE_INPUT_PROTO, "bind", moduleInputBind); + + jerry_value_t result = jerry_eval( + (const jerry_char_t *)INPUT_ACTION_SCRIPT, + strlen(INPUT_ACTION_SCRIPT), + JERRY_PARSE_NO_OPTS + ); + jerry_value_free(result); +} + +void moduleInputDispose(void) { + scriptProtoDispose(&MODULE_INPUT_PROTO); +} diff --git a/src/dusk/script/module/input/moduleinput.h b/src/dusk/script/module/input/moduleinput.h index 5a6c6e7e..0dca7d8c 100644 --- a/src/dusk/script/module/input/moduleinput.h +++ b/src/dusk/script/module/input/moduleinput.h @@ -11,12 +11,12 @@ #include "input/input.h" #include "input/inputbutton.h" -static scriptproto_t MODULE_INPUT_PROTO; +extern scriptproto_t MODULE_INPUT_PROTO; /** - * Validates an inputaction_t argument and returns a type error if bad. - * - * @param i Argument index. + * Validates an inputaction_t argument and returns a type error if invalid. + * + * @param i Argument index. * @param ctx Context string for error messages. */ #define moduleInputRequireAction(i, ctx) do { \ @@ -29,110 +29,58 @@ static scriptproto_t MODULE_INPUT_PROTO; } \ } while(0) -moduleBaseFunction(moduleInputIsDown) { - moduleInputRequireAction(0, "Input.isDown"); - return jerry_boolean( - inputIsDown((inputaction_t)moduleBaseArgInt(0)) - ); -} +/** + * Input.isDown(action) — true while the action is held this frame. + * @param args[0] Input action constant. + */ +moduleBaseFunction(moduleInputIsDown); -moduleBaseFunction(moduleInputWasDown) { - moduleInputRequireAction(0, "Input.wasDown"); - return jerry_boolean( - inputWasDown((inputaction_t)moduleBaseArgInt(0)) - ); -} +/** + * Input.wasDown(action) — true if the action was held last frame. + * @param args[0] Input action constant. + */ +moduleBaseFunction(moduleInputWasDown); -moduleBaseFunction(moduleInputPressed) { - moduleInputRequireAction(0, "Input.pressed"); - return jerry_boolean( - inputPressed((inputaction_t)moduleBaseArgInt(0)) - ); -} +/** + * Input.pressed(action) — true on the first frame the action is held. + * @param args[0] Input action constant. + */ +moduleBaseFunction(moduleInputPressed); -moduleBaseFunction(moduleInputReleased) { - moduleInputRequireAction(0, "Input.released"); - return jerry_boolean( - inputReleased((inputaction_t)moduleBaseArgInt(0)) - ); -} +/** + * Input.released(action) — true on the first frame the action is released. + * @param args[0] Input action constant. + */ +moduleBaseFunction(moduleInputReleased); -moduleBaseFunction(moduleInputGetValue) { - moduleInputRequireAction(0, "Input.getValue"); - return jerry_number( - inputGetCurrentValue((inputaction_t)moduleBaseArgInt(0)) - ); -} +/** + * Input.getValue(action) — returns the analog value (0–1) of the action. + * @param args[0] Input action constant. + */ +moduleBaseFunction(moduleInputGetValue); -moduleBaseFunction(moduleInputAxis) { - moduleInputRequireAction(0, "Input.axis"); - moduleInputRequireAction(1, "Input.axis"); - return jerry_number(inputAxis( - (inputaction_t)moduleBaseArgInt(0), - (inputaction_t)moduleBaseArgInt(1) - )); -} +/** + * Input.axis(negative, positive) — returns a signed axis value in [-1, 1]. + * @param args[0] Negative action constant. + * @param args[1] Positive action constant. + */ +moduleBaseFunction(moduleInputAxis); -moduleBaseFunction(moduleInputBind) { - moduleBaseRequireArgs(2); - moduleBaseRequireString(0); - if(!jerry_value_is_number(args[1])) { - return moduleBaseThrow("Input.bind: action must be a number"); - } - const inputaction_t action = (inputaction_t)moduleBaseArgInt(1); - if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) { - return moduleBaseThrow("Input.bind: invalid action"); - } +/** + * Input.bind(buttonName, action) — binds a named button to an action. + * @param args[0] Button name string. + * @param args[1] Input action constant. + */ +moduleBaseFunction(moduleInputBind); - char_t name[128]; - moduleBaseToString(args[0], name, sizeof(name)); - if(name[0] == '\0') { - return moduleBaseThrow("Input.bind: button name cannot be empty"); - } +/** + * Initializes the Input module, registers Input.isDown/wasDown/pressed/ + * released/getValue/axis/bind, and evaluates INPUT_ACTION_SCRIPT to populate + * action constants in the global scope. + */ +void moduleInputInit(void); - inputbutton_t btn = inputButtonGetByName(name); - if(btn.type == INPUT_BUTTON_TYPE_NONE) { - return moduleBaseThrow("Input.bind: unknown button name"); - } - - inputBind(btn, action); - return jerry_undefined(); -} - -static void moduleInputInit(void) { - scriptProtoInit(&MODULE_INPUT_PROTO, "Input", sizeof(uint8_t), NULL); - - scriptProtoDefineStaticFunc( - &MODULE_INPUT_PROTO, "isDown", moduleInputIsDown - ); - scriptProtoDefineStaticFunc( - &MODULE_INPUT_PROTO, "wasDown", moduleInputWasDown - ); - scriptProtoDefineStaticFunc( - &MODULE_INPUT_PROTO, "pressed", moduleInputPressed - ); - scriptProtoDefineStaticFunc( - &MODULE_INPUT_PROTO, "released", moduleInputReleased - ); - scriptProtoDefineStaticFunc( - &MODULE_INPUT_PROTO, "getValue", moduleInputGetValue - ); - scriptProtoDefineStaticFunc( - &MODULE_INPUT_PROTO, "axis", moduleInputAxis - ); - scriptProtoDefineStaticFunc( - &MODULE_INPUT_PROTO, "bind", moduleInputBind - ); - - /* Register INPUT_ACTION_* integer constants into the global scope. */ - jerry_value_t result = jerry_eval( - (const jerry_char_t *)INPUT_ACTION_SCRIPT, - strlen(INPUT_ACTION_SCRIPT), - JERRY_PARSE_NO_OPTS - ); - jerry_value_free(result); -} - -static void moduleInputDispose(void) { - scriptProtoDispose(&MODULE_INPUT_PROTO); -} +/** + * Disposes the Input module. + */ +void moduleInputDispose(void); diff --git a/src/dusk/script/module/math/CMakeLists.txt b/src/dusk/script/module/math/CMakeLists.txt new file mode 100644 index 00000000..da510807 --- /dev/null +++ b/src/dusk/script/module/math/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + modulevec3.c +) diff --git a/src/dusk/script/module/math/modulevec3.c b/src/dusk/script/module/math/modulevec3.c new file mode 100644 index 00000000..1e0090b1 --- /dev/null +++ b/src/dusk/script/module/math/modulevec3.c @@ -0,0 +1,108 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "modulevec3.h" + +scriptproto_t MODULE_VEC3_PROTO; + +float_t *moduleVec3Get(const jerry_call_info_t *callInfo) { + return (float_t *)scriptProtoGetValue( + &MODULE_VEC3_PROTO, callInfo->this_value + ); +} + +float_t *moduleVec3From(const jerry_value_t val) { + return (float_t *)scriptProtoGetValue(&MODULE_VEC3_PROTO, val); +} + +jerry_value_t moduleVec3Push(const vec3 v) { + return scriptProtoCreateValue(&MODULE_VEC3_PROTO, v); +} + +moduleBaseFunction(moduleVec3Constructor) { + float_t *ptr = (float_t *)memoryAllocate(sizeof(vec3)); + ptr[0] = moduleBaseOptFloat(0, 0.0f); + ptr[1] = moduleBaseOptFloat(1, 0.0f); + ptr[2] = moduleBaseOptFloat(2, 0.0f); + jerry_object_set_native_ptr( + callInfo->this_value, &MODULE_VEC3_PROTO.info, ptr + ); + return jerry_undefined(); +} + +moduleBaseFunction(moduleVec3GetX) { + float_t *v = moduleVec3Get(callInfo); + if(!v) return jerry_undefined(); + return jerry_number((double)v[0]); +} + +moduleBaseFunction(moduleVec3SetX) { + moduleBaseRequireArgs(1); + float_t *v = moduleVec3Get(callInfo); + if(!v) return jerry_undefined(); + v[0] = moduleBaseArgFloat(0); + return jerry_undefined(); +} + +moduleBaseFunction(moduleVec3GetY) { + float_t *v = moduleVec3Get(callInfo); + if(!v) return jerry_undefined(); + return jerry_number((double)v[1]); +} + +moduleBaseFunction(moduleVec3SetY) { + moduleBaseRequireArgs(1); + float_t *v = moduleVec3Get(callInfo); + if(!v) return jerry_undefined(); + v[1] = moduleBaseArgFloat(0); + return jerry_undefined(); +} + +moduleBaseFunction(moduleVec3GetZ) { + float_t *v = moduleVec3Get(callInfo); + if(!v) return jerry_undefined(); + return jerry_number((double)v[2]); +} + +moduleBaseFunction(moduleVec3SetZ) { + moduleBaseRequireArgs(1); + float_t *v = moduleVec3Get(callInfo); + if(!v) return jerry_undefined(); + v[2] = moduleBaseArgFloat(0); + return jerry_undefined(); +} + +moduleBaseFunction(moduleVec3ToString) { + float_t *v = moduleVec3Get(callInfo); + if(!v) return jerry_string_sz("Vec3:invalid"); + char_t buf[64]; + snprintf(buf, sizeof(buf), "Vec3(%g, %g, %g)", + (double)v[0], (double)v[1], (double)v[2] + ); + return jerry_string_sz(buf); +} + +void moduleVec3Init(void) { + scriptProtoInit( + &MODULE_VEC3_PROTO, "Vec3", + sizeof(vec3), moduleVec3Constructor + ); + scriptProtoDefineProp( + &MODULE_VEC3_PROTO, "x", moduleVec3GetX, moduleVec3SetX + ); + scriptProtoDefineProp( + &MODULE_VEC3_PROTO, "y", moduleVec3GetY, moduleVec3SetY + ); + scriptProtoDefineProp( + &MODULE_VEC3_PROTO, "z", moduleVec3GetZ, moduleVec3SetZ + ); + scriptProtoDefineToString(&MODULE_VEC3_PROTO, moduleVec3ToString); +} + +void moduleVec3Dispose(void) { + scriptProtoDispose(&MODULE_VEC3_PROTO); +} diff --git a/src/dusk/script/module/math/modulevec3.h b/src/dusk/script/module/math/modulevec3.h index 223d0020..cc267bfb 100644 --- a/src/dusk/script/module/math/modulevec3.h +++ b/src/dusk/script/module/math/modulevec3.h @@ -11,103 +11,59 @@ #include "util/memory.h" #include "cglm/cglm.h" -static scriptproto_t MODULE_VEC3_PROTO; +extern scriptproto_t MODULE_VEC3_PROTO; -float_t * moduleVec3Get(const jerry_call_info_t *callInfo) { - return (float_t *)scriptProtoGetValue( - &MODULE_VEC3_PROTO, callInfo->this_value - ); -} +/** + * Returns the raw float[3] pointer from a Vec3 JS instance. + * + * @param callInfo Call info whose this_value is the Vec3 instance. + * @return Pointer to the vec3 data, or NULL if not a Vec3. + */ +float_t *moduleVec3Get(const jerry_call_info_t *callInfo); -float_t * moduleVec3From(const jerry_value_t val) { - return (float_t *)scriptProtoGetValue(&MODULE_VEC3_PROTO, val); -} +/** + * Returns the raw float[3] pointer from an arbitrary Vec3 JS value. + * + * @param val The JS value to extract from. + * @return Pointer to the vec3 data, or NULL if not a Vec3. + */ +float_t *moduleVec3From(const jerry_value_t val); -jerry_value_t moduleVec3Push(const vec3 v) { - return scriptProtoCreateValue(&MODULE_VEC3_PROTO, v); -} +/** + * Creates a Vec3 JS object wrapping a copy of a C vec3. + * + * @param v The source vec3. + * @return A new Vec3 JS object. + */ +jerry_value_t moduleVec3Push(const vec3 v); -moduleBaseFunction(moduleVec3Constructor) { - float_t *ptr = (float_t *)memoryAllocate(sizeof(vec3)); - ptr[0] = moduleBaseOptFloat(0, 0.0f); - ptr[1] = moduleBaseOptFloat(1, 0.0f); - ptr[2] = moduleBaseOptFloat(2, 0.0f); - jerry_object_set_native_ptr( - callInfo->this_value, &MODULE_VEC3_PROTO.info, ptr - ); - return jerry_undefined(); -} +/** Vec3(x?, y?, z?) constructor. */ +moduleBaseFunction(moduleVec3Constructor); -moduleBaseFunction(moduleVec3GetX) { - float_t *v = moduleVec3Get(callInfo); - if(!v) return jerry_undefined(); - return jerry_number((double)v[0]); -} +/** @return The x component as a number. */ +moduleBaseFunction(moduleVec3GetX); +/** Sets the x component. @param args[0] New x value. */ +moduleBaseFunction(moduleVec3SetX); -moduleBaseFunction(moduleVec3SetX) { - moduleBaseRequireArgs(1); - float_t *v = moduleVec3Get(callInfo); - if(!v) return jerry_undefined(); - v[0] = moduleBaseArgFloat(0); - return jerry_undefined(); -} +/** @return The y component as a number. */ +moduleBaseFunction(moduleVec3GetY); +/** Sets the y component. @param args[0] New y value. */ +moduleBaseFunction(moduleVec3SetY); -moduleBaseFunction(moduleVec3GetY) { - float_t *v = moduleVec3Get(callInfo); - if(!v) return jerry_undefined(); - return jerry_number((double)v[1]); -} +/** @return The z component as a number. */ +moduleBaseFunction(moduleVec3GetZ); +/** Sets the z component. @param args[0] New z value. */ +moduleBaseFunction(moduleVec3SetZ); -moduleBaseFunction(moduleVec3SetY) { - moduleBaseRequireArgs(1); - float_t *v = moduleVec3Get(callInfo); - if(!v) return jerry_undefined(); - v[1] = moduleBaseArgFloat(0); - return jerry_undefined(); -} +/** @return "Vec3(x, y, z)" string. */ +moduleBaseFunction(moduleVec3ToString); -moduleBaseFunction(moduleVec3GetZ) { - float_t *v = moduleVec3Get(callInfo); - if(!v) return jerry_undefined(); - return jerry_number((double)v[2]); -} +/** + * Initializes the Vec3 module and registers the global Vec3 class. + */ +void moduleVec3Init(void); -moduleBaseFunction(moduleVec3SetZ) { - moduleBaseRequireArgs(1); - float_t *v = moduleVec3Get(callInfo); - if(!v) return jerry_undefined(); - v[2] = moduleBaseArgFloat(0); - return jerry_undefined(); -} - -moduleBaseFunction(moduleVec3ToString) { - float_t *v = moduleVec3Get(callInfo); - if(!v) return jerry_string_sz("Vec3:invalid"); - char_t buf[64]; - snprintf(buf, sizeof(buf), "Vec3(%g, %g, %g)", - (double)v[0], (double)v[1], (double)v[2] - ); - return jerry_string_sz(buf); -} - -static void moduleVec3Init(void) { - scriptProtoInit( - &MODULE_VEC3_PROTO, "Vec3", - sizeof(vec3), moduleVec3Constructor - ); - - scriptProtoDefineProp( - &MODULE_VEC3_PROTO, "x", moduleVec3GetX, moduleVec3SetX - ); - scriptProtoDefineProp( - &MODULE_VEC3_PROTO, "y", moduleVec3GetY, moduleVec3SetY - ); - scriptProtoDefineProp( - &MODULE_VEC3_PROTO, "z", moduleVec3GetZ, moduleVec3SetZ - ); - scriptProtoDefineToString(&MODULE_VEC3_PROTO, moduleVec3ToString); -} - -static void moduleVec3Dispose(void) { - scriptProtoDispose(&MODULE_VEC3_PROTO); -} +/** + * Disposes the Vec3 module. + */ +void moduleVec3Dispose(void); diff --git a/src/dusk/script/module/modulebase.h b/src/dusk/script/module/modulebase.h index f564d0fc..71e36f11 100644 --- a/src/dusk/script/module/modulebase.h +++ b/src/dusk/script/module/modulebase.h @@ -154,7 +154,7 @@ void moduleBaseSetNumber(const char_t *name, double value); void moduleBaseSetInt(const char_t *name, int32_t value); #define moduleBaseFunction(name) \ - static jerry_value_t name( \ + jerry_value_t name( \ const jerry_call_info_t *callInfo, \ const jerry_value_t args[], \ const jerry_length_t argc) diff --git a/src/dusk/script/module/modulelist.c b/src/dusk/script/module/modulelist.c index 2e27342e..ea005e64 100644 --- a/src/dusk/script/module/modulelist.c +++ b/src/dusk/script/module/modulelist.c @@ -14,8 +14,6 @@ #include "script/module/engine/moduleframe.h" #include "script/module/event/moduleevent.h" #include "script/module/engine/moduletimeout.h" -#include "script/module/entity/component/modulecomponentlist.h" -#include "script/module/entity/modulecomponent.h" #include "script/module/entity/moduleentity.h" #include "script/module/input/moduleinput.h" #include "script/module/math/modulevec3.h" @@ -35,9 +33,7 @@ void moduleListInit(void) { moduleFrameInit(); moduleTimeoutInit(); moduleVec3Init(); - moduleComponentInit(); moduleEntityInit(); - moduleComponentListInit(); moduleInputInit(); moduleRequireInit(); moduleSceneInit(); @@ -54,9 +50,7 @@ void moduleListDispose(void) { moduleSceneDispose(); moduleRequireDispose(); moduleInputDispose(); - moduleComponentListDispose(); moduleEntityDispose(); - moduleComponentDispose(); moduleVec3Dispose(); moduleTimeoutDispose(); moduleFrameDispose(); diff --git a/src/dusk/script/module/scene/CMakeLists.txt b/src/dusk/script/module/scene/CMakeLists.txt new file mode 100644 index 00000000..4ef416e3 --- /dev/null +++ b/src/dusk/script/module/scene/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + modulescene.c +) diff --git a/src/dusk/script/module/scene/modulescene.c b/src/dusk/script/module/scene/modulescene.c new file mode 100644 index 00000000..bfe70383 --- /dev/null +++ b/src/dusk/script/module/scene/modulescene.c @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "modulescene.h" + +scriptproto_t MODULE_SCENE_PROTO; + +void moduleSceneInit(void) { + scriptProtoInit(&MODULE_SCENE_PROTO, "Scene", sizeof(uint8_t), NULL); +} + +void moduleSceneDispose(void) { + scriptProtoDispose(&MODULE_SCENE_PROTO); +} diff --git a/src/dusk/script/module/scene/modulescene.h b/src/dusk/script/module/scene/modulescene.h index 668fd8ae..898a3c20 100644 --- a/src/dusk/script/module/scene/modulescene.h +++ b/src/dusk/script/module/scene/modulescene.h @@ -14,12 +14,14 @@ #include "asset/loader/assetloader.h" #include "util/memory.h" -static scriptproto_t MODULE_SCENE_PROTO; +extern scriptproto_t MODULE_SCENE_PROTO; -static void moduleSceneInit(void) { - scriptProtoInit(&MODULE_SCENE_PROTO, "Scene", sizeof(uint8_t), NULL); -} +/** + * Initializes the Scene module and registers the global Scene object. + */ +void moduleSceneInit(void); -static void moduleSceneDispose(void) { - scriptProtoDispose(&MODULE_SCENE_PROTO); -} +/** + * Disposes the Scene module. + */ +void moduleSceneDispose(void); diff --git a/src/dusk/script/module/system/CMakeLists.txt b/src/dusk/script/module/system/CMakeLists.txt new file mode 100644 index 00000000..b7f82eb8 --- /dev/null +++ b/src/dusk/script/module/system/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + modulesystem.c +) diff --git a/src/dusk/script/module/system/modulesystem.c b/src/dusk/script/module/system/modulesystem.c new file mode 100644 index 00000000..356c7aea --- /dev/null +++ b/src/dusk/script/module/system/modulesystem.c @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "modulesystem.h" + +scriptproto_t MODULE_SYSTEM_PROTO; + +moduleBaseFunction(moduleSystemGetPlatform) { + return jerry_number((double)systemGetPlatform()); +} + +void moduleSystemInit(void) { + scriptProtoInit(&MODULE_SYSTEM_PROTO, "System", sizeof(uint8_t), NULL); + + scriptProtoDefineStaticProp( + &MODULE_SYSTEM_PROTO, "platform", + moduleSystemGetPlatform, NULL + ); + + jerry_value_t target = MODULE_SYSTEM_PROTO.prototype; +#define SYSTEM_PLATFORM(name, value) \ + do { \ + jerry_value_t key = jerry_string_sz("PLATFORM_" #name); \ + jerry_value_t val = jerry_number((double)(value)); \ + jerry_object_set(target, key, val); \ + jerry_value_free(val); \ + jerry_value_free(key); \ + } while(0); + SYSTEM_PLATFORM_LIST +#undef SYSTEM_PLATFORM +} + +void moduleSystemDispose(void) { + scriptProtoDispose(&MODULE_SYSTEM_PROTO); +} diff --git a/src/dusk/script/module/system/modulesystem.h b/src/dusk/script/module/system/modulesystem.h index 8269af27..65d9da98 100644 --- a/src/dusk/script/module/system/modulesystem.h +++ b/src/dusk/script/module/system/modulesystem.h @@ -10,35 +10,20 @@ #include "script/scriptproto.h" #include "system/system.h" -static scriptproto_t MODULE_SYSTEM_PROTO; +extern scriptproto_t MODULE_SYSTEM_PROTO; -moduleBaseFunction(moduleSystemGetPlatform) { - return jerry_number((double)systemGetPlatform()); -} +/** + * @return Current platform as a System.PLATFORM_* constant. + */ +moduleBaseFunction(moduleSystemGetPlatform); -static void moduleSystemInit(void) { - scriptProtoInit(&MODULE_SYSTEM_PROTO, "System", sizeof(uint8_t), NULL); +/** + * Initializes the System module, registers the global System object with the + * platform property and all PLATFORM_* constants. + */ +void moduleSystemInit(void); - scriptProtoDefineStaticProp( - &MODULE_SYSTEM_PROTO, "platform", - moduleSystemGetPlatform, NULL - ); - - /* Register PLATFORM_* integer constants on the System global. */ - jerry_value_t target = MODULE_SYSTEM_PROTO.prototype; - -#define SYSTEM_PLATFORM(name, value) \ - do { \ - jerry_value_t key = jerry_string_sz("PLATFORM_" #name); \ - jerry_value_t val = jerry_number((double)(value)); \ - jerry_object_set(target, key, val); \ - jerry_value_free(val); \ - jerry_value_free(key); \ - } while(0); - SYSTEM_PLATFORM_LIST -#undef SYSTEM_PLATFORM -} - -static void moduleSystemDispose(void) { - scriptProtoDispose(&MODULE_SYSTEM_PROTO); -} +/** + * Disposes the System module. + */ +void moduleSystemDispose(void); diff --git a/types/asset/assetentry.d.ts b/types/asset/assetentry.d.ts index 384d164e..2bfe5e78 100644 --- a/types/asset/assetentry.d.ts +++ b/types/asset/assetentry.d.ts @@ -5,24 +5,6 @@ * 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