Test sprite from script

This commit is contained in:
2026-06-02 09:32:07 -05:00
parent 57766a9104
commit a25871a849
38 changed files with 1913 additions and 377 deletions
+22 -4
View File
@@ -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);
+10 -8
View File
@@ -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:
+4 -1
View File
@@ -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);
}
+3 -1
View File
@@ -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;
@@ -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");
}
-7
View File
@@ -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")));
}
+138
View File
@@ -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();
}
@@ -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);
}
@@ -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);
}
@@ -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);
}
@@ -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();
}
@@ -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);
}
@@ -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);
}
@@ -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);
}
+8
View File
@@ -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();
}
+70
View File
@@ -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),
+72
View File
@@ -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;
+54
View File
@@ -0,0 +1,54 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
/**
* 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;
+1 -8
View File
@@ -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;
}
+41
View File
@@ -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 0255. */
declare class Color {
/** @param r Red 0255 (default 0) */
/** @param g Green 0255 (default 0) */
/** @param b Blue 0255 (default 0) */
/** @param a Alpha 0255 (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;
}
+19
View File
@@ -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;
+28
View File
@@ -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;
+4 -13
View File
@@ -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;
}
-129
View File
@@ -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;
+39
View File
@@ -0,0 +1,39 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
/**
* 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;
+28
View File
@@ -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;
+39
View File
@@ -0,0 +1,39 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
/** 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;
+41
View File
@@ -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;
+71
View File
@@ -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;
+25
View File
@@ -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;
+29
View File
@@ -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;
+27 -8
View File
@@ -14,11 +14,30 @@
* { "compilerOptions": { "typeRoots": ["./types"] } }
*/
/// <reference path="./console.d.ts" />
/// <reference path="./screen.d.ts" />
/// <reference path="./engine.d.ts" />
/// <reference path="./input.d.ts" />
/// <reference path="./system.d.ts" />
/// <reference path="./vec3.d.ts" />
/// <reference path="./entity.d.ts" />
/// <reference path="./scene.d.ts" />
// math
/// <reference path="./math/vec3.d.ts" />
// display
/// <reference path="./display/color.d.ts" />
/// <reference path="./display/screen.d.ts" />
/// <reference path="./display/texture.d.ts" />
// asset
/// <reference path="./asset/assetentry.d.ts" />
/// <reference path="./asset/asset.d.ts" />
// engine systems
/// <reference path="./console/console.d.ts" />
/// <reference path="./engine/engine.d.ts" />
/// <reference path="./input/input.d.ts" />
/// <reference path="./scene/scene.d.ts" />
/// <reference path="./system/system.d.ts" />
// entity / components
/// <reference path="./entity/component.d.ts" />
/// <reference path="./entity/component/camera.d.ts" />
/// <reference path="./entity/component/physics.d.ts" />
/// <reference path="./entity/component/position.d.ts" />
/// <reference path="./entity/component/renderable.d.ts" />
/// <reference path="./entity/component/trigger.d.ts" />
/// <reference path="./entity/entity.d.ts" />
-115
View File
@@ -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.01.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.01.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;
+50
View File
@@ -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.01.0`. Digital buttons return 0 or 1. */
getValue(action: InputAction): number;
/**
* Signed axis value `-1.01.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;
View File
+1 -4
View File
@@ -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;
-28
View File
@@ -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;
+2 -13
View File
@@ -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;
}