From a25871a8490bc399bd45f50877bf16f4c564e5c6 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Tue, 2 Jun 2026 09:32:07 -0500 Subject: [PATCH] Test sprite from script --- assets/testentity.js | 26 +- src/dusk/asset/asset.c | 18 +- src/dusk/asset/loader/assetentry.c | 5 +- src/dusk/asset/loader/assetentry.h | 4 +- .../component/display/entityrenderable.c | 82 +++--- src/dusk/scene/initial/initialscene.c | 7 - src/dusk/script/module/asset/moduleasset.h | 138 ++++++++++ .../script/module/asset/moduleassetentry.h | 171 ++++++++++++ src/dusk/script/module/display/modulecolor.h | 154 +++++++++++ .../script/module/display/moduletexture.h | 108 ++++++++ .../entity/component/modulecomponentlist.h | 23 +- .../entity/component/physics/modulephysics.h | 204 +++++++++++++++ .../component/renderable/modulerenderable.h | 243 ++++++++++++++++++ .../entity/component/trigger/moduletrigger.h | 140 ++++++++++ src/dusk/script/module/modulelist.h | 8 + test/asset/test_asset.c | 70 +++++ types/asset/asset.d.ts | 72 ++++++ types/asset/assetentry.d.ts | 54 ++++ types/{ => console}/console.d.ts | 9 +- types/display/color.d.ts | 41 +++ types/display/screen.d.ts | 19 ++ types/display/texture.d.ts | 28 ++ types/{ => engine}/engine.d.ts | 17 +- types/entity.d.ts | 129 ---------- types/entity/component.d.ts | 39 +++ types/entity/component/camera.d.ts | 28 ++ types/entity/component/physics.d.ts | 39 +++ types/entity/component/position.d.ts | 41 +++ types/entity/component/renderable.d.ts | 71 +++++ types/entity/component/trigger.d.ts | 25 ++ types/entity/entity.d.ts | 29 +++ types/index.d.ts | 35 ++- types/input.d.ts | 115 --------- types/input/input.d.ts | 50 ++++ types/{ => math}/vec3.d.ts | 0 types/{ => scene}/scene.d.ts | 5 +- types/screen.d.ts | 28 -- types/{ => system}/system.d.ts | 15 +- 38 files changed, 1913 insertions(+), 377 deletions(-) create mode 100644 src/dusk/script/module/asset/moduleasset.h create mode 100644 src/dusk/script/module/asset/moduleassetentry.h create mode 100644 src/dusk/script/module/display/modulecolor.h create mode 100644 src/dusk/script/module/display/moduletexture.h create mode 100644 src/dusk/script/module/entity/component/physics/modulephysics.h create mode 100644 src/dusk/script/module/entity/component/renderable/modulerenderable.h create mode 100644 src/dusk/script/module/entity/component/trigger/moduletrigger.h create mode 100644 types/asset/asset.d.ts create mode 100644 types/asset/assetentry.d.ts rename types/{ => console}/console.d.ts (81%) create mode 100644 types/display/color.d.ts create mode 100644 types/display/screen.d.ts create mode 100644 types/display/texture.d.ts rename types/{ => engine}/engine.d.ts (50%) delete mode 100644 types/entity.d.ts create mode 100644 types/entity/component.d.ts create mode 100644 types/entity/component/camera.d.ts create mode 100644 types/entity/component/physics.d.ts create mode 100644 types/entity/component/position.d.ts create mode 100644 types/entity/component/renderable.d.ts create mode 100644 types/entity/component/trigger.d.ts create mode 100644 types/entity/entity.d.ts delete mode 100644 types/input.d.ts create mode 100644 types/input/input.d.ts rename types/{ => math}/vec3.d.ts (100%) rename types/{ => scene}/scene.d.ts (77%) delete mode 100644 types/screen.d.ts rename types/{ => system}/system.d.ts (67%) diff --git a/assets/testentity.js b/assets/testentity.js index 8d3623bb..5a1ef6ea 100644 --- a/assets/testentity.js +++ b/assets/testentity.js @@ -1,8 +1,26 @@ +// Load rosa. +Console.print('Asset time'); +const entry = Asset.lock('rosa.png', Asset.TYPE_TEXTURE, Texture.FORMAT_RGBA); +Asset.requireLoaded(entry); +Console.print('Asset loaded'); + +// Camera at (3,3,3) looking at origin const cam = Entity.create(); -const pos = cam.add(Component.POSITION); +const camPos = cam.add(Component.POSITION); cam.add(Component.CAMERA); +camPos.localPosition = new Vec3(3, 3, 3); +camPos.lookAt(new Vec3(0, 0, 0)); -pos.localPosition = new Vec3(3, 3, 3); -pos.lookAt(new Vec3(0, 0, 0)); +// Test entity at origin +const testEntity = Entity.create(); +const testPos = testEntity.add(Component.POSITION); -Console.print('Camera entity ID: ' + cam.toString()); +/** @type {RenderableSpritebatch} */ +const testRenderable = testEntity.add(Component.RENDERABLE); +testRenderable.type = Renderable.SPRITEBATCH; +testRenderable.setTexture(entry.texture); +testRenderable.addSprite( + 0, 0, 1, 1, + 0, 0, 1, 1 +); +testPos.localPosition = new Vec3(0, 0, 0); diff --git a/src/dusk/asset/asset.c b/src/dusk/asset/asset.c index 791d016c..424495b8 100644 --- a/src/dusk/asset/asset.c +++ b/src/dusk/asset/asset.c @@ -197,8 +197,12 @@ errorret_t assetUpdate(void) { switch(loading->entry->state) { // This thing is pending synchronous loading. case ASSET_ENTRY_STATE_PENDING_SYNC: - // Perform sync load. loading->entry->state = ASSET_ENTRY_STATE_LOADING_SYNC; + // Unlock before calling loadSync. The sync loader may re-enter + // assetUpdate (e.g. a script loading another asset), and the async + // thread never touches LOADING_SYNC entries, so this is safe. + threadMutexUnlock(&loading->mutex); + errorret_t ret = ( ASSET_LOADER_CALLBACKS[loading->type].loadSync(loading) ); @@ -212,8 +216,6 @@ errorret_t assetUpdate(void) { "Loader did not set entry state to loaded or error on finished load." ); - // If an error occured these things need to be true, basically just - // ensuring the sync loader is setting the error correctly. if(errorIsNotOk(ret)) { errorCatch(errorPrint(ret)); assertTrue( @@ -222,15 +224,15 @@ errorret_t assetUpdate(void) { ); } - threadMutexUnlock(&loading->mutex); loading++; break; case ASSET_ENTRY_STATE_LOADING_SYNC: - assertUnreachable( - "Entry is in a pending sync state still?" - ); - break; + // A re-entrant assetUpdate call (e.g. from a script loading another + // asset) will see this entry mid-sync-load. Skip it. + threadMutexUnlock(&loading->mutex); + loading++; + continue; // Done loading, we can just free it up. case ASSET_ENTRY_STATE_LOADED: diff --git a/src/dusk/asset/loader/assetentry.c b/src/dusk/asset/loader/assetentry.c index c8a815b7..7ceb7b1f 100644 --- a/src/dusk/asset/loader/assetentry.c +++ b/src/dusk/asset/loader/assetentry.c @@ -25,8 +25,11 @@ void assetEntryInit( memoryZero(entry, sizeof(assetentry_t)); stringCopy(entry->name, name, ASSET_FILE_NAME_MAX); entry->type = type; - entry->input = input; entry->state = ASSET_ENTRY_STATE_NOT_STARTED; + if(input) { + entry->inputData = *input; + entry->input = &entry->inputData; + } refInit(&entry->refs, entry, NULL, NULL, NULL); } diff --git a/src/dusk/asset/loader/assetentry.h b/src/dusk/asset/loader/assetentry.h index ab8c38ca..176379f2 100644 --- a/src/dusk/asset/loader/assetentry.h +++ b/src/dusk/asset/loader/assetentry.h @@ -35,7 +35,9 @@ typedef struct assetentry_s { // zero). Entries that nobody has ever locked are left alone so raw-pointer // callers (tests, requireLoaded before locking) are not surprised. bool_t wasLocked; - // Data that will be passed to the loader about how it should load. + // Owned copy of the loader input. input points here when non-NULL. + assetloaderinput_t inputData; + // Pointer to inputData, or NULL if no input was provided. assetloaderinput_t *input; } assetentry_t; diff --git a/src/dusk/entity/component/display/entityrenderable.c b/src/dusk/entity/component/display/entityrenderable.c index 74de17af..a30fa1be 100644 --- a/src/dusk/entity/component/display/entityrenderable.c +++ b/src/dusk/entity/component/display/entityrenderable.c @@ -36,7 +36,6 @@ void entityRenderableDispose( const entityid_t entityId, const componentid_t componentId ) { - } void entityRenderableSetType( @@ -80,6 +79,49 @@ void entityRenderableSetDraw( r->data.custom.drawUser = user; } +static errorret_t entityRenderableDrawSpritebatch( + const entityrenderablespritebatch_t *sb +) { + if(sb->spriteCount == 0) errorOk(); + + errorChain(displaySetState((displaystate_t){ + .flags = DISPLAY_STATE_FLAG_BLEND + })); + + spriteBatchClear(); + shadermaterial_t mat; + memoryZero(&mat, sizeof(shadermaterial_t)); + mat.unlit.texture = sb->texture; + mat.unlit.color = COLOR_WHITE; + errorChain(spriteBatchBuffer( + sb->sprites, sb->spriteCount, + SHADER_LIST_DEFS[SHADER_LIST_SHADER_UNLIT].shader, mat + )); + return spriteBatchFlush(); +} + +static errorret_t entityRenderableDrawMaterial( + const entityrenderablematerial_t *m +) { + errorChain(displaySetState(m->state)); + shader_t *shader = SHADER_LIST_DEFS[m->shaderType].shader; + assertNotNull(shader, "Shader cannot be null for material type"); + errorChain(shaderBind(shader)); + errorChain(shaderSetMaterial(shader, &m->material)); + for(uint8_t i = 0; i < m->meshCount; i++) { + errorChain(meshDraw(m->meshes[i], m->meshOffsets[i], m->meshCounts[i])); + } + errorOk(); +} + +static errorret_t entityRenderableDrawCustom( + const entityid_t entityId, + const componentid_t componentId, + const entityrenderablecustom_t *custom +) { + return custom->draw(entityId, componentId, custom->drawUser); +} + errorret_t entityRenderableDraw( const entityid_t entityId, const componentid_t componentId @@ -87,41 +129,13 @@ errorret_t entityRenderableDraw( entityrenderable_t *r = componentGetData( entityId, componentId, COMPONENT_TYPE_RENDERABLE ); - switch(r->type) { - case ENTITY_RENDERABLE_TYPE_SPRITEBATCH: { - const entityrenderablespritebatch_t *sb = &r->data.spritebatch; - errorChain(displaySetState((displaystate_t){ - .flags = DISPLAY_STATE_FLAG_BLEND - })); - spriteBatchClear(); - shadermaterial_t mat; - memoryZero(&mat, sizeof(shadermaterial_t)); - mat.unlit.texture = sb->texture; - mat.unlit.color = COLOR_WHITE; - errorChain(spriteBatchBuffer( - sb->sprites, sb->spriteCount, - SHADER_LIST_DEFS[SHADER_LIST_SHADER_UNLIT].shader, mat - )); - return spriteBatchFlush(); - } - - case ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL: { - const entityrenderablematerial_t *m = &r->data.material; - errorChain(displaySetState(m->state)); - shader_t *shader = SHADER_LIST_DEFS[m->shaderType].shader; - assertNotNull(shader, "Shader cannot be null for material type"); - errorChain(shaderBind(shader)); - errorChain(shaderSetMaterial(shader, &m->material)); - for(uint8_t i = 0; i < m->meshCount; i++) { - errorChain(meshDraw(m->meshes[i], m->meshOffsets[i], m->meshCounts[i])); - } - errorOk(); - } - + case ENTITY_RENDERABLE_TYPE_SPRITEBATCH: + return entityRenderableDrawSpritebatch(&r->data.spritebatch); + case ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL: + return entityRenderableDrawMaterial(&r->data.material); case ENTITY_RENDERABLE_TYPE_CUSTOM: - return r->data.custom.draw(entityId, componentId, r->data.custom.drawUser); - + return entityRenderableDrawCustom(entityId, componentId, &r->data.custom); default: assertUnreachable("Invalid renderable type"); } diff --git a/src/dusk/scene/initial/initialscene.c b/src/dusk/scene/initial/initialscene.c index 7c70abdd..6fe4a39c 100644 --- a/src/dusk/scene/initial/initialscene.c +++ b/src/dusk/scene/initial/initialscene.c @@ -16,13 +16,6 @@ void initialSceneInit(void) { consolePrint("Initial scene initialized"); - - // Cube entity — RENDERABLE init defaults to a white unit cube at origin - entityid_t cubeId = entityManagerAdd(); - SCENE.data.initial.cubeEntityId = cubeId; - entityAddComponent(cubeId, COMPONENT_TYPE_POSITION); - entityAddComponent(cubeId, COMPONENT_TYPE_RENDERABLE); - errorCatch(errorPrint(scriptExecFile("testentity.js"))); } diff --git a/src/dusk/script/module/asset/moduleasset.h b/src/dusk/script/module/asset/moduleasset.h new file mode 100644 index 00000000..d83ff21f --- /dev/null +++ b/src/dusk/script/module/asset/moduleasset.h @@ -0,0 +1,138 @@ +/** + * 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/display/moduletexture.h" +#include "script/module/asset/moduleassetentry.h" +#include "asset/asset.h" +#include "asset/loader/assetloader.h" + +static 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); + 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(); + 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); + moduleAssetEntryDispose(); +} diff --git a/src/dusk/script/module/asset/moduleassetentry.h b/src/dusk/script/module/asset/moduleassetentry.h new file mode 100644 index 00000000..d61dfb5a --- /dev/null +++ b/src/dusk/script/module/asset/moduleassetentry.h @@ -0,0 +1,171 @@ +/** + * 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/display/moduletexture.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; + +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. + */ +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); +} + +/* 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(); +} + +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); +} + +static void moduleAssetEntryInit(void) { + 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 + ); + scriptProtoDefineToString( + &MODULE_ASSET_ENTRY_PROTO, moduleAssetEntryToString + ); + + /* 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); + } + + +} + +static void moduleAssetEntryDispose(void) { + scriptProtoDispose(&MODULE_ASSET_ENTRY_PROTO); +} diff --git a/src/dusk/script/module/display/modulecolor.h b/src/dusk/script/module/display/modulecolor.h new file mode 100644 index 00000000..f62ddbc4 --- /dev/null +++ b/src/dusk/script/module/display/modulecolor.h @@ -0,0 +1,154 @@ +/** + * 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 "display/color.h" + +static 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. + */ +static inline color_t *moduleColorFrom(const jerry_value_t val) { + return (color_t *)scriptProtoGetValue(&MODULE_COLOR_PROTO, val); +} + +/** + * Creates a Color JS object from a C color_t value. + */ +static inline 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); +} + +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); +} diff --git a/src/dusk/script/module/display/moduletexture.h b/src/dusk/script/module/display/moduletexture.h new file mode 100644 index 00000000..da66fbc3 --- /dev/null +++ b/src/dusk/script/module/display/moduletexture.h @@ -0,0 +1,108 @@ +/** + * 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 "asset/asset.h" +#include "asset/loader/assetloader.h" +#include "display/texture/texture.h" +#include "util/memory.h" + +static scriptproto_t MODULE_TEXTURE_PROTO; + +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. + */ +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); +} + +moduleBaseFunction(moduleTextureCtor) { + (void)callInfo; (void)args; (void)argc; + return moduleBaseThrow("Texture cannot be instantiated with new"); +} + +static inline jstexture_t *moduleTextureSelf( + const jerry_call_info_t *callInfo +) { + return (jstexture_t *)scriptProtoGetValue( + &MODULE_TEXTURE_PROTO, callInfo->this_value + ); +} + +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); +} + +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; + + 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); +} diff --git a/src/dusk/script/module/entity/component/modulecomponentlist.h b/src/dusk/script/module/entity/component/modulecomponentlist.h index 463bfdd8..c5531ef9 100644 --- a/src/dusk/script/module/entity/component/modulecomponentlist.h +++ b/src/dusk/script/module/entity/component/modulecomponentlist.h @@ -8,7 +8,10 @@ #pragma once #include "script/module/entity/modulecomponent.h" #include "camera/modulecamera.h" +#include "physics/modulephysics.h" #include "position/moduleposition.h" +#include "renderable/modulerenderable.h" +#include "trigger/moduletrigger.h" /** * Returns a typed JS instance for a newly-added component. Falls back to the @@ -19,21 +22,33 @@ static jerry_value_t moduleComponentListCreateInstance( const jscomponent_t *comp ) { switch(type) { - case COMPONENT_TYPE_POSITION: - return scriptProtoCreateValue(&MODULE_POSITION_PROTO, comp); 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) { - modulePositionInit(); moduleCameraInit(); + modulePhysicsInit(); + modulePositionInit(); + moduleRenderableInit(); + moduleTriggerInit(); } static void moduleComponentListDispose(void) { - moduleCameraDispose(); + moduleTriggerDispose(); + moduleRenderableDispose(); modulePositionDispose(); + modulePhysicsDispose(); + moduleCameraDispose(); } diff --git a/src/dusk/script/module/entity/component/physics/modulephysics.h b/src/dusk/script/module/entity/component/physics/modulephysics.h new file mode 100644 index 00000000..8e7cffd6 --- /dev/null +++ b/src/dusk/script/module/entity/component/physics/modulephysics.h @@ -0,0 +1,204 @@ +/** + * 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/math/modulevec3.h" +#include "script/module/entity/modulecomponent.h" +#include "entity/component/physics/entityphysics.h" + +static scriptproto_t MODULE_PHYSICS_PROTO; + +moduleBaseFunction(modulePhysicsCtor) { + (void)callInfo; (void)args; (void)argc; + return moduleBaseThrow("Physics cannot be instantiated with new"); +} + +static inline jscomponent_t *modulePhysicsSelf( + const jerry_call_info_t *callInfo +) { + return (jscomponent_t *)scriptProtoGetValue( + &MODULE_PHYSICS_PROTO, callInfo->this_value + ); +} + +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); +} + +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); +} diff --git a/src/dusk/script/module/entity/component/renderable/modulerenderable.h b/src/dusk/script/module/entity/component/renderable/modulerenderable.h new file mode 100644 index 00000000..0e132117 --- /dev/null +++ b/src/dusk/script/module/entity/component/renderable/modulerenderable.h @@ -0,0 +1,243 @@ +/** + * 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/module/display/modulecolor.h" +#include "script/module/display/moduletexture.h" +#include "script/module/entity/modulecomponent.h" +#include "script/scriptproto.h" +#include "entity/component/display/entityrenderable.h" + +static scriptproto_t MODULE_RENDERABLE_PROTO; + +moduleBaseFunction(moduleRenderableCtor) { + (void)callInfo; (void)args; (void)argc; + return moduleBaseThrow("Renderable cannot be instantiated with new"); +} + +static inline jscomponent_t *moduleRenderableSelf( + const jerry_call_info_t *callInfo +) { + return (jscomponent_t *)scriptProtoGetValue( + &MODULE_RENDERABLE_PROTO, callInfo->this_value + ); +} + +static inline entityrenderable_t *moduleRenderableData(const jscomponent_t *c) { + return (entityrenderable_t *)componentGetData( + c->entityId, c->componentId, COMPONENT_TYPE_RENDERABLE + ); +} + +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(); +} + +/* setTexture(tex: Texture) — switches to SPRITEBATCH and stores the texture. */ +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.setTexture: expected Texture"); + } + r->type = ENTITY_RENDERABLE_TYPE_SPRITEBATCH; + r->data.spritebatch.texture = &tex->entry->data.texture; + /* Pin the Texture object so GC won't free the asset while we hold a pointer. */ + jerry_value_t pinKey = jerry_string_sz("_tex"); + jerry_object_set(callInfo->this_value, pinKey, args[0]); + jerry_value_free(pinKey); + return jerry_undefined(); +} + +/* + * addSprite(x1,y1,z1, x2,y2,z2, u1,v1, u2,v2) + * addSprite(x1,y1, x2,y2, u1,v1, u2,v2) — z defaults to 0 + */ +moduleBaseFunction(moduleRenderableAddSprite) { + jscomponent_t *c = moduleRenderableSelf(callInfo); + if(!c) return jerry_undefined(); + entityrenderable_t *r = moduleRenderableData(c); + if(!r) return jerry_undefined(); + entityrenderablespritebatch_t *sb = &r->data.spritebatch; + if(sb->spriteCount >= ENTITY_RENDERABLE_SPRITEBATCH_SPRITES_MAX) { + return moduleBaseThrow("Renderable.addSprite: sprite capacity reached"); + } + spritebatchsprite_t s; + if(argc >= 10) { + moduleBaseRequireArgs(10); + s.min[0] = moduleBaseArgFloat(0); + s.min[1] = moduleBaseArgFloat(1); + s.min[2] = moduleBaseArgFloat(2); + s.max[0] = moduleBaseArgFloat(3); + s.max[1] = moduleBaseArgFloat(4); + s.max[2] = moduleBaseArgFloat(5); + s.uvMin[0] = moduleBaseArgFloat(6); + s.uvMin[1] = moduleBaseArgFloat(7); + s.uvMax[0] = moduleBaseArgFloat(8); + s.uvMax[1] = moduleBaseArgFloat(9); + } else { + moduleBaseRequireArgs(8); + s.min[0] = moduleBaseArgFloat(0); + s.min[1] = moduleBaseArgFloat(1); + s.min[2] = 0.0f; + s.max[0] = moduleBaseArgFloat(2); + s.max[1] = moduleBaseArgFloat(3); + s.max[2] = 0.0f; + s.uvMin[0] = moduleBaseArgFloat(4); + s.uvMin[1] = moduleBaseArgFloat(5); + s.uvMax[0] = moduleBaseArgFloat(6); + s.uvMax[1] = moduleBaseArgFloat(7); + } + sb->sprites[sb->spriteCount++] = s; + return jerry_undefined(); +} + +/* clearSprites() — resets the sprite count to 0. */ +moduleBaseFunction(moduleRenderableClearSprites) { + jscomponent_t *c = moduleRenderableSelf(callInfo); + if(!c) return jerry_undefined(); + entityrenderable_t *r = moduleRenderableData(c); + if(!r) return jerry_undefined(); + r->data.spritebatch.spriteCount = 0; + 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); +} + +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 + ); + scriptProtoDefineFunc( + &MODULE_RENDERABLE_PROTO, "setTexture", moduleRenderableSetTexture + ); + scriptProtoDefineFunc( + &MODULE_RENDERABLE_PROTO, "addSprite", moduleRenderableAddSprite + ); + scriptProtoDefineFunc( + &MODULE_RENDERABLE_PROTO, "clearSprites", moduleRenderableClearSprites + ); + 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); +} diff --git a/src/dusk/script/module/entity/component/trigger/moduletrigger.h b/src/dusk/script/module/entity/component/trigger/moduletrigger.h new file mode 100644 index 00000000..0039aad0 --- /dev/null +++ b/src/dusk/script/module/entity/component/trigger/moduletrigger.h @@ -0,0 +1,140 @@ +/** + * 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/math/modulevec3.h" +#include "script/module/entity/modulecomponent.h" +#include "entity/component/trigger/entitytrigger.h" + +static scriptproto_t MODULE_TRIGGER_PROTO; + +moduleBaseFunction(moduleTriggerCtor) { + (void)callInfo; (void)args; (void)argc; + return moduleBaseThrow("Trigger cannot be instantiated with new"); +} + +static inline jscomponent_t *moduleTriggerSelf( + const jerry_call_info_t *callInfo +) { + return (jscomponent_t *)scriptProtoGetValue( + &MODULE_TRIGGER_PROTO, callInfo->this_value + ); +} + +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); +} + +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); +} diff --git a/src/dusk/script/module/modulelist.h b/src/dusk/script/module/modulelist.h index f0000402..0ddb4b24 100644 --- a/src/dusk/script/module/modulelist.h +++ b/src/dusk/script/module/modulelist.h @@ -6,7 +6,9 @@ */ #pragma once +#include "script/module/asset/moduleasset.h" #include "script/module/console/moduleconsole.h" +#include "script/module/display/modulecolor.h" #include "script/module/display/modulescreen.h" #include "script/module/engine/moduleengine.h" #include "script/module/entity/component/modulecomponentlist.h" @@ -18,6 +20,9 @@ #include "script/module/system/modulesystem.h" static void moduleListInit(void) { + moduleTextureInit(); + moduleColorInit(); + moduleAssetInit(); moduleConsoleInit(); moduleScreenInit(); moduleEngineInit(); @@ -41,4 +46,7 @@ static void moduleListDispose(void) { moduleEngineDispose(); moduleScreenDispose(); moduleConsoleDispose(); + moduleAssetDispose(); + moduleColorDispose(); + moduleTextureDispose(); } diff --git a/test/asset/test_asset.c b/test/asset/test_asset.c index 7f6d4b59..fdcefea8 100644 --- a/test/asset/test_asset.c +++ b/test/asset/test_asset.c @@ -267,6 +267,73 @@ static void test_update_error_slot_stays_occupied(void **state) { assert_int_equal(memoryGetAllocatedCount(), 0); } +// ============================================================ +// assetEntryInit — input copy +// ============================================================ + +static void test_getEntry_null_input_stays_null(void **state) { + assetentry_t *entry = assetGetEntry("test.locale", ASSET_LOADER_TYPE_LOCALE, NULL); + + assert_null(entry->input); + assert_int_equal(memoryGetAllocatedCount(), 0); +} + +static void test_getEntry_input_copied_into_entry(void **state) { + assetloaderinput_t input; + memoryZero(&input, sizeof(input)); + input.texture = (textureformat_t)42; + + assetentry_t *entry = assetGetEntry("test.texture", ASSET_LOADER_TYPE_TEXTURE, &input); + + // input must have been copied — entry->input must point inside the entry. + assert_non_null(entry->input); + assert_ptr_equal(entry->input, &entry->inputData); + assert_int_equal((int)entry->input->texture, 42); + + assert_int_equal(memoryGetAllocatedCount(), 0); +} + +// ============================================================ +// assetUpdate — re-entrant sync loader +// ============================================================ + +static assetentry_t *reentrant_inner_entry = NULL; +static bool_t reentrant_inner_loaded = false; + +static errorret_t reentrant_stub_load(assetloading_t *loading) { + if(reentrant_inner_entry == NULL) { + // Simulate a script loading another asset from within its own sync step. + reentrant_inner_entry = assetGetEntry( + "inner.json", ASSET_LOADER_TYPE_JSON, NULL + ); + errorret_t inner_ret = assetRequireLoaded(reentrant_inner_entry); + reentrant_inner_loaded = errorIsOk(inner_ret); + if(errorIsNotOk(inner_ret)) errorChain(inner_ret); + } + loading->entry->state = ASSET_ENTRY_STATE_LOADED; + errorOk(); +} + +static void test_update_reentrant_sync_loader(void **state) { + reentrant_inner_entry = NULL; + reentrant_inner_loaded = false; + // LOCALE uses the re-entrant loader; JSON keeps the default stub_load_success. + ASSET_LOADER_CALLBACKS[ASSET_LOADER_TYPE_LOCALE].loadSync = reentrant_stub_load; + + assetentry_t *outer = assetGetEntry("outer.locale", ASSET_LOADER_TYPE_LOCALE, NULL); + + errorret_t ret = assetRequireLoaded(outer); + assert_true(errorIsOk(ret)); + assert_int_equal(outer->state, ASSET_ENTRY_STATE_LOADED); + // Verify the re-entrant load ran and completed successfully. + // (The inner entry may have been reaped by the time we check here, + // so we capture the result inside the callback rather than reading state.) + assert_non_null(reentrant_inner_entry); + assert_true(reentrant_inner_loaded); + + assert_int_equal(memoryGetAllocatedCount(), 0); +} + // ============================================================ // assetGetEntry — dedup against non-NOT_STARTED entries // ============================================================ @@ -377,6 +444,8 @@ int main(void) { cmocka_unit_test_setup_teardown(test_getEntry_dedup, asset_setup, asset_teardown), cmocka_unit_test_setup_teardown(test_getEntry_distinct_names, asset_setup, asset_teardown), cmocka_unit_test_setup_teardown(test_getEntry_returns_loaded_entry, asset_setup, asset_teardown), + cmocka_unit_test_setup_teardown(test_getEntry_null_input_stays_null, asset_setup, asset_teardown), + cmocka_unit_test_setup_teardown(test_getEntry_input_copied_into_entry, asset_setup, asset_teardown), // assetUpdate — state machine cmocka_unit_test_setup_teardown(test_update_noop_on_empty_table, asset_setup, asset_teardown), @@ -388,6 +457,7 @@ int main(void) { cmocka_unit_test_setup_teardown(test_update_overflow_queues_entries, asset_setup, asset_teardown), cmocka_unit_test_setup_teardown(test_update_error_state, asset_setup, asset_teardown), cmocka_unit_test_setup_teardown(test_update_error_slot_stays_occupied, asset_setup, asset_teardown), + cmocka_unit_test_setup_teardown(test_update_reentrant_sync_loader, asset_setup, asset_teardown), // assetEntryDispose cmocka_unit_test_setup_teardown(test_entry_dispose_clears_entry, asset_setup, asset_teardown), diff --git a/types/asset/asset.d.ts b/types/asset/asset.d.ts new file mode 100644 index 00000000..1389b06a --- /dev/null +++ b/types/asset/asset.d.ts @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** Asset archive queries and cache management. */ +interface AssetNamespace { + // Loader type constants + readonly TYPE_MESH: number; + readonly TYPE_TEXTURE: number; + readonly TYPE_TILESET: number; + readonly TYPE_LOCALE: number; + readonly TYPE_JSON: number; + readonly TYPE_SCRIPT: number; + + // Mesh axis input constants (pass as `input` to lock with TYPE_MESH) + readonly MESH_AXIS_Y_UP: number; + readonly MESH_AXIS_Z_UP: number; + readonly MESH_AXIS_X_UP: number; + readonly MESH_AXIS_Y_DOWN: number; + readonly MESH_AXIS_Z_DOWN: number; + readonly MESH_AXIS_X_DOWN: number; + + /** + * Returns `true` if the given path exists in the asset archive (`dusk.dsk`). + * + * @param path - Archive-relative path, e.g. `"init.js"` or `"ui/hud.png"`. + */ + exists(path: string): boolean; + + /** + * Locks an entry in the asset cache and returns an `AssetEntry`. + * The entry begins loading in the background. Call `entry.requireLoaded()` + * to block until it is ready. + * + * The lock is released when the `AssetEntry` is GC'd or `entry.unlock()` + * is called explicitly. + * + * @param path - Archive-relative path. + * @param type - Loader type constant (`Asset.TYPE_*`). + * @param input - Optional loader-specific input constant. + * `TYPE_TEXTURE` → `Texture.FORMAT_*` + * `TYPE_MESH` → `Asset.MESH_AXIS_*` + * + * @example + * const entry = Asset.lock('data/map.json'); + * entry.requireLoaded(); + */ + lock(path: string, type: number, input?: number): AssetEntry; + + /** + * Blocks until the given entry is fully loaded. + * Returns the entry for chaining. + * @throws If the load fails. + * + * @example + * const entry = Asset.requireLoaded(Asset.lock('map.json', Asset.TYPE_JSON)); + */ + requireLoaded(entry: AssetEntry): AssetEntry; + + /** + * Releases the lock on an asset by path. + * Prefer calling `entry.unlock()` on the `AssetEntry` object directly. + * + * @param path - The path originally passed to `lock`. + */ + unlock(path: string): void; +} + +declare var Asset: AssetNamespace; diff --git a/types/asset/assetentry.d.ts b/types/asset/assetentry.d.ts new file mode 100644 index 00000000..7d93918e --- /dev/null +++ b/types/asset/assetentry.d.ts @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** + * A live reference to an entry in the asset cache. + * Holds a lock that keeps the entry alive; the lock is released automatically + * when the object is garbage collected, or immediately via `unlock()`. + */ +interface AssetEntry { + /** Archive-relative path used as the cache key. */ + readonly name: string; + /** Current loading state — compare against `AssetEntry.*` state constants. */ + readonly state: number; + /** Loader type — one of the `AssetEntry.TYPE_*` constants. */ + readonly type: number; + /** `true` when the entry has fully loaded (`state === AssetEntry.LOADED`). */ + readonly isLoaded: boolean; + /** + * Returns a `Texture` for this entry when it is a loaded texture asset. + * The `Texture` holds its own asset lock — independent of this `AssetEntry`. + * Returns `undefined` if the entry is not of type `Asset.TYPE_TEXTURE` or + * is not yet loaded. + */ + readonly texture: Texture | undefined; + /** + * Blocks until the entry reaches `LOADED` (or `ERROR`). + * Returns `this` for chaining. + * @throws If the load fails. + */ + requireLoaded(): this; + /** + * Releases the lock immediately. + * After this call the object is invalid — do not use it again. + */ + unlock(): void; + toString(): string; +} + +interface AssetEntryConstructor { + // Loading state constants + readonly NOT_STARTED: number; + readonly PENDING: number; + readonly LOADING: number; + readonly LOADED: number; + readonly ERROR: number; + + new(): never; +} + +declare var AssetEntry: AssetEntryConstructor; diff --git a/types/console.d.ts b/types/console/console.d.ts similarity index 81% rename from types/console.d.ts rename to types/console/console.d.ts index c55e4f2f..dc5e1cc7 100644 --- a/types/console.d.ts +++ b/types/console/console.d.ts @@ -5,16 +5,12 @@ * https://opensource.org/licenses/MIT */ -/** - * Interface for the in-game developer console. - */ +/** 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); */ @@ -23,9 +19,6 @@ interface ConsoleNamespace { /** * 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; } diff --git a/types/display/color.d.ts b/types/display/color.d.ts new file mode 100644 index 00000000..f6a0807e --- /dev/null +++ b/types/display/color.d.ts @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** An RGBA color with channels in the range 0–255. */ +declare class Color { + /** @param r Red 0–255 (default 0) */ + /** @param g Green 0–255 (default 0) */ + /** @param b Blue 0–255 (default 0) */ + /** @param a Alpha 0–255 (default 255) */ + constructor(r?: number, g?: number, b?: number, a?: number); + + r: number; + g: number; + b: number; + a: number; + + toString(): string; + + // Named color constants + static readonly WHITE: Color; + static readonly BLACK: Color; + static readonly RED: Color; + static readonly GREEN: Color; + static readonly BLUE: Color; + static readonly YELLOW: Color; + static readonly CYAN: Color; + static readonly MAGENTA: Color; + static readonly TRANSPARENT: Color; + static readonly GRAY: Color; + static readonly LIGHT_GRAY: Color; + static readonly DARK_GRAY: Color; + static readonly ORANGE: Color; + static readonly PURPLE: Color; + static readonly PINK: Color; + static readonly TEAL: Color; + static readonly CORNFLOWER_BLUE: Color; +} diff --git a/types/display/screen.d.ts b/types/display/screen.d.ts new file mode 100644 index 00000000..1bbd8696 --- /dev/null +++ b/types/display/screen.d.ts @@ -0,0 +1,19 @@ +/** + * 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. */ + readonly width: number; + /** Current render-target height in pixels. */ + readonly height: number; + /** Aspect ratio: `width / height`. */ + readonly aspect: number; +} + +/** Current display / render-target dimensions. */ +declare var Screen: ScreenNamespace; diff --git a/types/display/texture.d.ts b/types/display/texture.d.ts new file mode 100644 index 00000000..665ec7ad --- /dev/null +++ b/types/display/texture.d.ts @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** + * A loaded texture asset. Holds a lock on the underlying asset entry — + * the lock is released automatically when the object is garbage collected. + */ +interface Texture { + /** Pixel width of the texture. */ + readonly width: number; + /** Pixel height of the texture. */ + readonly height: number; + toString(): string; +} + +interface TextureConstructor { + /** RGBA 32-bit format (4 channels × 8 bits). */ + readonly FORMAT_RGBA: number; + /** Paletted format. */ + readonly FORMAT_PALETTE: number; + new(): never; +} + +declare var Texture: TextureConstructor; diff --git a/types/engine.d.ts b/types/engine/engine.d.ts similarity index 50% rename from types/engine.d.ts rename to types/engine/engine.d.ts index 55391d02..504ad743 100644 --- a/types/engine.d.ts +++ b/types/engine/engine.d.ts @@ -5,26 +5,17 @@ * https://opensource.org/licenses/MIT */ -/** - * Controls over the engine main loop. - */ +/** Controls over the engine main loop. */ interface EngineNamespace { /** - * Whether the engine main loop is still running (read-only). + * Whether the engine main loop is still running. * 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(); + * Requests an orderly shutdown. Sets `running` to `false`; the main loop + * exits at the end of the current tick. */ exit(): void; } diff --git a/types/entity.d.ts b/types/entity.d.ts deleted file mode 100644 index dbb72ae0..00000000 --- a/types/entity.d.ts +++ /dev/null @@ -1,129 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -// --------------------------------------------------------------------------- -// Component (generic base returned by entity.add() for untyped components) -// --------------------------------------------------------------------------- - -/** Base component returned by entity.add() for component types without a specific module. */ -interface Component { - /** Entity ID this component belongs to. */ - readonly entity: number; - /** Component slot ID. */ - readonly id: number; - toString(): string; -} - -interface ComponentConstructor { - /** Sentinel value for an invalid component ID. */ - readonly INVALID: number; - - // Component type constants (generated from componentlist.h) - readonly POSITION: number; - readonly CAMERA: number; - readonly RENDERABLE: number; - readonly PHYSICS: number; - readonly TRIGGER: number; - readonly OVERWORLD: number; - readonly PLAYER: number; - readonly INTERACTABLE: number; - readonly OVERWORLD_CAMERA: number; - readonly OVERWORLD_TRIGGER: number; - - new(): never; -} - -declare var Component: ComponentConstructor; - -// --------------------------------------------------------------------------- -// Position -// --------------------------------------------------------------------------- - -/** Position/rotation/scale transform component with optional parent hierarchy. */ -interface Position extends Component { - /** - * Local-space position. Assigning a Vec3 writes to the C transform and - * marks the world transform dirty. Reading returns a fresh Vec3 copy. - */ - localPosition: Vec3; - /** World-space position (accounts for all parent transforms). */ - worldPosition: Vec3; - /** Local-space euler rotation in radians (XYZ). */ - localRotation: Vec3; - /** World-space euler rotation in radians. */ - worldRotation: Vec3; - /** Local-space scale. */ - localScale: Vec3; - /** World-space scale. */ - worldScale: Vec3; - /** - * Orients the transform to look at a world-space target point. - * Uses the current local position as the eye. Optionally specify an up - * vector (defaults to Y-up). - */ - lookAt(target: Vec3, up?: Vec3): void; - /** - * Sets this component's parent in the transform hierarchy. - * Pass `null` or `undefined` to detach. - */ - setParent(parent: Position | null | undefined): void; - toString(): string; -} - -interface PositionConstructor { - new(): never; -} - -declare var Position: PositionConstructor; - -// --------------------------------------------------------------------------- -// Camera -// --------------------------------------------------------------------------- - -/** Camera projection component. */ -interface Camera extends Component { - /** Field of view in radians (perspective projections only). */ - fov: number; - /** Near clip plane distance. */ - nearClip: number; - /** Far clip plane distance. */ - farClip: number; - /** Projection type — one of the Camera.PERSPECTIVE / Camera.ORTHOGRAPHIC constants. */ - projType: number; - toString(): string; -} - -interface CameraConstructor { - readonly PERSPECTIVE: number; - readonly PERSPECTIVE_FLIPPED: number; - readonly ORTHOGRAPHIC: number; - new(): never; -} - -declare var Camera: CameraConstructor; - -// --------------------------------------------------------------------------- -// Entity -// --------------------------------------------------------------------------- - -interface Entity { - /** Add a component of the given type and return a typed instance. */ - add(type: CameraConstructor["PERSPECTIVE"] | number): Component; - toString(): string; -} - -interface EntityConstructor { - /** Sentinel value for an invalid entity ID. */ - readonly INVALID: number; - /** Creates a new entity and returns it. Returns Entity.INVALID slot if the pool is full. */ - create(): Entity; - /** Disposes the entity and all its components. */ - dispose(entity: Entity): void; - new(): never; -} - -declare var Entity: EntityConstructor; diff --git a/types/entity/component.d.ts b/types/entity/component.d.ts new file mode 100644 index 00000000..2820532c --- /dev/null +++ b/types/entity/component.d.ts @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** + * Base type for all components. `entity.add()` returns a subtype when the + * component type has a dedicated module; cast with `as Position` etc. when + * you need the specific API. + */ +interface Component { + /** Entity ID this component belongs to. */ + readonly entity: number; + /** Component slot index. */ + readonly id: number; + toString(): string; +} + +interface ComponentConstructor { + /** Sentinel for an invalid component ID. */ + readonly INVALID: number; + + readonly POSITION: number; + readonly CAMERA: number; + readonly RENDERABLE: number; + readonly PHYSICS: number; + readonly TRIGGER: number; + readonly OVERWORLD: number; + readonly PLAYER: number; + readonly INTERACTABLE: number; + readonly OVERWORLD_CAMERA: number; + readonly OVERWORLD_TRIGGER: number; + + new(): never; +} + +declare var Component: ComponentConstructor; diff --git a/types/entity/component/camera.d.ts b/types/entity/component/camera.d.ts new file mode 100644 index 00000000..d7771796 --- /dev/null +++ b/types/entity/component/camera.d.ts @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** Camera projection component. Only one camera is active at a time. */ +interface Camera extends Component { + /** Field of view in radians (perspective only). */ + fov: number; + /** Near clip plane distance. */ + nearClip: number; + /** Far clip plane distance. */ + farClip: number; + /** One of `Camera.PERSPECTIVE`, `Camera.PERSPECTIVE_FLIPPED`, or `Camera.ORTHOGRAPHIC`. */ + projType: number; + toString(): string; +} + +interface CameraConstructor { + readonly PERSPECTIVE: number; + readonly PERSPECTIVE_FLIPPED: number; + readonly ORTHOGRAPHIC: number; + new(): never; +} + +declare var Camera: CameraConstructor; diff --git a/types/entity/component/physics.d.ts b/types/entity/component/physics.d.ts new file mode 100644 index 00000000..c4616a24 --- /dev/null +++ b/types/entity/component/physics.d.ts @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** Physics body component. */ +interface Physics extends Component { + /** Body simulation type — `Physics.STATIC`, `DYNAMIC`, or `KINEMATIC`. */ + bodyType: number; + /** + * Collision shape type — one of the `Physics.SHAPE_*` constants. + * Changing the type preserves existing velocity and ground state. + */ + shape: number; + /** Current linear velocity (Vec3). */ + velocity: Vec3; + /** Gravity multiplier. 0 = no gravity, 1 = full, negative = inverted. */ + gravityScale: number; + /** `true` if the body rested on a surface during the last simulation step. */ + readonly onGround: boolean; + /** Applies an instantaneous velocity change. No-op on STATIC bodies. */ + applyImpulse(impulse: Vec3): void; + toString(): string; +} + +interface PhysicsConstructor { + readonly STATIC: number; + readonly DYNAMIC: number; + readonly KINEMATIC: number; + readonly SHAPE_CUBE: number; + readonly SHAPE_SPHERE: number; + readonly SHAPE_CAPSULE: number; + readonly SHAPE_PLANE: number; + new(): never; +} + +declare var Physics: PhysicsConstructor; diff --git a/types/entity/component/position.d.ts b/types/entity/component/position.d.ts new file mode 100644 index 00000000..5f7addfe --- /dev/null +++ b/types/entity/component/position.d.ts @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** Transform component — local/world PRS with an optional parent hierarchy. */ +interface Position extends Component { + /** + * Local-space position. Reading returns a Vec3 copy; assigning a Vec3 + * writes through to the C transform and marks descendants dirty. + */ + localPosition: Vec3; + /** World-space position (full parent-chain applied). */ + worldPosition: Vec3; + /** Local euler rotation in radians (XYZ order). */ + localRotation: Vec3; + /** World euler rotation in radians. */ + worldRotation: Vec3; + /** Local scale. */ + localScale: Vec3; + /** World scale (extracted from parent-chain matrix). */ + worldScale: Vec3; + /** + * Orients the transform so it faces `target`. Uses the current local + * position as the eye. `up` defaults to world Y-up. + */ + lookAt(target: Vec3, up?: Vec3): void; + /** + * Attaches this transform to a parent. Pass `null` / `undefined` to detach. + */ + setParent(parent: Position | null | undefined): void; + toString(): string; +} + +interface PositionConstructor { + new(): never; +} + +declare var Position: PositionConstructor; diff --git a/types/entity/component/renderable.d.ts b/types/entity/component/renderable.d.ts new file mode 100644 index 00000000..c20b30b0 --- /dev/null +++ b/types/entity/component/renderable.d.ts @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** Fields shared by every renderable type. */ +interface Renderable extends Component { + /** Current render type — one of the `Renderable.*` type constants. */ + type: number; + /** Render priority. 0 = auto. Higher = drawn later. */ + priority: number; + toString(): string; +} + +/** + * Renderable in `SHADER_MATERIAL` mode (default after `entity.add`). + * Renders a mesh with the unlit shader. + */ +interface RenderableMaterial extends Renderable { + /** + * Unlit material color. Reading returns a fresh `Color` copy; assigning + * a `Color` instance writes through to the C material. + * + * @example + * r.color = Color.RED; + * r.color = new Color(255, 128, 0); + */ + color: Color; +} + +/** + * Renderable in `SPRITEBATCH` mode. + * Activated by calling `setTexture`. + */ +interface RenderableSpritebatch extends Renderable { + /** + * Assigns a texture and switches to `SPRITEBATCH` mode. + * The Texture object is pinned (GC-safe) for the component's lifetime. + */ + setTexture(texture: Texture): void; + /** + * Adds a sprite quad to the spritebatch. + * + * 3D form (10 args): `addSprite(x1,y1,z1, x2,y2,z2, u1,v1, u2,v2)` + * 2D form (8 args): `addSprite(x1,y1, x2,y2, u1,v1, u2,v2)` — z defaults to 0 + */ + addSprite( + x1: number, y1: number, z1OrX2: number, + x2OrY2: number, y2OrZ2?: number, z2?: number, + u1?: number, v1?: number, u2?: number, v2?: number + ): void; + /** Resets the sprite count to zero. */ + clearSprites(): void; +} + +/** + * Renderable in `CUSTOM` mode. + * Draw logic is provided by a C callback set via `entityRenderableSetDraw`. + */ +interface RenderableCustom extends Renderable {} + +interface RenderableConstructor { + readonly SHADER_MATERIAL: number; + readonly SPRITEBATCH: number; + readonly CUSTOM: number; + new(): never; +} + +declare var Renderable: RenderableConstructor; diff --git a/types/entity/component/trigger.d.ts b/types/entity/component/trigger.d.ts new file mode 100644 index 00000000..ac86c20b --- /dev/null +++ b/types/entity/component/trigger.d.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** AABB trigger volume. `contains` tests whether a world point is inside. */ +interface Trigger extends Component { + /** Minimum corner of the AABB (Vec3). */ + min: Vec3; + /** Maximum corner of the AABB (Vec3). */ + max: Vec3; + /** Sets both corners at once. */ + setBounds(min: Vec3, max: Vec3): void; + /** Returns `true` if `point` is inside `[min, max]`. */ + contains(point: Vec3): boolean; + toString(): string; +} + +interface TriggerConstructor { + new(): never; +} + +declare var Trigger: TriggerConstructor; diff --git a/types/entity/entity.d.ts b/types/entity/entity.d.ts new file mode 100644 index 00000000..46d0808c --- /dev/null +++ b/types/entity/entity.d.ts @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +interface Entity { + /** + * Adds a component of the given type and returns it. + * Returns a typed subclass when the component has a dedicated module + * (`Position`, `Camera`, `Renderable`, `Trigger`, `Physics`); otherwise + * returns the base `Component`. Cast with `as Position` etc. when needed. + */ + add(type: number): Component; + toString(): string; +} + +interface EntityConstructor { + /** Sentinel for an invalid entity ID. */ + readonly INVALID: number; + /** Allocates a new entity from the fixed pool (max 64). */ + create(): Entity; + /** Disposes the entity and all of its components. */ + dispose(entity: Entity): void; + new(): never; +} + +declare var Entity: EntityConstructor; diff --git a/types/index.d.ts b/types/index.d.ts index cebc47f4..f9a497df 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -14,11 +14,30 @@ * { "compilerOptions": { "typeRoots": ["./types"] } } */ -/// -/// -/// -/// -/// -/// -/// -/// +// math +/// + +// display +/// +/// +/// + +// asset +/// +/// + +// engine systems +/// +/// +/// +/// +/// + +// entity / components +/// +/// +/// +/// +/// +/// +/// diff --git a/types/input.d.ts b/types/input.d.ts deleted file mode 100644 index 07c645bd..00000000 --- a/types/input.d.ts +++ /dev/null @@ -1,115 +0,0 @@ -/** - * 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/input/input.d.ts b/types/input/input.d.ts new file mode 100644 index 00000000..e39a2f24 --- /dev/null +++ b/types/input/input.d.ts @@ -0,0 +1,50 @@ +/** + * 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. */ +type InputAction = number; + +/** Polling-based input queries and button rebinding. */ +interface InputNamespace { + /** Returns `true` while the given action is held down this frame. */ + isDown(action: InputAction): boolean; + /** Returns `true` if the given action was held down last frame. */ + wasDown(action: InputAction): boolean; + /** Returns `true` on the first frame the action transitions up → down. */ + pressed(action: InputAction): boolean; + /** Returns `true` on the first frame the action transitions down → up. */ + released(action: InputAction): boolean; + /** Continuous (analog) value in `0.0–1.0`. Digital buttons return 0 or 1. */ + getValue(action: InputAction): number; + /** + * Signed axis value `-1.0–1.0`: `getValue(pos) - getValue(neg)`. + * @param neg - Action mapped to the negative direction. + * @param pos - Action mapped to the positive direction. + */ + axis(neg: InputAction, pos: InputAction): number; + /** + * Rebinds a physical button to a logical action at runtime. + * @param buttonName - Platform-specific button name, e.g. `"A"`, `"START"`. + * @param action - Target `INPUT_ACTION_*` constant. + */ + bind(buttonName: string, action: InputAction): void; +} + +/** Polling-based input system. */ +declare var Input: InputNamespace; + +// Input action constants — injected as globals by the engine at startup. +declare var INPUT_ACTION_UP: InputAction; +declare var INPUT_ACTION_DOWN: InputAction; +declare var INPUT_ACTION_LEFT: InputAction; +declare var INPUT_ACTION_RIGHT: InputAction; +declare var INPUT_ACTION_ACCEPT: InputAction; +declare var INPUT_ACTION_CANCEL: InputAction; +declare var INPUT_ACTION_RAGEQUIT: InputAction; +declare var INPUT_ACTION_CONSOLE: InputAction; +declare var INPUT_ACTION_POINTERX: InputAction; +declare var INPUT_ACTION_POINTERY: InputAction; diff --git a/types/vec3.d.ts b/types/math/vec3.d.ts similarity index 100% rename from types/vec3.d.ts rename to types/math/vec3.d.ts diff --git a/types/scene.d.ts b/types/scene/scene.d.ts similarity index 77% rename from types/scene.d.ts rename to types/scene/scene.d.ts index 79f1dc15..1769b3a4 100644 --- a/types/scene.d.ts +++ b/types/scene/scene.d.ts @@ -7,21 +7,18 @@ /** Scene management — request scene transitions and query the active scene. */ interface SceneNamespace { - /** The type constant of the currently active scene, or 0 if none. */ + /** Type constant of the currently active scene, or 0 if none. */ readonly current: number; /** * Requests a scene transition. The change takes effect at the start of the * next safe update tick (current scene is disposed, new scene is initialized). * - * @param type - A `Scene.*` scene type constant. - * * @example * Scene.set(Scene.OVERWORLD); */ set(type: number): void; - // Scene type constants (generated from scenelist.h) readonly INITIAL: number; readonly TEST: number; readonly OVERWORLD: number; diff --git a/types/screen.d.ts b/types/screen.d.ts deleted file mode 100644 index 1b303ffa..00000000 --- a/types/screen.d.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * 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/system.d.ts similarity index 67% rename from types/system.d.ts rename to types/system/system.d.ts index beceddf4..870e0ca1 100644 --- a/types/system.d.ts +++ b/types/system/system.d.ts @@ -5,12 +5,10 @@ * https://opensource.org/licenses/MIT */ -/** - * Runtime platform detection and system-level information. - */ +/** Runtime platform detection. */ interface SystemNamespace { /** - * Numeric identifier for the platform the engine is running on (read-only). + * Numeric identifier for the current platform. * Compare against the `System.PLATFORM_*` constants. * * @example @@ -18,19 +16,10 @@ interface SystemNamespace { */ 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; }