Cleanup of script modules.

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