Remove script
This commit is contained in:
@@ -1,21 +0,0 @@
|
||||
// Copyright (c) 2026 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
const platformNames = {
|
||||
[System.PLATFORM_LINUX]: 'Linux',
|
||||
[System.PLATFORM_KNULLI]: 'Knulli',
|
||||
[System.PLATFORM_PSP]: 'PSP',
|
||||
[System.PLATFORM_GAMECUBE]: 'GameCube',
|
||||
[System.PLATFORM_WII]: 'Wii',
|
||||
};
|
||||
|
||||
Console.print('Platform: ' + (platformNames[System.platform] || 'Unknown'));
|
||||
|
||||
UIFullboxOver.setColor(Color.BLACK);
|
||||
|
||||
requireAsync('testscene.js').then(Scene.set).catch(err => {
|
||||
Console.print('Error loading scene: ' + err);
|
||||
Engine.exit();
|
||||
});
|
||||
@@ -1,63 +0,0 @@
|
||||
// Copyright (c) 2026 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
const PLAYER_SPEED = 5.0;
|
||||
// 1 world unit = 16 pixels.
|
||||
const PIXEL_SCALE = 1.0 / 16.0;
|
||||
// Player sprite is 32x32 px (test.png dimensions).
|
||||
const PLAYER_W = 32 * PIXEL_SCALE;
|
||||
const PLAYER_H = 32 * PIXEL_SCALE;
|
||||
|
||||
var player = {};
|
||||
|
||||
player.getAssets = () => {
|
||||
return [
|
||||
{ path: 'test.png', type: Asset.TYPE_TEXTURE, format: Texture.FORMAT_RGBA }
|
||||
];
|
||||
}
|
||||
|
||||
player.init = function(scene) {
|
||||
var texture = scene.assets.getAssetByPath('test.png');
|
||||
Console.print('Player init: got texture ' + texture);
|
||||
|
||||
_entity = Entity.create();
|
||||
_position = _entity.add(Component.POSITION);
|
||||
_physics = _entity.add(Component.PHYSICS);
|
||||
|
||||
_physics.bodyType = Physics.DYNAMIC;
|
||||
_physics.shape = Physics.SHAPE_CUBE;
|
||||
_physics.gravityScale = 1.0;
|
||||
|
||||
var r = _entity.add(Component.RENDERABLE);
|
||||
r.texture = texture.texture;
|
||||
r.type = Renderable.SPRITEBATCH;
|
||||
r.color = new Color(220, 80, 80);
|
||||
// Upright quad centered on X, bottom-aligned on Y.
|
||||
r.sprites = [[-PLAYER_W/2, 0, 0, PLAYER_W/2, PLAYER_H, 0, 0, 1, 1, 0]];
|
||||
|
||||
_position.localPosition = new Vec3(0, PLAYER_H, 0);
|
||||
};
|
||||
|
||||
player.getPosition = function() {
|
||||
return _position;
|
||||
};
|
||||
|
||||
player.update = function() {
|
||||
if(!_physics) return;
|
||||
var vx = Input.axis(INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT) * PLAYER_SPEED;
|
||||
var vz = Input.axis(INPUT_ACTION_DOWN, INPUT_ACTION_UP) * PLAYER_SPEED;
|
||||
// Preserve vertical velocity so gravity and landing work correctly.
|
||||
var vy = _physics.velocity.y;
|
||||
_physics.velocity = new Vec3(vx, vy, vz);
|
||||
};
|
||||
|
||||
player.dispose = function() {
|
||||
Entity.dispose(_entity);
|
||||
_entity = null;
|
||||
_position = null;
|
||||
_physics = null;
|
||||
};
|
||||
|
||||
module.exports = player;
|
||||
@@ -1,42 +0,0 @@
|
||||
// Copyright (c) 2026 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
var scene = {};
|
||||
|
||||
// Pokemon DS-style camera: ~34 degrees elevation (atan(6/9)).
|
||||
// CAM_HEIGHT / CAM_DIST ratio controls the tilt - keep it under 0.7 for
|
||||
// the characteristically shallow DS angle.
|
||||
const CAM_HEIGHT = 6;
|
||||
const CAM_DIST = 9;
|
||||
|
||||
scene.init = async function() {
|
||||
// Camera
|
||||
scene.cam = Entity.create();
|
||||
var camPos = scene.cam.add(Component.POSITION);
|
||||
var cam = scene.cam.add(Component.CAMERA);
|
||||
camPos.localPosition = new Vec3(3, 3, 3);
|
||||
camPos.lookAt(new Vec3(0, 0, 0));
|
||||
|
||||
// Floor - large flat slab, no texture needed.
|
||||
scene.floor = Entity.create();
|
||||
var floorPos = scene.floor.add(Component.POSITION);
|
||||
var floorR = scene.floor.add(Component.RENDERABLE);
|
||||
floorR.type = Renderable.SHADER_MATERIAL;
|
||||
floorR.color = Color.BLUE;
|
||||
// floorPos.localScale = new Vec3(16, 0.2, 16);
|
||||
// floorPos.localPosition = new Vec3(0, -0.1, 0);
|
||||
|
||||
await UIFullboxOver.transition(Color.BLACK, Color.TRANSPARENT, 1.0);
|
||||
};
|
||||
|
||||
scene.update = function() {
|
||||
};
|
||||
|
||||
scene.dispose = function() {
|
||||
Entity.dispose(scene.floor);
|
||||
Entity.dispose(scene.cam);
|
||||
};
|
||||
|
||||
module.exports = scene;
|
||||
@@ -1,6 +0,0 @@
|
||||
module = {
|
||||
render() {
|
||||
Text.draw(0, 0, "Hello World");
|
||||
SpriteBatch.flush();
|
||||
}
|
||||
};
|
||||
@@ -9,37 +9,6 @@ if(NOT cglm_FOUND)
|
||||
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC cglm)
|
||||
endif()
|
||||
|
||||
if(NOT libzip_FOUND)
|
||||
find_package(libzip REQUIRED)
|
||||
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC zip)
|
||||
endif()
|
||||
|
||||
if(NOT stb_image_FOUND)
|
||||
find_package(stb REQUIRED)
|
||||
if(STB_IMAGE_FOUND)
|
||||
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC stb_image)
|
||||
else()
|
||||
message(FATAL_ERROR "stb_image not found. Please ensure stb is correctly fetched.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT yyjson_FOUND)
|
||||
find_package(yyjson REQUIRED)
|
||||
if(yyjson_FOUND)
|
||||
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC yyjson::yyjson)
|
||||
else()
|
||||
message(FATAL_ERROR "yyjson not found. Please ensure yyjson is correctly fetched.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT jerryscript_FOUND)
|
||||
find_package(jerryscript REQUIRED)
|
||||
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||
jerryscript::core
|
||||
jerryscript::ext
|
||||
jerryscript::port
|
||||
)
|
||||
endif()
|
||||
|
||||
if(DUSK_BACKTRACE)
|
||||
target_link_options(${DUSK_LIBRARY_TARGET_NAME} PUBLIC -rdynamic)
|
||||
@@ -64,8 +33,6 @@ target_sources(${DUSK_BINARY_TARGET_NAME}
|
||||
add_subdirectory(animation)
|
||||
add_subdirectory(event)
|
||||
add_subdirectory(assert)
|
||||
add_subdirectory(asset)
|
||||
add_subdirectory(cutscene)
|
||||
add_subdirectory(item)
|
||||
add_subdirectory(story)
|
||||
add_subdirectory(console)
|
||||
@@ -77,8 +44,6 @@ add_subdirectory(error)
|
||||
add_subdirectory(input)
|
||||
add_subdirectory(locale)
|
||||
add_subdirectory(physics)
|
||||
add_subdirectory(scene)
|
||||
add_subdirectory(script)
|
||||
add_subdirectory(system)
|
||||
add_subdirectory(time)
|
||||
add_subdirectory(ui)
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
asset.c
|
||||
assetbatch.c
|
||||
assetfile.c
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(loader)
|
||||
@@ -1,450 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "asset.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
#include "assert/assert.h"
|
||||
#include "engine/engine.h"
|
||||
#include "util/string.h"
|
||||
#include "console/console.h"
|
||||
#include <unistd.h>
|
||||
|
||||
asset_t ASSET;
|
||||
|
||||
errorret_t assetInit(void) {
|
||||
memoryZero(&ASSET, sizeof(asset_t));
|
||||
|
||||
for(size_t i = 0; i < ASSET_LOADING_COUNT_MAX; i++) {
|
||||
threadMutexInit(&ASSET.loading[i].mutex);
|
||||
}
|
||||
|
||||
// assetInitPlatform must either define ASSET.zip or throw an error.
|
||||
errorChain(assetInitPlatform());
|
||||
assertNotNull(ASSET.zip, "Asset zip null without error.");
|
||||
threadInit(&ASSET.loadThread, assetUpdateAsync);
|
||||
threadStart(&ASSET.loadThread);
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
bool_t assetFileExists(const char_t *filename) {
|
||||
assertStrLenMax(filename, ASSET_FILE_NAME_MAX, "Filename too long.");
|
||||
|
||||
zip_int64_t idx = zip_name_locate(ASSET.zip, filename, 0);
|
||||
if(idx < 0) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
assetentry_t * assetGetEntry(
|
||||
const char_t *name,
|
||||
const assetloadertype_t type,
|
||||
assetloaderinput_t *input
|
||||
) {
|
||||
// Is there an existing asset?
|
||||
assetentry_t *entry = ASSET.entries;
|
||||
do {
|
||||
if(entry->type == ASSET_LOADER_TYPE_NULL) {
|
||||
entry++;
|
||||
continue;
|
||||
}
|
||||
if(stringEquals(entry->name, name)) {
|
||||
assertTrue(entry->type == type, "Asset entry type mismatch.");
|
||||
return entry;
|
||||
}
|
||||
entry++;
|
||||
} while(entry < ASSET.entries + ASSET_ENTRY_COUNT_MAX);
|
||||
|
||||
// We did not find one existing, Find first available slot.
|
||||
entry = ASSET.entries;
|
||||
do {
|
||||
if(entry->type != ASSET_LOADER_TYPE_NULL) {
|
||||
entry++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(entry->state == ASSET_ENTRY_STATE_NOT_STARTED) {
|
||||
assetEntryInit(entry, name, type, input);
|
||||
return entry;
|
||||
}
|
||||
entry++;
|
||||
} while(entry < ASSET.entries + ASSET_ENTRY_COUNT_MAX);
|
||||
|
||||
assertUnreachable("No available asset entry slots.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint32_t assetGetEntriesOfType(
|
||||
assetentry_t **outEntries,
|
||||
const assetloadertype_t type
|
||||
) {
|
||||
assertNotNull(outEntries, "Output entries cannot be NULL.");
|
||||
|
||||
uint32_t count = 0;
|
||||
assetentry_t *entry = ASSET.entries;
|
||||
do {
|
||||
if(entry->type == type) {
|
||||
outEntries[count++] = entry;
|
||||
}
|
||||
entry++;
|
||||
} while(entry < ASSET.entries + ASSET_ENTRY_COUNT_MAX);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
errorret_t assetRequireLoaded(assetentry_t *entry) {
|
||||
assertNotNull(entry, "Entry cannot be NULL.");
|
||||
assertTrue(entry->type != ASSET_LOADER_TYPE_NULL, "Invalid loader type.");
|
||||
assertIsMainThread("Currently only works on main thread.");
|
||||
|
||||
if(entry->state == ASSET_ENTRY_STATE_LOADED) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
// Lock to prevent the reaper from collecting the entry mid-spin.
|
||||
assetEntryLock(entry);
|
||||
|
||||
while(entry->state != ASSET_ENTRY_STATE_LOADED) {
|
||||
usleep(1000);
|
||||
errorret_t ret = assetUpdate();
|
||||
if(errorIsNotOk(ret)) {
|
||||
assetEntryUnlock(entry);
|
||||
errorChain(ret);
|
||||
}
|
||||
}
|
||||
|
||||
assetEntryUnlock(entry);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t assetRequireDisposed(assetentry_t *entry) {
|
||||
assertNotNull(entry, "Entry cannot be NULL.");
|
||||
assertTrue(entry->type != ASSET_LOADER_TYPE_NULL, "Invalid loader type.");
|
||||
assertIsMainThread("Currently only works on main thread.");
|
||||
|
||||
if(entry->type == ASSET_LOADER_TYPE_NULL) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
if(
|
||||
entry->state == ASSET_ENTRY_STATE_NOT_STARTED ||
|
||||
entry->state == ASSET_ENTRY_STATE_ERROR
|
||||
) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
assertTrue(
|
||||
entry->refs.count == 0,
|
||||
"Cannot require disposal of an entry with active references."
|
||||
);
|
||||
|
||||
// Lock to prevent the reaper from collecting the entry mid-spin.
|
||||
assetEntryLock(entry);
|
||||
|
||||
while(entry->type != ASSET_LOADER_TYPE_NULL) {
|
||||
usleep(1000);
|
||||
errorret_t ret = assetUpdate();
|
||||
if(errorIsNotOk(ret)) {
|
||||
assetEntryUnlock(entry);
|
||||
errorChain(ret);
|
||||
}
|
||||
}
|
||||
|
||||
assetEntryUnlock(entry);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
assetentry_t * assetLock(
|
||||
const char_t *name,
|
||||
const assetloadertype_t type,
|
||||
assetloaderinput_t *input
|
||||
) {
|
||||
assetentry_t *entry = assetGetEntry(name, type, input);
|
||||
assetEntryLock(entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
void assetUnlock(const char_t *name) {
|
||||
assertNotNull(name, "Name cannot be NULL.");
|
||||
|
||||
assetentry_t *entry = ASSET.entries;
|
||||
do {
|
||||
if(
|
||||
entry->type != ASSET_LOADER_TYPE_NULL &&
|
||||
stringEquals(entry->name, name)
|
||||
) {
|
||||
assetEntryUnlock(entry);
|
||||
return;
|
||||
}
|
||||
entry++;
|
||||
} while(entry < ASSET.entries + ASSET_ENTRY_COUNT_MAX);
|
||||
|
||||
assertUnreachable("Asset entry not found for unlock.");
|
||||
}
|
||||
|
||||
void assetUnlockEntry(assetentry_t *entry) {
|
||||
assertNotNull(entry, "Entry cannot be NULL.");
|
||||
assetEntryUnlock(entry);
|
||||
}
|
||||
|
||||
errorret_t assetUpdate(void) {
|
||||
assertIsMainThread("assetUpdate must be called from the main thread.");
|
||||
|
||||
// Determine how many available loading slots we have.
|
||||
assetloading_t *availableLoading[ASSET_LOADING_COUNT_MAX];
|
||||
uint8_t availableLoadingCount = 0;
|
||||
assetloading_t *loading = ASSET.loading;
|
||||
assetentry_t *entry;
|
||||
|
||||
|
||||
do {
|
||||
// We only care about NULL entry references. Nothing async touches this so
|
||||
// it's fine to use raw here.
|
||||
if(loading->entry != NULL) {
|
||||
loading++;
|
||||
continue;
|
||||
}
|
||||
availableLoading[availableLoadingCount++] = loading;
|
||||
loading++;
|
||||
} while(loading < ASSET.loading + ASSET_LOADING_COUNT_MAX);
|
||||
|
||||
|
||||
// Now we can check for pending asset entries, we can't do anything if there
|
||||
// is no available slots though.
|
||||
if(availableLoadingCount > 0) {
|
||||
entry = ASSET.entries;
|
||||
do {
|
||||
// Is this asset "ready to start loading" ?
|
||||
if(entry->type == ASSET_LOADER_TYPE_NULL) {
|
||||
entry++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// We only care about assets not started.
|
||||
if(entry->state != ASSET_ENTRY_STATE_NOT_STARTED) {
|
||||
entry++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Pop a loading slot for this asset entry.
|
||||
loading = availableLoading[--availableLoadingCount];
|
||||
|
||||
// Start loading this asset.
|
||||
assetEntryStartLoading(entry, loading);
|
||||
entry++;
|
||||
|
||||
// Did we run out of loading slots?
|
||||
if(availableLoadingCount == 0) {
|
||||
break;
|
||||
}
|
||||
} while(entry < ASSET.entries + ASSET_ENTRY_COUNT_MAX);
|
||||
}
|
||||
|
||||
// Now walk over all the loading slots and see what needs to be done.
|
||||
loading = ASSET.loading;
|
||||
do {
|
||||
// Is the loading slot in use? Entry can only be modified synchronously.
|
||||
if(loading->entry == NULL) {
|
||||
loading++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Lock the loading slot. This will prevent any async modifications.
|
||||
threadMutexLock(&loading->mutex);
|
||||
|
||||
// Check the state of the entry.
|
||||
switch(loading->entry->state) {
|
||||
// This thing is pending synchronous loading.
|
||||
case ASSET_ENTRY_STATE_PENDING_SYNC:
|
||||
loading->entry->state = ASSET_ENTRY_STATE_LOADING_SYNC;
|
||||
// Unlock before calling loadSync. The sync loader may re-enter
|
||||
// assetUpdate (e.g. a script loading another asset), and the async
|
||||
// thread never touches LOADING_SYNC entries, so this is safe.
|
||||
threadMutexUnlock(&loading->mutex);
|
||||
|
||||
errorret_t ret = (
|
||||
ASSET_LOADER_CALLBACKS[loading->type].loadSync(loading)
|
||||
);
|
||||
|
||||
// After a sync load, these are the only valid states.
|
||||
assertTrue(
|
||||
loading->entry->state == ASSET_ENTRY_STATE_LOADED ||
|
||||
loading->entry->state == ASSET_ENTRY_STATE_ERROR ||
|
||||
loading->entry->state == ASSET_ENTRY_STATE_PENDING_SYNC ||
|
||||
loading->entry->state == ASSET_ENTRY_STATE_PENDING_ASYNC,
|
||||
"Loader did not set entry state to loaded or error on finished load."
|
||||
);
|
||||
|
||||
if(errorIsNotOk(ret)) {
|
||||
errorCatch(errorPrint(ret));
|
||||
assertTrue(
|
||||
loading->entry->state == ASSET_ENTRY_STATE_ERROR,
|
||||
"Loader did not set entry state to error on failed load."
|
||||
);
|
||||
} else if(loading->entry->state == ASSET_ENTRY_STATE_LOADED) {
|
||||
eventInvoke(&loading->entry->onLoaded, loading->entry);
|
||||
}
|
||||
|
||||
loading++;
|
||||
break;
|
||||
|
||||
case ASSET_ENTRY_STATE_LOADING_SYNC:
|
||||
// A re-entrant assetUpdate call (e.g. from a script loading another
|
||||
// asset) will see this entry mid-sync-load. Skip it.
|
||||
threadMutexUnlock(&loading->mutex);
|
||||
loading++;
|
||||
continue;
|
||||
|
||||
// Done loading, we can just free it up.
|
||||
case ASSET_ENTRY_STATE_LOADED:
|
||||
loading->entry = NULL;
|
||||
threadMutexUnlock(&loading->mutex);
|
||||
loading++;
|
||||
break;
|
||||
|
||||
case ASSET_ENTRY_STATE_ERROR: {
|
||||
assetentry_t *errEntry = loading->entry;
|
||||
loading->entry = NULL;
|
||||
threadMutexUnlock(&loading->mutex);
|
||||
eventInvoke(&errEntry->onError, errEntry);
|
||||
errorThrow("Failed to load asset asynchronously.");
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
threadMutexUnlock(&loading->mutex);
|
||||
loading++;
|
||||
continue;
|
||||
}
|
||||
} while(loading < ASSET.loading + ASSET_LOADING_COUNT_MAX);
|
||||
|
||||
|
||||
// Reap unused entries.
|
||||
entry = ASSET.entries;
|
||||
do {
|
||||
if(entry->state != ASSET_ENTRY_STATE_LOADED) {
|
||||
entry++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(entry->type == ASSET_LOADER_TYPE_NULL) {
|
||||
entry++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(entry->refs.count > 0) {
|
||||
entry++;
|
||||
continue;
|
||||
}
|
||||
|
||||
consolePrint("Reaping asset %s", entry->name);
|
||||
errorChain(assetEntryDispose(entry));
|
||||
entry++;
|
||||
} while(entry < ASSET.entries + ASSET_ENTRY_COUNT_MAX);
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void assetUpdateAsync(thread_t *thread) {
|
||||
assertNotMainThread("assetUpdateAsync must not run on the main thread.");
|
||||
|
||||
while(!threadShouldStop(thread)) {
|
||||
// Walk over each asset
|
||||
assetloading_t *loading;
|
||||
loading = ASSET.loading;
|
||||
|
||||
do {
|
||||
threadMutexLock(&loading->mutex);
|
||||
|
||||
if(loading->entry == NULL) {
|
||||
threadMutexUnlock(&loading->mutex);
|
||||
loading++;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch(loading->entry->state) {
|
||||
case ASSET_ENTRY_STATE_PENDING_ASYNC:
|
||||
loading->entry->state = ASSET_ENTRY_STATE_LOADING_ASYNC;
|
||||
assertNotNull(
|
||||
ASSET_LOADER_CALLBACKS[loading->type].loadAsync,
|
||||
"Loader does not support async loading."
|
||||
);
|
||||
errorret_t ret = (
|
||||
ASSET_LOADER_CALLBACKS[loading->type].loadAsync(loading)
|
||||
);
|
||||
|
||||
if(errorIsNotOk(ret)) {
|
||||
errorCatch(errorPrint(ret));
|
||||
assertTrue(
|
||||
loading->entry->state == ASSET_ENTRY_STATE_ERROR,
|
||||
"Loader did not set entry state to error on failed load."
|
||||
);
|
||||
}
|
||||
|
||||
threadMutexUnlock(&loading->mutex);
|
||||
loading++;
|
||||
break;
|
||||
|
||||
case ASSET_ENTRY_STATE_LOADING_ASYNC:
|
||||
assertUnreachable(
|
||||
"Entry is in a pending async state still?"
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
threadMutexUnlock(&loading->mutex);
|
||||
loading++;
|
||||
continue;
|
||||
}
|
||||
} while(loading < ASSET.loading + ASSET_LOADING_COUNT_MAX);
|
||||
|
||||
if(threadShouldStop(thread)) break;
|
||||
usleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
errorret_t assetDispose(void) {
|
||||
assertIsMainThread("Must be called from the main thread.");
|
||||
threadStop(&ASSET.loadThread);
|
||||
|
||||
// Free any script read-buffers left behind by an in-flight async load
|
||||
// that was interrupted before the sync eval phase ran.
|
||||
for(size_t i = 0; i < ASSET_LOADING_COUNT_MAX; i++) {
|
||||
assetloading_t *loading = &ASSET.loading[i];
|
||||
if(
|
||||
loading->entry != NULL &&
|
||||
loading->type == ASSET_LOADER_TYPE_SCRIPT &&
|
||||
loading->loading.script.buffer != NULL
|
||||
) {
|
||||
memoryFree(loading->loading.script.buffer);
|
||||
loading->loading.script.buffer = NULL;
|
||||
}
|
||||
threadMutexDispose(&loading->mutex);
|
||||
}
|
||||
|
||||
// Dispose every non-null entry so type-specific dispose callbacks
|
||||
// (e.g. assetScriptDispose freeing jerry values) run before the
|
||||
// scripting engine is torn down.
|
||||
assetentry_t *entry = ASSET.entries;
|
||||
do {
|
||||
if(entry->type != ASSET_LOADER_TYPE_NULL) {
|
||||
errorChain(assetEntryDispose(entry));
|
||||
}
|
||||
entry++;
|
||||
} while(entry < ASSET.entries + ASSET_ENTRY_COUNT_MAX);
|
||||
|
||||
// Cleanup zip file.
|
||||
if(ASSET.zip != NULL) {
|
||||
if(zip_close(ASSET.zip) != 0) {
|
||||
errorThrow("Failed to close asset zip archive.");
|
||||
}
|
||||
ASSET.zip = NULL;
|
||||
}
|
||||
|
||||
errorChain(assetDisposePlatform());
|
||||
errorOk();
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "asset/assetplatform.h"
|
||||
#include "assetfile.h"
|
||||
#include "thread/thread.h"
|
||||
#include "asset/loader/assetentry.h"
|
||||
#include "asset/loader/assetloading.h"
|
||||
|
||||
#ifndef assetInitPlatform
|
||||
#error "Platform must define assetInitPlatform function."
|
||||
#endif
|
||||
#ifndef assetDisposePlatform
|
||||
#error "Platform must define assetDisposePlatform function."
|
||||
#endif
|
||||
|
||||
#define ASSET_FILE_NAME "dusk.dsk"
|
||||
#define ASSET_HEADER_SIZE 3
|
||||
|
||||
#define ASSET_LOADING_COUNT_MAX 4
|
||||
#define ASSET_ENTRY_COUNT_MAX 128
|
||||
|
||||
typedef struct asset_s {
|
||||
zip_t *zip;
|
||||
assetplatform_t platform;
|
||||
|
||||
// Background loading thread.
|
||||
thread_t loadThread;
|
||||
|
||||
// Assets that are mid loading.
|
||||
assetloading_t loading[ASSET_LOADING_COUNT_MAX];
|
||||
assetentry_t entries[ASSET_ENTRY_COUNT_MAX];
|
||||
} asset_t;
|
||||
|
||||
extern asset_t ASSET;
|
||||
|
||||
/**
|
||||
* Initializes the asset system.
|
||||
*
|
||||
* @return An error code if the asset system could not be initialized properly.
|
||||
*/
|
||||
errorret_t assetInit(void);
|
||||
|
||||
/**
|
||||
* Checks if an asset file exists.
|
||||
*
|
||||
* @param filename The filename of the asset to check.
|
||||
* @return true if the asset file exists, false otherwise.
|
||||
*/
|
||||
bool_t assetFileExists(const char_t *filename);
|
||||
|
||||
/**
|
||||
* Gets, or creates, a new asset entry. Internal - prefer assetLock.
|
||||
*
|
||||
* @param name Filename of the asset.
|
||||
* @param type Type of the asset.
|
||||
* @param input Loader-specific parameters.
|
||||
*/
|
||||
assetentry_t * assetGetEntry(
|
||||
const char_t *name,
|
||||
const assetloadertype_t type,
|
||||
assetloaderinput_t *input
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets all asset entries of a given type.
|
||||
*
|
||||
* @param outEntries Output array to write the entries to.
|
||||
* @param type Type of the asset entries to get.
|
||||
* @return The number of entries written to outEntries.
|
||||
*/
|
||||
uint32_t assetGetEntriesOfType(
|
||||
assetentry_t **outEntries,
|
||||
const assetloadertype_t type
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets, creates, and locks an asset entry. The asset will begin loading on
|
||||
* the next assetUpdate. Call assetUnlock when done to allow the entry to be
|
||||
* reclaimed.
|
||||
*
|
||||
* @param name Filename of the asset.
|
||||
* @param type Type of the asset.
|
||||
* @param input Loader-specific parameters.
|
||||
* @return The locked asset entry.
|
||||
*/
|
||||
assetentry_t * assetLock(
|
||||
const char_t *name,
|
||||
const assetloadertype_t type,
|
||||
assetloaderinput_t *input
|
||||
);
|
||||
|
||||
/**
|
||||
* Releases a lock on an asset entry by name. When all locks are released the
|
||||
* entry will be reclaimed at the start of the next assetUpdate.
|
||||
*
|
||||
* @param name Filename of the asset to unlock.
|
||||
*/
|
||||
void assetUnlock(const char_t *name);
|
||||
|
||||
/**
|
||||
* Releases a lock on an asset entry by pointer. When all locks are released
|
||||
* the entry will be reclaimed at the start of the next assetUpdate.
|
||||
*
|
||||
* @param entry The asset entry to unlock.
|
||||
*/
|
||||
void assetUnlockEntry(assetentry_t *entry);
|
||||
|
||||
/**
|
||||
* Requires an asset entry to be loaded. This will block until the asset entry
|
||||
* is fully loaded.
|
||||
*
|
||||
* @param entry The asset entry to require.
|
||||
* @return An error code if the asset entry could not be loaded properly.
|
||||
*/
|
||||
errorret_t assetRequireLoaded(assetentry_t *entry);
|
||||
|
||||
/**
|
||||
* Requires an asset entry to be disposed. This will block until the asset entry
|
||||
* is fully disposed.
|
||||
*
|
||||
* @param entry The asset entry to require disposal of.
|
||||
* @return An error code if the asset entry could not be disposed properly.
|
||||
*/
|
||||
errorret_t assetRequireDisposed(assetentry_t *entry);
|
||||
|
||||
/**
|
||||
* Updates the asset system.
|
||||
*
|
||||
* @return An error code if the asset system could not be updated properly.
|
||||
*/
|
||||
errorret_t assetUpdate(void);
|
||||
|
||||
/**
|
||||
* Starts the background asset loading thread. The thread runs assetUpdate
|
||||
* in a loop with a short sleep until stopped.
|
||||
*
|
||||
* @param thread The thread runner.
|
||||
*/
|
||||
void assetUpdateAsync(thread_t *thread);
|
||||
|
||||
/**
|
||||
* Disposes/cleans up the asset system.
|
||||
*
|
||||
* @return An error code if the asset system could not be disposed properly.
|
||||
*/
|
||||
errorret_t assetDispose(void);
|
||||
@@ -1,165 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "assetbatch.h"
|
||||
#include "asset.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include <unistd.h>
|
||||
|
||||
void assetBatchInit(
|
||||
assetbatch_t *batch,
|
||||
const uint16_t count,
|
||||
const assetbatchdesc_t *descs
|
||||
) {
|
||||
assertNotNull(batch, "Batch cannot be NULL.");
|
||||
assertNotNull(descs, "Descs cannot be NULL.");
|
||||
assertTrue(count > 0, "Count must be greater than 0.");
|
||||
assertTrue(
|
||||
count <= ASSET_BATCH_COUNT_MAX, "Count exceeds ASSET_BATCH_COUNT_MAX."
|
||||
);
|
||||
|
||||
memoryZero(batch, sizeof(assetbatch_t));
|
||||
batch->count = count;
|
||||
|
||||
eventInit(
|
||||
&batch->onLoaded,
|
||||
batch->onLoadedCallbacks, batch->onLoadedUsers, ASSET_BATCH_EVENT_MAX
|
||||
);
|
||||
eventInit(
|
||||
&batch->onEntryLoaded,
|
||||
batch->onEntryLoadedCallbacks,
|
||||
batch->onEntryLoadedUsers,
|
||||
ASSET_BATCH_EVENT_MAX
|
||||
);
|
||||
eventInit(
|
||||
&batch->onError,
|
||||
batch->onErrorCallbacks, batch->onErrorUsers, ASSET_BATCH_EVENT_MAX
|
||||
);
|
||||
eventInit(
|
||||
&batch->onEntryError,
|
||||
batch->onEntryErrorCallbacks,
|
||||
batch->onEntryErrorUsers,
|
||||
ASSET_BATCH_EVENT_MAX
|
||||
);
|
||||
|
||||
for(uint16_t i = 0; i < count; i++) {
|
||||
batch->inputs[i] = descs[i].input;
|
||||
batch->entries[i] = assetLock(
|
||||
descs[i].path, descs[i].type, &batch->inputs[i]
|
||||
);
|
||||
|
||||
if(batch->entries[i]->state == ASSET_ENTRY_STATE_LOADED) {
|
||||
// Already loaded (cached) - count it now, no subscription needed.
|
||||
batch->loadedCount++;
|
||||
} else if(batch->entries[i]->state == ASSET_ENTRY_STATE_ERROR) {
|
||||
batch->errorCount++;
|
||||
} else {
|
||||
eventSubscribe(
|
||||
&batch->entries[i]->onLoaded, assetBatchEntryOnLoadedCb, batch
|
||||
);
|
||||
eventSubscribe(
|
||||
&batch->entries[i]->onError, assetBatchEntryOnErrorCb, batch
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void assetBatchLock(assetbatch_t *batch) {
|
||||
assertNotNull(batch, "Batch cannot be NULL.");
|
||||
for(uint16_t i = 0; i < batch->count; i++) {
|
||||
assetEntryLock(batch->entries[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void assetBatchUnlock(assetbatch_t *batch) {
|
||||
assertNotNull(batch, "Batch cannot be NULL.");
|
||||
for(uint16_t i = 0; i < batch->count; i++) {
|
||||
assetEntryUnlock(batch->entries[i]);
|
||||
}
|
||||
}
|
||||
|
||||
bool_t assetBatchIsLoaded(const assetbatch_t *batch) {
|
||||
assertNotNull(batch, "Batch cannot be NULL.");
|
||||
for(uint16_t i = 0; i < batch->count; i++) {
|
||||
if(batch->entries[i]->state != ASSET_ENTRY_STATE_LOADED) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool_t assetBatchHasError(const assetbatch_t *batch) {
|
||||
assertNotNull(batch, "Batch cannot be NULL.");
|
||||
for(uint16_t i = 0; i < batch->count; i++) {
|
||||
if(batch->entries[i]->state == ASSET_ENTRY_STATE_ERROR) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
errorret_t assetBatchRequireLoaded(assetbatch_t *batch) {
|
||||
assertNotNull(batch, "Batch cannot be NULL.");
|
||||
|
||||
bool_t allDone;
|
||||
do {
|
||||
allDone = true;
|
||||
for(uint16_t i = 0; i < batch->count; i++) {
|
||||
const assetentrystate_t state = batch->entries[i]->state;
|
||||
if(state == ASSET_ENTRY_STATE_ERROR) {
|
||||
errorThrow("Asset '%s' failed to load.", batch->entries[i]->name);
|
||||
}
|
||||
if(state != ASSET_ENTRY_STATE_LOADED) {
|
||||
allDone = false;
|
||||
}
|
||||
}
|
||||
if(!allDone) {
|
||||
usleep(1000);
|
||||
errorChain(assetUpdate());
|
||||
}
|
||||
} while(!allDone);
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void assetBatchDispose(assetbatch_t *batch) {
|
||||
assertNotNull(batch, "Batch cannot be NULL.");
|
||||
for(uint16_t i = 0; i < batch->count; i++) {
|
||||
if(batch->entries[i]) {
|
||||
// Unsubscribe while we still hold a lock so the entry is live.
|
||||
eventUnsubscribe(&batch->entries[i]->onLoaded, assetBatchEntryOnLoadedCb);
|
||||
eventUnsubscribe(&batch->entries[i]->onError, assetBatchEntryOnErrorCb);
|
||||
assetUnlockEntry(batch->entries[i]);
|
||||
}
|
||||
}
|
||||
memoryZero(batch, sizeof(assetbatch_t));
|
||||
}
|
||||
|
||||
void assetBatchEntryOnLoadedCb(void *params, void *user) {
|
||||
assetentry_t *entry = (assetentry_t *)params;
|
||||
assetbatch_t *batch = (assetbatch_t *)user;
|
||||
|
||||
batch->loadedCount++;
|
||||
eventInvoke(&batch->onEntryLoaded, entry);
|
||||
|
||||
if((uint16_t)(batch->loadedCount + batch->errorCount) >= batch->count) {
|
||||
if(batch->errorCount == 0) {
|
||||
eventInvoke(&batch->onLoaded, batch);
|
||||
} else {
|
||||
eventInvoke(&batch->onError, batch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void assetBatchEntryOnErrorCb(void *params, void *user) {
|
||||
assetentry_t *entry = (assetentry_t *)params;
|
||||
assetbatch_t *batch = (assetbatch_t *)user;
|
||||
|
||||
batch->errorCount++;
|
||||
eventInvoke(&batch->onEntryError, entry);
|
||||
|
||||
if((uint16_t)(batch->loadedCount + batch->errorCount) >= batch->count) {
|
||||
eventInvoke(&batch->onError, batch);
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "asset/loader/assetentry.h"
|
||||
#include "asset/loader/assetloader.h"
|
||||
#include "event/event.h"
|
||||
|
||||
#define ASSET_BATCH_COUNT_MAX 64
|
||||
#define ASSET_BATCH_EVENT_MAX 4
|
||||
|
||||
typedef struct {
|
||||
const char_t *path;
|
||||
assetloadertype_t type;
|
||||
assetloaderinput_t input;
|
||||
} assetbatchdesc_t;
|
||||
|
||||
typedef struct {
|
||||
assetentry_t *entries[ASSET_BATCH_COUNT_MAX];
|
||||
assetloaderinput_t inputs[ASSET_BATCH_COUNT_MAX];
|
||||
uint16_t count;
|
||||
uint16_t loadedCount;
|
||||
uint16_t errorCount;
|
||||
|
||||
/** Fires once when every entry loaded. params = assetbatch_t * */
|
||||
event_t onLoaded;
|
||||
eventcallback_t onLoadedCallbacks[ASSET_BATCH_EVENT_MAX];
|
||||
void *onLoadedUsers[ASSET_BATCH_EVENT_MAX];
|
||||
|
||||
/** Fires each time a single entry loads. params = assetentry_t * */
|
||||
event_t onEntryLoaded;
|
||||
eventcallback_t onEntryLoadedCallbacks[ASSET_BATCH_EVENT_MAX];
|
||||
void *onEntryLoadedUsers[ASSET_BATCH_EVENT_MAX];
|
||||
|
||||
/** Fires when all entries finish (any with errors). params: assetbatch_t * */
|
||||
event_t onError;
|
||||
eventcallback_t onErrorCallbacks[ASSET_BATCH_EVENT_MAX];
|
||||
void *onErrorUsers[ASSET_BATCH_EVENT_MAX];
|
||||
|
||||
/** Fires each time a single entry errors. params = assetentry_t * */
|
||||
event_t onEntryError;
|
||||
eventcallback_t onEntryErrorCallbacks[ASSET_BATCH_EVENT_MAX];
|
||||
void *onEntryErrorUsers[ASSET_BATCH_EVENT_MAX];
|
||||
} assetbatch_t;
|
||||
|
||||
/**
|
||||
* Initialises the batch from an array of descriptors. Each entry is locked
|
||||
* and queued for loading immediately.
|
||||
*
|
||||
* @param batch Batch to initialise.
|
||||
* @param descs Array of entry descriptors (need not outlive this call).
|
||||
* @param count Number of descriptors (must be <= ASSET_BATCH_COUNT_MAX).
|
||||
*/
|
||||
void assetBatchInit(
|
||||
assetbatch_t *batch,
|
||||
uint16_t count,
|
||||
const assetbatchdesc_t *descs
|
||||
);
|
||||
|
||||
/**
|
||||
* Acquires one additional lock on every entry in the batch.
|
||||
*
|
||||
* @param batch Batch to lock.
|
||||
*/
|
||||
void assetBatchLock(assetbatch_t *batch);
|
||||
|
||||
/**
|
||||
* Releases one lock from every entry in the batch. When an entry's lock
|
||||
* count reaches zero it will be reaped on the next assetUpdate.
|
||||
*
|
||||
* @param batch Batch to unlock.
|
||||
*/
|
||||
void assetBatchUnlock(assetbatch_t *batch);
|
||||
|
||||
/**
|
||||
* Returns true if every entry in the batch has finished loading.
|
||||
*
|
||||
* @param batch Batch to query.
|
||||
*/
|
||||
bool_t assetBatchIsLoaded(const assetbatch_t *batch);
|
||||
|
||||
/**
|
||||
* Returns true if any entry in the batch is in an error state.
|
||||
*
|
||||
* @param batch Batch to query.
|
||||
*/
|
||||
bool_t assetBatchHasError(const assetbatch_t *batch);
|
||||
|
||||
/**
|
||||
* Blocks until every entry is loaded. Returns an error if any entry fails.
|
||||
*
|
||||
* @param batch Batch to wait on.
|
||||
*/
|
||||
errorret_t assetBatchRequireLoaded(assetbatch_t *batch);
|
||||
|
||||
/**
|
||||
* Releases the batch's lock on every entry and clears the batch. After this
|
||||
* call the batch struct may be reused with assetBatchInit.
|
||||
*
|
||||
* @param batch Batch to dispose.
|
||||
*/
|
||||
void assetBatchDispose(assetbatch_t *batch);
|
||||
|
||||
/**
|
||||
* Event trampoline invoked when a batch entry finishes loading.
|
||||
* Increments the loaded counter and fires batch-level events.
|
||||
*
|
||||
* @param params The loaded assetentry_t pointer.
|
||||
* @param user The owning assetbatch_t pointer.
|
||||
*/
|
||||
void assetBatchEntryOnLoadedCb(void *params, void *user);
|
||||
|
||||
/**
|
||||
* Event trampoline invoked when a batch entry fails to load.
|
||||
* Increments the error counter and fires batch-level events.
|
||||
*
|
||||
* @param params The errored assetentry_t pointer.
|
||||
* @param user The owning assetbatch_t pointer.
|
||||
*/
|
||||
void assetBatchEntryOnErrorCb(void *params, void *user);
|
||||
@@ -1,380 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "asset/asset.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/math.h"
|
||||
|
||||
errorret_t assetFileInit(
|
||||
assetfile_t *file,
|
||||
const char_t *filename,
|
||||
void *params,
|
||||
void *output
|
||||
) {
|
||||
assertNotNull(file, "Asset file cannot be NULL.");
|
||||
assertStrLenMax(filename, ASSET_FILE_NAME_MAX, "Filename too long.");
|
||||
|
||||
memoryZero(file, sizeof(assetfile_t));
|
||||
memoryCopy(file->filename, filename, ASSET_FILE_NAME_MAX);
|
||||
file->params = params;
|
||||
file->output = output;
|
||||
|
||||
// Stat the file
|
||||
zip_stat_init(&file->stat);
|
||||
if(!zip_stat(ASSET.zip, filename, 0, &file->stat) == 0) {
|
||||
errorThrow("Failed to stat asset file: %s", filename);
|
||||
}
|
||||
|
||||
// Minimum file size.
|
||||
file->size = (zip_int64_t)file->stat.size;
|
||||
if(file->size <= 0) {
|
||||
errorThrow("Invalid asset file size: %s", filename);
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t assetFileRewind(assetfile_t *file) {
|
||||
assertNotNull(file, "Asset file cannot be NULL.");
|
||||
assertNotNull(file->zipFile, "Asset file must be opened before rewinding.");
|
||||
|
||||
if(file->position == 0) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorChain(assetFileClose(file));
|
||||
errorChain(assetFileOpen(file));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t assetFileOpen(assetfile_t *file) {
|
||||
assertNotNull(file, "Asset file cannot be NULL.");
|
||||
assertNotNull(file->filename, "Asset file filename cannot be NULL.");
|
||||
assertNotNull(ASSET.zip, "Asset zip cannot be NULL.");
|
||||
assertNull(file->zipFile, "Asset file already open.");
|
||||
|
||||
file->zipFile = zip_fopen(ASSET.zip, file->filename, 0);
|
||||
if(file->zipFile == NULL) {
|
||||
errorThrow("Failed to open asset file: %s", file->filename);
|
||||
}
|
||||
file->position = 0;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t assetFileRead(
|
||||
assetfile_t *file,
|
||||
void *buffer,
|
||||
const size_t bufferSize
|
||||
) {
|
||||
assertNotNull(file, "Asset file cannot be NULL.");
|
||||
assertNotNull(file->zipFile, "Asset file must be opened before reading.");
|
||||
|
||||
if(buffer == NULL) {
|
||||
size_t bytesRemaining = bufferSize;
|
||||
uint8_t tempBuffer[256];
|
||||
while(bytesRemaining > 0) {
|
||||
size_t chunkSize = mathMin(bytesRemaining, sizeof(tempBuffer));
|
||||
errorChain(assetFileRead(file, tempBuffer, chunkSize));
|
||||
file->position += chunkSize;
|
||||
bytesRemaining -= chunkSize;
|
||||
}
|
||||
file->lastRead = bufferSize;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
// I assume zip_fread takes buffer NULL for skipping?
|
||||
zip_int64_t bytesRead = zip_fread(file->zipFile, buffer, bufferSize);
|
||||
if(bytesRead < 0) {
|
||||
errorThrow("Failed to read from asset file: %s", file->filename);
|
||||
}
|
||||
file->position += bytesRead;
|
||||
file->lastRead = bytesRead;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t assetFileClose(assetfile_t *file) {
|
||||
assertNotNull(file, "Asset file cannot be NULL.");
|
||||
assertNotNull(file->zipFile, "Asset file must be opened before closing.");
|
||||
|
||||
if(zip_fclose(file->zipFile) != 0) {
|
||||
errorThrow("Failed to close asset file: %s", file->filename);
|
||||
}
|
||||
file->zipFile = NULL;
|
||||
file->position = 0;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t assetFileDispose(assetfile_t *file) {
|
||||
if(file->zipFile != NULL) {
|
||||
errorChain(assetFileClose(file));
|
||||
}
|
||||
memoryZero(file, sizeof(assetfile_t));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t assetFileReadEntire(
|
||||
assetfile_t *file,
|
||||
uint8_t **outBuffer,
|
||||
size_t *outSize
|
||||
) {
|
||||
assertNotNull(file, "Asset file cannot be NULL.");
|
||||
assertNotNull(outBuffer, "outBuffer cannot be NULL.");
|
||||
assertNotNull(outSize, "outSize cannot be NULL.");
|
||||
assertTrue(
|
||||
file->size > 0,
|
||||
"Asset file has no size; call assetFileInit first."
|
||||
);
|
||||
|
||||
// File should be closed currently.
|
||||
assertNull(file->zipFile, "Asset file must be closed before reading entire.");
|
||||
|
||||
// Open file
|
||||
errorret_t ret = assetFileOpen(file);
|
||||
if(errorIsNotOk(ret)) {
|
||||
errorChain(ret);
|
||||
}
|
||||
|
||||
// Set output.
|
||||
size_t size = (size_t)file->size;
|
||||
uint8_t *buffer = (uint8_t *)memoryAllocate(size);
|
||||
|
||||
// Read entire file.
|
||||
ret = assetFileRead(file, buffer, size);
|
||||
if(errorIsNotOk(ret)) {
|
||||
memoryFree(buffer);
|
||||
errorChain(ret);
|
||||
}
|
||||
|
||||
// Close the file.
|
||||
ret = assetFileClose(file);
|
||||
if(errorIsNotOk(ret)) {
|
||||
memoryFree(buffer);
|
||||
errorChain(ret);
|
||||
}
|
||||
|
||||
*outBuffer = buffer;
|
||||
*outSize = size;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
// Line Reader;
|
||||
void assetFileLineReaderInit(
|
||||
assetfilelinereader_t *reader,
|
||||
assetfile_t *file,
|
||||
uint8_t *readBuffer,
|
||||
const size_t readBufferSize,
|
||||
uint8_t *outBuffer,
|
||||
const size_t outBufferSize
|
||||
) {
|
||||
assertNotNull(reader, "Line reader cannot be NULL.");
|
||||
assertNotNull(file, " File cannot be NULL.");
|
||||
assertNotNull(readBuffer, "Read buffer cannot be NULL.");
|
||||
assertNotNull(outBuffer, "Output buffer cannot be NULL.");
|
||||
assertTrue(readBufferSize > 0, "Read buffer size must be greater than 0.");
|
||||
assertTrue(outBufferSize > 0, "Output buffer size must be greater than 0.");
|
||||
|
||||
memoryZero(reader, sizeof(assetfilelinereader_t));
|
||||
|
||||
reader->file = file;
|
||||
reader->readBuffer = readBuffer;
|
||||
reader->readBufferSize = readBufferSize;
|
||||
reader->outBuffer = outBuffer;
|
||||
reader->outBufferSize = outBufferSize;
|
||||
}
|
||||
|
||||
size_t assetFileLineReaderUnreadBytes(const assetfilelinereader_t *reader) {
|
||||
assertNotNull(reader, "Reader cannot be NULL.");
|
||||
assertTrue(reader->bufferEnd >= reader->bufferStart, "Invalid buffer state.");
|
||||
return reader->bufferEnd - reader->bufferStart;
|
||||
}
|
||||
|
||||
const uint8_t *assetFileLineReaderUnreadPtr(
|
||||
const assetfilelinereader_t *reader
|
||||
) {
|
||||
assertNotNull(reader, "Reader cannot be NULL.");
|
||||
assertNotNull(reader->readBuffer, "Read buffer cannot be NULL.");
|
||||
return reader->readBuffer + reader->bufferStart;
|
||||
}
|
||||
|
||||
static errorret_t assetFileLineReaderAppend(
|
||||
assetfilelinereader_t *reader,
|
||||
const uint8_t *src,
|
||||
size_t srcLength
|
||||
) {
|
||||
assertNotNull(reader, "Reader cannot be NULL.");
|
||||
assertNotNull(reader->outBuffer, "Out buffer cannot be NULL.");
|
||||
|
||||
if(srcLength == 0) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
/* reserve room for optional NUL terminator */
|
||||
if(reader->lineLength + srcLength >= reader->outBufferSize) {
|
||||
errorThrow("Line length exceeds output buffer size.");
|
||||
}
|
||||
|
||||
memoryCopy(reader->outBuffer + reader->lineLength, src, srcLength);
|
||||
reader->lineLength += srcLength;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
static void assetFileLineReaderTerminate(assetfilelinereader_t *reader) {
|
||||
assertNotNull(reader, "Reader cannot be NULL.");
|
||||
assertNotNull(reader->outBuffer, "Out buffer cannot be NULL.");
|
||||
assertTrue(
|
||||
reader->lineLength < reader->outBufferSize,
|
||||
"Line length exceeds out buffer."
|
||||
);
|
||||
reader->outBuffer[reader->lineLength] = '\0';
|
||||
}
|
||||
|
||||
static ssize_t assetFileLineReaderFindNewline(
|
||||
const assetfilelinereader_t *reader
|
||||
) {
|
||||
size_t i;
|
||||
|
||||
assertNotNull(reader, "Reader cannot be NULL.");
|
||||
assertNotNull(reader->readBuffer, "Read buffer cannot be NULL.");
|
||||
|
||||
for(i = reader->bufferStart; i < reader->bufferEnd; ++i) {
|
||||
if(reader->readBuffer[i] == '\n') {
|
||||
return (ssize_t)i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
errorret_t assetFileLineReaderFill(assetfilelinereader_t *reader) {
|
||||
|
||||
assertNotNull(reader, "Reader cannot be NULL.");
|
||||
assertNotNull(reader->file, "File cannot be NULL.");
|
||||
assertNotNull(reader->readBuffer, "Read buffer cannot be NULL.");
|
||||
|
||||
if(reader->eof) errorOk();
|
||||
|
||||
errorret_t ret;
|
||||
|
||||
size_t unreadBytes = assetFileLineReaderUnreadBytes(reader);
|
||||
|
||||
/* If buffer is fully consumed, refill from start. */
|
||||
if(unreadBytes == 0) {
|
||||
reader->bufferStart = 0;
|
||||
reader->bufferEnd = 0;
|
||||
|
||||
errorChain(assetFileRead(
|
||||
reader->file,
|
||||
reader->readBuffer,
|
||||
reader->readBufferSize
|
||||
));
|
||||
|
||||
if(reader->file->lastRead == 0) {
|
||||
reader->eof = true;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
reader->bufferStart = 0;
|
||||
reader->bufferEnd = reader->file->lastRead;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
/*
|
||||
* There are unread bytes left but no newline in them.
|
||||
* If bufferStart > 0, slide unread bytes to front so we can read more.
|
||||
* This only happens when necessary to make space.
|
||||
*/
|
||||
if(reader->bufferEnd == reader->readBufferSize) {
|
||||
if(reader->bufferStart == 0) {
|
||||
/*
|
||||
* Entire read buffer is unread and contains no newline.
|
||||
* Caller must have a large enough outBuffer to accumulate across fills,
|
||||
* so we consume these bytes into outBuffer before refilling.
|
||||
*/
|
||||
errorOk();
|
||||
}
|
||||
|
||||
memoryMove(
|
||||
reader->readBuffer,
|
||||
reader->readBuffer + reader->bufferStart,
|
||||
unreadBytes
|
||||
);
|
||||
reader->bufferStart = 0;
|
||||
reader->bufferEnd = unreadBytes;
|
||||
}
|
||||
|
||||
errorChain(assetFileRead(
|
||||
reader->file,
|
||||
reader->readBuffer + reader->bufferEnd,
|
||||
reader->readBufferSize - reader->bufferEnd
|
||||
));
|
||||
|
||||
if(reader->file->lastRead == 0) {
|
||||
reader->eof = true;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
reader->bufferEnd += reader->file->lastRead;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t assetFileLineReaderNext(assetfilelinereader_t *reader) {
|
||||
assertNotNull(reader, "Reader cannot be NULL.");
|
||||
assertNotNull(reader->file, "File cannot be NULL.");
|
||||
assertNotNull(reader->readBuffer, "Read buffer cannot be NULL.");
|
||||
assertNotNull(reader->outBuffer, "Out buffer cannot be NULL.");
|
||||
|
||||
reader->lineLength = 0;
|
||||
|
||||
for(;;) {
|
||||
ssize_t newlineIndex = assetFileLineReaderFindNewline(reader);
|
||||
|
||||
if(newlineIndex >= 0) {
|
||||
size_t chunkLength = (size_t)newlineIndex - reader->bufferStart;
|
||||
errorret_t ret;
|
||||
|
||||
/* strip CR in CRLF */
|
||||
if(
|
||||
chunkLength > 0 &&
|
||||
reader->readBuffer[(size_t)newlineIndex - 1] == '\r'
|
||||
) {
|
||||
chunkLength--;
|
||||
}
|
||||
|
||||
errorChain(assetFileLineReaderAppend(
|
||||
reader,
|
||||
reader->readBuffer + reader->bufferStart,
|
||||
chunkLength
|
||||
));
|
||||
|
||||
reader->bufferStart = (size_t)newlineIndex + 1;
|
||||
assetFileLineReaderTerminate(reader);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
if(assetFileLineReaderUnreadBytes(reader) > 0) {
|
||||
errorChain(assetFileLineReaderAppend(
|
||||
reader,
|
||||
assetFileLineReaderUnreadPtr(reader),
|
||||
assetFileLineReaderUnreadBytes(reader)
|
||||
));
|
||||
|
||||
reader->bufferStart = reader->bufferEnd;
|
||||
}
|
||||
|
||||
if(reader->eof) {
|
||||
if(reader->lineLength > 0) {
|
||||
assetFileLineReaderTerminate(reader);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorThrow("End of file reached.");
|
||||
}
|
||||
|
||||
errorChain(assetFileLineReaderFill(reader));
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include <zip.h>
|
||||
|
||||
#define ASSET_FILE_NAME_MAX 48
|
||||
|
||||
typedef struct assetfile_s assetfile_t;
|
||||
|
||||
typedef errorret_t (*assetfileloader_t)(assetfile_t *file);
|
||||
|
||||
// Describes a file not yet loaded.
|
||||
typedef struct assetfile_s {
|
||||
char_t filename[ASSET_FILE_NAME_MAX];
|
||||
void *params;
|
||||
void *output;
|
||||
|
||||
zip_stat_t stat;
|
||||
zip_int64_t size;
|
||||
zip_int64_t position;
|
||||
zip_int64_t lastRead;
|
||||
zip_file_t *zipFile;
|
||||
} assetfile_t;
|
||||
|
||||
/**
|
||||
* Initializes the asset file structure in preparation for loading. This will
|
||||
* stat the file but not open the handle.
|
||||
*
|
||||
* @param file The asset file structure to initialize.
|
||||
* @param filename The name of the asset file to load.
|
||||
* @param params Optional loader params.
|
||||
* @param output Output pointer for the loader.
|
||||
* @return Error indicating success or failure.
|
||||
*/
|
||||
errorret_t assetFileInit(
|
||||
assetfile_t *file,
|
||||
const char_t *filename,
|
||||
void *params,
|
||||
void *output
|
||||
);
|
||||
|
||||
/**
|
||||
* Opens the asset file for reading. After opening the loader is responsible
|
||||
* for closing the file.
|
||||
*
|
||||
* @param file The asset file to open.
|
||||
* @return An error code if the file could not be opened.
|
||||
*/
|
||||
errorret_t assetFileOpen(assetfile_t *file);
|
||||
|
||||
/**
|
||||
* Rewind the file to the initial position.
|
||||
*
|
||||
* @param file The asset file to rewind.
|
||||
*/
|
||||
errorret_t assetFileRewind(assetfile_t *file);
|
||||
|
||||
/**
|
||||
* Read bytes from the asset file. Assumes the file has already been opened
|
||||
* prior to trying to read anything.
|
||||
*
|
||||
* @param file File to read from.
|
||||
* @param buffer Buffer to read the file data into., or NULL to skip bytes.
|
||||
* @param size Size of the buffer to read into.
|
||||
*/
|
||||
errorret_t assetFileRead(
|
||||
assetfile_t *file,
|
||||
void *buffer,
|
||||
const size_t bufferSize
|
||||
);
|
||||
|
||||
/**
|
||||
* Closes the asset file and releases any resources associated with it.
|
||||
*
|
||||
* @param file The asset file to close.
|
||||
* @return An error code if the file could not be closed properly.
|
||||
*/
|
||||
errorret_t assetFileClose(assetfile_t *file);
|
||||
|
||||
/**
|
||||
* Disposes the asset file structure, closing any open handles and zeroing
|
||||
* out the structure.
|
||||
*
|
||||
* @param file The asset file to dispose.
|
||||
* @return An error code if the file could not be disposed properly.
|
||||
*/
|
||||
errorret_t assetFileDispose(assetfile_t *file);
|
||||
|
||||
/**
|
||||
* Reads the entire contents of the asset file into a newly allocated buffer.
|
||||
* The caller is responsible for freeing the buffer with memoryFree.
|
||||
*
|
||||
* @param file The asset file to read. Must be initialized but not open.
|
||||
* @param outBuffer Receives a pointer to the allocated buffer.
|
||||
* @param outSize Receives the number of bytes written to the buffer.
|
||||
* @return An error code if the file could not be read.
|
||||
*/
|
||||
errorret_t assetFileReadEntire(
|
||||
assetfile_t *file,
|
||||
uint8_t **outBuffer,
|
||||
size_t *outSize
|
||||
);
|
||||
|
||||
typedef struct {
|
||||
assetfile_t *file;
|
||||
uint8_t *readBuffer;
|
||||
size_t readBufferSize;
|
||||
uint8_t *outBuffer;
|
||||
size_t outBufferSize;
|
||||
|
||||
// A
|
||||
size_t bufferStart;
|
||||
size_t bufferEnd;
|
||||
bool_t eof;//?
|
||||
|
||||
// Updated each reach:
|
||||
size_t lineLength;
|
||||
} assetfilelinereader_t;
|
||||
|
||||
/**
|
||||
* Initializes a line reader for the given asset file. The line reader will read
|
||||
* lines from the file into the provided line buffer, using the provided buffer
|
||||
* for reading chunks of the file.
|
||||
*
|
||||
* @param file The asset file to read from. Must already be opened.
|
||||
* @param readBuffer Buffer to use for reading chunks of the file.
|
||||
* @param readBufferSize Size of the read buffer.
|
||||
* @param outBuffer Buffer to read lines into. Lines will be null-terminated.
|
||||
* @param outBufferSize Size of the output buffer.
|
||||
* @return An initialized line reader structure.
|
||||
*/
|
||||
void assetFileLineReaderInit(
|
||||
assetfilelinereader_t *reader,
|
||||
assetfile_t *file,
|
||||
uint8_t *readBuffer,
|
||||
const size_t readBufferSize,
|
||||
uint8_t *outBuffer,
|
||||
const size_t outBufferSize
|
||||
);
|
||||
|
||||
/**
|
||||
* Reads the next line from the asset file into the line buffer. The line
|
||||
* buffer is null-terminated and does not include the newline character.
|
||||
*
|
||||
* @param reader The line reader to read from.
|
||||
* @return An error code if a failure occurs, or errorOk() if a line was read
|
||||
* successfully. If the end of the file is reached, errorEndOfFile() is
|
||||
* returned.
|
||||
*/
|
||||
errorret_t assetFileLineReaderNext(assetfilelinereader_t *reader);
|
||||
@@ -1,18 +0,0 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
assetentry.c
|
||||
assetloader.c
|
||||
)
|
||||
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(display)
|
||||
add_subdirectory(locale)
|
||||
add_subdirectory(json)
|
||||
add_subdirectory(script)
|
||||
@@ -1,104 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "assetentry.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
|
||||
void assetEntryInit(
|
||||
assetentry_t *entry,
|
||||
const char_t *name,
|
||||
const assetloadertype_t type,
|
||||
assetloaderinput_t *input
|
||||
) {
|
||||
assertNotNull(entry, "Entry cannot be NULL");
|
||||
assertStrLenMin(name, 1, "Name cannot be empty");
|
||||
assertStrLenMax(name, ASSET_FILE_NAME_MAX - 1, "Name too long");
|
||||
assertTrue(type != ASSET_LOADER_TYPE_NULL, "Invalid loader type.");
|
||||
assertTrue(type < ASSET_LOADER_TYPE_COUNT, "Invalid loader type.");
|
||||
assertIsMainThread("Must be called from the main thread.");
|
||||
|
||||
memoryZero(entry, sizeof(assetentry_t));
|
||||
stringCopy(entry->name, name, ASSET_FILE_NAME_MAX);
|
||||
entry->type = type;
|
||||
entry->state = ASSET_ENTRY_STATE_NOT_STARTED;
|
||||
if(input) {
|
||||
entry->inputData = *input;
|
||||
entry->input = &entry->inputData;
|
||||
} else {
|
||||
memoryZero(&entry->inputData, sizeof(assetloaderinput_t));
|
||||
entry->input = NULL;
|
||||
}
|
||||
refInit(&entry->refs, entry, NULL, NULL, NULL);
|
||||
|
||||
eventInit(
|
||||
&entry->onLoaded,
|
||||
entry->onLoadedCallbacks, entry->onLoadedUsers,
|
||||
ASSET_ENTRY_EVENT_MAX
|
||||
);
|
||||
eventInit(
|
||||
&entry->onUnloaded,
|
||||
entry->onUnloadedCallbacks, entry->onUnloadedUsers,
|
||||
ASSET_ENTRY_EVENT_MAX
|
||||
);
|
||||
eventInit(
|
||||
&entry->onError,
|
||||
entry->onErrorCallbacks, entry->onErrorUsers,
|
||||
ASSET_ENTRY_EVENT_MAX
|
||||
);
|
||||
}
|
||||
|
||||
void assetEntryLock(assetentry_t *entry) {
|
||||
assertNotNull(entry, "Entry cannot be NULL");
|
||||
assertTrue(entry->type != ASSET_LOADER_TYPE_NULL, "Invalid loader type.");
|
||||
|
||||
refLock(&entry->refs);
|
||||
}
|
||||
|
||||
void assetEntryUnlock(assetentry_t *entry) {
|
||||
assertNotNull(entry, "Entry cannot be NULL");
|
||||
assertTrue(entry->type != ASSET_LOADER_TYPE_NULL, "Invalid loader type.");
|
||||
|
||||
refUnlock(&entry->refs);
|
||||
}
|
||||
|
||||
void assetEntryStartLoading(
|
||||
assetentry_t *entry,
|
||||
assetloading_t *loading
|
||||
) {
|
||||
assertNotNull(entry, "Entry cannot be NULL");
|
||||
assertNotNull(loading, "Loading cannot be NULL");
|
||||
assertTrue(entry->type != ASSET_LOADER_TYPE_NULL, "Invalid loader type.");
|
||||
assertTrue(
|
||||
entry->state == ASSET_ENTRY_STATE_NOT_STARTED,
|
||||
"Can only start loading from NOT_STARTED state."
|
||||
);
|
||||
assertIsMainThread("Must be called from the main thread.");
|
||||
|
||||
entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
|
||||
memoryZero(&loading->loading, sizeof(assetloaderloading_t));
|
||||
loading->type = entry->type;
|
||||
loading->entry = entry;
|
||||
// At this point the asset manager will manage this thing's loading
|
||||
}
|
||||
|
||||
errorret_t assetEntryDispose(assetentry_t *entry) {
|
||||
assertNotNull(entry, "Entry cannot be NULL");
|
||||
assertTrue(entry->type != ASSET_LOADER_TYPE_NULL, "Invalid loader type.");
|
||||
assertTrue(entry->type < ASSET_LOADER_TYPE_COUNT, "Invalid loader type.");
|
||||
assertIsMainThread("Must be called from the main thread.");
|
||||
assertTrue(
|
||||
entry->refs.count == 0,
|
||||
"Asset entry still refed at dispose time."
|
||||
);
|
||||
|
||||
eventInvoke(&entry->onUnloaded, entry);
|
||||
errorChain(ASSET_LOADER_CALLBACKS[entry->type].dispose(entry));
|
||||
memoryZero(entry, sizeof(assetentry_t));
|
||||
errorOk();
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "asset/loader/assetloading.h"
|
||||
#include "event/event.h"
|
||||
#include "util/ref.h"
|
||||
|
||||
typedef enum {
|
||||
ASSET_ENTRY_STATE_NOT_STARTED,
|
||||
ASSET_ENTRY_STATE_PENDING_ASYNC,
|
||||
ASSET_ENTRY_STATE_LOADING_ASYNC,
|
||||
ASSET_ENTRY_STATE_PENDING_SYNC,
|
||||
ASSET_ENTRY_STATE_LOADING_SYNC,
|
||||
ASSET_ENTRY_STATE_LOADED,
|
||||
ASSET_ENTRY_STATE_ERROR
|
||||
} assetentrystate_t;
|
||||
|
||||
/** Maximum number of subscribers for each per-entry event. */
|
||||
#define ASSET_ENTRY_EVENT_MAX 2
|
||||
|
||||
typedef struct assetentry_s assetentry_t;
|
||||
|
||||
struct assetentry_s {
|
||||
char_t name[ASSET_FILE_NAME_MAX];
|
||||
assetloadertype_t type;
|
||||
assetloaderoutput_t data;
|
||||
assetentrystate_t state;
|
||||
ref_t refs;
|
||||
assetloaderinput_t *input;
|
||||
assetloaderinput_t inputData;
|
||||
/**
|
||||
* Fired once when loading completes successfully (params = assetentry_t *).
|
||||
* Always invoked on the main thread.
|
||||
*/
|
||||
event_t onLoaded;
|
||||
eventcallback_t onLoadedCallbacks[ASSET_ENTRY_EVENT_MAX];
|
||||
void *onLoadedUsers[ASSET_ENTRY_EVENT_MAX];
|
||||
|
||||
/**
|
||||
* Fired once when the entry is disposed/reaped (params = assetentry_t *).
|
||||
* The asset data is still accessible when the callback runs.
|
||||
* Always invoked on the main thread.
|
||||
*/
|
||||
event_t onUnloaded;
|
||||
eventcallback_t onUnloadedCallbacks[ASSET_ENTRY_EVENT_MAX];
|
||||
void *onUnloadedUsers[ASSET_ENTRY_EVENT_MAX];
|
||||
|
||||
/**
|
||||
* Fired once when loading fails (params = assetentry_t *).
|
||||
* Always invoked on the main thread.
|
||||
*/
|
||||
event_t onError;
|
||||
eventcallback_t onErrorCallbacks[ASSET_ENTRY_EVENT_MAX];
|
||||
void *onErrorUsers[ASSET_ENTRY_EVENT_MAX];
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes an asset entry with the given name and type. This does not load
|
||||
* the asset.
|
||||
*
|
||||
* @param entry The asset entry to initialize.
|
||||
* @param name The name of the asset, used as a key for loading and caching.
|
||||
* @param type The type of asset this entry represents.
|
||||
* @param input Data that will be passed to the loader about how it should load.
|
||||
*/
|
||||
void assetEntryInit(
|
||||
assetentry_t *entry,
|
||||
const char_t *name,
|
||||
const assetloadertype_t type,
|
||||
assetloaderinput_t *input
|
||||
);
|
||||
|
||||
/**
|
||||
* Locks an asset entry, preventing it from being freed until it is unlocked.
|
||||
*
|
||||
* @param entry The asset entry to lock.
|
||||
*/
|
||||
void assetEntryLock(assetentry_t *entry);
|
||||
|
||||
/**
|
||||
* Unlocks an asset entry, allowing it to be freed if there are no more locks.
|
||||
*
|
||||
* @param entry The asset entry to unlock.
|
||||
*/
|
||||
void assetEntryUnlock(assetentry_t *entry);
|
||||
|
||||
/**
|
||||
* Starts loading the given asset entry using an assetloading slot. This will
|
||||
* be called by the asset manager when it deems it's a good time to begin the
|
||||
* loading of this asset entry.
|
||||
*
|
||||
* Currently we return the error but in future this will not be returned.
|
||||
*
|
||||
* @param entry The asset entry to start loading.
|
||||
* @param loading The assetloading slot to use for loading this asset entry.
|
||||
* @return Any error that occurs during loading.
|
||||
*/
|
||||
void assetEntryStartLoading(assetentry_t *entry, assetloading_t *loading);
|
||||
|
||||
/**
|
||||
* Disposes an asset entry, freeing any resources it holds.
|
||||
* Fires the onUnloaded event before releasing asset data.
|
||||
*
|
||||
* @param entry The asset entry to dispose.
|
||||
* @return Any error that occurs during disposal.
|
||||
*/
|
||||
errorret_t assetEntryDispose(assetentry_t *entry);
|
||||
@@ -1,48 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "assetloader.h"
|
||||
|
||||
assetloadercallbacks_t ASSET_LOADER_CALLBACKS[ASSET_LOADER_TYPE_COUNT] = {
|
||||
[ASSET_LOADER_TYPE_NULL] = { 0 },
|
||||
|
||||
[ASSET_LOADER_TYPE_MESH] = {
|
||||
.loadSync = assetMeshLoaderSync,
|
||||
.loadAsync = assetMeshLoaderAsync,
|
||||
.dispose = assetMeshDispose
|
||||
},
|
||||
|
||||
[ASSET_LOADER_TYPE_TEXTURE] = {
|
||||
.loadSync = assetTextureLoaderSync,
|
||||
.loadAsync = assetTextureLoaderAsync,
|
||||
.dispose = assetTextureDispose
|
||||
},
|
||||
|
||||
[ASSET_LOADER_TYPE_TILESET] = {
|
||||
.loadSync = assetTilesetLoaderSync,
|
||||
.loadAsync = assetTilesetLoaderAsync,
|
||||
.dispose = assetTilesetDispose
|
||||
},
|
||||
|
||||
[ASSET_LOADER_TYPE_LOCALE] = {
|
||||
.loadSync = assetLocaleLoaderSync,
|
||||
.loadAsync = assetLocaleLoaderAsync,
|
||||
.dispose = assetLocaleDispose
|
||||
},
|
||||
|
||||
[ASSET_LOADER_TYPE_JSON] = {
|
||||
.loadSync = assetJsonLoaderSync,
|
||||
.loadAsync = assetJsonLoaderAsync,
|
||||
.dispose = assetJsonDispose
|
||||
},
|
||||
|
||||
[ASSET_LOADER_TYPE_SCRIPT] = {
|
||||
.loadSync = assetScriptLoaderSync,
|
||||
.loadAsync = assetScriptLoaderAsync,
|
||||
.dispose = assetScriptDispose
|
||||
},
|
||||
};
|
||||
@@ -1,96 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "asset/loader/display/assetmeshloader.h"
|
||||
#include "asset/loader/display/assettextureloader.h"
|
||||
#include "asset/loader/display/assettilesetloader.h"
|
||||
#include "asset/loader/locale/assetlocaleloader.h"
|
||||
#include "asset/loader/json/assetjsonloader.h"
|
||||
#include "asset/loader/script/assetscriptloader.h"
|
||||
|
||||
typedef enum {
|
||||
ASSET_LOADER_TYPE_NULL,
|
||||
|
||||
ASSET_LOADER_TYPE_MESH,
|
||||
ASSET_LOADER_TYPE_TEXTURE,
|
||||
ASSET_LOADER_TYPE_TILESET,
|
||||
ASSET_LOADER_TYPE_LOCALE,
|
||||
ASSET_LOADER_TYPE_JSON,
|
||||
ASSET_LOADER_TYPE_SCRIPT,
|
||||
|
||||
ASSET_LOADER_TYPE_COUNT
|
||||
} assetloadertype_t;
|
||||
|
||||
typedef union {
|
||||
assetmeshloaderinput_t mesh;
|
||||
assettextureloaderinput_t texture;
|
||||
assettilesetloaderinput_t tileset;
|
||||
assetlocaleloaderinput_t locale;
|
||||
assetjsonloaderinput_t json;
|
||||
assetscriptloaderinput_t script;
|
||||
} assetloaderinput_t;
|
||||
|
||||
typedef union {
|
||||
assetmeshloaderloading_t mesh;
|
||||
assettextureloaderloading_t texture;
|
||||
assettilesetloaderloading_t tileset;
|
||||
assetlocaleloaderloading_t locale;
|
||||
assetjsonloaderloading_t json;
|
||||
assetscriptloaderloading_t script;
|
||||
} assetloaderloading_t;
|
||||
|
||||
typedef union {
|
||||
assetmeshoutput_t mesh;
|
||||
assettextureoutput_t texture;
|
||||
assettilesetoutput_t tileset;
|
||||
assetlocaleoutput_t locale;
|
||||
assetjsonoutput_t json;
|
||||
assetscriptoutput_t script;
|
||||
} assetloaderoutput_t;
|
||||
|
||||
typedef struct assetloading_s assetloading_t;
|
||||
typedef struct assetentry_s assetentry_t;
|
||||
|
||||
typedef errorret_t (assetloadersynccallback_t)(assetloading_t *loading);
|
||||
typedef errorret_t (assetloaderasynccallback_t)(assetloading_t *loading);
|
||||
typedef errorret_t (assetloaderdisposecallback_t)(assetentry_t *entry);
|
||||
|
||||
typedef struct {
|
||||
assetloadersynccallback_t *loadSync;
|
||||
assetloaderasynccallback_t *loadAsync;
|
||||
assetloaderdisposecallback_t *dispose;
|
||||
} assetloadercallbacks_t;
|
||||
|
||||
extern assetloadercallbacks_t ASSET_LOADER_CALLBACKS[ASSET_LOADER_TYPE_COUNT];
|
||||
|
||||
/**
|
||||
* Shorthand method to both chain an error (against the loader state) and to
|
||||
* set the asset entry state to error.
|
||||
*
|
||||
* @param loading The asset loading slot.
|
||||
* @param ret The error return value to check and chain if it's an error.
|
||||
*/
|
||||
#define assetLoaderErrorChain(loading, _expr) {\
|
||||
errorret_t _alec = (_expr); \
|
||||
if(errorIsNotOk(_alec)) { \
|
||||
(loading)->entry->state = ASSET_ENTRY_STATE_ERROR; \
|
||||
errorChain(_alec); \
|
||||
} \
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand method to both throw an error (against the loader state) and to
|
||||
* set the asset entry state to error.
|
||||
*
|
||||
* @param loading The asset loading slot.
|
||||
* @param ... Format string and arguments for the error message.
|
||||
*/
|
||||
#define assetLoaderErrorThrow(loading, ...) {\
|
||||
loading->entry->state = ASSET_ENTRY_STATE_ERROR; \
|
||||
errorThrow(__VA_ARGS__); \
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "assetloader.h"
|
||||
#include "asset/assetfile.h"
|
||||
#include "thread/threadmutex.h"
|
||||
|
||||
typedef struct assetentry_s assetentry_t;
|
||||
|
||||
typedef struct assetloading_s {
|
||||
threadmutex_t mutex;
|
||||
assetloadertype_t type;
|
||||
assetentry_t *entry;
|
||||
assetloaderloading_t loading;
|
||||
} assetloading_t;
|
||||
|
||||
typedef errorret_t (assetloadingcallback_t)(assetloading_t *loading);
|
||||
|
||||
typedef struct {
|
||||
assetloadingcallback_t *loadSync;
|
||||
} assetloadingcallbacks_t;
|
||||
@@ -1,12 +0,0 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
assetmeshloader.c
|
||||
assettextureloader.c
|
||||
assettilesetloader.c
|
||||
)
|
||||
@@ -1,180 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "assetmeshloader.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/endian.h"
|
||||
#include "util/memory.h"
|
||||
#include "asset/loader/assetloading.h"
|
||||
#include "asset/loader/assetentry.h"
|
||||
|
||||
errorret_t assetMeshLoaderAsync(assetloading_t *loading) {
|
||||
assertNotNull(loading, "Loading cannot be NULL");
|
||||
|
||||
if(loading->loading.mesh.state != ASSET_MESH_LOADING_STATE_READ_FILE) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
assetmeshoutput_t *out = &loading->entry->data.mesh;
|
||||
assetfile_t *file = &loading->loading.mesh.file;
|
||||
assetmeshinputaxis_t axis = loading->entry->inputData.mesh;
|
||||
|
||||
assetLoaderErrorChain(loading,
|
||||
assetFileInit(file, loading->entry->name, NULL, NULL)
|
||||
);
|
||||
assetLoaderErrorChain(loading, assetFileOpen(file));
|
||||
|
||||
// Skip the 80-byte STL header.
|
||||
assetLoaderErrorChain(loading, assetFileRead(file, NULL, 80));
|
||||
if(file->lastRead != 80) {
|
||||
assetLoaderErrorThrow(loading, "Failed to skip STL header.");
|
||||
}
|
||||
|
||||
uint32_t triangleCount;
|
||||
assetLoaderErrorChain(loading,
|
||||
assetFileRead(file, &triangleCount, sizeof(uint32_t))
|
||||
);
|
||||
if(file->lastRead != sizeof(uint32_t)) {
|
||||
assetLoaderErrorThrow(loading, "Failed to read tri count");
|
||||
}
|
||||
triangleCount = endianLittleToHost32(triangleCount);
|
||||
|
||||
out->vertices = memoryAllocate(sizeof(meshvertex_t) * triangleCount * 3);
|
||||
meshvertex_t *verts = out->vertices;
|
||||
|
||||
errorret_t ret;
|
||||
for(uint32_t i = 0; i < triangleCount; i++) {
|
||||
assetmeshstltriangle_t triData;
|
||||
ret = assetFileRead(file, &triData, sizeof(triData));
|
||||
if(errorIsNotOk(ret)) {
|
||||
memoryFree(verts);
|
||||
out->vertices = NULL;
|
||||
assetLoaderErrorChain(loading, ret);
|
||||
}
|
||||
if(file->lastRead != sizeof(triData)) {
|
||||
memoryFree(verts);
|
||||
out->vertices = NULL;
|
||||
assetLoaderErrorThrow(loading, "Failed to read triangle data");
|
||||
}
|
||||
|
||||
for(uint8_t j = 0; j < 3; j++) {
|
||||
#if MESH_ENABLE_COLOR
|
||||
verts[i * 3 + j].color.r = (
|
||||
(uint8_t)(endianLittleToHostFloat(triData.normal[0]) * 255.0f)
|
||||
);
|
||||
verts[i * 3 + j].color.g = (
|
||||
(uint8_t)(endianLittleToHostFloat(triData.normal[1]) * 255.0f)
|
||||
);
|
||||
verts[i * 3 + j].color.b = (
|
||||
(uint8_t)(endianLittleToHostFloat(triData.normal[2]) * 255.0f)
|
||||
);
|
||||
verts[i * 3 + j].color.a = 0xFF;
|
||||
#endif
|
||||
|
||||
verts[i * 3 + j].uv[0] = 0.0f;
|
||||
verts[i * 3 + j].uv[1] = 0.0f;
|
||||
|
||||
for(uint8_t k = 0; k < 3; k++) {
|
||||
verts[i * 3 + j].pos[k] = endianLittleToHostFloat(
|
||||
triData.positions[j][k]
|
||||
);
|
||||
}
|
||||
|
||||
switch(axis) {
|
||||
case MESH_INPUT_AXIS_Z_UP: {
|
||||
float_t temp = verts[i * 3 + j].pos[1];
|
||||
verts[i * 3 + j].pos[1] = verts[i * 3 + j].pos[2];
|
||||
verts[i * 3 + j].pos[2] = temp;
|
||||
break;
|
||||
}
|
||||
case MESH_INPUT_AXIS_X_UP: {
|
||||
float_t temp = verts[i * 3 + j].pos[0];
|
||||
verts[i * 3 + j].pos[0] = verts[i * 3 + j].pos[1];
|
||||
verts[i * 3 + j].pos[1] = temp;
|
||||
break;
|
||||
}
|
||||
case MESH_INPUT_AXIS_Y_DOWN:
|
||||
verts[i * 3 + j].pos[1] = -verts[i * 3 + j].pos[1];
|
||||
break;
|
||||
case MESH_INPUT_AXIS_Z_DOWN: {
|
||||
float_t temp = verts[i * 3 + j].pos[1];
|
||||
verts[i * 3 + j].pos[1] = -verts[i * 3 + j].pos[2];
|
||||
verts[i * 3 + j].pos[2] = temp;
|
||||
break;
|
||||
}
|
||||
case MESH_INPUT_AXIS_X_DOWN: {
|
||||
float_t temp = verts[i * 3 + j].pos[0];
|
||||
verts[i * 3 + j].pos[0] = verts[i * 3 + j].pos[1];
|
||||
verts[i * 3 + j].pos[1] = -temp;
|
||||
break;
|
||||
}
|
||||
case MESH_INPUT_AXIS_Y_UP:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret = assetFileClose(file);
|
||||
if(errorIsNotOk(ret)) {
|
||||
memoryFree(verts);
|
||||
out->vertices = NULL;
|
||||
assetLoaderErrorChain(loading, ret);
|
||||
}
|
||||
assetFileDispose(file);
|
||||
|
||||
loading->loading.mesh.triangleCount = triangleCount;
|
||||
loading->loading.mesh.state = ASSET_MESH_LOADING_STATE_CREATE_MESH;
|
||||
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t assetMeshLoaderSync(assetloading_t *loading) {
|
||||
assertNotNull(loading, "Loading cannot be NULL");
|
||||
assertTrue(loading->type == ASSET_LOADER_TYPE_MESH, "Invalid type.");
|
||||
|
||||
switch(loading->loading.mesh.state) {
|
||||
case ASSET_MESH_LOADING_STATE_INITIAL:
|
||||
loading->loading.mesh.state = ASSET_MESH_LOADING_STATE_READ_FILE;
|
||||
loading->entry->state = ASSET_ENTRY_STATE_PENDING_ASYNC;
|
||||
errorOk();
|
||||
break;
|
||||
|
||||
case ASSET_MESH_LOADING_STATE_CREATE_MESH:
|
||||
break;
|
||||
|
||||
default:
|
||||
errorOk();
|
||||
}
|
||||
|
||||
assetmeshoutput_t *out = &loading->entry->data.mesh;
|
||||
assertNotNull(out->vertices, "Mesh vertices should have been loaded by now.");
|
||||
|
||||
errorret_t ret = meshInit(
|
||||
&out->mesh,
|
||||
MESH_PRIMITIVE_TYPE_TRIANGLES,
|
||||
loading->loading.mesh.triangleCount * 3,
|
||||
out->vertices
|
||||
);
|
||||
if(errorIsNotOk(ret)) {
|
||||
loading->entry->state = ASSET_ENTRY_STATE_ERROR;
|
||||
memoryFree(out->vertices);
|
||||
out->vertices = NULL;
|
||||
assetLoaderErrorChain(loading, ret);
|
||||
}
|
||||
|
||||
loading->entry->state = ASSET_ENTRY_STATE_LOADED;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t assetMeshDispose(assetentry_t *entry) {
|
||||
assertNotNull(entry, "Asset entry cannot be NULL");
|
||||
assertTrue(entry->type == ASSET_LOADER_TYPE_MESH, "Invalid type.");
|
||||
errorChain(meshDispose(&entry->data.mesh.mesh));
|
||||
memoryFree(entry->data.mesh.vertices);
|
||||
errorOk();
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "asset/assetfile.h"
|
||||
#include "display/mesh/mesh.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
typedef struct assetloading_s assetloading_t;
|
||||
typedef struct assetentry_s assetentry_t;
|
||||
|
||||
typedef enum {
|
||||
MESH_INPUT_AXIS_Y_UP,
|
||||
MESH_INPUT_AXIS_Z_UP,
|
||||
MESH_INPUT_AXIS_X_UP,
|
||||
|
||||
MESH_INPUT_AXIS_Y_DOWN,
|
||||
MESH_INPUT_AXIS_Z_DOWN,
|
||||
MESH_INPUT_AXIS_X_DOWN,
|
||||
} assetmeshinputaxis_t;
|
||||
|
||||
typedef assetmeshinputaxis_t assetmeshloaderinput_t;
|
||||
|
||||
typedef enum {
|
||||
ASSET_MESH_LOADING_STATE_INITIAL,
|
||||
ASSET_MESH_LOADING_STATE_READ_FILE,
|
||||
ASSET_MESH_LOADING_STATE_CREATE_MESH,
|
||||
ASSET_MESH_LOADING_STATE_DONE
|
||||
} assetmeshloadingstate_t;
|
||||
|
||||
typedef struct {
|
||||
assetfile_t file;
|
||||
assetmeshloadingstate_t state;
|
||||
uint32_t triangleCount;
|
||||
} assetmeshloaderloading_t;
|
||||
|
||||
typedef struct {
|
||||
mesh_t mesh;
|
||||
meshvertex_t *vertices;
|
||||
} assetmeshoutput_t;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
vec3 normal;
|
||||
float_t positions[3][3];
|
||||
uint16_t attributeByteCount;
|
||||
} assetmeshstltriangle_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
assertStructSize(assetmeshstltriangle_t, 50);
|
||||
|
||||
errorret_t assetMeshLoaderAsync(assetloading_t *loading);
|
||||
errorret_t assetMeshLoaderSync(assetloading_t *loading);
|
||||
errorret_t assetMeshDispose(assetentry_t *entry);
|
||||
@@ -1,171 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "assettextureloader.h"
|
||||
#include "assert/assert.h"
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "stb_image.h"
|
||||
#include "log/log.h"
|
||||
#include "util/endian.h"
|
||||
#include "asset/loader/assetloading.h"
|
||||
#include "asset/loader/assetentry.h"
|
||||
|
||||
stbi_io_callbacks ASSET_TEXTURE_STB_CALLBACKS = {
|
||||
.read = assetTextureReader,
|
||||
.skip = assetTextureSkipper,
|
||||
.eof = assetTextureEOF
|
||||
};
|
||||
|
||||
int assetTextureReader(void *user, char *data, int size) {
|
||||
assertNotNull(data, "Data buffer for stb_image callbacks cannot be NULL.");
|
||||
|
||||
assetfile_t *file = (assetfile_t*)user;
|
||||
assertNotNull(file, "Asset file in stb_image callbacks cannot be NULL.");
|
||||
|
||||
errorret_t ret = assetFileRead(file, data, (size_t)size);
|
||||
if(errorIsNotOk(ret)) {
|
||||
errorCatch(errorPrint(ret));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return file->lastRead;
|
||||
}
|
||||
|
||||
void assetTextureSkipper(void *user, int n) {
|
||||
assetfile_t *file = (assetfile_t*)user;
|
||||
assertNotNull(file, "Asset file in stb_image callbacks cannot be NULL.");
|
||||
|
||||
errorret_t ret = assetFileRead(file, NULL, (size_t)n);
|
||||
if(errorIsNotOk(ret)) {
|
||||
errorCatch(errorPrint(ret));
|
||||
}
|
||||
}
|
||||
|
||||
int assetTextureEOF(void *user) {
|
||||
assetfile_t *file = (assetfile_t*)user;
|
||||
assertNotNull(file, "Asset file in stb_image callbacks cannot be NULL.");
|
||||
|
||||
return file->position >= file->size;
|
||||
}
|
||||
|
||||
errorret_t assetTextureLoaderAsync(assetloading_t *loading) {
|
||||
assertNotNull(loading, "Loading cannot be NULL");
|
||||
assertNotMainThread("Should be called from an async thread.");
|
||||
|
||||
// Only care about loading pixels.
|
||||
if(loading->loading.texture.state != ASSET_TEXTURE_LOADING_STATE_LOAD_PIXELS){
|
||||
errorOk();
|
||||
}
|
||||
|
||||
// Init the file
|
||||
assertNull(
|
||||
loading->loading.texture.data, "Pixels already defined?"
|
||||
);
|
||||
|
||||
assetfile_t *file = &loading->loading.texture.file;
|
||||
assetLoaderErrorChain(loading, assetFileInit(
|
||||
file,
|
||||
loading->entry->name,
|
||||
NULL,
|
||||
&loading->entry->data.texture
|
||||
));
|
||||
assetLoaderErrorChain(loading, assetFileOpen(file));
|
||||
|
||||
// Determine channels
|
||||
int channelsDesired;
|
||||
switch(loading->entry->inputData.texture) {
|
||||
case TEXTURE_FORMAT_RGBA:
|
||||
channelsDesired = 4;
|
||||
break;
|
||||
|
||||
default:
|
||||
assetLoaderErrorThrow(loading, "Bad texture format.");
|
||||
}
|
||||
|
||||
// Load image pixels.
|
||||
loading->loading.texture.data = stbi_load_from_callbacks(
|
||||
&ASSET_TEXTURE_STB_CALLBACKS,
|
||||
file,
|
||||
&loading->loading.texture.width,
|
||||
&loading->loading.texture.height,
|
||||
&loading->loading.texture.channels,
|
||||
channelsDesired
|
||||
);
|
||||
|
||||
// Close out the file.
|
||||
assetLoaderErrorChain(loading, assetFileClose(file));
|
||||
assetLoaderErrorChain(loading, assetFileDispose(file));
|
||||
|
||||
// Ensure we loaded correctly.
|
||||
if(loading->loading.texture.data == NULL) {
|
||||
const char_t *errorStr = stbi_failure_reason();
|
||||
assetLoaderErrorThrow(
|
||||
loading, "Failed to load texture from file %s.", errorStr
|
||||
);
|
||||
}
|
||||
|
||||
// Fixes a specific bug probably with Dolphin but for now just assuming endian
|
||||
if(!isHostLittleEndian()) {
|
||||
stbi__vertical_flip(
|
||||
loading->loading.texture.data,
|
||||
loading->loading.texture.width,
|
||||
loading->loading.texture.height,
|
||||
loading->loading.texture.channels
|
||||
);
|
||||
}
|
||||
|
||||
loading->loading.texture.state = ASSET_TEXTURE_LOADING_STATE_CREATE_TEXTURE;
|
||||
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t assetTextureLoaderSync(assetloading_t *loading) {
|
||||
assertNotNull(loading, "Loading cannot be NULL");
|
||||
assertIsMainThread("Must be called from the main thread.");
|
||||
|
||||
switch(loading->loading.texture.state) {
|
||||
case ASSET_TEXTURE_LOADING_STATE_INITIAL:
|
||||
loading->loading.texture.state = ASSET_TEXTURE_LOADING_STATE_LOAD_PIXELS;
|
||||
loading->entry->state = ASSET_ENTRY_STATE_PENDING_ASYNC;
|
||||
errorOk();
|
||||
break;
|
||||
|
||||
case ASSET_TEXTURE_LOADING_STATE_CREATE_TEXTURE:
|
||||
break;
|
||||
|
||||
default:
|
||||
errorOk();
|
||||
}
|
||||
|
||||
// Create the texture.
|
||||
assertNotNull(
|
||||
loading->loading.texture.data, "Pixels should have been loaded by now."
|
||||
);
|
||||
|
||||
assetLoaderErrorChain(loading, textureInit(
|
||||
(texture_t*)&loading->entry->data.texture,
|
||||
loading->loading.texture.width,
|
||||
loading->loading.texture.height,
|
||||
loading->entry->inputData.texture,
|
||||
(texturedata_t){
|
||||
.rgbaColors = (color_t*)loading->loading.texture.data
|
||||
}
|
||||
));
|
||||
|
||||
// Free the pixels.
|
||||
stbi_image_free(loading->loading.texture.data);
|
||||
|
||||
loading->entry->state = ASSET_ENTRY_STATE_LOADED;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t assetTextureDispose(assetentry_t *entry) {
|
||||
assertNotNull(entry, "Asset entry cannot be NULL");
|
||||
assertIsMainThread("Must be called from the main thread.");
|
||||
|
||||
return textureDispose(&entry->data.texture);
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "asset/assetfile.h"
|
||||
#include "display/texture/texture.h"
|
||||
|
||||
typedef struct assetloading_s assetloading_t;
|
||||
typedef struct assetentry_s assetentry_t;
|
||||
typedef textureformat_t assettextureloaderinput_t;
|
||||
|
||||
typedef enum {
|
||||
ASSET_TEXTURE_LOADING_STATE_INITIAL,
|
||||
ASSET_TEXTURE_LOADING_STATE_LOAD_PIXELS,
|
||||
ASSET_TEXTURE_LOADING_STATE_CREATE_TEXTURE,
|
||||
ASSET_TEXTURE_LOADING_STATE_DONE
|
||||
} assettextureloadingstate_t;
|
||||
|
||||
typedef struct {
|
||||
assetfile_t file;
|
||||
assettextureloadingstate_t state;
|
||||
|
||||
int channels, width, height;
|
||||
uint8_t *data;
|
||||
} assettextureloaderloading_t;
|
||||
|
||||
typedef texture_t assettextureoutput_t;
|
||||
|
||||
/**
|
||||
* STB image read callback for asset files.
|
||||
*
|
||||
* @param user User data passed to the callback, should be an assetfile_t*.
|
||||
* @param data Buffer to read the file data into.
|
||||
* @param size Size of the buffer to read into.
|
||||
* @return Number of bytes read, or -1 on error.
|
||||
*/
|
||||
int assetTextureReader(void *user, char *data, int size);
|
||||
|
||||
/**
|
||||
* STB image skip callback for asset files.
|
||||
*
|
||||
* @param user User data passed to the callback, should be an assetfile_t*.
|
||||
* @param n Number of bytes to skip in the file.
|
||||
*/
|
||||
void assetTextureSkipper(void *user, int n);
|
||||
|
||||
/**
|
||||
* STB image EOF callback for asset files.
|
||||
*
|
||||
* @param user User data passed to the callback, should be an assetfile_t*.
|
||||
* @return Non-zero if end of file has been reached, zero otherwise.
|
||||
*/
|
||||
int assetTextureEOF(void *user);
|
||||
|
||||
/**
|
||||
* Synchronous loader for texture assets.
|
||||
*
|
||||
* @param loading Loading information for the asset being loaded.
|
||||
* @return Error code indicating success or failure of the load operation.
|
||||
*/
|
||||
errorret_t assetTextureLoaderAsync(assetloading_t *loading);
|
||||
|
||||
/**
|
||||
* Synchronous loader for texture assets.
|
||||
*
|
||||
* @param loading Loading information for the asset being loaded.
|
||||
* @return Error code indicating success or failure of the load operation.
|
||||
*/
|
||||
errorret_t assetTextureLoaderSync(assetloading_t *loading);
|
||||
|
||||
/**
|
||||
* Disposer for texture assets.
|
||||
*
|
||||
* @param entry Asset entry containing the texture to dispose.
|
||||
* @return Error code indicating success or failure of the dispose operation.
|
||||
*/
|
||||
errorret_t assetTextureDispose(assetentry_t *entry);
|
||||
@@ -1,124 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "assettilesetloader.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/endian.h"
|
||||
#include "asset/loader/assetloading.h"
|
||||
#include "asset/loader/assetentry.h"
|
||||
|
||||
errorret_t assetTilesetLoaderAsync(assetloading_t *loading) {
|
||||
assertNotNull(loading, "Loading cannot be NULL");
|
||||
assertNotMainThread("Should be called from an async thread.");
|
||||
|
||||
if(loading->loading.tileset.state != ASSET_TILESET_LOADING_STATE_READ_FILE) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
assertNull(loading->loading.tileset.data, "Data already defined?");
|
||||
|
||||
assetfile_t *file = &loading->loading.tileset.file;
|
||||
assetLoaderErrorChain(loading,
|
||||
assetFileInit(file, loading->entry->name, NULL, NULL)
|
||||
);
|
||||
|
||||
uint8_t *data = memoryAllocate(file->size);
|
||||
assetLoaderErrorChain(loading, assetFileOpen(file));
|
||||
assetLoaderErrorChain(loading, assetFileRead(file, data, file->size));
|
||||
assetLoaderErrorChain(loading, assetFileClose(file));
|
||||
assetLoaderErrorChain(loading, assetFileDispose(file));
|
||||
assertTrue(
|
||||
file->lastRead == file->size,
|
||||
"Failed to read entire tileset file."
|
||||
);
|
||||
|
||||
loading->loading.tileset.data = data;
|
||||
loading->loading.tileset.state = ASSET_TILESET_LOADING_STATE_PARSE;
|
||||
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t assetTilesetLoaderSync(assetloading_t *loading) {
|
||||
assertNotNull(loading, "Loading cannot be NULL");
|
||||
assertTrue(loading->type == ASSET_LOADER_TYPE_TILESET, "Invalid type.");
|
||||
assertIsMainThread("Must be called from the main thread.");
|
||||
|
||||
switch(loading->loading.tileset.state) {
|
||||
case ASSET_TILESET_LOADING_STATE_INITIAL:
|
||||
loading->loading.tileset.state = ASSET_TILESET_LOADING_STATE_READ_FILE;
|
||||
loading->entry->state = ASSET_ENTRY_STATE_PENDING_ASYNC;
|
||||
errorOk();
|
||||
break;
|
||||
|
||||
case ASSET_TILESET_LOADING_STATE_PARSE:
|
||||
break;
|
||||
|
||||
default:
|
||||
errorOk();
|
||||
}
|
||||
|
||||
uint8_t *data = loading->loading.tileset.data;
|
||||
assertNotNull(data, "Tileset data should have been loaded by now.");
|
||||
|
||||
tileset_t *out = &loading->entry->data.tileset;
|
||||
|
||||
if(data[0] != 'D' || data[1] != 'T' || data[2] != 'F') {
|
||||
memoryFree(data);
|
||||
assetLoaderErrorThrow(loading, "Invalid tileset header");
|
||||
}
|
||||
|
||||
if(data[3] != 0x00) {
|
||||
memoryFree(data);
|
||||
assetLoaderErrorThrow(loading, "Unsupported tileset version");
|
||||
}
|
||||
|
||||
out->tileWidth = endianLittleToHost16(*(uint16_t *)(data + 4));
|
||||
out->tileHeight = endianLittleToHost16(*(uint16_t *)(data + 6));
|
||||
out->columns = endianLittleToHost16(*(uint16_t *)(data + 8));
|
||||
out->rows = endianLittleToHost16(*(uint16_t *)(data + 10));
|
||||
|
||||
if(out->tileWidth == 0) {
|
||||
memoryFree(data);
|
||||
assetLoaderErrorThrow(loading, "Tile width cannot be 0");
|
||||
}
|
||||
if(out->tileHeight == 0) {
|
||||
memoryFree(data);
|
||||
assetLoaderErrorThrow(loading, "Tile height cannot be 0");
|
||||
}
|
||||
if(out->columns == 0) {
|
||||
memoryFree(data);
|
||||
assetLoaderErrorThrow(loading, "Column count cannot be 0");
|
||||
}
|
||||
if(out->rows == 0) {
|
||||
memoryFree(data);
|
||||
assetLoaderErrorThrow(loading, "Row count cannot be 0");
|
||||
}
|
||||
|
||||
out->uv[0] = endianLittleToHostFloat(*(float *)(data + 16));
|
||||
out->uv[1] = endianLittleToHostFloat(*(float *)(data + 20));
|
||||
|
||||
if(out->uv[1] < 0.0f || out->uv[1] > 1.0f) {
|
||||
memoryFree(data);
|
||||
assetLoaderErrorThrow(loading, "Invalid v0 value in tileset");
|
||||
}
|
||||
|
||||
out->tileCount = out->columns * out->rows;
|
||||
memoryFree(data);
|
||||
loading->loading.tileset.data = NULL;
|
||||
|
||||
loading->entry->state = ASSET_ENTRY_STATE_LOADED;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t assetTilesetDispose(assetentry_t *entry) {
|
||||
assertNotNull(entry, "Entry cannot be NULL");
|
||||
assertTrue(entry->type == ASSET_LOADER_TYPE_TILESET, "Invalid type.");
|
||||
assertIsMainThread("Must be called from the main thread.");
|
||||
|
||||
errorOk();
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "asset/assetfile.h"
|
||||
#include "display/texture/tileset.h"
|
||||
|
||||
typedef struct assetloading_s assetloading_t;
|
||||
typedef struct assetentry_s assetentry_t;
|
||||
|
||||
typedef struct {
|
||||
void *nothing;
|
||||
} assettilesetloaderinput_t;
|
||||
|
||||
typedef enum {
|
||||
ASSET_TILESET_LOADING_STATE_INITIAL,
|
||||
ASSET_TILESET_LOADING_STATE_READ_FILE,
|
||||
ASSET_TILESET_LOADING_STATE_PARSE,
|
||||
ASSET_TILESET_LOADING_STATE_DONE
|
||||
} assettilesetloadingstate_t;
|
||||
|
||||
typedef struct {
|
||||
assetfile_t file;
|
||||
assettilesetloadingstate_t state;
|
||||
|
||||
uint8_t *data;
|
||||
} assettilesetloaderloading_t;
|
||||
|
||||
typedef tileset_t assettilesetoutput_t;
|
||||
|
||||
/**
|
||||
* Asynchronous loader for tileset assets. Reads the raw DTF file bytes into
|
||||
* the loading buffer so the sync phase can parse without blocking the main
|
||||
* thread on I/O.
|
||||
*
|
||||
* @param loading Loading information for the asset being loaded.
|
||||
* @return Error code indicating success or failure of the load operation.
|
||||
*/
|
||||
errorret_t assetTilesetLoaderAsync(assetloading_t *loading);
|
||||
|
||||
/**
|
||||
* Synchronous loader for tileset assets. Parses the DTF binary previously
|
||||
* read by the async phase and populates the output tileset_t.
|
||||
*
|
||||
* @param loading Loading information for the asset being loaded.
|
||||
* @return Error code indicating success or failure of the load operation.
|
||||
*/
|
||||
errorret_t assetTilesetLoaderSync(assetloading_t *loading);
|
||||
|
||||
/**
|
||||
* Disposer for tileset assets.
|
||||
*
|
||||
* @param entry Asset entry containing the tileset to dispose.
|
||||
* @return Error code indicating success or failure of the dispose operation.
|
||||
*/
|
||||
errorret_t assetTilesetDispose(assetentry_t *entry);
|
||||
@@ -1,10 +0,0 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
assetjsonloader.c
|
||||
)
|
||||
@@ -1,95 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "assetjsonloader.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
#include "asset/loader/assetloading.h"
|
||||
#include "asset/loader/assetentry.h"
|
||||
|
||||
errorret_t assetJsonLoaderAsync(assetloading_t *loading) {
|
||||
assertNotNull(loading, "Loading cannot be NULL");
|
||||
assertNotMainThread("Async loader should not be on main thread.");
|
||||
|
||||
if(loading->loading.json.state != ASSET_JSON_LOADING_STATE_READ_FILE) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
assertNull(loading->loading.json.buffer, "Buffer already defined?");
|
||||
|
||||
assetfile_t *file = &loading->loading.json.file;
|
||||
assetLoaderErrorChain(loading,
|
||||
assetFileInit(file, loading->entry->name, NULL, NULL)
|
||||
);
|
||||
|
||||
if(file->size > ASSET_JSON_FILE_SIZE_MAX) {
|
||||
assetLoaderErrorThrow(loading, "JSON exceeds maximum allowed size");
|
||||
}
|
||||
|
||||
size_t fileSize = (size_t)file->size;
|
||||
uint8_t *buffer = memoryAllocate(fileSize);
|
||||
assetLoaderErrorChain(loading, assetFileOpen(file));
|
||||
assetLoaderErrorChain(loading, assetFileRead(file, buffer, fileSize));
|
||||
assertTrue(file->lastRead == file->size, "Failed to read entire JSON file.");
|
||||
assetLoaderErrorChain(loading, assetFileClose(file));
|
||||
assetLoaderErrorChain(loading, assetFileDispose(file));
|
||||
|
||||
loading->loading.json.buffer = buffer;
|
||||
loading->loading.json.size = fileSize;
|
||||
loading->loading.json.state = ASSET_JSON_LOADING_STATE_PARSE;
|
||||
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t assetJsonLoaderSync(assetloading_t *loading) {
|
||||
assertNotNull(loading, "Loading cannot be NULL");
|
||||
assertTrue(loading->type == ASSET_LOADER_TYPE_JSON, "Invalid type.");
|
||||
assertIsMainThread("Must be called from the main thread.");
|
||||
|
||||
switch(loading->loading.json.state) {
|
||||
case ASSET_JSON_LOADING_STATE_INITIAL:
|
||||
loading->loading.json.state = ASSET_JSON_LOADING_STATE_READ_FILE;
|
||||
loading->entry->state = ASSET_ENTRY_STATE_PENDING_ASYNC;
|
||||
errorOk();
|
||||
break;
|
||||
|
||||
case ASSET_JSON_LOADING_STATE_PARSE:
|
||||
break;
|
||||
|
||||
default:
|
||||
errorOk();
|
||||
}
|
||||
|
||||
uint8_t *buffer = loading->loading.json.buffer;
|
||||
assertNotNull(buffer, "JSON buffer should have been loaded by now.");
|
||||
|
||||
loading->entry->data.json = yyjson_read(
|
||||
(char *)buffer,
|
||||
loading->loading.json.size,
|
||||
YYJSON_READ_ALLOW_COMMENTS | YYJSON_READ_ALLOW_TRAILING_COMMAS
|
||||
);
|
||||
memoryFree(buffer);
|
||||
loading->loading.json.buffer = NULL;
|
||||
|
||||
if(!loading->entry->data.json) {
|
||||
assetLoaderErrorThrow(loading, "Failed to parse JSON");
|
||||
}
|
||||
|
||||
loading->entry->state = ASSET_ENTRY_STATE_LOADED;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t assetJsonDispose(assetentry_t *entry) {
|
||||
assertNotNull(entry, "Asset entry cannot be NULL");
|
||||
assertTrue(entry->type == ASSET_LOADER_TYPE_JSON, "Invalid type.");
|
||||
assertIsMainThread("Must be called from the main thread.");
|
||||
|
||||
yyjson_doc_free(entry->data.json);
|
||||
entry->data.json = NULL;
|
||||
|
||||
errorOk();
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "asset/assetfile.h"
|
||||
#include "yyjson.h"
|
||||
|
||||
#define ASSET_JSON_FILE_SIZE_MAX 1024*256
|
||||
|
||||
typedef struct assetloading_s assetloading_t;
|
||||
typedef struct assetentry_s assetentry_t;
|
||||
|
||||
typedef struct { void *nothing; } assetjsonloaderinput_t;
|
||||
|
||||
typedef enum {
|
||||
ASSET_JSON_LOADING_STATE_INITIAL,
|
||||
ASSET_JSON_LOADING_STATE_READ_FILE,
|
||||
ASSET_JSON_LOADING_STATE_PARSE,
|
||||
ASSET_JSON_LOADING_STATE_DONE
|
||||
} assetjsonloadingstate_t;
|
||||
|
||||
typedef struct {
|
||||
assetfile_t file;
|
||||
assetjsonloadingstate_t state;
|
||||
uint8_t *buffer;
|
||||
size_t size;
|
||||
} assetjsonloaderloading_t;
|
||||
|
||||
typedef yyjson_doc * assetjsonoutput_t;
|
||||
|
||||
errorret_t assetJsonLoaderAsync(assetloading_t *loading);
|
||||
errorret_t assetJsonLoaderSync(assetloading_t *loading);
|
||||
errorret_t assetJsonDispose(assetentry_t *entry);
|
||||
@@ -1,10 +0,0 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
assetlocaleloader.c
|
||||
)
|
||||
@@ -1,860 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "assetlocaleloader.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/math.h"
|
||||
#include "util/string.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
#include "asset/loader/assetloading.h"
|
||||
#include "asset/loader/assetentry.h"
|
||||
|
||||
errorret_t assetLocaleLoaderAsync(assetloading_t *loading) {
|
||||
assertNotNull(loading, "Loading cannot be NULL");
|
||||
assertNotMainThread("Async loader should not be on main thread.");
|
||||
|
||||
if(loading->loading.locale.state != ASSET_LOCALE_LOADER_STATE_LOAD_HEADER) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
assetlocalefile_t *localeFile = &loading->entry->data.locale;
|
||||
memoryZero(localeFile, sizeof(assetlocalefile_t));
|
||||
assetLoaderErrorChain(loading, assetFileInit(
|
||||
&localeFile->file, loading->entry->name, NULL, NULL
|
||||
));
|
||||
assetLoaderErrorChain(loading, assetFileOpen(&localeFile->file));
|
||||
|
||||
char_t buffer[1024];
|
||||
assetLoaderErrorChain(loading, assetLocaleGetString(
|
||||
localeFile, "", 0, buffer, sizeof(buffer)
|
||||
));
|
||||
assetLoaderErrorChain(loading, assetLocaleParseHeader(
|
||||
localeFile, buffer, sizeof(buffer)
|
||||
));
|
||||
|
||||
loading->loading.locale.state = ASSET_LOCALE_LOADER_STATE_DONE;
|
||||
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t assetLocaleLoaderSync(assetloading_t *loading) {
|
||||
assertNotNull(loading, "Loading cannot be NULL");
|
||||
assertTrue(loading->type == ASSET_LOADER_TYPE_LOCALE, "Invalid type.");
|
||||
assertIsMainThread("Must be called from the main thread.");
|
||||
|
||||
switch(loading->loading.locale.state) {
|
||||
case ASSET_LOCALE_LOADER_STATE_INITIAL:
|
||||
loading->loading.locale.state = ASSET_LOCALE_LOADER_STATE_LOAD_HEADER;
|
||||
loading->entry->state = ASSET_ENTRY_STATE_PENDING_ASYNC;
|
||||
errorOk();
|
||||
break;
|
||||
|
||||
case ASSET_LOCALE_LOADER_STATE_DONE:
|
||||
break;
|
||||
|
||||
default:
|
||||
errorOk();
|
||||
}
|
||||
|
||||
loading->entry->state = ASSET_ENTRY_STATE_LOADED;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t assetLocaleDispose(assetentry_t *entry) {
|
||||
assertNotNull(entry, "Asset entry cannot be NULL");
|
||||
assertTrue(entry->type == ASSET_LOADER_TYPE_LOCALE, "Invalid type.");
|
||||
assertIsMainThread("Must be called from the main thread.");
|
||||
|
||||
assetlocalefile_t *localeFile = &entry->data.locale;
|
||||
errorChain(assetFileClose(&localeFile->file));
|
||||
return assetFileDispose(&localeFile->file);
|
||||
}
|
||||
|
||||
// These functions probably need some cleaning;
|
||||
errorret_t assetLocaleParseHeader(
|
||||
assetlocalefile_t *localeFile,
|
||||
char_t *headerBuffer,
|
||||
const size_t headerBufferSize
|
||||
) {
|
||||
assertNotNull(localeFile, "Locale file cannot be NULL.");
|
||||
assertNotNull(headerBuffer, "Header buffer cannot be NULL.");
|
||||
assertTrue(headerBufferSize > 0, "Header buffer size must be > 0.");
|
||||
|
||||
// Find "Plural-Forms: " line and parse out plural form info
|
||||
char_t *pluralFormsLine = strstr(headerBuffer, "Plural-Forms:");
|
||||
if(!pluralFormsLine) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
pluralFormsLine += strlen("Plural-Forms:");
|
||||
|
||||
// Expect nplurals
|
||||
char_t *npluralsStr = strstr(pluralFormsLine, "nplurals=");
|
||||
if(!npluralsStr) {
|
||||
errorThrow("Failed to find nplurals in Plural-Forms header.");
|
||||
}
|
||||
npluralsStr += strlen("nplurals=");
|
||||
localeFile->pluralStateCount = (uint8_t)atoi(npluralsStr);
|
||||
|
||||
if(localeFile->pluralStateCount == 0) {
|
||||
errorThrow("nplurals must be greater than 0.");
|
||||
}
|
||||
if(localeFile->pluralStateCount > ASSET_LOCALE_FILE_PLURAL_FORM_COUNT) {
|
||||
errorThrow(
|
||||
"nplurals exceeds maximum supported plural forms: %d > %d",
|
||||
localeFile->pluralStateCount,
|
||||
ASSET_LOCALE_FILE_PLURAL_FORM_COUNT
|
||||
);
|
||||
}
|
||||
|
||||
// Expect plural=
|
||||
char_t *pluralStr = strstr(pluralFormsLine, "plural=");
|
||||
if(!pluralStr) {
|
||||
errorThrow("Failed to find plural in Plural-Forms header.");
|
||||
}
|
||||
pluralStr += strlen("plural=");
|
||||
|
||||
// Expect ( [expressions] )
|
||||
char_t *openParen = strchr(pluralStr, '(');
|
||||
char_t *closeParen = strrchr(pluralStr, ')');
|
||||
if(!openParen || !closeParen || closeParen < openParen) {
|
||||
errorThrow("Failed to find plural expression in Plural-Forms header.");
|
||||
}
|
||||
|
||||
// Parse:
|
||||
// n [op] value ? index : n [op] value ? index : ... : final_index
|
||||
char_t *ptr = openParen + 1;
|
||||
uint8_t pluralIndex = 0;
|
||||
uint8_t definedCount = 0;
|
||||
|
||||
while(1) {
|
||||
while(*ptr == ' ') ptr++;
|
||||
|
||||
// Allow grouped subexpressions like:
|
||||
// (n<7 ? 2 : 3)
|
||||
// or
|
||||
// (((3)))
|
||||
uint8_t parenDepth = 0;
|
||||
while(*ptr == '(') {
|
||||
parenDepth++;
|
||||
ptr++;
|
||||
while(*ptr == ' ') ptr++;
|
||||
}
|
||||
|
||||
// Final fallback: just an integer
|
||||
if(*ptr != 'n') {
|
||||
char_t *endPtr = NULL;
|
||||
int32_t finalIndex = (int32_t)strtol(ptr, &endPtr, 10);
|
||||
if(endPtr == ptr) {
|
||||
errorThrow("Expected final plural index.");
|
||||
}
|
||||
ptr = endPtr;
|
||||
|
||||
while(*ptr == ' ') ptr++;
|
||||
while(parenDepth > 0) {
|
||||
if(*ptr != ')') {
|
||||
errorThrow("Expected ')' after final plural index.");
|
||||
}
|
||||
ptr++;
|
||||
parenDepth--;
|
||||
|
||||
while(*ptr == ' ') ptr++;
|
||||
}
|
||||
|
||||
if(*ptr != ')') {
|
||||
errorThrow("Expected ')' at end of plural expression.");
|
||||
}
|
||||
|
||||
if(finalIndex < 0 || finalIndex >= localeFile->pluralStateCount) {
|
||||
errorThrow(
|
||||
"Final plural expression index out of bounds: %d (nplurals: %d)",
|
||||
finalIndex,
|
||||
localeFile->pluralStateCount
|
||||
);
|
||||
}
|
||||
|
||||
localeFile->pluralDefaultIndex = (uint8_t)finalIndex;
|
||||
definedCount++;
|
||||
break;
|
||||
}
|
||||
|
||||
if(pluralIndex >= localeFile->pluralStateCount - 1) {
|
||||
errorThrow(
|
||||
"Too many plural conditions. Expected %d clauses for nplurals=%d.",
|
||||
localeFile->pluralStateCount - 1,
|
||||
localeFile->pluralStateCount
|
||||
);
|
||||
}
|
||||
|
||||
ptr++; // skip 'n'
|
||||
|
||||
while(*ptr == ' ') ptr++;
|
||||
|
||||
// Determine operator
|
||||
assetlocalepluraloperation_t op;
|
||||
if(strncmp(ptr, "==", 2) == 0) {
|
||||
op = ASSET_LOCALE_PLURAL_OP_EQUAL;
|
||||
ptr += 2;
|
||||
} else if(strncmp(ptr, "!=", 2) == 0) {
|
||||
op = ASSET_LOCALE_PLURAL_OP_NOT_EQUAL;
|
||||
ptr += 2;
|
||||
} else if(strncmp(ptr, "<=", 2) == 0) {
|
||||
op = ASSET_LOCALE_PLURAL_OP_LESS_EQUAL;
|
||||
ptr += 2;
|
||||
} else if(strncmp(ptr, ">=", 2) == 0) {
|
||||
op = ASSET_LOCALE_PLURAL_OP_GREATER_EQUAL;
|
||||
ptr += 2;
|
||||
} else if(*ptr == '<') {
|
||||
op = ASSET_LOCALE_PLURAL_OP_LESS;
|
||||
ptr++;
|
||||
} else if(*ptr == '>') {
|
||||
op = ASSET_LOCALE_PLURAL_OP_GREATER;
|
||||
ptr++;
|
||||
} else {
|
||||
errorThrow("Unsupported plural operator.");
|
||||
}
|
||||
|
||||
while(*ptr == ' ') ptr++;
|
||||
|
||||
// Parse the comparitor value
|
||||
char_t *endPtr = NULL;
|
||||
int32_t value = (int32_t)strtol(ptr, &endPtr, 10);
|
||||
if(endPtr == ptr) {
|
||||
errorThrow("Expected value for plural expression.");
|
||||
}
|
||||
ptr = endPtr;
|
||||
|
||||
while(*ptr == ' ') ptr++;
|
||||
|
||||
// Parse ternary operator
|
||||
if(*ptr != '?') {
|
||||
errorThrow("Expected '?' after plural expression.");
|
||||
}
|
||||
ptr++;
|
||||
|
||||
while(*ptr == ' ') ptr++;
|
||||
|
||||
// Parse the indice
|
||||
endPtr = NULL;
|
||||
int32_t index = (int32_t)strtol(ptr, &endPtr, 10);
|
||||
if(endPtr == ptr) {
|
||||
errorThrow("Expected index for plural expression.");
|
||||
}
|
||||
ptr = endPtr;
|
||||
|
||||
if(index < 0 || index >= localeFile->pluralStateCount) {
|
||||
errorThrow(
|
||||
"Plural expression index out of bounds: %d (nplurals: %d)",
|
||||
index,
|
||||
localeFile->pluralStateCount
|
||||
);
|
||||
}
|
||||
|
||||
// Store plural expression.
|
||||
localeFile->pluralIndices[pluralIndex] = (uint8_t)index;
|
||||
localeFile->pluralOps[pluralIndex] = op;
|
||||
localeFile->pluralValues[pluralIndex] = value;
|
||||
pluralIndex++;
|
||||
definedCount++;
|
||||
|
||||
while(*ptr == ' ') ptr++;
|
||||
|
||||
// Close any grouping parens that wrapped this conditional branch
|
||||
while(parenDepth > 0) {
|
||||
if(*ptr != ')') {
|
||||
break;
|
||||
}
|
||||
ptr++;
|
||||
parenDepth--;
|
||||
|
||||
while(*ptr == ' ') ptr++;
|
||||
}
|
||||
|
||||
if(*ptr != ':') {
|
||||
errorThrow("Expected ':' after plural expression.");
|
||||
}
|
||||
ptr++;
|
||||
}
|
||||
|
||||
// Must define exactly nplurals outcomes:
|
||||
// (nplurals - 1) conditional results + 1 final fallback result
|
||||
if(
|
||||
pluralIndex != localeFile->pluralStateCount - 1 ||
|
||||
definedCount != localeFile->pluralStateCount
|
||||
) {
|
||||
errorThrow("Plural expression count does not match nplurals.");
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
|
||||
uint8_t assetLocaleEvaluatePlural(
|
||||
assetlocalefile_t *file,
|
||||
const int32_t pluralCount
|
||||
) {
|
||||
assertNotNull(file, "Locale file cannot be NULL.");
|
||||
assertTrue(pluralCount >= 0, "Plural count cannot be negative.");
|
||||
|
||||
for(uint8_t i = 0; i < file->pluralStateCount - 1; i++) {
|
||||
int32_t value = file->pluralValues[i];
|
||||
switch(file->pluralOps[i]) {
|
||||
case ASSET_LOCALE_PLURAL_OP_EQUAL:
|
||||
if(pluralCount == value) return file->pluralIndices[i];
|
||||
break;
|
||||
|
||||
case ASSET_LOCALE_PLURAL_OP_NOT_EQUAL:
|
||||
if(pluralCount != value) return file->pluralIndices[i];
|
||||
break;
|
||||
|
||||
case ASSET_LOCALE_PLURAL_OP_LESS:
|
||||
if(pluralCount < value) return file->pluralIndices[i];
|
||||
break;
|
||||
|
||||
case ASSET_LOCALE_PLURAL_OP_LESS_EQUAL:
|
||||
if(pluralCount <= value) return file->pluralIndices[i];
|
||||
break;
|
||||
|
||||
case ASSET_LOCALE_PLURAL_OP_GREATER:
|
||||
if(pluralCount > value) return file->pluralIndices[i];
|
||||
break;
|
||||
|
||||
case ASSET_LOCALE_PLURAL_OP_GREATER_EQUAL:
|
||||
if(pluralCount >= value) return file->pluralIndices[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return file->pluralDefaultIndex;
|
||||
}
|
||||
|
||||
errorret_t assetLocaleLineSkipBlanks(
|
||||
assetfilelinereader_t *reader,
|
||||
uint8_t *lineBuffer
|
||||
) {
|
||||
while(!reader->eof) {
|
||||
// Skip blank lines
|
||||
if(lineBuffer[0] == '\0') {
|
||||
errorret_t r = assetFileLineReaderNext(reader);
|
||||
if(errorIsNotOk(r)) {
|
||||
errorCatch(r);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip comment lines
|
||||
if(lineBuffer[0] == '#') {
|
||||
errorret_t r = assetFileLineReaderNext(reader);
|
||||
if(errorIsNotOk(r)) {
|
||||
errorCatch(r);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is line only spaces?
|
||||
size_t lineLength = strlen((char_t *)lineBuffer);
|
||||
size_t i;
|
||||
bool_t onlySpaces = true;
|
||||
for(i = 0; i < lineLength; i++) {
|
||||
if(lineBuffer[i] != ' ') {
|
||||
onlySpaces = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(onlySpaces) {
|
||||
errorret_t r = assetFileLineReaderNext(reader);
|
||||
if(errorIsNotOk(r)) {
|
||||
errorCatch(r);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t assetLocaleLineUnbuffer(
|
||||
assetfilelinereader_t *reader,
|
||||
uint8_t *lineBuffer,
|
||||
uint8_t *stringBuffer,
|
||||
const size_t stringBufferSize
|
||||
) {
|
||||
stringBuffer[0] = '\0';
|
||||
|
||||
// At the point this funciton is called, we are looking for an opening quote.
|
||||
char_t *start = strchr((char_t *)lineBuffer, '"');
|
||||
if(!start) {
|
||||
errorThrow("Expected open (0) \"");
|
||||
}
|
||||
|
||||
char *end = strchr(start + 1, '"');
|
||||
if(!end) {
|
||||
errorThrow("Expected close (0) \"");
|
||||
}
|
||||
*end = '\0';
|
||||
|
||||
if(strlen(start) >= stringBufferSize) {
|
||||
errorThrow("String buffer overflow");
|
||||
}
|
||||
memoryCopy(stringBuffer, start + 1, strlen(start));
|
||||
|
||||
// Now start buffering lines out
|
||||
while(!reader->eof) {
|
||||
errorret_t r = assetFileLineReaderNext(reader);
|
||||
if(errorIsNotOk(r)) {
|
||||
errorCatch(r);
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip blank lines
|
||||
r = assetLocaleLineSkipBlanks(reader, lineBuffer);
|
||||
if(errorIsNotOk(r)) {
|
||||
errorCatch(r);
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip starting spaces
|
||||
char_t *ptr = (char_t *)lineBuffer;
|
||||
while(*ptr == ' ') {
|
||||
ptr++;
|
||||
}
|
||||
|
||||
// Only consider lines starting with quote
|
||||
if(*ptr != '"') {
|
||||
break;
|
||||
}
|
||||
|
||||
ptr++; // move past first quote
|
||||
|
||||
bool_t escaping = false;
|
||||
char_t *dest = (char_t *)stringBuffer + strlen((char_t *)stringBuffer);
|
||||
while(*ptr) {
|
||||
if(escaping) {
|
||||
// Handle escape sequences
|
||||
switch(*ptr) {
|
||||
case 'n': *dest++ = '\n'; break;
|
||||
case 't': *dest++ = '\t'; break;
|
||||
case '\\': *dest++ = '\\'; break;
|
||||
case '"': *dest++ = '"'; break;
|
||||
default:
|
||||
errorThrow("Unknown escape sequence: \\%c", *ptr);
|
||||
}
|
||||
escaping = false;
|
||||
} else if(*ptr == '\\') {
|
||||
escaping = true;
|
||||
} else if(*ptr == '"') {
|
||||
// End of string
|
||||
break;
|
||||
} else {
|
||||
// Regular character
|
||||
*dest++ = *ptr;
|
||||
}
|
||||
if((size_t)(dest - (char_t *)stringBuffer) >= stringBufferSize) {
|
||||
errorThrow("String buffer overflow");
|
||||
}
|
||||
ptr++;
|
||||
}
|
||||
*dest = '\0';
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t assetLocaleGetString(
|
||||
assetlocalefile_t *file,
|
||||
const char_t *messageId,
|
||||
const int32_t pluralCount,
|
||||
char_t *stringBuffer,
|
||||
const size_t stringBufferSize
|
||||
) {
|
||||
assertNotNull(file, "Asset file cannot be NULL.");
|
||||
assertNotNull(messageId, "Message ID cannot be NULL.");
|
||||
assertTrue(pluralCount >= 0, "Plural index cannot be negative.");
|
||||
assertNotNull(stringBuffer, "String buffer cannot be NULL.");
|
||||
assertTrue(stringBufferSize > 0, "String buffer size must be > 0");
|
||||
assetfilelinereader_t reader;
|
||||
|
||||
bool_t msgidFound = false, msgidPluralFound = false, msgstrFound = false;
|
||||
uint8_t msgidBuffer[256];
|
||||
uint8_t msgidPluralBuffer[256];
|
||||
uint8_t readBuffer[1024];
|
||||
uint8_t lineBuffer[1024];
|
||||
uint8_t pluralIndex = 0xFF;
|
||||
|
||||
msgidBuffer[0] = '\0';
|
||||
msgidPluralBuffer[0] = '\0';
|
||||
stringBuffer[0] = '\0';
|
||||
|
||||
// Rewind and start reading lines.
|
||||
errorChain(assetFileRewind(&file->file));
|
||||
assetFileLineReaderInit(
|
||||
&reader,
|
||||
&file->file,
|
||||
readBuffer,
|
||||
sizeof(readBuffer),
|
||||
lineBuffer,
|
||||
sizeof(lineBuffer)
|
||||
);
|
||||
|
||||
// Skip blanks, comments, etc and start looking for msgid's
|
||||
errorChain(assetLocaleLineSkipBlanks(&reader, lineBuffer));
|
||||
|
||||
while(!reader.eof) {
|
||||
// Is this msgid?
|
||||
if(memoryCompare(lineBuffer, "msgid", 5) != 0) {
|
||||
errorChain(assetFileLineReaderNext(&reader));
|
||||
msgidBuffer[0] = '\0';
|
||||
continue;
|
||||
}
|
||||
|
||||
// Unbuffer the msgid
|
||||
assetLocaleLineUnbuffer(
|
||||
&reader, lineBuffer, (uint8_t *)msgidBuffer, sizeof(msgidBuffer)
|
||||
);
|
||||
|
||||
// Is this the needle?
|
||||
if(memoryCompare(msgidBuffer, messageId, strlen(messageId)) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
msgidFound = true;
|
||||
break;
|
||||
}
|
||||
if(!msgidFound) {
|
||||
errorThrow("Failed to find message ID: %s", messageId);
|
||||
}
|
||||
|
||||
// We are either going to see a msgstr or a msgid_plural
|
||||
while(!reader.eof) {
|
||||
errorChain(assetLocaleLineSkipBlanks(&reader, lineBuffer));
|
||||
|
||||
// Is msgid_plural?
|
||||
if(
|
||||
!msgidPluralFound &&
|
||||
memoryCompare(lineBuffer, "msgid_plural", 12) == 0
|
||||
) {
|
||||
// Yes, start reading plural ID
|
||||
assetLocaleLineUnbuffer(
|
||||
&reader,
|
||||
lineBuffer,
|
||||
(uint8_t *)msgidPluralBuffer,
|
||||
sizeof(msgidPluralBuffer)
|
||||
);
|
||||
msgidPluralFound = true;
|
||||
|
||||
// At this point we determine the plural index to use by running the
|
||||
// plural formula
|
||||
pluralIndex = assetLocaleEvaluatePlural(
|
||||
file,
|
||||
pluralCount
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Should be msgstr if not plural.
|
||||
if(memoryCompare(lineBuffer, "msgstr", 6) != 0) {
|
||||
errorThrow("Expected msgstr after msgid, found: %s", lineBuffer);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If plural we need an index
|
||||
if(msgidPluralFound) {
|
||||
// Skip blank chars
|
||||
char_t *ptr = (char_t *)lineBuffer + 6;
|
||||
while(*ptr == ' ') {
|
||||
ptr++;
|
||||
}
|
||||
|
||||
if(*ptr != '[') {
|
||||
errorThrow("Expected [ for plural form, found: %s", lineBuffer);
|
||||
}
|
||||
|
||||
ptr++; // move past [
|
||||
|
||||
// Parse until ]
|
||||
char *end = strchr(ptr, ']');
|
||||
if(!end) {
|
||||
errorThrow("Expected ] for plural form, found: %s", lineBuffer);
|
||||
}
|
||||
*end = '\0';
|
||||
|
||||
int32_t index = atoi(ptr);
|
||||
if(index != pluralIndex) {
|
||||
// Not the plural form we want, skip to the next useable line
|
||||
while(!reader.eof) {
|
||||
errorChain(assetFileLineReaderNext(&reader));
|
||||
errorChain(assetLocaleLineSkipBlanks(&reader, lineBuffer));
|
||||
if(
|
||||
lineBuffer[0] == '\"' ||
|
||||
lineBuffer[0] == '\0' ||
|
||||
lineBuffer[0] == '#'
|
||||
) continue;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Undo damage to line buffer from unbuffering.
|
||||
*end = ']';
|
||||
}
|
||||
|
||||
// Parse out msgstr
|
||||
errorChain(assetLocaleLineUnbuffer(
|
||||
&reader, lineBuffer, (uint8_t *)stringBuffer, stringBufferSize
|
||||
));
|
||||
msgstrFound = true;
|
||||
break;
|
||||
};
|
||||
|
||||
if(!msgstrFound) {
|
||||
errorThrow("Failed to find msgstr for message ID: %s", messageId);
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t assetLocaleGetStringWithVA(
|
||||
assetlocalefile_t *file,
|
||||
const char_t *messageId,
|
||||
const int32_t pluralIndex,
|
||||
char_t *buffer,
|
||||
const size_t bufferSize,
|
||||
...
|
||||
) {
|
||||
assertNotNull(file, "Asset file cannot be NULL.");
|
||||
assertNotNull(messageId, "Message ID cannot be NULL.");
|
||||
assertNotNull(buffer, "Buffer cannot be NULL.");
|
||||
assertTrue(bufferSize > 0, "Buffer size must be > 0.");
|
||||
assertTrue(pluralIndex >= 0, "Plural cannot be negative.");
|
||||
|
||||
char_t *tempBuffer;
|
||||
tempBuffer = memoryAllocate(bufferSize);
|
||||
errorret_t ret = assetLocaleGetString(
|
||||
file,
|
||||
messageId,
|
||||
pluralIndex,
|
||||
tempBuffer,
|
||||
bufferSize
|
||||
);
|
||||
if(errorIsNotOk(ret)) {
|
||||
memoryFree(tempBuffer);
|
||||
return ret;
|
||||
}
|
||||
|
||||
va_list args;
|
||||
va_start(args, bufferSize);
|
||||
int result = vsnprintf(buffer, bufferSize, tempBuffer, args);
|
||||
va_end(args);
|
||||
memoryFree(tempBuffer);
|
||||
|
||||
if(result < 0) {
|
||||
errorThrow("Failed to format locale string for ID: %s", messageId);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
errorret_t assetLocaleGetStringWithArgs(
|
||||
assetlocalefile_t *file,
|
||||
const char_t *id,
|
||||
const int32_t plural,
|
||||
char_t *buffer,
|
||||
const size_t bufferSize,
|
||||
const assetlocalearg_t *args,
|
||||
const size_t argCount
|
||||
) {
|
||||
assertNotNull(id, "Message ID cannot be NULL.");
|
||||
assertNotNull(buffer, "Buffer cannot be NULL.");
|
||||
assertTrue(bufferSize > 0, "Buffer size must be > 0.");
|
||||
assertTrue(plural >= 0, "Plural cannot be negative.");
|
||||
assertTrue(
|
||||
argCount == 0 || args != NULL,
|
||||
"Args cannot be NULL when argCount > 0."
|
||||
);
|
||||
|
||||
char_t *format = memoryAllocate(bufferSize);
|
||||
if(format == NULL) {
|
||||
errorThrow("Failed to allocate format buffer.");
|
||||
}
|
||||
|
||||
errorret_t ret = assetLocaleGetString(
|
||||
file,
|
||||
id,
|
||||
plural,
|
||||
format,
|
||||
bufferSize
|
||||
);
|
||||
if(errorIsNotOk(ret)) {
|
||||
memoryFree(format);
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t inIndex = 0;
|
||||
size_t outIndex = 0;
|
||||
size_t nextArg = 0;
|
||||
|
||||
buffer[0] = '\0';
|
||||
|
||||
while(format[inIndex] != '\0') {
|
||||
if(format[inIndex] != '%') {
|
||||
if(outIndex + 1 >= bufferSize) {
|
||||
memoryFree(format);
|
||||
errorThrow("Formatted locale string buffer overflow for ID: %s", id);
|
||||
}
|
||||
|
||||
buffer[outIndex++] = format[inIndex++];
|
||||
continue;
|
||||
}
|
||||
|
||||
inIndex++;
|
||||
|
||||
/* Escaped percent */
|
||||
if(format[inIndex] == '%') {
|
||||
if(outIndex + 1 >= bufferSize) {
|
||||
memoryFree(format);
|
||||
errorThrow("Formatted locale string buffer overflow for ID: %s", id);
|
||||
}
|
||||
|
||||
buffer[outIndex++] = '%';
|
||||
inIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(nextArg >= argCount) {
|
||||
memoryFree(format);
|
||||
errorThrow("Not enough locale arguments for ID: %s", id);
|
||||
}
|
||||
|
||||
{
|
||||
char_t specBuffer[32];
|
||||
char_t valueBuffer[256];
|
||||
size_t specLength = 0;
|
||||
int written = 0;
|
||||
char_t specifier;
|
||||
|
||||
specBuffer[specLength++] = '%';
|
||||
|
||||
/* Allow flags / width / precision */
|
||||
while(format[inIndex] != '\0') {
|
||||
char_t ch = format[inIndex];
|
||||
|
||||
if(
|
||||
ch == '-' || ch == '+' || ch == ' ' || ch == '#' || ch == '0' ||
|
||||
ch == '.' || (ch >= '0' && ch <= '9')
|
||||
) {
|
||||
if(specLength + 1 >= sizeof(specBuffer)) {
|
||||
memoryFree(format);
|
||||
errorThrow("Format specifier too long for ID: %s", id);
|
||||
}
|
||||
|
||||
specBuffer[specLength++] = ch;
|
||||
inIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if(format[inIndex] == '\0') {
|
||||
memoryFree(format);
|
||||
errorThrow("Incomplete format specifier for ID: %s", id);
|
||||
}
|
||||
|
||||
specifier = format[inIndex++];
|
||||
|
||||
if(specifier != 's' && specifier != 'd' && specifier != 'f') {
|
||||
memoryFree(format);
|
||||
errorThrow(
|
||||
"Unsupported format specifier '%%%c' for ID: %s",
|
||||
specifier,
|
||||
id
|
||||
);
|
||||
}
|
||||
|
||||
specBuffer[specLength++] = specifier;
|
||||
specBuffer[specLength] = '\0';
|
||||
|
||||
switch(specifier) {
|
||||
case 's':
|
||||
if(args[nextArg].type != ASSET_LOCALE_ARG_STRING) {
|
||||
memoryFree(format);
|
||||
errorThrow("Expected string locale argument for ID: %s", id);
|
||||
}
|
||||
|
||||
written = snprintf(
|
||||
valueBuffer,
|
||||
sizeof(valueBuffer),
|
||||
specBuffer,
|
||||
args[nextArg].stringValue ? args[nextArg].stringValue : ""
|
||||
);
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
if(args[nextArg].type != ASSET_LOCALE_ARG_INT) {
|
||||
memoryFree(format);
|
||||
errorThrow("Expected int locale argument for ID: %s", id);
|
||||
}
|
||||
|
||||
written = snprintf(
|
||||
valueBuffer,
|
||||
sizeof(valueBuffer),
|
||||
specBuffer,
|
||||
args[nextArg].intValue
|
||||
);
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
if(
|
||||
args[nextArg].type != ASSET_LOCALE_ARG_FLOAT &&
|
||||
args[nextArg].type != ASSET_LOCALE_ARG_INT
|
||||
) {
|
||||
memoryFree(format);
|
||||
errorThrow("Expected float or int locale argument for ID: %s", id);
|
||||
}
|
||||
|
||||
float_t floatValue = (
|
||||
args[nextArg].type == ASSET_LOCALE_ARG_FLOAT ?
|
||||
args[nextArg].floatValue :
|
||||
(float_t)args[nextArg].intValue
|
||||
);
|
||||
|
||||
written = snprintf(
|
||||
valueBuffer,
|
||||
sizeof(valueBuffer),
|
||||
specBuffer,
|
||||
floatValue
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
nextArg++;
|
||||
|
||||
if(written < 0) {
|
||||
memoryFree(format);
|
||||
errorThrow("Failed to format locale string for ID: %s", id);
|
||||
}
|
||||
|
||||
if(outIndex + (size_t)written >= bufferSize) {
|
||||
memoryFree(format);
|
||||
errorThrow("Formatted locale string buffer overflow for ID: %s", id);
|
||||
}
|
||||
|
||||
memoryCopy(buffer + outIndex, valueBuffer, (size_t)written);
|
||||
outIndex += (size_t)written;
|
||||
}
|
||||
}
|
||||
|
||||
buffer[outIndex] = '\0';
|
||||
memoryFree(format);
|
||||
errorOk();
|
||||
}
|
||||
@@ -1,281 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "asset/assetfile.h"
|
||||
|
||||
typedef struct assetloading_s assetloading_t;
|
||||
typedef struct assetentry_s assetentry_t;
|
||||
|
||||
/** Input passed to the locale loader - currently unused. */
|
||||
typedef struct { void *nothing; } assetlocaleloaderinput_t;
|
||||
|
||||
typedef enum {
|
||||
ASSET_LOCALE_LOADER_STATE_INITIAL,
|
||||
ASSET_LOCALE_LOADER_STATE_LOAD_HEADER,
|
||||
ASSET_LOCALE_LOADER_STATE_DONE
|
||||
} assetlocaleloaderstate_t;
|
||||
|
||||
/** Per-slot scratch data used while the locale file is loading. */
|
||||
typedef struct {
|
||||
assetlocaleloaderstate_t state;
|
||||
} assetlocaleloaderloading_t;
|
||||
|
||||
/** Maximum number of distinct plural forms a locale file may declare. */
|
||||
#define ASSET_LOCALE_FILE_PLURAL_FORM_COUNT 6
|
||||
|
||||
/**
|
||||
* Comparison operator used in a plural-form expression.
|
||||
*
|
||||
* Each condition in the plural-form header is evaluated as
|
||||
* `n <op> value`
|
||||
* where `n` is the runtime plural count.
|
||||
*/
|
||||
typedef enum {
|
||||
ASSET_LOCALE_PLURAL_OP_EQUAL,
|
||||
ASSET_LOCALE_PLURAL_OP_NOT_EQUAL,
|
||||
ASSET_LOCALE_PLURAL_OP_LESS,
|
||||
ASSET_LOCALE_PLURAL_OP_LESS_EQUAL,
|
||||
ASSET_LOCALE_PLURAL_OP_GREATER,
|
||||
ASSET_LOCALE_PLURAL_OP_GREATER_EQUAL
|
||||
} assetlocalepluraloperation_t;
|
||||
|
||||
/**
|
||||
* Discriminator tag for a locale string argument.
|
||||
* @see assetlocalearg_t
|
||||
*/
|
||||
typedef enum {
|
||||
ASSET_LOCALE_ARG_STRING,
|
||||
ASSET_LOCALE_ARG_INT,
|
||||
ASSET_LOCALE_ARG_FLOAT
|
||||
} assetlocaleargtype_t;
|
||||
|
||||
/**
|
||||
* A single typed argument for locale string substitution.
|
||||
*
|
||||
* Used with @ref assetLocaleGetStringWithArgs to fill `%s`, `%d`, or `%f`
|
||||
* placeholders inside a translated string.
|
||||
*/
|
||||
typedef struct {
|
||||
/** Which union member is active. */
|
||||
assetlocaleargtype_t type;
|
||||
union {
|
||||
const char_t *stringValue;
|
||||
int32_t intValue;
|
||||
float_t floatValue;
|
||||
};
|
||||
} assetlocalearg_t;
|
||||
|
||||
/**
|
||||
* Runtime state for an open locale file.
|
||||
*
|
||||
* Loaded once by @ref assetLocaleLoaderSync and kept alive for the lifetime
|
||||
* of the asset entry. The plural-form fields are populated from the PO header
|
||||
* by @ref assetLocaleParseHeader.
|
||||
*
|
||||
* Plural evaluation works as a linear scan over `pluralStateCount - 1`
|
||||
* conditions; if none match, `pluralDefaultIndex` is returned.
|
||||
*/
|
||||
typedef struct {
|
||||
/** Underlying file handle used to rewind and re-read the PO data. */
|
||||
assetfile_t file;
|
||||
|
||||
/** Comparison operator for each conditional plural clause. */
|
||||
assetlocalepluraloperation_t pluralOps[ASSET_LOCALE_FILE_PLURAL_FORM_COUNT];
|
||||
|
||||
/** Right-hand value for each conditional plural clause. */
|
||||
int32_t pluralValues[ASSET_LOCALE_FILE_PLURAL_FORM_COUNT];
|
||||
|
||||
/** Form index returned when the corresponding condition is true. */
|
||||
int32_t pluralIndices[ASSET_LOCALE_FILE_PLURAL_FORM_COUNT];
|
||||
|
||||
/** Total number of plural forms declared by `nplurals=`. */
|
||||
uint8_t pluralStateCount;
|
||||
|
||||
/** Form index used when no conditional clause matches. */
|
||||
uint8_t pluralDefaultIndex;
|
||||
} assetlocalefile_t;
|
||||
|
||||
/** Convenience alias - the loaded output type of a locale asset entry. */
|
||||
typedef assetlocalefile_t assetlocaleoutput_t;
|
||||
|
||||
/**
|
||||
* Asynchronous loader callback. Opens the locale file, reads the PO header,
|
||||
* and parses plural-form rules. All I/O happens here so the main thread is
|
||||
* not blocked. Sets entry state to `ASSET_ENTRY_STATE_PENDING_SYNC` on
|
||||
* success or `ASSET_ENTRY_STATE_ERROR` on failure.
|
||||
*
|
||||
* @param loading The loading slot for this asset entry.
|
||||
* @return OK on success, error otherwise.
|
||||
*/
|
||||
errorret_t assetLocaleLoaderAsync(assetloading_t *loading);
|
||||
|
||||
/**
|
||||
* Synchronous loader callback. Confirms the async phase completed and marks
|
||||
* the entry as `ASSET_ENTRY_STATE_LOADED`.
|
||||
*
|
||||
* @param loading The loading slot for this asset entry.
|
||||
* @return OK on success, error otherwise.
|
||||
*/
|
||||
errorret_t assetLocaleLoaderSync(assetloading_t *loading);
|
||||
|
||||
/**
|
||||
* Dispose callback. Closes the open file handle and zeros the locale data.
|
||||
*
|
||||
* @param entry The asset entry to dispose.
|
||||
* @return OK on success, error otherwise.
|
||||
*/
|
||||
errorret_t assetLocaleDispose(assetentry_t *entry);
|
||||
|
||||
/**
|
||||
* Parses the `Plural-Forms:` line from a PO header string and populates the
|
||||
* plural-form fields of `localeFile`. If no `Plural-Forms:` key is present
|
||||
* the function returns OK without modifying the struct.
|
||||
*
|
||||
* Supports the `nplurals=N; plural=(<expr>);` syntax where `<expr>` is a
|
||||
* chain of `n <op> value ? index : ...` ternary conditions ending with a
|
||||
* fallback index.
|
||||
*
|
||||
* @param localeFile Struct whose plural fields will be filled in.
|
||||
* @param headerBuffer The raw PO header string (msgstr of msgid "").
|
||||
* @param headerBufferSize Size of `headerBuffer` in bytes.
|
||||
* @return OK on success, error if the plural expression is malformed.
|
||||
*/
|
||||
errorret_t assetLocaleParseHeader(
|
||||
assetlocalefile_t *localeFile,
|
||||
char_t *headerBuffer,
|
||||
const size_t headerBufferSize
|
||||
);
|
||||
|
||||
/**
|
||||
* Evaluates which plural form index to use for a given count.
|
||||
*
|
||||
* Walks the conditions stored in `file` in order; returns the index for the
|
||||
* first condition that matches `pluralCount`. Falls back to
|
||||
* `file->pluralDefaultIndex` if none match.
|
||||
*
|
||||
* @param file Locale file with parsed plural rules.
|
||||
* @param pluralCount The runtime count (e.g. number of items).
|
||||
* @return Zero-based plural form index.
|
||||
*/
|
||||
uint8_t assetLocaleEvaluatePlural(
|
||||
assetlocalefile_t *file,
|
||||
const int32_t pluralCount
|
||||
);
|
||||
|
||||
/**
|
||||
* Advances the line reader past blank lines, comment lines (starting with
|
||||
* `#`), and lines containing only spaces. Stops at the first content line or
|
||||
* end of file.
|
||||
*
|
||||
* @param reader Line reader positioned at the current line.
|
||||
* @param lineBuffer Output buffer that the reader writes each line into.
|
||||
* @return OK on success, error if reading fails.
|
||||
*/
|
||||
errorret_t assetLocaleLineSkipBlanks(
|
||||
assetfilelinereader_t *reader,
|
||||
uint8_t *lineBuffer
|
||||
);
|
||||
|
||||
/**
|
||||
* Reads a PO quoted string value from the current line and any immediately
|
||||
* following continuation lines that begin with `"`.
|
||||
*
|
||||
* Handles standard C escape sequences (`\n`, `\t`, `\\`, `\"`).
|
||||
*
|
||||
* @param reader Line reader positioned at the line containing the opening
|
||||
* quote (e.g. `msgstr "..."`).
|
||||
* @param lineBuffer Buffer filled on each @ref assetFileLineReaderNext
|
||||
* call; also used to detect continuation lines.
|
||||
* @param stringBuffer Destination for the unescaped string content.
|
||||
* @param stringBufferSize Capacity of `stringBuffer` in bytes.
|
||||
* @return OK on success, error if a quote or escape sequence is malformed or
|
||||
* the buffer would overflow.
|
||||
*/
|
||||
errorret_t assetLocaleLineUnbuffer(
|
||||
assetfilelinereader_t *reader,
|
||||
uint8_t *lineBuffer,
|
||||
uint8_t *stringBuffer,
|
||||
const size_t stringBufferSize
|
||||
);
|
||||
|
||||
/**
|
||||
* Looks up a translated string by message ID from the open locale file.
|
||||
*
|
||||
* Rewinds the file and scans from the beginning on every call. For plural
|
||||
* entries (`msgid_plural`) the `pluralCount` is evaluated against the loaded
|
||||
* plural rules to select the correct `msgstr[N]` form.
|
||||
*
|
||||
* @param file Locale file to search. Must be open.
|
||||
* @param messageId PO message ID to find (`""` retrieves the header entry).
|
||||
* @param pluralCount Count used to select the plural form (ignored for
|
||||
* singular entries).
|
||||
* @param stringBuffer Buffer to receive the translated string.
|
||||
* @param stringBufferSize Capacity of `stringBuffer` in bytes.
|
||||
* @return OK on success, error if the message ID is not found or I/O fails.
|
||||
*/
|
||||
errorret_t assetLocaleGetString(
|
||||
assetlocalefile_t *file,
|
||||
const char_t *messageId,
|
||||
const int32_t pluralCount,
|
||||
char_t *stringBuffer,
|
||||
const size_t stringBufferSize
|
||||
);
|
||||
|
||||
/**
|
||||
* Looks up a translated string and formats it with a `printf`-style variadic
|
||||
* argument list.
|
||||
*
|
||||
* Retrieves the raw format string via @ref assetLocaleGetString then applies
|
||||
* `vsnprintf` with the provided arguments.
|
||||
*
|
||||
* @param file Locale file to search. Must be open.
|
||||
* @param messageId PO message ID to find.
|
||||
* @param pluralCount Count used to select the plural form.
|
||||
* @param buffer Buffer to receive the formatted string.
|
||||
* @param bufferSize Capacity of `buffer` in bytes.
|
||||
* @param ... Format arguments matching the specifiers in the translated
|
||||
* string.
|
||||
* @return OK on success, error if the message is not found or formatting
|
||||
* fails.
|
||||
*/
|
||||
errorret_t assetLocaleGetStringWithVA(
|
||||
assetlocalefile_t *file,
|
||||
const char_t *messageId,
|
||||
const int32_t pluralCount,
|
||||
char_t *buffer,
|
||||
const size_t bufferSize,
|
||||
...
|
||||
);
|
||||
|
||||
/**
|
||||
* Looks up a translated string and substitutes typed arguments into its
|
||||
* `%s`, `%d`, and `%f` placeholders.
|
||||
*
|
||||
* Unlike @ref assetLocaleGetStringWithVA this variant accepts an explicit
|
||||
* array of @ref assetlocalearg_t values, which is safer to use from
|
||||
* generated or scripted call sites.
|
||||
*
|
||||
* @param file Locale file to search. Must be open.
|
||||
* @param messageId PO message ID to find.
|
||||
* @param pluralCount Count used to select the plural form.
|
||||
* @param buffer Buffer to receive the formatted string.
|
||||
* @param bufferSize Capacity of `buffer` in bytes.
|
||||
* @param args Array of typed arguments; may be NULL when `argCount` is 0.
|
||||
* @param argCount Number of elements in `args`.
|
||||
* @return OK on success, error if the message is not found, an argument type
|
||||
* mismatches the specifier, or the buffer would overflow.
|
||||
*/
|
||||
errorret_t assetLocaleGetStringWithArgs(
|
||||
assetlocalefile_t *file,
|
||||
const char_t *messageId,
|
||||
const int32_t pluralCount,
|
||||
char_t *buffer,
|
||||
const size_t bufferSize,
|
||||
const assetlocalearg_t *args,
|
||||
const size_t argCount
|
||||
);
|
||||
@@ -1,9 +0,0 @@
|
||||
# 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
|
||||
assetscriptloader.c
|
||||
)
|
||||
@@ -1,137 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "assetscriptloader.h"
|
||||
#include "asset/loader/assetloading.h"
|
||||
#include "asset/loader/assetentry.h"
|
||||
#include "asset/loader/assetloader.h"
|
||||
#include "script/module/require/modulerequire.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
#include <jerryscript.h>
|
||||
|
||||
errorret_t assetScriptLoaderAsync(assetloading_t *loading) {
|
||||
assertNotNull(loading, "Loading cannot be NULL");
|
||||
assertNotMainThread("Async loader should not be on main thread.");
|
||||
|
||||
if(loading->loading.script.state != ASSET_SCRIPT_LOADING_STATE_READ_FILE) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
assertNull(loading->loading.script.buffer, "Buffer already defined?");
|
||||
|
||||
assetfile_t *file = &loading->loading.script.file;
|
||||
assetLoaderErrorChain(
|
||||
loading, assetFileInit(file, loading->entry->name, NULL, NULL)
|
||||
);
|
||||
|
||||
uint8_t *buffer = NULL;
|
||||
size_t size = 0;
|
||||
assetLoaderErrorChain(loading, assetFileReadEntire(file, &buffer, &size));
|
||||
assetLoaderErrorChain(loading, assetFileDispose(file));
|
||||
|
||||
// Null-terminate for jerry_eval.
|
||||
memoryResize((void **)&buffer, size, size + 1);
|
||||
buffer[size] = '\0';
|
||||
|
||||
loading->loading.script.buffer = buffer;
|
||||
loading->loading.script.size = size;
|
||||
loading->loading.script.state = ASSET_SCRIPT_LOADING_STATE_EXEC;
|
||||
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t assetScriptLoaderSync(assetloading_t *loading) {
|
||||
assertNotNull(loading, "Loading cannot be NULL");
|
||||
assertTrue(loading->type == ASSET_LOADER_TYPE_SCRIPT, "Invalid type.");
|
||||
assertIsMainThread("Must be called from the main thread.");
|
||||
|
||||
switch(loading->loading.script.state) {
|
||||
case ASSET_SCRIPT_LOADING_STATE_INITIAL:
|
||||
loading->loading.script.state = ASSET_SCRIPT_LOADING_STATE_READ_FILE;
|
||||
loading->entry->state = ASSET_ENTRY_STATE_PENDING_ASYNC;
|
||||
errorOk();
|
||||
break;
|
||||
|
||||
case ASSET_SCRIPT_LOADING_STATE_EXEC:
|
||||
break;
|
||||
|
||||
default:
|
||||
errorOk();
|
||||
}
|
||||
|
||||
// Get read buffer
|
||||
uint8_t *buffer = loading->loading.script.buffer;
|
||||
assertNotNull(buffer, "Script buffer should have been loaded by now.");
|
||||
|
||||
// Get the current global script realm
|
||||
jerry_value_t global = jerry_current_realm();
|
||||
|
||||
// Replace globalThis.module with a new `module = {}`
|
||||
jerry_value_t oldModule = jerry_object_get_sz(global, "module");
|
||||
|
||||
jerry_value_t module = jerry_object();
|
||||
jerry_object_set_sz(global, "module", module);
|
||||
|
||||
// Eval the script, we handle failure later down the code.
|
||||
jerry_value_t result = jerry_eval(
|
||||
buffer,
|
||||
loading->loading.script.size,
|
||||
JERRY_PARSE_NO_OPTS
|
||||
);
|
||||
|
||||
// Free the read buffer
|
||||
memoryFree(buffer);
|
||||
loading->loading.script.buffer = NULL;
|
||||
|
||||
// Restore globalThis.module
|
||||
jerry_object_set_sz(global, "module", oldModule);
|
||||
jerry_value_free(oldModule);
|
||||
jerry_value_free(global);
|
||||
|
||||
if(jerry_value_is_exception(result)) {
|
||||
jerry_value_free(module);
|
||||
|
||||
loading->entry->data.script.exports = jerry_undefined();
|
||||
loading->entry->state = ASSET_ENTRY_STATE_ERROR;
|
||||
|
||||
// Get error string
|
||||
char_t buf[256];
|
||||
moduleBaseExceptionMessage(result, buf, sizeof(buf));
|
||||
jerry_value_free(result);
|
||||
assetLoaderErrorThrow(
|
||||
loading,
|
||||
"Script execution failed: %s: %s", loading->entry->name, buf
|
||||
);
|
||||
}
|
||||
|
||||
// Get module.exports
|
||||
jerry_value_t exports = jerry_object_get_sz(module, "exports");
|
||||
jerry_value_free(result);
|
||||
jerry_value_free(module);
|
||||
|
||||
// Store the exports.
|
||||
loading->entry->data.script.exports = exports;
|
||||
loading->entry->state = ASSET_ENTRY_STATE_LOADED;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t assetScriptDispose(assetentry_t *entry) {
|
||||
assertNotNull(entry, "Asset entry cannot be NULL");
|
||||
assertTrue(entry->type == ASSET_LOADER_TYPE_SCRIPT, "Invalid type.");
|
||||
assertIsMainThread("Must be called from the main thread.");
|
||||
|
||||
if(
|
||||
entry->data.script.exports != 0 &&
|
||||
!jerry_value_is_undefined(entry->data.script.exports)
|
||||
) {
|
||||
jerry_value_free(entry->data.script.exports);
|
||||
entry->data.script.exports = 0;
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "asset/assetfile.h"
|
||||
#include "script/scriptmodule.h"
|
||||
|
||||
#define ASSET_SCRIPT_CHUNK_SIZE 1024
|
||||
|
||||
typedef struct assetloading_s assetloading_t;
|
||||
typedef struct assetentry_s assetentry_t;
|
||||
|
||||
typedef struct {
|
||||
void *nothing;
|
||||
} assetscriptloaderinput_t;
|
||||
|
||||
typedef scriptmodule_t assetscriptoutput_t;
|
||||
|
||||
typedef enum {
|
||||
ASSET_SCRIPT_LOADING_STATE_INITIAL,
|
||||
ASSET_SCRIPT_LOADING_STATE_READ_FILE,
|
||||
ASSET_SCRIPT_LOADING_STATE_EXEC,
|
||||
ASSET_SCRIPT_LOADING_STATE_DONE
|
||||
} assetscriptloadingstate_t;
|
||||
|
||||
typedef struct {
|
||||
assetfile_t file;
|
||||
assetscriptloadingstate_t state;
|
||||
uint8_t *buffer;
|
||||
size_t size;
|
||||
} assetscriptloaderloading_t;
|
||||
|
||||
/**
|
||||
* Asynchronous loader for a script asset/module.
|
||||
*
|
||||
* @param loading The loading context.
|
||||
* @returns An error code and state.
|
||||
*/
|
||||
errorret_t assetScriptLoaderAsync(assetloading_t *loading);
|
||||
|
||||
/**
|
||||
* Synchronous loader for a script asset/module. This executes the script after
|
||||
* it has been loaded by the async loader.
|
||||
*
|
||||
* @param loading The loading context.
|
||||
* @returns An error code and state.
|
||||
*/
|
||||
errorret_t assetScriptLoaderSync(assetloading_t *loading);
|
||||
|
||||
/**
|
||||
* Disposes of a loaded script asset/module.
|
||||
*
|
||||
* @param entry The asset entry to dispose.
|
||||
* @returns An error code and state.
|
||||
*/
|
||||
errorret_t assetScriptDispose(assetentry_t *entry);
|
||||
@@ -65,6 +65,7 @@ void consoleUpdate(void) {
|
||||
|
||||
errorret_t consoleDraw(void) {
|
||||
if(!CONSOLE.visible) errorOk();
|
||||
if(!FONT_DEFAULT.tileset) errorOk();
|
||||
|
||||
for(uint32_t i = 0; i < CONSOLE_HISTORY_MAX; i++) {
|
||||
errorChain(textDraw(
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# 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
|
||||
cutscene.c
|
||||
)
|
||||
@@ -1,96 +0,0 @@
|
||||
// Copyright (c) 2026 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#include "cutscene.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "console/console.h"
|
||||
#include "time/time.h"
|
||||
|
||||
cutscene_t CUTSCENE;
|
||||
|
||||
errorret_t cutsceneInit(void) {
|
||||
memoryZero(&CUTSCENE, sizeof(cutscene_t));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t cutsceneUpdate(void) {
|
||||
#ifdef DUSK_TIME_DYNAMIC
|
||||
if(TIME.dynamicUpdate) {
|
||||
errorOk();
|
||||
}
|
||||
#endif
|
||||
|
||||
if(!CUTSCENE.active) errorOk();
|
||||
|
||||
cutsceneevent_t *event = &CUTSCENE.events[CUTSCENE.eventCurrent];
|
||||
if(event->onUpdate) errorChain(event->onUpdate());
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t cutscenePlay(
|
||||
const cutsceneevent_t *events,
|
||||
const uint8_t eventCount
|
||||
) {
|
||||
assertNotNull(events, "Events cannot be null");
|
||||
assertTrue(eventCount > 0, "Event count must be greater than zero");
|
||||
assertTrue(
|
||||
eventCount <= CUTSCENE_EVENT_COUNT_MAX,
|
||||
"Event count exceeds CUTSCENE_EVENT_COUNT_MAX"
|
||||
);
|
||||
|
||||
if(CUTSCENE.active) {
|
||||
errorChain(cutsceneStop());
|
||||
}
|
||||
|
||||
memoryCopy(CUTSCENE.events, events, sizeof(cutsceneevent_t) * eventCount);
|
||||
CUTSCENE.eventCount = eventCount;
|
||||
CUTSCENE.eventCurrent = 0;
|
||||
CUTSCENE.active = true;
|
||||
|
||||
cutsceneevent_t *firstEvent = &CUTSCENE.events[0];
|
||||
if(firstEvent->onStart) errorChain(firstEvent->onStart());
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t cutsceneAdvance(void) {
|
||||
if(!CUTSCENE.active) errorOk();
|
||||
|
||||
cutsceneevent_t *currentEvent = &CUTSCENE.events[CUTSCENE.eventCurrent];
|
||||
if(currentEvent->onEnd) errorChain(currentEvent->onEnd());
|
||||
CUTSCENE.eventCurrent++;
|
||||
|
||||
if(CUTSCENE.eventCurrent >= CUTSCENE.eventCount) {
|
||||
if(CUTSCENE.onStop) errorChain(CUTSCENE.onStop());
|
||||
CUTSCENE.active = false;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
cutsceneevent_t *nextEvent = &CUTSCENE.events[CUTSCENE.eventCurrent];
|
||||
if(nextEvent->onStart) errorChain(nextEvent->onStart());
|
||||
consolePrint("Cutscene advance");
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t cutsceneStop(void) {
|
||||
if(!CUTSCENE.active) errorOk();
|
||||
|
||||
cutsceneevent_t *currentEvent = &CUTSCENE.events[CUTSCENE.eventCurrent];
|
||||
if(currentEvent->onEnd) errorChain(currentEvent->onEnd());
|
||||
|
||||
if(CUTSCENE.onStop) errorChain(CUTSCENE.onStop());
|
||||
|
||||
CUTSCENE.active = false;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t cutsceneDispose(void) {
|
||||
errorChain(cutsceneStop());
|
||||
errorOk();
|
||||
}
|
||||
|
||||
bool_t cutsceneIsActive(void) {
|
||||
return CUTSCENE.active;
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
// Copyright (c) 2026 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
|
||||
#define CUTSCENE_EVENT_COUNT_MAX 16
|
||||
|
||||
typedef struct {
|
||||
errorret_t (*onStart)(void);
|
||||
errorret_t (*onEnd)(void);
|
||||
errorret_t (*onUpdate)(void);
|
||||
} cutsceneevent_t;
|
||||
|
||||
typedef struct {
|
||||
cutsceneevent_t events[CUTSCENE_EVENT_COUNT_MAX];
|
||||
uint8_t eventCount;
|
||||
uint8_t eventCurrent;
|
||||
errorret_t (*onStop)(void);
|
||||
bool_t active;
|
||||
} cutscene_t;
|
||||
|
||||
extern cutscene_t CUTSCENE;
|
||||
|
||||
/**
|
||||
* Initializes the cutscene manager.
|
||||
*
|
||||
* @return Any error state that happened.
|
||||
*/
|
||||
errorret_t cutsceneInit(void);
|
||||
|
||||
/**
|
||||
* Ticks the active cutscene event, calling its onUpdate callback.
|
||||
* Does nothing when no cutscene is playing.
|
||||
*
|
||||
* @return Any error state that happened.
|
||||
*/
|
||||
errorret_t cutsceneUpdate(void);
|
||||
|
||||
/**
|
||||
* Copies the given event array and begins playing from the first
|
||||
* event. If a cutscene is already playing it is stopped first.
|
||||
*
|
||||
* @param events Array of events to copy.
|
||||
* @param eventCount Number of events. Must be > 0 and
|
||||
* <= CUTSCENE_EVENT_COUNT_MAX.
|
||||
* @return Any error state that happened.
|
||||
*/
|
||||
errorret_t cutscenePlay(
|
||||
const cutsceneevent_t *events,
|
||||
const uint8_t eventCount
|
||||
);
|
||||
|
||||
/**
|
||||
* Ends the current event and starts the next one.
|
||||
* Marks the cutscene as inactive after the last event ends.
|
||||
* Does nothing when no cutscene is playing.
|
||||
*
|
||||
* @return Any error state that happened.
|
||||
*/
|
||||
errorret_t cutsceneAdvance(void);
|
||||
|
||||
/**
|
||||
* Ends the current event and stops the cutscene immediately.
|
||||
* Does nothing when no cutscene is playing.
|
||||
*
|
||||
* @return Any error state that happened.
|
||||
*/
|
||||
errorret_t cutsceneStop(void);
|
||||
|
||||
/**
|
||||
* Disposes of the cutscene manager, stopping any active cutscene.
|
||||
*
|
||||
* @return Any error state that happened.
|
||||
*/
|
||||
errorret_t cutsceneDispose(void);
|
||||
|
||||
/**
|
||||
* Returns whether a cutscene is currently playing.
|
||||
*
|
||||
* @return true if a cutscene is active.
|
||||
*/
|
||||
bool_t cutsceneIsActive(void);
|
||||
@@ -7,6 +7,7 @@
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
display.c
|
||||
renderpipeline.c
|
||||
)
|
||||
|
||||
# Subdirectories
|
||||
|
||||
+39
-12
@@ -1,13 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "display/display.h"
|
||||
#include "display/framebuffer/framebuffer.h"
|
||||
#include "scene/scene.h"
|
||||
#include "display/renderpipeline.h"
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
#include "display/mesh/quad.h"
|
||||
#include "display/mesh/cube.h"
|
||||
@@ -21,8 +21,10 @@
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
#include "asset/asset.h"
|
||||
#include "display/shader/shaderlist.h"
|
||||
#include "display/shader/shaderunlit.h"
|
||||
#include "entity/component/display/entitycamera.h"
|
||||
#include "entity/component.h"
|
||||
#include "time/time.h"
|
||||
|
||||
display_t DISPLAY = { 0 };
|
||||
@@ -46,14 +48,12 @@ errorret_t displayInit(void) {
|
||||
errorChain(planeInit());
|
||||
errorChain(capsuleInit());
|
||||
errorChain(triPrismInit());
|
||||
|
||||
|
||||
errorChain(frameBufferInitBackBuffer());
|
||||
errorChain(spriteBatchInit());
|
||||
errorChain(textInit());
|
||||
errorChain(screenInit());
|
||||
|
||||
// Setup initial shader with default values
|
||||
|
||||
errorChain(shaderListInit());
|
||||
|
||||
errorOk();
|
||||
@@ -67,15 +67,43 @@ errorret_t displayUpdate(void) {
|
||||
// Reset state
|
||||
spriteBatchClear();
|
||||
errorChain(frameBufferBind(NULL));
|
||||
|
||||
// Bind screen and render scene
|
||||
|
||||
// Bind screen and render
|
||||
errorChain(screenBind());
|
||||
frameBufferClear(
|
||||
FRAMEBUFFER_CLEAR_COLOR | FRAMEBUFFER_CLEAR_DEPTH,
|
||||
SCREEN.background
|
||||
);
|
||||
|
||||
errorChain(sceneRender());
|
||||
errorChain(componentRenderAll());
|
||||
errorChain(renderPipeline(entityCameraGetCurrent()));
|
||||
|
||||
// UI Rendering
|
||||
mat4 proj, view, ident;
|
||||
glm_mat4_identity(ident);
|
||||
errorChain(shaderBind(&SHADER_UNLIT));
|
||||
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, ident));
|
||||
|
||||
glm_ortho(
|
||||
0.0f, SCREEN.width,
|
||||
SCREEN.height, 0.0f,
|
||||
0.1f, 100.0f,
|
||||
proj
|
||||
);
|
||||
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_PROJECTION, proj));
|
||||
|
||||
glm_lookat(
|
||||
(vec3){ 0.0f, 0.0f, 1.0f },
|
||||
(vec3){ 0.0f, 0.0f, 0.0f },
|
||||
(vec3){ 0.0f, 1.0f, 0.0f },
|
||||
view
|
||||
);
|
||||
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, view));
|
||||
|
||||
errorChain(displaySetState((displaystate_t){
|
||||
.flags = DISPLAY_STATE_FLAG_BLEND
|
||||
}));
|
||||
errorChain(uiRender());
|
||||
|
||||
// Finish up
|
||||
screenUnbind();
|
||||
@@ -100,11 +128,10 @@ errorret_t displayDispose(void) {
|
||||
errorChain(spriteBatchDispose());
|
||||
screenDispose();
|
||||
errorChain(textDispose());
|
||||
|
||||
|
||||
#ifdef displayPlatformDispose
|
||||
displayPlatformDispose();
|
||||
#endif
|
||||
|
||||
// For now, we just return an OK error.
|
||||
errorOk();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "scenerenderpipeline.h"
|
||||
#include "renderpipeline.h"
|
||||
#include "entity/entitymanager.h"
|
||||
#include "entity/component/display/entityposition.h"
|
||||
#include "entity/component/display/entitycamera.h"
|
||||
@@ -14,7 +14,7 @@
|
||||
#include "display/displaystate.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
int8_t sceneRenderPipelineGetPriority(const entityrenderable_t *r) {
|
||||
int8_t renderPipelineGetPriority(const entityrenderable_t *r) {
|
||||
assertNotNull(r, "Renderable cannot be null");
|
||||
if(r->priority != 0) return r->priority;
|
||||
switch(r->type) {
|
||||
@@ -29,15 +29,15 @@ int8_t sceneRenderPipelineGetPriority(const entityrenderable_t *r) {
|
||||
}
|
||||
}
|
||||
|
||||
int_t sceneRenderPipelineCompare(const void *a, const void *b) {
|
||||
int_t renderPipelineCompare(const void *a, const void *b) {
|
||||
assertNotNull(a, "Entry a cannot be null");
|
||||
assertNotNull(b, "Entry b cannot be null");
|
||||
const scenerenderpipelineentry_t *ea = (const scenerenderpipelineentry_t *)a;
|
||||
const scenerenderpipelineentry_t *eb = (const scenerenderpipelineentry_t *)b;
|
||||
const renderpipelineentry_t *ea = (const renderpipelineentry_t *)a;
|
||||
const renderpipelineentry_t *eb = (const renderpipelineentry_t *)b;
|
||||
return (int_t)ea->effectivePriority - (int_t)eb->effectivePriority;
|
||||
}
|
||||
|
||||
shader_t *sceneRenderPipelineGetShader(const entityrenderable_t *r) {
|
||||
shader_t *renderPipelineGetShader(const entityrenderable_t *r) {
|
||||
assertNotNull(r, "Renderable cannot be null");
|
||||
switch(r->type) {
|
||||
case ENTITY_RENDERABLE_TYPE_SPRITEBATCH:
|
||||
@@ -50,7 +50,7 @@ shader_t *sceneRenderPipelineGetShader(const entityrenderable_t *r) {
|
||||
}
|
||||
}
|
||||
|
||||
errorret_t sceneRenderPipeline(const entityid_t cameraId) {
|
||||
errorret_t renderPipeline(const entityid_t cameraId) {
|
||||
mat4 proj, view, model, ident;
|
||||
glm_mat4_identity(ident);
|
||||
|
||||
@@ -62,20 +62,20 @@ errorret_t sceneRenderPipeline(const entityid_t cameraId) {
|
||||
|
||||
if(cameraId == ENTITY_ID_INVALID || entCount == 0) errorOk();
|
||||
|
||||
scenerenderpipelineentry_t pipeline[ENTITY_COUNT_MAX];
|
||||
renderpipelineentry_t pipeline[ENTITY_COUNT_MAX];
|
||||
for(entityid_t i = 0; i < entCount; i++) {
|
||||
entityrenderable_t *r = componentGetData(
|
||||
entities[i], components[i], COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
pipeline[i].entityId = entities[i];
|
||||
pipeline[i].componentId = components[i];
|
||||
pipeline[i].effectivePriority = sceneRenderPipelineGetPriority(r);
|
||||
pipeline[i].effectivePriority = renderPipelineGetPriority(r);
|
||||
}
|
||||
sort(
|
||||
pipeline,
|
||||
(size_t)entCount,
|
||||
sizeof(scenerenderpipelineentry_t),
|
||||
sceneRenderPipelineCompare
|
||||
sizeof(renderpipelineentry_t),
|
||||
renderPipelineCompare
|
||||
);
|
||||
|
||||
componentid_t camPos = entityGetComponent(cameraId, COMPONENT_TYPE_POSITION);
|
||||
@@ -111,7 +111,7 @@ errorret_t sceneRenderPipeline(const entityid_t cameraId) {
|
||||
entityrenderable_t *r = componentGetData(
|
||||
eid, cid, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
shader_t *s = sceneRenderPipelineGetShader(r);
|
||||
shader_t *s = renderPipelineGetShader(r);
|
||||
|
||||
componentid_t posComp = entityGetComponent(eid, COMPONENT_TYPE_POSITION);
|
||||
if(posComp == COMPONENT_ID_INVALID) {
|
||||
@@ -15,27 +15,28 @@ typedef struct {
|
||||
entityid_t entityId;
|
||||
componentid_t componentId;
|
||||
int8_t effectivePriority;
|
||||
} scenerenderpipelineentry_t;
|
||||
} renderpipelineentry_t;
|
||||
|
||||
/**
|
||||
* Returns the effective render priority for a renderable. When the renderable's
|
||||
* explicit priority is non-zero that value is returned directly; otherwise an
|
||||
* automatic value is derived from the renderable type and display state flags.
|
||||
* Returns the effective render priority for a renderable. When the
|
||||
* renderable's explicit priority is non-zero that value is returned directly;
|
||||
* otherwise an automatic value is derived from the renderable type and display
|
||||
* state flags.
|
||||
*
|
||||
* @param r The renderable component data.
|
||||
* @return Effective priority: lower renders first, higher renders last.
|
||||
*/
|
||||
int8_t sceneRenderPipelineGetPriority(const entityrenderable_t *r);
|
||||
int8_t renderPipelineGetPriority(const entityrenderable_t *r);
|
||||
|
||||
/**
|
||||
* sortcompare_t comparator for scenerenderpipelineentry_t. Compares by
|
||||
* sortcompare_t comparator for renderpipelineentry_t. Compares by
|
||||
* effectivePriority ascending so lower-priority entries sort first.
|
||||
*
|
||||
* @param a Pointer to the first scenerenderpipelineentry_t.
|
||||
* @param b Pointer to the second scenerenderpipelineentry_t.
|
||||
* @param a Pointer to the first renderpipelineentry_t.
|
||||
* @param b Pointer to the second renderpipelineentry_t.
|
||||
* @return Negative, zero, or positive.
|
||||
*/
|
||||
int_t sceneRenderPipelineCompare(const void *a, const void *b);
|
||||
int_t renderPipelineCompare(const void *a, const void *b);
|
||||
|
||||
/**
|
||||
* Returns the shader that will be used to render the given renderable.
|
||||
@@ -45,7 +46,7 @@ int_t sceneRenderPipelineCompare(const void *a, const void *b);
|
||||
* @param r The renderable component data.
|
||||
* @return Pointer to the shader, never NULL.
|
||||
*/
|
||||
shader_t *sceneRenderPipelineGetShader(const entityrenderable_t *r);
|
||||
shader_t *renderPipelineGetShader(const entityrenderable_t *r);
|
||||
|
||||
/**
|
||||
* Renders all entities with renderable components in priority order.
|
||||
@@ -56,4 +57,4 @@ shader_t *sceneRenderPipelineGetShader(const entityrenderable_t *r);
|
||||
* @param cameraId The entity ID of the active camera, or ENTITY_ID_INVALID.
|
||||
* @return Error state.
|
||||
*/
|
||||
errorret_t sceneRenderPipeline(const entityid_t cameraId);
|
||||
errorret_t renderPipeline(const entityid_t cameraId);
|
||||
@@ -9,34 +9,18 @@
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
#include "asset/asset.h"
|
||||
#include "asset/loader/display/assettextureloader.h"
|
||||
#include "asset/loader/display/assettilesetloader.h"
|
||||
#include "display/shader/shaderunlit.h"
|
||||
|
||||
font_t FONT_DEFAULT;
|
||||
|
||||
errorret_t textInit(void) {
|
||||
assetloaderinput_t input = { .texture = TEXTURE_FORMAT_RGBA };
|
||||
assetentry_t *entryTexture = assetLock(
|
||||
"ui/minogram.png", ASSET_LOADER_TYPE_TEXTURE, &input
|
||||
);
|
||||
assetentry_t *entryTileset = assetLock(
|
||||
"ui/minogram.dtf", ASSET_LOADER_TYPE_TILESET, NULL
|
||||
);
|
||||
errorChain(assetRequireLoaded(entryTexture));
|
||||
errorChain(assetRequireLoaded(entryTileset));
|
||||
|
||||
FONT_DEFAULT.texture = &entryTexture->data.texture;
|
||||
FONT_DEFAULT.tileset = &entryTileset->data.tileset;
|
||||
memoryZero(&FONT_DEFAULT, sizeof(font_t));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t textDispose(void) {
|
||||
FONT_DEFAULT.texture = NULL;
|
||||
FONT_DEFAULT.tileset = NULL;
|
||||
assetUnlock("ui/minogram.png");
|
||||
assetUnlock("ui/minogram.dtf");
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -44,6 +28,7 @@ spritebatchsprite_t textGetSprite(
|
||||
const vec2 pos, const char_t c, const font_t *font
|
||||
) {
|
||||
assertNotNull(font, "Font cannot be NULL");
|
||||
assertNotNull(font->tileset, "Font tileset not loaded.");
|
||||
|
||||
// Change char from ASCII to a tile index.
|
||||
int32_t tileIndex = (int32_t)(c) - TEXT_CHAR_START;
|
||||
@@ -81,6 +66,8 @@ errorret_t textDraw(
|
||||
font_t *font
|
||||
) {
|
||||
assertNotNull(text, "Text cannot be NULL");
|
||||
assertNotNull(font, "Font cannot be NULL");
|
||||
if(!font->tileset) errorOk();
|
||||
|
||||
spritebatchsprite_t sprite;
|
||||
shadermaterial_t material = {
|
||||
@@ -130,8 +117,10 @@ void textMeasure(
|
||||
int32_t *outHeight
|
||||
) {
|
||||
assertNotNull(text, "Text cannot be NULL");
|
||||
assertNotNull(font, "Font cannot be NULL");
|
||||
assertNotNull(outWidth, "Output width pointer cannot be NULL");
|
||||
assertNotNull(outHeight, "Output height pointer cannot be NULL");
|
||||
if(!font->tileset) { *outWidth = 0; *outHeight = 0; return; }
|
||||
|
||||
int32_t width = 0;
|
||||
int32_t height = font->tileset->tileHeight;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "asset/asset.h"
|
||||
#include "display/text/font.h"
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
|
||||
|
||||
@@ -11,9 +11,6 @@
|
||||
#include "input/input.h"
|
||||
#include "locale/localemanager.h"
|
||||
#include "display/display.h"
|
||||
#include "scene/scene.h"
|
||||
#include "cutscene/cutscene.h"
|
||||
#include "asset/asset.h"
|
||||
#include "ui/ui.h"
|
||||
#include "ui/uitextbox.h"
|
||||
#include "assert/assert.h"
|
||||
@@ -23,7 +20,6 @@
|
||||
#include "network/network.h"
|
||||
#include "system/system.h"
|
||||
#include "console/console.h"
|
||||
#include "script/script.h"
|
||||
#include "item/backpack.h"
|
||||
#include "save/save.h"
|
||||
|
||||
@@ -42,22 +38,17 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
||||
timeInit();
|
||||
consoleInit();
|
||||
errorChain(inputInit());
|
||||
errorChain(assetInit());
|
||||
// errorChain(saveInit());
|
||||
errorChain(localeManagerInit());
|
||||
errorChain(displayInit());
|
||||
errorChain(uiInit());
|
||||
errorChain(uiTextboxInit());
|
||||
errorChain(cutsceneInit());
|
||||
entityManagerInit();
|
||||
backpackInit();
|
||||
physicsManagerInit();
|
||||
errorChain(networkInit());
|
||||
errorChain(scriptInit());
|
||||
errorChain(sceneInit());
|
||||
|
||||
consolePrint("Engine initialized");
|
||||
errorChain(scriptExecFile("init.js"));
|
||||
|
||||
errorOk();
|
||||
}
|
||||
@@ -73,10 +64,6 @@ errorret_t engineUpdate(void) {
|
||||
errorChain(uiTextboxUpdate());
|
||||
physicsManagerUpdate();
|
||||
errorChain(displayUpdate());
|
||||
errorChain(cutsceneUpdate());
|
||||
errorChain(sceneUpdate());
|
||||
errorChain(assetUpdate());
|
||||
errorChain(scriptUpdate());
|
||||
|
||||
if(inputPressed(INPUT_ACTION_RAGEQUIT)) ENGINE.running = false;
|
||||
errorOk();
|
||||
@@ -88,9 +75,6 @@ void engineExit(void) {
|
||||
|
||||
errorret_t engineDispose(void) {
|
||||
uiTextboxDispose();
|
||||
cutsceneDispose();
|
||||
errorChain(sceneDispose());
|
||||
errorChain(scriptDispose());
|
||||
errorChain(networkDispose());
|
||||
entityManagerDispose();
|
||||
localeManagerDispose();
|
||||
@@ -98,7 +82,6 @@ errorret_t engineDispose(void) {
|
||||
consoleDispose();
|
||||
errorChain(displayDispose());
|
||||
// errorChain(saveDispose());
|
||||
errorChain(assetDispose());
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
@@ -19,25 +19,10 @@ errorret_t localeManagerInit() {
|
||||
|
||||
errorret_t localeManagerSetLocale(const localeinfo_t *locale) {
|
||||
assertNotNull(locale, "Locale cannot be NULL");
|
||||
|
||||
if(LOCALE.entry != NULL) {
|
||||
assetEntryUnlock(LOCALE.entry);
|
||||
LOCALE.entry = NULL;
|
||||
}
|
||||
|
||||
LOCALE.locale = locale;
|
||||
LOCALE.entry = assetGetEntry(locale->file, ASSET_LOADER_TYPE_LOCALE, NULL);
|
||||
assetEntryLock(LOCALE.entry);
|
||||
errorChain(assetRequireLoaded(LOCALE.entry));
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void localeManagerDispose() {
|
||||
if(LOCALE.entry != NULL) {
|
||||
assetEntryUnlock(LOCALE.entry);
|
||||
LOCALE.entry = NULL;
|
||||
}
|
||||
|
||||
LOCALE.locale = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
@@ -9,75 +9,29 @@
|
||||
#include "error/error.h"
|
||||
#include "localemanager.h"
|
||||
#include "locale/localeinfo.h"
|
||||
#include "asset/asset.h"
|
||||
|
||||
typedef struct {
|
||||
const localeinfo_t *locale;
|
||||
assetentry_t *entry;
|
||||
} localemanager_t;
|
||||
|
||||
extern localemanager_t LOCALE;
|
||||
|
||||
/**
|
||||
* Initialize the locale system.
|
||||
*
|
||||
*
|
||||
* @return An error code if a failure occurs.
|
||||
*/
|
||||
errorret_t localeManagerInit();
|
||||
|
||||
/**
|
||||
* Set the current locale.
|
||||
*
|
||||
*
|
||||
* @param locale The locale to set.
|
||||
* @return An error code if a failure occurs.
|
||||
*/
|
||||
errorret_t localeManagerSetLocale(const localeinfo_t *locale);
|
||||
|
||||
/**
|
||||
* Get a localized string for the given message ID.
|
||||
*
|
||||
* @param id The message ID to retrieve.
|
||||
* @param buffer Buffer to write the retrieved string to.
|
||||
* @param bufferSize Size of the buffer.
|
||||
* @param plural Plural index to retrieve.
|
||||
* @param ... Additional arguments for formatting the string.
|
||||
* @return An error code if a failure occurs.
|
||||
*/
|
||||
#define localeManagerGetText(id, buffer, bufferSize, plural, ...) \
|
||||
assetLocaleGetStringWithVA( \
|
||||
&LOCALE.entry->data.locale, \
|
||||
id, \
|
||||
plural, \
|
||||
buffer, \
|
||||
bufferSize, \
|
||||
__VA_ARGS__ \
|
||||
)
|
||||
|
||||
/**
|
||||
* Get a localized string for the given message ID with a list of arguments.
|
||||
*
|
||||
* @param id The message ID to retrieve.
|
||||
* @param buffer Buffer to write the retrieved string to.
|
||||
* @param bufferSize Size of the buffer.
|
||||
* @param plural Plural index to retrieve.
|
||||
* @param args List of arguments for formatting the string.
|
||||
* @param argCount Number of arguments in the list.
|
||||
* @return An error code if a failure occurs.
|
||||
*/
|
||||
#define localeManagerGetTextArgs( \
|
||||
id, buffer, bufferSize, plural, args, argCount \
|
||||
) \
|
||||
assetLocaleGetStringWithArgs( \
|
||||
&LOCALE.entry->data.locale, \
|
||||
id, \
|
||||
plural, \
|
||||
buffer, \
|
||||
bufferSize, \
|
||||
args, \
|
||||
argCount \
|
||||
)
|
||||
|
||||
/**
|
||||
* Dispose of the locale system.
|
||||
*/
|
||||
void localeManagerDispose();
|
||||
void localeManagerDispose();
|
||||
|
||||
@@ -7,11 +7,12 @@
|
||||
|
||||
#include "map.h"
|
||||
#include "assert/assert.h"
|
||||
#include "asset/assetfile.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
#include "console/console.h"
|
||||
|
||||
#define MAP_PATH_MAX 128
|
||||
|
||||
map_t MAP;
|
||||
|
||||
chunkindex_t mapChunkRelToIndex(
|
||||
@@ -39,7 +40,7 @@ errorret_t mapLoad(const char_t *handle) {
|
||||
memoryZero(&MAP, sizeof(map_t));
|
||||
stringCopy(MAP.handle, handle, MAP_HANDLE_MAX);
|
||||
|
||||
char_t path[ASSET_FILE_NAME_MAX];
|
||||
char_t path[MAP_PATH_MAX];
|
||||
stringFormat(path, sizeof(path), "maps/%s/init.js", handle);
|
||||
|
||||
consolePrint("Map loaded: %s", handle);
|
||||
|
||||
@@ -10,34 +10,11 @@
|
||||
#include "entity/entitymanager.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
#include "asset/asset.h"
|
||||
#include "console/console.h"
|
||||
|
||||
errorret_t mapChunkLoad(mapchunk_t *chunk) {
|
||||
chunk->entityCount = 0;
|
||||
memoryZero(chunk->entities, sizeof(chunk->entities));
|
||||
|
||||
if(MAP.handle[0] == '\0') errorOk();
|
||||
|
||||
char_t path[ASSET_FILE_NAME_MAX];
|
||||
stringFormat(
|
||||
path, sizeof(path),
|
||||
"maps/%s/chunks/%d_%d_%d.js",
|
||||
MAP.handle,
|
||||
(int)chunk->position.x,
|
||||
(int)chunk->position.y,
|
||||
(int)chunk->position.z
|
||||
);
|
||||
|
||||
if(!assetFileExists(path)) errorOk();
|
||||
|
||||
consolePrint(
|
||||
"Chunk loaded: %s [%d,%d,%d]",
|
||||
path,
|
||||
(int)chunk->position.x,
|
||||
(int)chunk->position.y,
|
||||
(int)chunk->position.z
|
||||
);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
include(duskjs2c)
|
||||
dusk_embed_js(${DUSK_LIBRARY_TARGET_NAME}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/scene.js
|
||||
)
|
||||
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
scene.c
|
||||
scenerenderpipeline.c
|
||||
)
|
||||
@@ -1,85 +0,0 @@
|
||||
// Copyright (c) 2026 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#include "scene.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "time/time.h"
|
||||
#include "display/screen/screen.h"
|
||||
#include "entity/entitymanager.h"
|
||||
#include "display/shader/shaderunlit.h"
|
||||
#include "display/display.h"
|
||||
#include "ui/ui.h"
|
||||
#include "scene/scenerenderpipeline.h"
|
||||
#include "entity/component.h"
|
||||
#include "asset/asset.h"
|
||||
#include "asset/loader/assetloader.h"
|
||||
#include "script/module/modulebase.h"
|
||||
#include "console/console.h"
|
||||
#include "script/script.h"
|
||||
#include "scene_js.h"
|
||||
|
||||
scene_t SCENE;
|
||||
|
||||
errorret_t sceneInit(void) {
|
||||
memoryZero(&SCENE, sizeof(scene_t));
|
||||
errorChain(scriptExecString(SCENE_JS));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t sceneUpdate(void) {
|
||||
#ifdef DUSK_TIME_DYNAMIC
|
||||
if(TIME.dynamicUpdate) {
|
||||
errorChain(scriptExecString("Scene.dynamicUpdate();"));
|
||||
} else {
|
||||
errorChain(scriptExecString("Scene.update();"));
|
||||
}
|
||||
#else
|
||||
errorChain(scriptExecString("Scene.update();"));
|
||||
errorChain(scriptExecString("Scene.dynamicUpdate();"));
|
||||
#endif
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t sceneRender(void) {
|
||||
mat4 proj, view, ident;
|
||||
glm_mat4_identity(ident);
|
||||
|
||||
errorChain(componentRenderAll());
|
||||
errorChain(sceneRenderPipeline(entityCameraGetCurrent()));
|
||||
|
||||
// UI Rendering
|
||||
errorChain(shaderBind(&SHADER_UNLIT));
|
||||
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, ident));
|
||||
|
||||
glm_ortho(
|
||||
0.0f, SCREEN.width,
|
||||
SCREEN.height, 0.0f,
|
||||
0.1f, 100.0f,
|
||||
proj
|
||||
);
|
||||
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_PROJECTION, proj));
|
||||
|
||||
glm_lookat(
|
||||
(vec3){ 0.0f, 0.0f, 1.0f },
|
||||
(vec3){ 0.0f, 0.0f, 0.0f },
|
||||
(vec3){ 0.0f, 1.0f, 0.0f },
|
||||
view
|
||||
);
|
||||
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, view));
|
||||
|
||||
errorChain(displaySetState((displaystate_t){
|
||||
.flags = DISPLAY_STATE_FLAG_BLEND
|
||||
}));
|
||||
errorChain(uiRender());
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t sceneDispose(void) {
|
||||
errorChain(scriptExecString("Scene.dispose();"));
|
||||
errorOk();
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
|
||||
typedef struct {
|
||||
void *nothing;
|
||||
} scene_t;
|
||||
|
||||
extern scene_t SCENE;
|
||||
|
||||
/**
|
||||
* Initialises the scene manager.
|
||||
*
|
||||
* @return An error if the init failed, or errorOk() if it succeeded.
|
||||
*/
|
||||
errorret_t sceneInit(void);
|
||||
|
||||
/**
|
||||
* Ticks the scene manager. Processes any pending scene transition, then
|
||||
* calls scriptSceneUpdate on the active scene.
|
||||
*
|
||||
* @return An error if the update failed, or errorOk() if it succeeded.
|
||||
*/
|
||||
errorret_t sceneUpdate(void);
|
||||
|
||||
/**
|
||||
* Renders the current scene (entities, render pipeline, UI).
|
||||
*
|
||||
* @return An error if the render failed, or errorOk() if it succeeded.
|
||||
*/
|
||||
errorret_t sceneRender(void);
|
||||
|
||||
/**
|
||||
* Disposes the active scene immediately.
|
||||
*
|
||||
* @return An error if the dispose failed, or errorOk() if it succeeded.
|
||||
*/
|
||||
errorret_t sceneDispose(void);
|
||||
@@ -1,40 +0,0 @@
|
||||
// Copyright (c) 2026 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
var Scene = {
|
||||
};
|
||||
|
||||
Scene.update = () => {
|
||||
if(!Scene.current || !Scene.current.update) return;
|
||||
Scene.current.update();
|
||||
}
|
||||
|
||||
Scene.dynamicUpdate = () => {
|
||||
if(!Scene.current || !Scene.current.dynamicUpdate) return;
|
||||
Scene.current.dynamicUpdate();
|
||||
}
|
||||
|
||||
Scene.set = (newScene) => {
|
||||
// Current scene active?
|
||||
if(Scene.current && Scene.current.dispose) {
|
||||
Scene.current.dispose();
|
||||
}
|
||||
|
||||
// Set new scene
|
||||
Scene.current = newScene;
|
||||
if(!newScene) return;
|
||||
|
||||
// Init
|
||||
if(newScene.init) {
|
||||
newScene.init();
|
||||
}
|
||||
};
|
||||
|
||||
Scene.dispose = () => {
|
||||
if(Scene.current && Scene.current.dispose) {
|
||||
Scene.current.dispose();
|
||||
}
|
||||
Scene.current = null;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
script.c
|
||||
scriptpromisepend.c
|
||||
scriptproto.c
|
||||
)
|
||||
|
||||
# Subdirectories
|
||||
add_subdirectory(module)
|
||||
@@ -1,30 +0,0 @@
|
||||
# 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
|
||||
modulebase.c
|
||||
modulelist.c
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(animation)
|
||||
add_subdirectory(asset)
|
||||
add_subdirectory(console)
|
||||
add_subdirectory(display)
|
||||
add_subdirectory(engine)
|
||||
add_subdirectory(entity)
|
||||
add_subdirectory(event)
|
||||
add_subdirectory(input)
|
||||
add_subdirectory(item)
|
||||
add_subdirectory(locale)
|
||||
add_subdirectory(math)
|
||||
add_subdirectory(overworld)
|
||||
add_subdirectory(require)
|
||||
add_subdirectory(save)
|
||||
add_subdirectory(scene)
|
||||
add_subdirectory(story)
|
||||
add_subdirectory(system)
|
||||
add_subdirectory(ui)
|
||||
@@ -1,10 +0,0 @@
|
||||
# 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
|
||||
moduleanimation.c
|
||||
moduleeasing.c
|
||||
)
|
||||
@@ -1,88 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "moduleanimation.h"
|
||||
#include "animation/animation.h"
|
||||
|
||||
typedef struct {
|
||||
keyframe_t keyframes[MODULE_ANIMATION_KEYFRAME_MAX];
|
||||
animation_t anim;
|
||||
} moduleanimationdata_t;
|
||||
|
||||
scriptproto_t MODULE_ANIMATION_PROTO;
|
||||
|
||||
moduleBaseFunction(moduleAnimationCtor) {
|
||||
if(argc < 1 || !jerry_value_is_array(args[0])) {
|
||||
return moduleBaseThrow(
|
||||
"Animation: expected array of keyframe objects"
|
||||
);
|
||||
}
|
||||
uint32_t len = jerry_array_length(args[0]);
|
||||
if(len == 0) {
|
||||
return moduleBaseThrow(
|
||||
"Animation: keyframe array must not be empty"
|
||||
);
|
||||
}
|
||||
const uint16_t count = len > (uint32_t)MODULE_ANIMATION_KEYFRAME_MAX
|
||||
? (uint16_t)MODULE_ANIMATION_KEYFRAME_MAX
|
||||
: (uint16_t)len;
|
||||
|
||||
moduleanimationdata_t *d = (moduleanimationdata_t *)memoryAllocate(
|
||||
sizeof(moduleanimationdata_t)
|
||||
);
|
||||
|
||||
for(uint16_t i = 0; i < count; i++) {
|
||||
jerry_value_t elem = jerry_object_get_index(args[0], (uint32_t)i);
|
||||
jerry_value_t jtm = moduleBaseGetProp(elem, "time");
|
||||
jerry_value_t jvl = moduleBaseGetProp(elem, "value");
|
||||
jerry_value_t jea = moduleBaseGetProp(elem, "easing");
|
||||
|
||||
d->keyframes[i].time = moduleBaseValueFloat(jtm);
|
||||
d->keyframes[i].value = moduleBaseValueFloat(jvl);
|
||||
d->keyframes[i].easing = jerry_value_is_number(jea)
|
||||
? (easingtype_t)moduleBaseValueInt(jea)
|
||||
: EASING_LINEAR;
|
||||
|
||||
jerry_value_free(jea);
|
||||
jerry_value_free(jvl);
|
||||
jerry_value_free(jtm);
|
||||
jerry_value_free(elem);
|
||||
}
|
||||
|
||||
animationInit(&d->anim, d->keyframes, count);
|
||||
|
||||
jerry_object_set_native_ptr(
|
||||
callInfo->this_value, &MODULE_ANIMATION_PROTO.info, d
|
||||
);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleAnimationGetValue) {
|
||||
moduleBaseRequireArgs(1);
|
||||
moduleBaseRequireNumber(0);
|
||||
moduleanimationdata_t *d = (moduleanimationdata_t *)scriptProtoGetValue(
|
||||
&MODULE_ANIMATION_PROTO, callInfo->this_value
|
||||
);
|
||||
if(!d) return jerry_undefined();
|
||||
return jerry_number(
|
||||
(double)animationGetValue(&d->anim, moduleBaseArgFloat(0))
|
||||
);
|
||||
}
|
||||
|
||||
void moduleAnimationInit(void) {
|
||||
scriptProtoInit(
|
||||
&MODULE_ANIMATION_PROTO, "Animation",
|
||||
sizeof(moduleanimationdata_t), moduleAnimationCtor
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_ANIMATION_PROTO, "getValue", moduleAnimationGetValue
|
||||
);
|
||||
}
|
||||
|
||||
void moduleAnimationDispose(void) {
|
||||
scriptProtoDispose(&MODULE_ANIMATION_PROTO);
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
/** Maximum keyframes per Animation instance. */
|
||||
#define MODULE_ANIMATION_KEYFRAME_MAX 64
|
||||
|
||||
extern scriptproto_t MODULE_ANIMATION_PROTO;
|
||||
|
||||
/**
|
||||
* new Animation(keyframes) - creates an Animation from a JS array of
|
||||
* `{time, value, easing?}` descriptor objects.
|
||||
*
|
||||
* @param args[0] Array of keyframe descriptors.
|
||||
*/
|
||||
moduleBaseFunction(moduleAnimationCtor);
|
||||
|
||||
/**
|
||||
* animation.getValue(time) - interpolates the animation at `time`.
|
||||
*
|
||||
* @param args[0] Time in seconds (number).
|
||||
* @return Interpolated float value.
|
||||
*/
|
||||
moduleBaseFunction(moduleAnimationGetValue);
|
||||
|
||||
/**
|
||||
* Initializes the Animation module and registers the Animation constructor.
|
||||
*/
|
||||
void moduleAnimationInit(void);
|
||||
|
||||
/**
|
||||
* Disposes the Animation module.
|
||||
*/
|
||||
void moduleAnimationDispose(void);
|
||||
@@ -1,51 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "moduleeasing.h"
|
||||
#include "animation/easing.h"
|
||||
|
||||
scriptproto_t MODULE_EASING_PROTO;
|
||||
|
||||
moduleBaseFunction(moduleEasingApply) {
|
||||
moduleBaseRequireArgs(2);
|
||||
moduleBaseRequireNumber(0);
|
||||
moduleBaseRequireNumber(1);
|
||||
const int32_t type = moduleBaseArgInt(0);
|
||||
if(type < 0 || type >= (int32_t)EASING_COUNT) {
|
||||
return moduleBaseThrow("Easing.apply: invalid easing type");
|
||||
}
|
||||
const float_t t = moduleBaseArgFloat(1);
|
||||
return jerry_number((double)easingApply((easingtype_t)type, t));
|
||||
}
|
||||
|
||||
void moduleEasingInit(void) {
|
||||
scriptProtoInit(&MODULE_EASING_PROTO, "Easing", 0, NULL);
|
||||
scriptProtoDefineStaticFunc(
|
||||
&MODULE_EASING_PROTO, "apply", moduleEasingApply
|
||||
);
|
||||
|
||||
moduleBaseSetInt("EASING_LINEAR", EASING_LINEAR);
|
||||
moduleBaseSetInt("EASING_IN_SINE", EASING_IN_SINE);
|
||||
moduleBaseSetInt("EASING_OUT_SINE", EASING_OUT_SINE);
|
||||
moduleBaseSetInt("EASING_IN_OUT_SINE", EASING_IN_OUT_SINE);
|
||||
moduleBaseSetInt("EASING_IN_QUAD", EASING_IN_QUAD);
|
||||
moduleBaseSetInt("EASING_OUT_QUAD", EASING_OUT_QUAD);
|
||||
moduleBaseSetInt("EASING_IN_OUT_QUAD", EASING_IN_OUT_QUAD);
|
||||
moduleBaseSetInt("EASING_IN_CUBIC", EASING_IN_CUBIC);
|
||||
moduleBaseSetInt("EASING_OUT_CUBIC", EASING_OUT_CUBIC);
|
||||
moduleBaseSetInt("EASING_IN_OUT_CUBIC", EASING_IN_OUT_CUBIC);
|
||||
moduleBaseSetInt("EASING_IN_QUART", EASING_IN_QUART);
|
||||
moduleBaseSetInt("EASING_OUT_QUART", EASING_OUT_QUART);
|
||||
moduleBaseSetInt("EASING_IN_OUT_QUART", EASING_IN_OUT_QUART);
|
||||
moduleBaseSetInt("EASING_IN_BACK", EASING_IN_BACK);
|
||||
moduleBaseSetInt("EASING_OUT_BACK", EASING_OUT_BACK);
|
||||
moduleBaseSetInt("EASING_IN_OUT_BACK", EASING_IN_OUT_BACK);
|
||||
}
|
||||
|
||||
void moduleEasingDispose(void) {
|
||||
scriptProtoDispose(&MODULE_EASING_PROTO);
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
|
||||
extern scriptproto_t MODULE_EASING_PROTO;
|
||||
|
||||
/**
|
||||
* Easing.apply(type, t) - applies easing function `type` to normalized
|
||||
* time `t` and returns the eased value.
|
||||
*
|
||||
* @param args[0] Easing type constant (EASING_*).
|
||||
* @param args[1] Normalized input time in [0, 1].
|
||||
* @return Eased value as a number.
|
||||
*/
|
||||
moduleBaseFunction(moduleEasingApply);
|
||||
|
||||
/**
|
||||
* Initializes the Easing module and injects all EASING_* constants.
|
||||
*/
|
||||
void moduleEasingInit(void);
|
||||
|
||||
/**
|
||||
* Disposes the Easing module.
|
||||
*/
|
||||
void moduleEasingDispose(void);
|
||||
@@ -1,11 +0,0 @@
|
||||
# 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
|
||||
)
|
||||
@@ -1,132 +0,0 @@
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "script/module/display/moduletexture.h"
|
||||
#include "script/module/asset/moduleassetentry.h"
|
||||
#include "script/module/asset/moduleassetbatch.h"
|
||||
#include "asset/asset.h"
|
||||
#include "asset/loader/assetloader.h"
|
||||
|
||||
extern scriptproto_t MODULE_ASSET_PROTO;
|
||||
|
||||
/**
|
||||
* Asset.exists(path) - returns true if the path exists in the asset archive.
|
||||
* @param args[0] Archive-relative path string.
|
||||
*/
|
||||
moduleBaseFunction(moduleAssetExists);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Disposes the Asset module and calls moduleAssetBatchDispose() and
|
||||
* moduleAssetEntryDispose().
|
||||
*/
|
||||
void moduleAssetDispose(void);
|
||||
@@ -1,388 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "moduleassetbatch.h"
|
||||
#include "util/string.h"
|
||||
|
||||
#define ASSET_BATCH_PEND_MAX 8
|
||||
|
||||
scriptproto_t MODULE_ASSET_BATCH_PROTO;
|
||||
|
||||
static scriptpromisepend_t ASSET_BATCH_PEND[ASSET_BATCH_PEND_MAX];
|
||||
static uint32_t ASSET_BATCH_PEND_COUNT = 0;
|
||||
|
||||
void assetBatchOnLoadedFire(void *params, void *user) {
|
||||
assetbatch_t *batch = (assetbatch_t *)user;
|
||||
jerry_value_t undef = jerry_undefined();
|
||||
uint32_t n = scriptPromisePendResolve(
|
||||
ASSET_BATCH_PEND, &ASSET_BATCH_PEND_COUNT, batch, undef
|
||||
);
|
||||
jerry_value_free(undef);
|
||||
if(!n) return;
|
||||
eventUnsubscribe(&batch->onLoaded, assetBatchOnLoadedFire);
|
||||
eventUnsubscribe(&batch->onError, assetBatchOnErrorFire);
|
||||
}
|
||||
|
||||
void assetBatchOnErrorFire(void *params, void *user) {
|
||||
assetbatch_t *batch = (assetbatch_t *)user;
|
||||
jerry_value_t err = jerry_string_sz("Asset batch failed to load");
|
||||
uint32_t n = scriptPromisePendReject(
|
||||
ASSET_BATCH_PEND, &ASSET_BATCH_PEND_COUNT, batch, err
|
||||
);
|
||||
jerry_value_free(err);
|
||||
if(!n) return;
|
||||
eventUnsubscribe(&batch->onLoaded, assetBatchOnLoadedFire);
|
||||
eventUnsubscribe(&batch->onError, assetBatchOnErrorFire);
|
||||
}
|
||||
|
||||
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_PEND_COUNT >= ASSET_BATCH_PEND_MAX) {
|
||||
jerry_value_free(promise);
|
||||
return moduleBaseThrow("AssetBatch.loaded: too many pending");
|
||||
}
|
||||
|
||||
if(!scriptPromisePendHas(
|
||||
ASSET_BATCH_PEND, ASSET_BATCH_PEND_COUNT, b->batch
|
||||
)) {
|
||||
eventSubscribe(
|
||||
&b->batch->onLoaded, assetBatchOnLoadedFire, b->batch
|
||||
);
|
||||
eventSubscribe(
|
||||
&b->batch->onError, assetBatchOnErrorFire, b->batch
|
||||
);
|
||||
}
|
||||
|
||||
scriptPromisePendAdd(
|
||||
ASSET_BATCH_PEND, &ASSET_BATCH_PEND_COUNT,
|
||||
ASSET_BATCH_PEND_MAX, b->batch, promise
|
||||
);
|
||||
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();
|
||||
assetBatchUnlock(b->batch);
|
||||
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(moduleAssetBatchGetAssetByPath) {
|
||||
moduleBaseRequireArgs(1);
|
||||
jsassetbatch_t *b = moduleAssetBatchSelf(callInfo);
|
||||
if(!b || !b->batch) return jerry_undefined();
|
||||
if(!jerry_value_is_string(args[0])) return jerry_undefined();
|
||||
|
||||
char_t path[ASSET_FILE_NAME_MAX];
|
||||
jerry_size_t pathLen = jerry_string_to_buffer(
|
||||
args[0], JERRY_ENCODING_UTF8,
|
||||
(jerry_char_t *)path, ASSET_FILE_NAME_MAX - 1
|
||||
);
|
||||
path[pathLen] = '\0';
|
||||
|
||||
for(uint16_t i = 0; i < b->batch->count; i++) {
|
||||
assetentry_t *entry = b->batch->entries[i];
|
||||
if(!entry) continue;
|
||||
if(!stringEquals(entry->name, path)) continue;
|
||||
assetEntryLock(entry);
|
||||
jsassetentry_t e = { .entry = entry };
|
||||
return scriptProtoCreateValue(&MODULE_ASSET_ENTRY_PROTO, &e);
|
||||
}
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
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_PEND_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
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_ASSET_BATCH_PROTO, "getAssetByPath",
|
||||
moduleAssetBatchGetAssetByPath
|
||||
);
|
||||
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_PEND_COUNT; i++) {
|
||||
bool_t seen = false;
|
||||
for(uint32_t j = 0; j < i; j++) {
|
||||
if(ASSET_BATCH_PEND[j].key == ASSET_BATCH_PEND[i].key) {
|
||||
seen = true; break;
|
||||
}
|
||||
}
|
||||
if(!seen) {
|
||||
assetbatch_t *b = (assetbatch_t *)ASSET_BATCH_PEND[i].key;
|
||||
eventUnsubscribe(&b->onLoaded, assetBatchOnLoadedFire);
|
||||
eventUnsubscribe(&b->onError, assetBatchOnErrorFire);
|
||||
}
|
||||
}
|
||||
scriptPromisePendFreeAll(ASSET_BATCH_PEND, &ASSET_BATCH_PEND_COUNT);
|
||||
scriptProtoDispose(&MODULE_ASSET_BATCH_PROTO);
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "script/scriptpromisepend.h"
|
||||
#include "script/module/asset/moduleassetentry.h"
|
||||
#include "asset/assetbatch.h"
|
||||
#include "asset/asset.h"
|
||||
#include "asset/loader/assetloader.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
extern scriptproto_t MODULE_ASSET_BATCH_PROTO;
|
||||
|
||||
/** C struct wrapped by every AssetBatch JS instance. */
|
||||
typedef struct {
|
||||
assetbatch_t *batch;
|
||||
} jsassetbatch_t;
|
||||
|
||||
/**
|
||||
* Resolves all pending loaded() promises for this batch, then
|
||||
* unsubscribes from both onLoaded and onError.
|
||||
*
|
||||
* @param params Unused.
|
||||
* @param user The assetbatch_t pointer that finished loading.
|
||||
*/
|
||||
void assetBatchOnLoadedFire(void *params, void *user);
|
||||
|
||||
/**
|
||||
* Rejects all pending loaded() promises for this batch, then
|
||||
* unsubscribes from both onLoaded and onError.
|
||||
*
|
||||
* @param params Unused.
|
||||
* @param user The assetbatch_t pointer that errored.
|
||||
*/
|
||||
void assetBatchOnErrorFire(void *params, void *user);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Returns the jsassetbatch_t pointer from the current this_value.
|
||||
*
|
||||
* @param callInfo The call info.
|
||||
* @return Pointer to the jsassetbatch_t, or NULL if invalid.
|
||||
*/
|
||||
jsassetbatch_t *moduleAssetBatchSelf(const jerry_call_info_t *callInfo);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/** @return Number of entries in the batch. */
|
||||
moduleBaseFunction(moduleAssetBatchGetCount);
|
||||
|
||||
/** @return True when every entry has reached LOADED. */
|
||||
moduleBaseFunction(moduleAssetBatchGetIsLoaded);
|
||||
|
||||
/** @return True if any entry is in an ERROR state. */
|
||||
moduleBaseFunction(moduleAssetBatchGetHasError);
|
||||
|
||||
/**
|
||||
* requireLoaded() - blocks until every entry is loaded.
|
||||
* @return this for chaining.
|
||||
* @throws If any entry fails to load.
|
||||
*/
|
||||
moduleBaseFunction(moduleAssetBatchRequireLoaded);
|
||||
|
||||
/**
|
||||
* loaded() - returns a Promise that resolves when all entries load, or
|
||||
* rejects if any entry errors. Resolves immediately if already loaded.
|
||||
*/
|
||||
moduleBaseFunction(moduleAssetBatchLoaded);
|
||||
|
||||
/**
|
||||
* lock() - acquires one additional lock on every entry.
|
||||
* @return this for chaining.
|
||||
*/
|
||||
moduleBaseFunction(moduleAssetBatchLock);
|
||||
|
||||
/**
|
||||
* unlock() - releases all locks and disposes the batch. The object is
|
||||
* invalid after this call.
|
||||
*/
|
||||
moduleBaseFunction(moduleAssetBatchUnlock);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* getAssetByPath(path) - finds the first entry whose name matches path
|
||||
* and returns it as a locked AssetEntry, or undefined if not found.
|
||||
* The returned entry must be unlocked separately when no longer needed.
|
||||
* @param args[0] Path string to match against entry names.
|
||||
* @return AssetEntry, or undefined if no entry matches.
|
||||
*/
|
||||
moduleBaseFunction(moduleAssetBatchGetAssetByPath);
|
||||
|
||||
/** @return The onLoaded Event (fires once when all entries load). */
|
||||
moduleBaseFunction(moduleAssetBatchGetOnLoaded);
|
||||
|
||||
/** @return The onEntryLoaded Event (fires per entry). */
|
||||
moduleBaseFunction(moduleAssetBatchGetOnEntryLoaded);
|
||||
|
||||
/** @return The onError Event (fires once when any entry errors). */
|
||||
moduleBaseFunction(moduleAssetBatchGetOnError);
|
||||
|
||||
/** @return The onEntryError Event (fires per errored entry). */
|
||||
moduleBaseFunction(moduleAssetBatchGetOnEntryError);
|
||||
|
||||
/** @return "AssetBatch(n)" string. */
|
||||
moduleBaseFunction(moduleAssetBatchToString);
|
||||
|
||||
/**
|
||||
* Initializes the AssetBatch module and registers the global AssetBatch
|
||||
* class.
|
||||
*/
|
||||
void moduleAssetBatchInit(void);
|
||||
|
||||
/** Disposes the AssetBatch module. */
|
||||
void moduleAssetBatchDispose(void);
|
||||
@@ -1,269 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "moduleassetentry.h"
|
||||
|
||||
#define ASSET_ENTRY_PEND_MAX 16
|
||||
|
||||
scriptproto_t MODULE_ASSET_ENTRY_PROTO;
|
||||
|
||||
static scriptpromisepend_t ASSET_ENTRY_PEND[ASSET_ENTRY_PEND_MAX];
|
||||
static uint32_t ASSET_ENTRY_PEND_COUNT = 0;
|
||||
|
||||
void assetEntryOnLoadedFire(void *params, void *user) {
|
||||
assetentry_t *entry = (assetentry_t *)user;
|
||||
jerry_value_t undef = jerry_undefined();
|
||||
uint32_t n = scriptPromisePendResolve(
|
||||
ASSET_ENTRY_PEND, &ASSET_ENTRY_PEND_COUNT, entry, undef
|
||||
);
|
||||
jerry_value_free(undef);
|
||||
if(!n) return;
|
||||
eventUnsubscribe(&entry->onLoaded, assetEntryOnLoadedFire);
|
||||
eventUnsubscribe(&entry->onError, assetEntryOnErrorFire);
|
||||
}
|
||||
|
||||
void assetEntryOnErrorFire(void *params, void *user) {
|
||||
assetentry_t *entry = (assetentry_t *)user;
|
||||
jerry_value_t err = jerry_string_sz("Asset failed to load");
|
||||
uint32_t n = scriptPromisePendReject(
|
||||
ASSET_ENTRY_PEND, &ASSET_ENTRY_PEND_COUNT, entry, err
|
||||
);
|
||||
jerry_value_free(err);
|
||||
if(!n) return;
|
||||
eventUnsubscribe(&entry->onLoaded, assetEntryOnLoadedFire);
|
||||
eventUnsubscribe(&entry->onError, assetEntryOnErrorFire);
|
||||
}
|
||||
|
||||
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_PEND_COUNT >= ASSET_ENTRY_PEND_MAX) {
|
||||
jerry_value_free(promise);
|
||||
return moduleBaseThrow("AssetEntry.loaded: too many pending");
|
||||
}
|
||||
if(!scriptPromisePendHas(
|
||||
ASSET_ENTRY_PEND, ASSET_ENTRY_PEND_COUNT, e->entry
|
||||
)) {
|
||||
eventSubscribe(
|
||||
&e->entry->onLoaded, assetEntryOnLoadedFire, e->entry
|
||||
);
|
||||
eventSubscribe(
|
||||
&e->entry->onError, assetEntryOnErrorFire, e->entry
|
||||
);
|
||||
}
|
||||
scriptPromisePendAdd(
|
||||
ASSET_ENTRY_PEND, &ASSET_ENTRY_PEND_COUNT,
|
||||
ASSET_ENTRY_PEND_MAX, e->entry, promise
|
||||
);
|
||||
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_PEND_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_PEND_COUNT; i++) {
|
||||
bool_t seen = false;
|
||||
for(uint32_t j = 0; j < i; j++) {
|
||||
if(ASSET_ENTRY_PEND[j].key == ASSET_ENTRY_PEND[i].key) {
|
||||
seen = true; break;
|
||||
}
|
||||
}
|
||||
if(!seen) {
|
||||
assetentry_t *e = (assetentry_t *)ASSET_ENTRY_PEND[i].key;
|
||||
eventUnsubscribe(&e->onLoaded, assetEntryOnLoadedFire);
|
||||
eventUnsubscribe(&e->onError, assetEntryOnErrorFire);
|
||||
}
|
||||
}
|
||||
scriptPromisePendFreeAll(ASSET_ENTRY_PEND, &ASSET_ENTRY_PEND_COUNT);
|
||||
scriptProtoDispose(&MODULE_ASSET_ENTRY_PROTO);
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "script/scriptpromisepend.h"
|
||||
#include "script/module/display/moduletexture.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"
|
||||
|
||||
extern scriptproto_t MODULE_ASSET_ENTRY_PROTO;
|
||||
|
||||
/** C struct wrapped by every AssetEntry JS instance. */
|
||||
typedef struct {
|
||||
assetentry_t *entry;
|
||||
} jsassetentry_t;
|
||||
|
||||
/**
|
||||
* Resolves all pending loaded() promises for this entry, then
|
||||
* unsubscribes from both onLoaded and onError.
|
||||
*
|
||||
* @param params Unused.
|
||||
* @param user The assetentry_t pointer that finished loading.
|
||||
*/
|
||||
void assetEntryOnLoadedFire(void *params, void *user);
|
||||
|
||||
/**
|
||||
* Rejects all pending loaded() promises for this entry, then
|
||||
* unsubscribes from both onLoaded and onError.
|
||||
*
|
||||
* @param params Unused.
|
||||
* @param user The assetentry_t pointer that errored.
|
||||
*/
|
||||
void assetEntryOnErrorFire(void *params, void *user);
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
void moduleAssetEntryFree(void *ptr, jerry_object_native_info_t *info);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/** AssetEntry() constructor - always throws; not directly instantiable. */
|
||||
moduleBaseFunction(moduleAssetEntryCtor);
|
||||
|
||||
/** @return Archive-relative path used as the cache key. */
|
||||
moduleBaseFunction(moduleAssetEntryGetName);
|
||||
|
||||
/** @return Current loading state as a number (AssetEntry.* constants). */
|
||||
moduleBaseFunction(moduleAssetEntryGetState);
|
||||
|
||||
/** @return Loader type constant. */
|
||||
moduleBaseFunction(moduleAssetEntryGetType);
|
||||
|
||||
/** @return True when the entry has fully loaded. */
|
||||
moduleBaseFunction(moduleAssetEntryGetIsLoaded);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/** @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);
|
||||
@@ -1,202 +0,0 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
/* Merged into script/module/event/moduleevent.h */
|
||||
#pragma once
|
||||
#include "script/module/event/moduleevent.h"
|
||||
@@ -1,9 +0,0 @@
|
||||
# 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
|
||||
)
|
||||
@@ -1,66 +0,0 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "console/console.h"
|
||||
|
||||
extern scriptproto_t MODULE_CONSOLE_PROTO;
|
||||
|
||||
/**
|
||||
* Console.print(...args) - concatenates all args tab-separated and prints
|
||||
* to the engine console.
|
||||
*/
|
||||
moduleBaseFunction(moduleConsolePrint);
|
||||
|
||||
/** @return Whether the console overlay is currently visible. */
|
||||
moduleBaseFunction(moduleConsoleGetVisible);
|
||||
|
||||
/** Sets console visibility. @param args[0] Boolean visible state. */
|
||||
moduleBaseFunction(moduleConsoleSetVisible);
|
||||
|
||||
/**
|
||||
* Initializes the Console module and registers the global Console object with
|
||||
* print() and the visible property.
|
||||
*/
|
||||
void moduleConsoleInit(void);
|
||||
|
||||
/**
|
||||
* Disposes the Console module.
|
||||
*/
|
||||
void moduleConsoleDispose(void);
|
||||
@@ -1,11 +0,0 @@
|
||||
# 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
|
||||
)
|
||||
@@ -1,149 +0,0 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "util/memory.h"
|
||||
#include "display/color.h"
|
||||
|
||||
extern scriptproto_t MODULE_COLOR_PROTO;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
color_t *moduleColorFrom(const jerry_value_t val);
|
||||
|
||||
/**
|
||||
* Creates a Color JS object wrapping a copy of a C color_t.
|
||||
*
|
||||
* @param c The source color.
|
||||
* @return A new Color JS object.
|
||||
*/
|
||||
jerry_value_t moduleColorPush(const color_t c);
|
||||
|
||||
/** Color(r?, g?, b?, a?) constructor. */
|
||||
moduleBaseFunction(moduleColorConstructor);
|
||||
|
||||
/** @return The red channel as a number. */
|
||||
moduleBaseFunction(moduleColorGetR);
|
||||
/** Sets the red channel. @param args[0] New r value (0-255). */
|
||||
moduleBaseFunction(moduleColorSetR);
|
||||
|
||||
/** @return The green channel as a number. */
|
||||
moduleBaseFunction(moduleColorGetG);
|
||||
/** Sets the green channel. @param args[0] New g value (0-255). */
|
||||
moduleBaseFunction(moduleColorSetG);
|
||||
|
||||
/** @return The blue channel as a number. */
|
||||
moduleBaseFunction(moduleColorGetB);
|
||||
/** Sets the blue channel. @param args[0] New b value (0-255). */
|
||||
moduleBaseFunction(moduleColorSetB);
|
||||
|
||||
/** @return The alpha channel as a number. */
|
||||
moduleBaseFunction(moduleColorGetA);
|
||||
/** Sets the alpha channel. @param args[0] New a value (0-255). */
|
||||
moduleBaseFunction(moduleColorSetA);
|
||||
|
||||
/** @return "Color(r,g,b,a)" string. */
|
||||
moduleBaseFunction(moduleColorToString);
|
||||
|
||||
/**
|
||||
* Initializes the Color module and registers the global Color class with named
|
||||
* color constants (Color.WHITE, Color.RED, etc.).
|
||||
*/
|
||||
void moduleColorInit(void);
|
||||
|
||||
/**
|
||||
* Disposes the Color module.
|
||||
*/
|
||||
void moduleColorDispose(void);
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "display/screen/screen.h"
|
||||
|
||||
extern scriptproto_t MODULE_SCREEN_PROTO;
|
||||
|
||||
/** @return Current screen width in pixels. */
|
||||
moduleBaseFunction(moduleScreenGetWidth);
|
||||
|
||||
/** @return Current screen height in pixels. */
|
||||
moduleBaseFunction(moduleScreenGetHeight);
|
||||
|
||||
/** @return Current screen aspect ratio (width / height). */
|
||||
moduleBaseFunction(moduleScreenGetAspect);
|
||||
|
||||
/**
|
||||
* Initializes the Screen module and registers read-only width/height/aspect
|
||||
* properties on the global Screen object.
|
||||
*/
|
||||
void moduleScreenInit(void);
|
||||
|
||||
/**
|
||||
* Disposes the Screen module.
|
||||
*/
|
||||
void moduleScreenDispose(void);
|
||||
@@ -1,85 +0,0 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "asset/asset.h"
|
||||
#include "asset/loader/assetloader.h"
|
||||
#include "display/texture/texture.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
extern scriptproto_t MODULE_TEXTURE_PROTO;
|
||||
|
||||
/** C struct wrapped by every Texture JS instance. */
|
||||
typedef struct {
|
||||
assetentry_t *entry;
|
||||
} jstexture_t;
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
void moduleTextureFree(void *ptr, jerry_object_native_info_t *info);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/** Texture() constructor - always throws; not directly instantiable. */
|
||||
moduleBaseFunction(moduleTextureCtor);
|
||||
|
||||
/** @return Texture width in pixels, or undefined if not loaded. */
|
||||
moduleBaseFunction(moduleTextureGetWidth);
|
||||
|
||||
/** @return Texture height in pixels, or undefined if not loaded. */
|
||||
moduleBaseFunction(moduleTextureGetHeight);
|
||||
|
||||
/** @return "Texture(WxH)" string. */
|
||||
moduleBaseFunction(moduleTextureToString);
|
||||
|
||||
/**
|
||||
* Initializes the Texture module and registers the global Texture class with
|
||||
* FORMAT_RGBA and FORMAT_PALETTE constants.
|
||||
*/
|
||||
void moduleTextureInit(void);
|
||||
|
||||
/**
|
||||
* Disposes the Texture module.
|
||||
*/
|
||||
void moduleTextureDispose(void);
|
||||
@@ -1,11 +0,0 @@
|
||||
# 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
|
||||
)
|
||||
@@ -1,34 +0,0 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "engine/engine.h"
|
||||
|
||||
extern scriptproto_t MODULE_ENGINE_PROTO;
|
||||
|
||||
/** @return True if the engine main loop is still running. */
|
||||
moduleBaseFunction(moduleEngineGetRunning);
|
||||
|
||||
/** Signals the engine to stop on the next frame. */
|
||||
moduleBaseFunction(moduleEngineExit);
|
||||
|
||||
/**
|
||||
* Initializes the Engine module and registers the global Engine object with the
|
||||
* running property and exit() method.
|
||||
*/
|
||||
void moduleEngineInit(void);
|
||||
|
||||
/**
|
||||
* Disposes the Engine module.
|
||||
*/
|
||||
void moduleEngineDispose(void);
|
||||
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
|
||||
/** Maximum number of concurrent frame() awaits. */
|
||||
#define MODULE_FRAME_PENDING_MAX 64
|
||||
|
||||
extern jerry_value_t MODULE_FRAME_PENDING[MODULE_FRAME_PENDING_MAX];
|
||||
extern uint32_t MODULE_FRAME_PENDING_COUNT;
|
||||
|
||||
/**
|
||||
* frame() - returns a Promise that resolves at the start of the next frame.
|
||||
* Used as `await frame()` inside an async script loop.
|
||||
*/
|
||||
moduleBaseFunction(moduleFrameFrame);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Initializes the frame module and registers the global frame() function.
|
||||
*/
|
||||
void moduleFrameInit(void);
|
||||
|
||||
/**
|
||||
* Disposes the frame module, releasing any unresolved pending promises.
|
||||
*/
|
||||
void moduleFrameDispose(void);
|
||||
@@ -1,61 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "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;
|
||||
|
||||
extern moduletimeoutentry_t MODULE_TIMEOUT_PENDING[MODULE_TIMEOUT_PENDING_MAX];
|
||||
extern uint32_t MODULE_TIMEOUT_PENDING_COUNT;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Resolves any pending timeout() promises whose target time has passed.
|
||||
* Must be called once per frame before jerry_run_jobs().
|
||||
*/
|
||||
void moduleTimeoutFlush(void);
|
||||
|
||||
/**
|
||||
* Initializes the timeout module and registers the global timeout() function.
|
||||
*/
|
||||
void moduleTimeoutInit(void);
|
||||
|
||||
/**
|
||||
* Disposes the timeout module, releasing any unresolved pending promises.
|
||||
*/
|
||||
void moduleTimeoutDispose(void);
|
||||
@@ -1,13 +0,0 @@
|
||||
# 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)
|
||||
@@ -1,14 +0,0 @@
|
||||
# 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)
|
||||
@@ -1,11 +0,0 @@
|
||||
# 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
|
||||
)
|
||||
@@ -1,169 +0,0 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "script/module/entity/modulecomponent.h"
|
||||
#include "entity/component/display/entitycamera.h"
|
||||
|
||||
extern scriptproto_t MODULE_CAMERA_PROTO;
|
||||
|
||||
/** Camera() constructor - always throws; not directly instantiable. */
|
||||
moduleBaseFunction(moduleCameraCtor);
|
||||
|
||||
/** @return Entity ID that owns this camera component. */
|
||||
moduleBaseFunction(moduleCameraGetEntity);
|
||||
|
||||
/** @return This component's ID. */
|
||||
moduleBaseFunction(moduleCameraGetId);
|
||||
|
||||
/** @return Field of view in degrees. */
|
||||
moduleBaseFunction(moduleCameraGetFov);
|
||||
|
||||
/** Sets field of view. @param args[0] FOV in degrees. */
|
||||
moduleBaseFunction(moduleCameraSetFov);
|
||||
|
||||
/** @return Near clip plane distance. */
|
||||
moduleBaseFunction(moduleCameraGetNearClip);
|
||||
|
||||
/** Sets near clip plane. @param args[0] Near clip distance. */
|
||||
moduleBaseFunction(moduleCameraSetNearClip);
|
||||
|
||||
/** @return Far clip plane distance. */
|
||||
moduleBaseFunction(moduleCameraGetFarClip);
|
||||
|
||||
/** Sets far clip plane. @param args[0] Far clip distance. */
|
||||
moduleBaseFunction(moduleCameraSetFarClip);
|
||||
|
||||
/** @return Projection type constant (Camera.PERSPECTIVE etc.). */
|
||||
moduleBaseFunction(moduleCameraGetProjType);
|
||||
|
||||
/** Sets projection type. @param args[0] entitycameraprojectiontype_t value. */
|
||||
moduleBaseFunction(moduleCameraSetProjType);
|
||||
|
||||
/** @return "Camera(id)" string. */
|
||||
moduleBaseFunction(moduleCameraToString);
|
||||
|
||||
/**
|
||||
* Initializes the Camera module and registers the global Camera class with
|
||||
* fov/nearClip/farClip/projType properties and PERSPECTIVE/ORTHOGRAPHIC
|
||||
* constants.
|
||||
*/
|
||||
void moduleCameraInit(void);
|
||||
|
||||
/**
|
||||
* Disposes the Camera module.
|
||||
*/
|
||||
void moduleCameraDispose(void);
|
||||
@@ -1,244 +0,0 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "script/module/math/modulevec3.h"
|
||||
#include "script/module/entity/modulecomponent.h"
|
||||
#include "entity/component/display/entityposition.h"
|
||||
|
||||
extern scriptproto_t MODULE_POSITION_PROTO;
|
||||
|
||||
/** Position() constructor - always throws; not directly instantiable. */
|
||||
moduleBaseFunction(modulePositionCtor);
|
||||
|
||||
/** @return Entity ID that owns this position component. */
|
||||
moduleBaseFunction(modulePositionGetEntity);
|
||||
|
||||
/** @return This component's ID. */
|
||||
moduleBaseFunction(modulePositionGetId);
|
||||
|
||||
/** @return Local position as a Vec3. */
|
||||
moduleBaseFunction(modulePositionGetLocalPos);
|
||||
|
||||
/** Sets local position. @param args[0] Vec3. */
|
||||
moduleBaseFunction(modulePositionSetLocalPos);
|
||||
|
||||
/** @return World position as a Vec3. */
|
||||
moduleBaseFunction(modulePositionGetWorldPos);
|
||||
|
||||
/** Sets world position. @param args[0] Vec3. */
|
||||
moduleBaseFunction(modulePositionSetWorldPos);
|
||||
|
||||
/** @return Local rotation as a Vec3 (Euler angles). */
|
||||
moduleBaseFunction(modulePositionGetLocalRot);
|
||||
|
||||
/** Sets local rotation. @param args[0] Vec3. */
|
||||
moduleBaseFunction(modulePositionSetLocalRot);
|
||||
|
||||
/** @return World rotation as a Vec3 (Euler angles). */
|
||||
moduleBaseFunction(modulePositionGetWorldRot);
|
||||
|
||||
/** Sets world rotation. @param args[0] Vec3. */
|
||||
moduleBaseFunction(modulePositionSetWorldRot);
|
||||
|
||||
/** @return Local scale as a Vec3. */
|
||||
moduleBaseFunction(modulePositionGetLocalScale);
|
||||
|
||||
/** Sets local scale. @param args[0] Vec3. */
|
||||
moduleBaseFunction(modulePositionSetLocalScale);
|
||||
|
||||
/** @return World scale as a Vec3. */
|
||||
moduleBaseFunction(modulePositionGetWorldScale);
|
||||
|
||||
/** Sets world scale. @param args[0] Vec3. */
|
||||
moduleBaseFunction(modulePositionSetWorldScale);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/** @return "Position(id)" string. */
|
||||
moduleBaseFunction(modulePositionToString);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Disposes the Position module.
|
||||
*/
|
||||
void modulePositionDispose(void);
|
||||
@@ -1,278 +0,0 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/module/display/modulecolor.h"
|
||||
#include "script/module/display/moduletexture.h"
|
||||
#include "script/module/entity/modulecomponent.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "entity/component/display/entityrenderable.h"
|
||||
|
||||
extern scriptproto_t MODULE_RENDERABLE_PROTO;
|
||||
|
||||
/** Renderable() constructor - always throws; not directly instantiable. */
|
||||
moduleBaseFunction(moduleRenderableCtor);
|
||||
|
||||
/** @return Entity ID that owns this renderable component. */
|
||||
moduleBaseFunction(moduleRenderableGetEntity);
|
||||
|
||||
/** @return This component's ID. */
|
||||
moduleBaseFunction(moduleRenderableGetId);
|
||||
|
||||
/** @return Render type constant (Renderable.SPRITEBATCH etc.). */
|
||||
moduleBaseFunction(moduleRenderableGetType);
|
||||
|
||||
/** Sets render type. @param args[0] entityrenderabletype_t value. */
|
||||
moduleBaseFunction(moduleRenderableSetType);
|
||||
|
||||
/** @return Render priority (signed integer). */
|
||||
moduleBaseFunction(moduleRenderableGetPriority);
|
||||
|
||||
/** Sets render priority. @param args[0] Priority integer. */
|
||||
moduleBaseFunction(moduleRenderableSetPriority);
|
||||
|
||||
/** @return Material color as a Color object. */
|
||||
moduleBaseFunction(moduleRenderableGetColor);
|
||||
|
||||
/** Sets material color. @param args[0] Color object. */
|
||||
moduleBaseFunction(moduleRenderableSetColor);
|
||||
|
||||
/** @return The pinned Texture JS instance, or undefined if none set. */
|
||||
moduleBaseFunction(moduleRenderableGetTexture);
|
||||
|
||||
/**
|
||||
* 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(moduleRenderableSetTexture);
|
||||
|
||||
/**
|
||||
* @return JS array of sprite sub-arrays, each with 10 numbers:
|
||||
* [x1,y1,z1, x2,y2,z2, u1,v1, u2,v2].
|
||||
*/
|
||||
moduleBaseFunction(moduleRenderableGetSprites);
|
||||
|
||||
/**
|
||||
* 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(moduleRenderableSetSprites);
|
||||
|
||||
/** @return "Renderable(id)" string. */
|
||||
moduleBaseFunction(moduleRenderableToString);
|
||||
|
||||
/**
|
||||
* Initializes the Renderable module and registers the global Renderable class
|
||||
* with type/priority/color/texture/sprites properties and SPRITEBATCH/
|
||||
* SHADER_MATERIAL/CUSTOM constants.
|
||||
*/
|
||||
void moduleRenderableInit(void);
|
||||
|
||||
/**
|
||||
* Disposes the Renderable module.
|
||||
*/
|
||||
void moduleRenderableDispose(void);
|
||||
@@ -1,44 +0,0 @@
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/entity/modulecomponent.h"
|
||||
#include "display/modulecamera.h"
|
||||
#include "display/moduleposition.h"
|
||||
#include "display/modulerenderable.h"
|
||||
#include "physics/modulephysics.h"
|
||||
#include "trigger/moduletrigger.h"
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
jerry_value_t moduleComponentListCreateInstance(
|
||||
const componenttype_t type,
|
||||
const jscomponent_t *comp
|
||||
);
|
||||
|
||||
/**
|
||||
* Initializes all component sub-modules (camera, physics, position, renderable,
|
||||
* trigger).
|
||||
*/
|
||||
void moduleComponentListInit(void);
|
||||
|
||||
/**
|
||||
* Disposes all component sub-modules in reverse init order.
|
||||
*/
|
||||
void moduleComponentListDispose(void);
|
||||
@@ -1,9 +0,0 @@
|
||||
# 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
|
||||
)
|
||||
@@ -1,222 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "modulephysics.h"
|
||||
#include "util/memory.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;
|
||||
memoryZero(&shape, sizeof(physicsshape_t));
|
||||
shape.type = (physicshapetype_t)moduleBaseArgInt(0);
|
||||
switch(shape.type) {
|
||||
case PHYSICS_SHAPE_CUBE:
|
||||
shape.data.cube.halfExtents[0] = 0.5f;
|
||||
shape.data.cube.halfExtents[1] = 0.5f;
|
||||
shape.data.cube.halfExtents[2] = 0.5f;
|
||||
break;
|
||||
case PHYSICS_SHAPE_SPHERE:
|
||||
shape.data.sphere.radius = 0.5f;
|
||||
break;
|
||||
case PHYSICS_SHAPE_CAPSULE:
|
||||
shape.data.capsule.radius = 0.5f;
|
||||
shape.data.capsule.halfHeight = 0.5f;
|
||||
break;
|
||||
case PHYSICS_SHAPE_PLANE:
|
||||
shape.data.plane.normal[1] = 1.0f;
|
||||
shape.data.plane.distance = 0.0f;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
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);
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "script/module/math/modulevec3.h"
|
||||
#include "script/module/entity/modulecomponent.h"
|
||||
#include "entity/component/physics/entityphysics.h"
|
||||
|
||||
extern scriptproto_t MODULE_PHYSICS_PROTO;
|
||||
|
||||
/** Physics() constructor - always throws; not directly instantiable. */
|
||||
moduleBaseFunction(modulePhysicsCtor);
|
||||
|
||||
/** @return Entity ID that owns this physics component. */
|
||||
moduleBaseFunction(modulePhysicsGetEntity);
|
||||
|
||||
/** @return This component's ID. */
|
||||
moduleBaseFunction(modulePhysicsGetId);
|
||||
|
||||
/** @return Body type constant (Physics.STATIC, .DYNAMIC, .KINEMATIC). */
|
||||
moduleBaseFunction(modulePhysicsGetBodyType);
|
||||
|
||||
/** Sets body type. @param args[0] physicsbodytype_t value. */
|
||||
moduleBaseFunction(modulePhysicsSetBodyType);
|
||||
|
||||
/** @return Collision shape type constant (Physics.SHAPE_*). */
|
||||
moduleBaseFunction(modulePhysicsGetShape);
|
||||
|
||||
/** Sets collision shape type. @param args[0] physicshapetype_t value. */
|
||||
moduleBaseFunction(modulePhysicsSetShape);
|
||||
|
||||
/** @return Linear velocity as a Vec3. */
|
||||
moduleBaseFunction(modulePhysicsGetVelocity);
|
||||
|
||||
/** Sets linear velocity. @param args[0] Vec3. */
|
||||
moduleBaseFunction(modulePhysicsSetVelocity);
|
||||
|
||||
/** @return Gravity scale multiplier (float). */
|
||||
moduleBaseFunction(modulePhysicsGetGravityScale);
|
||||
|
||||
/** Sets gravity scale. @param args[0] Float multiplier. */
|
||||
moduleBaseFunction(modulePhysicsSetGravityScale);
|
||||
|
||||
/** @return True when the body is resting on a surface. */
|
||||
moduleBaseFunction(modulePhysicsGetOnGround);
|
||||
|
||||
/**
|
||||
* applyImpulse(impulse) - applies an instantaneous force to the body.
|
||||
* @param args[0] Vec3 impulse vector.
|
||||
*/
|
||||
moduleBaseFunction(modulePhysicsApplyImpulse);
|
||||
|
||||
/** @return "Physics(id)" string. */
|
||||
moduleBaseFunction(modulePhysicsToString);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Disposes the Physics module.
|
||||
*/
|
||||
void modulePhysicsDispose(void);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user