From 2b78370cb8d8134c8f434d61f024d31878430645 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Mon, 1 Jun 2026 22:20:57 -0500 Subject: [PATCH] Add asset reaping --- src/dusk/asset/asset.c | 55 ++++++++ src/dusk/asset/asset.h | 38 +++++- src/dusk/display/text/text.c | 20 +-- src/dusk/locale/localemanager.c | 12 +- src/dusk/script/module/display/modulescreen.h | 46 +++++++ .../entity/component/modulecomponentlist.h | 16 +++ .../script/module/entity/modulecomponent.h | 93 +++++++++++++ src/dusk/script/module/entity/moduleentity.h | 105 +++++++++++++++ src/dusk/script/module/input/moduleinput.h | 7 +- src/dusk/script/module/math/modulevec3.h | 127 ++++++++++++++++++ src/dusk/script/module/modulelist.h | 18 +++ src/dusk/script/module/scene/modulescene.h | 21 +++ src/dusk/script/script.c | 4 +- src/dusk/util/ref.c | 6 +- types/console.d.ts | 34 +++++ types/engine.d.ts | 33 +++++ types/index.d.ts | 21 +++ types/input.d.ts | 115 ++++++++++++++++ types/screen.d.ts | 28 ++++ types/system.d.ts | 38 ++++++ 20 files changed, 813 insertions(+), 24 deletions(-) create mode 100644 src/dusk/script/module/display/modulescreen.h create mode 100644 src/dusk/script/module/entity/component/modulecomponentlist.h create mode 100644 src/dusk/script/module/entity/modulecomponent.h create mode 100644 src/dusk/script/module/entity/moduleentity.h create mode 100644 src/dusk/script/module/math/modulevec3.h create mode 100644 src/dusk/script/module/scene/modulescene.h create mode 100644 types/console.d.ts create mode 100644 types/engine.d.ts create mode 100644 types/index.d.ts create mode 100644 types/input.d.ts create mode 100644 types/screen.d.ts create mode 100644 types/system.d.ts diff --git a/src/dusk/asset/asset.c b/src/dusk/asset/asset.c index b3f747cb..aa55e948 100644 --- a/src/dusk/asset/asset.c +++ b/src/dusk/asset/asset.c @@ -96,6 +96,34 @@ errorret_t assetRequireLoaded(assetentry_t *entry) { errorOk(); } +assetentry_t * assetLock( + const char_t *name, + const assetloadertype_t type, + assetloaderinput_t *input +) { + assetentry_t *entry = assetGetEntry(name, type, input); + assetEntryLock(entry); + return entry; +} + +void assetUnlock(const char_t *name) { + assertNotNull(name, "Name cannot be NULL."); + assetentry_t *entry = ASSET.entries; + do { + if(entry->type != ASSET_LOADER_TYPE_NULL && stringEquals(entry->name, name)) { + assetEntryUnlock(entry); + return; + } + entry++; + } while(entry < ASSET.entries + ASSET_ENTRY_COUNT_MAX); + assertUnreachable("Asset entry not found for unlock."); +} + +void assetUnlockEntry(assetentry_t *entry) { + assertNotNull(entry, "Entry cannot be NULL."); + assetEntryUnlock(entry); +} + errorret_t assetUpdate(void) { // Determine how many available loading slots we have. assetloading_t *availableLoading[ASSET_LOADING_COUNT_MAX]; @@ -216,6 +244,33 @@ errorret_t assetUpdate(void) { continue; } } while(loading < ASSET.loading + ASSET_LOADING_COUNT_MAX); + + + // Reap entries that have no external locks (refs.count == 1 means only the + // system hold remains). Only safe to reap LOADED and NOT_STARTED states — + // mid-load entries are left for the next cycle. + entry = ASSET.entries; + do { + if(entry->state != ASSET_ENTRY_STATE_LOADED) { + entry++; + continue; + } + + if(entry->type == ASSET_LOADER_TYPE_NULL) { + entry++; + continue; + } + + if(entry->refs.count > 0) { + entry++; + continue; + } + + consolePrint("Reaping asset %s", entry->name); + errorChain(assetEntryDispose(entry)); + entry++; + } while(entry < ASSET.entries + ASSET_ENTRY_COUNT_MAX); + errorOk(); } diff --git a/src/dusk/asset/asset.h b/src/dusk/asset/asset.h index 13162d03..e98697be 100644 --- a/src/dusk/asset/asset.h +++ b/src/dusk/asset/asset.h @@ -56,11 +56,11 @@ errorret_t assetInit(void); bool_t assetFileExists(const char_t *filename); /** - * Gets, or creates, a new asset entry. You will need to lock the asset soon - * after creating or else it will be freed up on the next update cycle. - * + * Gets, or creates, a new asset entry. Internal — prefer assetLock. + * * @param name Filename of the asset. * @param type Type of the asset. + * @param input Loader-specific parameters. */ assetentry_t * assetGetEntry( const char_t *name, @@ -68,6 +68,38 @@ assetentry_t * assetGetEntry( assetloaderinput_t *input ); +/** + * Gets, creates, and locks an asset entry. The asset will begin loading on + * the next assetUpdate. Call assetUnlock when done to allow the entry to be + * reclaimed. + * + * @param name Filename of the asset. + * @param type Type of the asset. + * @param input Loader-specific parameters. + * @return The locked asset entry. + */ +assetentry_t * assetLock( + const char_t *name, + const assetloadertype_t type, + assetloaderinput_t *input +); + +/** + * Releases a lock on an asset entry by name. When all locks are released the + * entry will be reclaimed at the start of the next assetUpdate. + * + * @param name Filename of the asset to unlock. + */ +void assetUnlock(const char_t *name); + +/** + * Releases a lock on an asset entry by pointer. When all locks are released + * the entry will be reclaimed at the start of the next assetUpdate. + * + * @param entry The asset entry to unlock. + */ +void assetUnlockEntry(assetentry_t *entry); + /** * Requires an asset entry to be loaded. This will block until the asset entry * is fully loaded. diff --git a/src/dusk/display/text/text.c b/src/dusk/display/text/text.c index e71310a7..fe8cc2ba 100644 --- a/src/dusk/display/text/text.c +++ b/src/dusk/display/text/text.c @@ -20,35 +20,25 @@ errorret_t textInit(void) { assetloaderinput_t input = { .texture = TEXTURE_FORMAT_RGBA }; - assetentry_t *entryTexture = assetGetEntry( + assetentry_t *entryTexture = assetLock( "ui/minogram.png", ASSET_LOADER_TYPE_TEXTURE, &input ); - assetentry_t *entryTileset = assetGetEntry( + assetentry_t *entryTileset = assetLock( "ui/minogram.dtf", ASSET_LOADER_TYPE_TILESET, NULL - // "ui/minogram.dtx", ASSET_LOADER_TYPE_TILESET, NULL ); errorChain(assetRequireLoaded(entryTexture)); errorChain(assetRequireLoaded(entryTileset)); + FONT_DEFAULT.texture = &entryTexture->data.texture; FONT_DEFAULT.tileset = &entryTileset->data.tileset; - - // assetentry_t *entryTileset = assetGetEntry( - // "ui/minogram.dtf", ASSET_LOADER_TYPE_TILESET - // ); - - // assetbatch_t batch; - // assetBatchInit(&batch, NULL, NULL, NULL); - // assetBatchTexture(&batch, "ui/minogram.png", TEXTURE_FORMAT_RGBA, &FONT_DEFAULT.texture); - // assetBatchTileset(&batch, "ui/minogram.dtf", &FONT_DEFAULT.tileset); - // errorChain(assetBatchLoad(&batch)); errorOk(); } errorret_t textDispose(void) { - // assetCacheRelease(&ASSET.cache, "ui/minogram.png"); - // assetCacheRelease(&ASSET.cache, "ui/minogram.dtf"); FONT_DEFAULT.texture = NULL; FONT_DEFAULT.tileset = NULL; + assetUnlock("ui/minogram.png"); + assetUnlock("ui/minogram.dtf"); errorOk(); } diff --git a/src/dusk/locale/localemanager.c b/src/dusk/locale/localemanager.c index 948b7f41..046e6080 100644 --- a/src/dusk/locale/localemanager.c +++ b/src/dusk/locale/localemanager.c @@ -20,14 +20,24 @@ errorret_t localeManagerInit() { errorret_t localeManagerSetLocale(const localeinfo_t *locale) { assertNotNull(locale, "Locale cannot be NULL"); + if(LOCALE.entry != NULL) { + assetEntryUnlock(LOCALE.entry); + LOCALE.entry = NULL; + } + LOCALE.locale = locale; LOCALE.entry = assetGetEntry(locale->file, ASSET_LOADER_TYPE_LOCALE, NULL); + assetEntryLock(LOCALE.entry); errorChain(assetRequireLoaded(LOCALE.entry)); errorOk(); } void localeManagerDispose() { - LOCALE.entry = NULL; + if(LOCALE.entry != NULL) { + assetEntryUnlock(LOCALE.entry); + LOCALE.entry = NULL; + } + LOCALE.locale = NULL; } \ No newline at end of file diff --git a/src/dusk/script/module/display/modulescreen.h b/src/dusk/script/module/display/modulescreen.h new file mode 100644 index 00000000..c2bc3204 --- /dev/null +++ b/src/dusk/script/module/display/modulescreen.h @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/module/modulebase.h" +#include "script/scriptproto.h" +#include "display/screen/screen.h" + +static 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); +} + +static 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 + ); +} + +static void moduleScreenDispose(void) { + scriptProtoDispose(&MODULE_SCREEN_PROTO); +} diff --git a/src/dusk/script/module/entity/component/modulecomponentlist.h b/src/dusk/script/module/entity/component/modulecomponentlist.h new file mode 100644 index 00000000..f50c4def --- /dev/null +++ b/src/dusk/script/module/entity/component/modulecomponentlist.h @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once + +/* Include component modules here as they are added. */ + +static void moduleComponentListInit(void) { +} + +static void moduleComponentListDispose(void) { +} diff --git a/src/dusk/script/module/entity/modulecomponent.h b/src/dusk/script/module/entity/modulecomponent.h new file mode 100644 index 00000000..dc036b00 --- /dev/null +++ b/src/dusk/script/module/entity/modulecomponent.h @@ -0,0 +1,93 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/module/modulebase.h" +#include "script/scriptproto.h" +#include "entity/component.h" + +/** C struct wrapped by every Component JS instance. */ +typedef struct { + entityid_t entityId; + componentid_t componentId; +} jscomponent_t; + +static scriptproto_t MODULE_COMPONENT_PROTO; + +moduleBaseFunction(moduleComponentCtor) { + (void)callInfo; (void)args; (void)argc; + return moduleBaseThrow("Component cannot be instantiated with new"); +} + +moduleBaseFunction(moduleComponentGetEntity) { + jscomponent_t *comp = scriptProtoGetValue( + &MODULE_COMPONENT_PROTO, callInfo->this_value + ); + if(!comp) return jerry_undefined(); + return jerry_number((double)comp->entityId); +} + +moduleBaseFunction(moduleComponentGetId) { + jscomponent_t *comp = scriptProtoGetValue( + &MODULE_COMPONENT_PROTO, callInfo->this_value + ); + if(!comp) return jerry_undefined(); + return jerry_number((double)comp->componentId); +} + +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; +} + +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); +} diff --git a/src/dusk/script/module/entity/moduleentity.h b/src/dusk/script/module/entity/moduleentity.h new file mode 100644 index 00000000..460bdbed --- /dev/null +++ b/src/dusk/script/module/entity/moduleentity.h @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/module/modulebase.h" +#include "script/scriptproto.h" +#include "script/module/entity/modulecomponent.h" +#include "entity/entitymanager.h" + +/** C struct wrapped by every Entity JS instance. */ +typedef struct { + entityid_t id; +} jsentity_t; + +static scriptproto_t MODULE_ENTITY_PROTO; + +moduleBaseFunction(moduleEntityCtor) { + (void)callInfo; (void)args; (void)argc; + 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 scriptProtoCreateValue(&MODULE_COMPONENT_PROTO, &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); +} diff --git a/src/dusk/script/module/input/moduleinput.h b/src/dusk/script/module/input/moduleinput.h index bc1e9080..5a6c6e7e 100644 --- a/src/dusk/script/module/input/moduleinput.h +++ b/src/dusk/script/module/input/moduleinput.h @@ -13,7 +13,12 @@ static scriptproto_t MODULE_INPUT_PROTO; -/** Validates an inputaction_t argument and returns a type error if bad. */ +/** + * Validates an inputaction_t argument and returns a type error if bad. + * + * @param i Argument index. + * @param ctx Context string for error messages. + */ #define moduleInputRequireAction(i, ctx) do { \ if(!jerry_value_is_number(args[(i)])) { \ return moduleBaseThrow(ctx ": action must be a number"); \ diff --git a/src/dusk/script/module/math/modulevec3.h b/src/dusk/script/module/math/modulevec3.h new file mode 100644 index 00000000..e2695706 --- /dev/null +++ b/src/dusk/script/module/math/modulevec3.h @@ -0,0 +1,127 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/module/modulebase.h" +#include "script/scriptproto.h" +#include "util/memory.h" +#include "cglm/cglm.h" + +static scriptproto_t MODULE_VEC3_PROTO; + +/** + * Returns the native float[3] pointer from the Vec3 instance that owns + * the current call (this_value). Returns NULL if not a Vec3. + */ +static inline float_t *moduleVec3Get(const jerry_call_info_t *callInfo) { + return (float_t *)scriptProtoGetValue( + &MODULE_VEC3_PROTO, callInfo->this_value + ); +} + +/** + * Returns the native float[3] pointer from any jerry value. + * Returns NULL if the value is not a Vec3 instance. + */ +static inline float_t *moduleVec3From(const jerry_value_t val) { + return (float_t *)scriptProtoGetValue(&MODULE_VEC3_PROTO, val); +} + +/** + * Creates a Vec3 JS object from a C vec3 array. + * + * @param v Source vec3 to copy. + * @return A new Vec3 JS instance owning a copy of the data. + */ +static inline jerry_value_t moduleVec3Push(const vec3 v) { + 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); +} + +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); +} diff --git a/src/dusk/script/module/modulelist.h b/src/dusk/script/module/modulelist.h index 4376276e..f0000402 100644 --- a/src/dusk/script/module/modulelist.h +++ b/src/dusk/script/module/modulelist.h @@ -7,20 +7,38 @@ #pragma once #include "script/module/console/moduleconsole.h" +#include "script/module/display/modulescreen.h" #include "script/module/engine/moduleengine.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" +#include "script/module/scene/modulescene.h" #include "script/module/system/modulesystem.h" static void moduleListInit(void) { moduleConsoleInit(); + moduleScreenInit(); moduleEngineInit(); + moduleVec3Init(); + moduleComponentInit(); + moduleEntityInit(); + moduleComponentListInit(); moduleInputInit(); + moduleSceneInit(); moduleSystemInit(); } static void moduleListDispose(void) { moduleSystemDispose(); + moduleSceneDispose(); moduleInputDispose(); + moduleComponentListDispose(); + moduleEntityDispose(); + moduleComponentDispose(); + moduleVec3Dispose(); moduleEngineDispose(); + moduleScreenDispose(); moduleConsoleDispose(); } diff --git a/src/dusk/script/module/scene/modulescene.h b/src/dusk/script/module/scene/modulescene.h new file mode 100644 index 00000000..4ea309cd --- /dev/null +++ b/src/dusk/script/module/scene/modulescene.h @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/module/modulebase.h" +#include "script/scriptproto.h" +#include "scene/scene.h" + +static scriptproto_t MODULE_SCENE_PROTO; + +static void moduleSceneInit(void) { + scriptProtoInit(&MODULE_SCENE_PROTO, "Scene", sizeof(uint8_t), NULL); +} + +static void moduleSceneDispose(void) { + scriptProtoDispose(&MODULE_SCENE_PROTO); +} diff --git a/src/dusk/script/script.c b/src/dusk/script/script.c index aa5799de..a08ba1c4 100644 --- a/src/dusk/script/script.c +++ b/src/dusk/script/script.c @@ -59,9 +59,11 @@ errorret_t scriptExecFile(const char_t *path) { assertNotNull(path, "Path cannot be NULL"); assertTrue(SCRIPT.initialized, "Script system not initialized"); - assetentry_t *entry = assetGetEntry(path, ASSET_LOADER_TYPE_SCRIPT, NULL); + assetentry_t *entry = assetLock(path, ASSET_LOADER_TYPE_SCRIPT, NULL); assertNotNull(entry, "Failed to get asset entry for script"); errorChain(assetRequireLoaded(entry)); + assetUnlockEntry(entry); + errorOk(); } diff --git a/src/dusk/util/ref.c b/src/dusk/util/ref.c index 4ea507e0..faf84a9c 100644 --- a/src/dusk/util/ref.c +++ b/src/dusk/util/ref.c @@ -18,7 +18,7 @@ void refInit( ) { assertNotNull(ref, "Ref cannot be NULL."); memoryZero(ref, sizeof(ref_t)); - ref->count = 1; + ref->count = 0; ref->data = data; ref->onLock = onLock; ref->onUnlock = onUnlock; @@ -27,14 +27,14 @@ void refInit( void refLock(ref_t *ref) { assertNotNull(ref, "Ref cannot be NULL."); - assertTrue(ref->count > 0, "Cannot lock a ref with zero count."); + assertTrue(ref->count >= 0, "Cannot lock a ref with negative count."); ref->count++; if(ref->onLock != NULL) ref->onLock(ref); } bool_t refUnlock(ref_t *ref) { assertNotNull(ref, "Ref cannot be NULL."); - assertTrue(ref->count > 0, "Cannot unlock a ref with zero count."); + assertTrue(ref->count >= 0, "Cannot unlock a ref with negative count."); ref->count--; diff --git a/types/console.d.ts b/types/console.d.ts new file mode 100644 index 00000000..c55e4f2f --- /dev/null +++ b/types/console.d.ts @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** + * Interface for the in-game developer console. + */ +interface ConsoleNamespace { + /** + * Prints one or more values to the in-game console, separated by tabs. + * Each argument is coerced to a string before printing. + * + * @param args - Values to print. + * + * @example + * Console.print("x =", player.x, "y =", player.y); + */ + print(...args: unknown[]): void; + + /** + * Whether the in-game console overlay is currently visible. + * Set to `true` to show the console, `false` to hide it. + * + * @example + * Console.visible = true; + */ + visible: boolean; +} + +/** In-game developer console. */ +declare var Console: ConsoleNamespace; diff --git a/types/engine.d.ts b/types/engine.d.ts new file mode 100644 index 00000000..55391d02 --- /dev/null +++ b/types/engine.d.ts @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** + * Controls over the engine main loop. + */ +interface EngineNamespace { + /** + * Whether the engine main loop is still running (read-only). + * Becomes `false` after `Engine.exit()` is called. + * + * @example + * while (Engine.running) { ... } + */ + readonly running: boolean; + + /** + * Requests an orderly shutdown of the engine. + * Sets the internal running flag to `false`; the main loop exits at the end + * of the current tick. + * + * @example + * Engine.exit(); + */ + exit(): void; +} + +/** Engine lifecycle controls. */ +declare var Engine: EngineNamespace; diff --git a/types/index.d.ts b/types/index.d.ts new file mode 100644 index 00000000..6da3d3ba --- /dev/null +++ b/types/index.d.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + * + * Root type declarations for the Dusk engine built-in JavaScript modules. + * These globals are injected by the native JerryScript runtime — they are not + * ES modules and cannot be imported. + * + * Reference this file from a jsconfig.json or tsconfig.json to get + * IntelliSense across all game scripts: + * + * { "compilerOptions": { "typeRoots": ["./types"] } } + */ + +/// +/// +/// +/// +/// diff --git a/types/input.d.ts b/types/input.d.ts new file mode 100644 index 00000000..07c645bd --- /dev/null +++ b/types/input.d.ts @@ -0,0 +1,115 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** + * Opaque type alias for input action identifiers. + * Use the `INPUT_ACTION_*` constants rather than raw numbers. + */ +type InputAction = number; + +/** + * Polling-based input queries and button rebinding. + */ +interface InputNamespace { + /** + * Returns `true` while the given action is held down this frame. + * + * @param action - An `INPUT_ACTION_*` constant. + * + * @example + * if (Input.isDown(INPUT_ACTION_UP)) { player.moveUp(); } + */ + isDown(action: InputAction): boolean; + + /** + * Returns `true` if the given action was held down in the previous frame. + * + * @param action - An `INPUT_ACTION_*` constant. + */ + wasDown(action: InputAction): boolean; + + /** + * Returns `true` on the first frame the action transitions from up → down. + * + * @param action - An `INPUT_ACTION_*` constant. + * + * @example + * if (Input.pressed(INPUT_ACTION_ACCEPT)) { confirmSelection(); } + */ + pressed(action: InputAction): boolean; + + /** + * Returns `true` on the first frame the action transitions from down → up. + * + * @param action - An `INPUT_ACTION_*` constant. + */ + released(action: InputAction): boolean; + + /** + * Returns the continuous (analog) value of an action in the range `0.0–1.0`. + * Digital buttons return either `0` or `1`. + * + * @param action - An `INPUT_ACTION_*` constant. + * + * @example + * const speed = Input.getValue(INPUT_ACTION_UP) * MAX_SPEED; + */ + getValue(action: InputAction): number; + + /** + * Returns a signed axis value in the range `-1.0–1.0`, derived from two + * opposing actions: `getValue(pos) - getValue(neg)`. + * + * @param neg - Action mapped to the negative direction. + * @param pos - Action mapped to the positive direction. + * + * @example + * const moveX = Input.axis(INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT); + */ + axis(neg: InputAction, pos: InputAction): number; + + /** + * Rebinds a physical button (by name) to a logical input action at runtime. + * Throws if `buttonName` is unknown or `action` is out of range. + * + * @param buttonName - Platform-specific button identifier string (e.g. `"A"`, `"START"`). + * @param action - Target `INPUT_ACTION_*` constant. + * + * @example + * Input.bind("A", INPUT_ACTION_ACCEPT); + */ + bind(buttonName: string, action: InputAction): void; +} + +/** Polling-based input system. */ +declare var Input: InputNamespace; + +// --------------------------------------------------------------------------- +// Input action constants. +// Injected as plain global variables by the engine at startup. +// --------------------------------------------------------------------------- + +/** Move / navigate upward. */ +declare var INPUT_ACTION_UP: InputAction; +/** Move / navigate downward. */ +declare var INPUT_ACTION_DOWN: InputAction; +/** Move / navigate left. */ +declare var INPUT_ACTION_LEFT: InputAction; +/** Move / navigate right. */ +declare var INPUT_ACTION_RIGHT: InputAction; +/** Confirm / accept the current selection. */ +declare var INPUT_ACTION_ACCEPT: InputAction; +/** Cancel / go back. */ +declare var INPUT_ACTION_CANCEL: InputAction; +/** Emergency quit (e.g. hold-to-exit on embedded platforms). */ +declare var INPUT_ACTION_RAGEQUIT: InputAction; +/** Toggle the developer console overlay. */ +declare var INPUT_ACTION_CONSOLE: InputAction; +/** Pointer / cursor horizontal position (analog). */ +declare var INPUT_ACTION_POINTERX: InputAction; +/** Pointer / cursor vertical position (analog). */ +declare var INPUT_ACTION_POINTERY: InputAction; diff --git a/types/screen.d.ts b/types/screen.d.ts new file mode 100644 index 00000000..1b303ffa --- /dev/null +++ b/types/screen.d.ts @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** + * Read-only information about the current display surface. + */ +interface ScreenNamespace { + /** Current render-target width in pixels (read-only). */ + readonly width: number; + + /** Current render-target height in pixels (read-only). */ + readonly height: number; + + /** + * Aspect ratio of the current render target: `width / height` (read-only). + * + * @example + * if (Screen.aspect > 1) { /* landscape *\/ } + */ + readonly aspect: number; +} + +/** Current display / render-target dimensions. */ +declare var Screen: ScreenNamespace; diff --git a/types/system.d.ts b/types/system.d.ts new file mode 100644 index 00000000..beceddf4 --- /dev/null +++ b/types/system.d.ts @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** + * Runtime platform detection and system-level information. + */ +interface SystemNamespace { + /** + * Numeric identifier for the platform the engine is running on (read-only). + * Compare against the `System.PLATFORM_*` constants. + * + * @example + * if (System.platform === System.PLATFORM_PSP) { useLowResAssets(); } + */ + readonly platform: number; + + /** Linux desktop. */ + readonly PLATFORM_LINUX: number; + + /** Knulli handheld (Linux-based). */ + readonly PLATFORM_KNULLI: number; + + /** Sony PlayStation Portable. */ + readonly PLATFORM_PSP: number; + + /** Nintendo GameCube. */ + readonly PLATFORM_GAMECUBE: number; + + /** Nintendo Wii. */ + readonly PLATFORM_WII: number; +} + +/** Platform detection and system-level information. */ +declare var System: SystemNamespace;