Compare commits
6 Commits
scriptentity
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 5a08384ae1 | |||
| 45d8fda0e4 | |||
| a9e664492f | |||
| 3c8b6cb2cc | |||
| 2b3abbe13b | |||
| 241a52b94a |
+20
-3
@@ -1,4 +1,4 @@
|
||||
Console.print('This is called from JavaScript');
|
||||
|
||||
|
||||
const platformNames = {
|
||||
[System.PLATFORM_LINUX]: 'Linux',
|
||||
@@ -8,5 +8,22 @@ const platformNames = {
|
||||
[System.PLATFORM_WII]: 'Wii',
|
||||
};
|
||||
|
||||
const platformName = platformNames[System.platform] || 'Unknown';
|
||||
Console.print('Platform: ' + platformName);
|
||||
Console.print('Platform: ' + (platformNames[System.platform] || 'Unknown'));
|
||||
|
||||
const test = require('test.js');
|
||||
Console.print(test.str());
|
||||
test.increment();
|
||||
Console.print(test.str());
|
||||
test.decrement();
|
||||
Console.print(test.str());
|
||||
|
||||
// Scene.set('testscene.js');
|
||||
// Console.print('Loading scene...');
|
||||
// requireAsync('./testscene.js', function(scene) {
|
||||
// throw "test";
|
||||
// Console.print('Initializing scene...');
|
||||
// var batch = scene.load();
|
||||
// batch.lock();
|
||||
// batch.requireLoaded();
|
||||
// scene.init();
|
||||
// });
|
||||
@@ -0,0 +1,15 @@
|
||||
Console.print('test included');
|
||||
|
||||
var x = 1 + 2;
|
||||
|
||||
module.exports = {
|
||||
str: function() {
|
||||
return x.toString();
|
||||
},
|
||||
increment: function() {
|
||||
x++;
|
||||
},
|
||||
decrement: function() {
|
||||
x--;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// Load rosa.
|
||||
Console.print('Asset time');
|
||||
const entry = Asset.lock('test.png', Asset.TYPE_TEXTURE, Texture.FORMAT_RGBA);
|
||||
Asset.requireLoaded(entry);
|
||||
Console.print('Asset loaded');
|
||||
|
||||
// Camera at (3,3,3) looking at origin
|
||||
const cam = Entity.create();
|
||||
const camPos = cam.add(Component.POSITION);
|
||||
cam.add(Component.CAMERA);
|
||||
camPos.localPosition = new Vec3(3, 3, 3);
|
||||
camPos.lookAt(new Vec3(0, 0, 0));
|
||||
|
||||
// Test entity at origin
|
||||
const testEntity = Entity.create();
|
||||
const testPos = testEntity.add(Component.POSITION);
|
||||
|
||||
/** @type {RenderableSpritebatch} */
|
||||
const testRenderable = testEntity.add(Component.RENDERABLE);
|
||||
testRenderable.texture = entry.texture;
|
||||
testRenderable.sprites = [
|
||||
[0, 0, 1, 1, 0, 1, 1, 0]
|
||||
];
|
||||
testPos.localPosition = new Vec3(0, 0, 0);
|
||||
@@ -0,0 +1,50 @@
|
||||
var scene = {
|
||||
'test': 'teststring'
|
||||
};
|
||||
|
||||
var assets = AssetBatch([
|
||||
{ path: 'test.png', type: Asset.TYPE_TEXTURE, format: Texture.FORMAT_RGBA }
|
||||
]);
|
||||
|
||||
var cam;
|
||||
var camPos;
|
||||
var testEntity;
|
||||
var testPos;
|
||||
var testRenderable;
|
||||
var texEntry;
|
||||
|
||||
scene.init = function() {
|
||||
assets.lock();
|
||||
assets.onLoaded[0] = scene.loaded;
|
||||
};
|
||||
|
||||
scene.loaded = function() {
|
||||
texEntry = assets.entry(0);
|
||||
|
||||
// Camera at (3, 3, 3) looking at origin
|
||||
cam = Entity.create();
|
||||
camPos = cam.add(Component.POSITION);
|
||||
cam.add(Component.CAMERA);
|
||||
camPos.localPosition = new Vec3(3, 3, 3);
|
||||
camPos.lookAt(new Vec3(0, 0, 0));
|
||||
|
||||
// Test entity with textured quad at origin
|
||||
testEntity = Entity.create();
|
||||
testPos = testEntity.add(Component.POSITION);
|
||||
testRenderable = testEntity.add(Component.RENDERABLE);
|
||||
|
||||
testRenderable.texture = texEntry.texture;
|
||||
testRenderable.sprites = [
|
||||
[0, 0, 1, 1, 0, 1, 1, 0]
|
||||
];
|
||||
testPos.localPosition = new Vec3(0, 0, 0);
|
||||
}
|
||||
|
||||
scene.dispose = function() {
|
||||
Console.print('Scene Dispose');
|
||||
Entity.dispose(cam);
|
||||
Entity.dispose(testEntity);
|
||||
assets.unlock();
|
||||
};
|
||||
|
||||
module.exports = scene;
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#include "easing.h"
|
||||
#include "assert/assert.h"
|
||||
#include <math.h>
|
||||
#include "util/math.h"
|
||||
|
||||
const easingfn_t EASING_FUNCTIONS[EASING_COUNT] = {
|
||||
easingLinear,
|
||||
@@ -36,15 +36,15 @@ float_t easingLinear(const float_t t) {
|
||||
}
|
||||
|
||||
float_t easingInSine(const float_t t) {
|
||||
return 1.0f - cosf(t * EASING_PI * 0.5f);
|
||||
return 1.0f - cosf(t * MATH_PI * 0.5f);
|
||||
}
|
||||
|
||||
float_t easingOutSine(const float_t t) {
|
||||
return sinf(t * EASING_PI * 0.5f);
|
||||
return sinf(t * MATH_PI * 0.5f);
|
||||
}
|
||||
|
||||
float_t easingInOutSine(const float_t t) {
|
||||
return -(cosf(EASING_PI * t) - 1.0f) * 0.5f;
|
||||
return -(cosf(MATH_PI * t) - 1.0f) * 0.5f;
|
||||
}
|
||||
|
||||
float_t easingInQuad(const float_t t) {
|
||||
|
||||
@@ -10,7 +10,17 @@
|
||||
#include "util/string.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
#ifdef DUSK_THREAD_PTHREAD
|
||||
pthread_t ASSERT_MAIN_THREAD_ID = 0;
|
||||
#endif
|
||||
|
||||
#ifndef DUSK_ASSERTIONS_FAKED
|
||||
void assertInit(void) {
|
||||
#ifdef DUSK_THREAD_PTHREAD
|
||||
ASSERT_MAIN_THREAD_ID = pthread_self();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef DUSK_TEST_ASSERT
|
||||
void assertTrueImpl(
|
||||
const char *file,
|
||||
@@ -132,4 +142,29 @@
|
||||
) {
|
||||
assertTrueImpl(file, line, stringCompare(a, b) == 0, message);
|
||||
}
|
||||
|
||||
void assertIsMainThreadImpl(
|
||||
const char *file,
|
||||
const int32_t line,
|
||||
const char *message
|
||||
) {
|
||||
#ifdef DUSK_THREAD_PTHREAD
|
||||
assertTrueImpl(
|
||||
file, line, pthread_self() == ASSERT_MAIN_THREAD_ID, message
|
||||
);
|
||||
#endif
|
||||
}
|
||||
|
||||
void assertNotMainThreadImpl(
|
||||
const char *file,
|
||||
const int32_t line,
|
||||
const char *message
|
||||
) {
|
||||
#ifdef DUSK_THREAD_PTHREAD
|
||||
assertTrueImpl(
|
||||
file, line, pthread_self() != ASSERT_MAIN_THREAD_ID, message
|
||||
);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -16,7 +16,18 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef DUSK_THREAD_PTHREAD
|
||||
#include "thread/thread.h"
|
||||
extern pthread_t ASSERT_MAIN_THREAD_ID;
|
||||
#endif
|
||||
|
||||
#ifndef DUSK_ASSERTIONS_FAKED
|
||||
/**
|
||||
* Initializes the assert system. Must be the very first call in engine
|
||||
* startup.
|
||||
*/
|
||||
void assertInit(void);
|
||||
|
||||
/**
|
||||
* Assert a given value to be true.
|
||||
*
|
||||
@@ -121,6 +132,28 @@
|
||||
const char *message
|
||||
);
|
||||
|
||||
/**
|
||||
* Asserts that the current thread is the main thread.
|
||||
*
|
||||
* @param message Message to throw against assertion failure.
|
||||
*/
|
||||
void assertIsMainThreadImpl(
|
||||
const char *file,
|
||||
const int32_t line,
|
||||
const char *message
|
||||
);
|
||||
|
||||
/**
|
||||
* Asserts that the current thread is NOT the main thread.
|
||||
*
|
||||
* @param message Message to throw against assertion failure.
|
||||
*/
|
||||
void assertNotMainThreadImpl(
|
||||
const char *file,
|
||||
const int32_t line,
|
||||
const char *message
|
||||
);
|
||||
|
||||
/**
|
||||
* Asserts a given value to be true.
|
||||
*
|
||||
@@ -205,8 +238,28 @@
|
||||
#define assertStringEqual(a, b, message) \
|
||||
assertStringEqualImpl(__FILE__, __LINE__, a, b, message)
|
||||
|
||||
/**
|
||||
* Asserts that the current thread is the main thread.
|
||||
*
|
||||
* @param message Message to throw against assertion failure.
|
||||
*/
|
||||
#define assertIsMainThread(message) \
|
||||
assertIsMainThreadImpl(__FILE__, __LINE__, message)
|
||||
|
||||
/**
|
||||
* Asserts that the current thread is NOT the main thread.
|
||||
*
|
||||
* @param message Message to throw against assertion failure.
|
||||
*/
|
||||
#define assertNotMainThread(message) \
|
||||
assertNotMainThreadImpl(__FILE__, __LINE__, message)
|
||||
|
||||
#else
|
||||
// If assertions are faked, we define the macros to do nothing.
|
||||
#define assertInit() ((void)0)
|
||||
#define assertMainThreadInit() ((void)0)
|
||||
#define assertIsMainThread(message) ((void)0)
|
||||
#define assertNotMainThread(message) ((void)0)
|
||||
#define assertTrue(x, message) ((void)0)
|
||||
#define assertFalse(x, message) ((void)0)
|
||||
#define assertUnreachable(message) ((void)0)
|
||||
@@ -215,11 +268,13 @@
|
||||
#define assertDeprecated(message) ((void)0)
|
||||
#define assertStrLenMax(str, len, message) ((void)0)
|
||||
#define assertStrLenMin(str, len, message) ((void)0)
|
||||
#define assertStringEqual(a, b, message) ((void)0)
|
||||
#define assertIsMainThread(message) ((void)0)
|
||||
#define assertNotMainThread(message) ((void)0)
|
||||
|
||||
#endif
|
||||
|
||||
// Static Assertions
|
||||
|
||||
#define assertStructSize(struct, size) \
|
||||
_Static_assert(sizeof(struct) == size, "Size of " #struct " must be " #size)
|
||||
|
||||
|
||||
+94
-10
@@ -78,9 +78,28 @@ assetentry_t * assetGetEntry(
|
||||
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();
|
||||
@@ -102,6 +121,43 @@ errorret_t assetRequireLoaded(assetentry_t *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,
|
||||
@@ -114,6 +170,7 @@ assetentry_t * assetLock(
|
||||
|
||||
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)) {
|
||||
@@ -122,6 +179,7 @@ void assetUnlock(const char_t *name) {
|
||||
}
|
||||
entry++;
|
||||
} while(entry < ASSET.entries + ASSET_ENTRY_COUNT_MAX);
|
||||
|
||||
assertUnreachable("Asset entry not found for unlock.");
|
||||
}
|
||||
|
||||
@@ -131,6 +189,8 @@ void assetUnlockEntry(assetentry_t *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;
|
||||
@@ -222,6 +282,8 @@ errorret_t assetUpdate(void) {
|
||||
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++;
|
||||
@@ -241,10 +303,14 @@ errorret_t assetUpdate(void) {
|
||||
loading++;
|
||||
break;
|
||||
|
||||
case ASSET_ENTRY_STATE_ERROR:
|
||||
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);
|
||||
@@ -254,9 +320,7 @@ errorret_t assetUpdate(void) {
|
||||
} while(loading < ASSET.loading + ASSET_LOADING_COUNT_MAX);
|
||||
|
||||
|
||||
// Reap entries that have no external locks (refs.count == 0) AND have been
|
||||
// explicitly locked at least once. Entries that were never locked are left
|
||||
// alone — raw-pointer callers hold no ref but should not lose the entry.
|
||||
// Reap unused entries.
|
||||
entry = ASSET.entries;
|
||||
do {
|
||||
if(entry->state != ASSET_ENTRY_STATE_LOADED) {
|
||||
@@ -269,11 +333,6 @@ errorret_t assetUpdate(void) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!entry->wasLocked) {
|
||||
entry++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(entry->refs.count > 0) {
|
||||
entry++;
|
||||
continue;
|
||||
@@ -288,6 +347,8 @@ errorret_t assetUpdate(void) {
|
||||
}
|
||||
|
||||
void assetUpdateAsync(thread_t *thread) {
|
||||
assertNotMainThread("assetUpdateAsync must not run on the main thread.");
|
||||
|
||||
while(!threadShouldStop(thread)) {
|
||||
// Walk over each asset
|
||||
assetloading_t *loading;
|
||||
@@ -344,11 +405,34 @@ void assetUpdateAsync(thread_t *thread) {
|
||||
}
|
||||
|
||||
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++) {
|
||||
threadMutexDispose(&ASSET.loading[i].mutex);
|
||||
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) {
|
||||
|
||||
@@ -68,6 +68,18 @@ assetentry_t * assetGetEntry(
|
||||
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
|
||||
@@ -109,6 +121,15 @@ void assetUnlockEntry(assetentry_t *entry);
|
||||
*/
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -11,6 +11,38 @@
|
||||
#include "util/memory.h"
|
||||
#include <unistd.h>
|
||||
|
||||
/* ---- Per-entry event trampolines ----------------------------------------- */
|
||||
|
||||
static 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static 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);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Public API ---------------------------------------------------------- */
|
||||
|
||||
void assetBatchInit(
|
||||
assetbatch_t *batch,
|
||||
const uint16_t count,
|
||||
@@ -24,10 +56,40 @@ void assetBatchInit(
|
||||
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++) {
|
||||
// Copy input into batch-owned storage so the descriptor need not persist.
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +150,12 @@ errorret_t assetBatchRequireLoaded(assetbatch_t *batch) {
|
||||
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 guaranteed live. */
|
||||
eventUnsubscribe(&batch->entries[i]->onLoaded, assetBatchEntryOnLoadedCb);
|
||||
eventUnsubscribe(&batch->entries[i]->onError, assetBatchEntryOnErrorCb);
|
||||
assetUnlockEntry(batch->entries[i]);
|
||||
}
|
||||
}
|
||||
memoryZero(batch, sizeof(assetbatch_t));
|
||||
}
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
#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;
|
||||
@@ -21,6 +23,28 @@ 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 has loaded successfully. 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 finishes loading. params = assetentry_t * */
|
||||
event_t onEntryLoaded;
|
||||
eventcallback_t onEntryLoadedCallbacks[ASSET_BATCH_EVENT_MAX];
|
||||
void *onEntryLoadedUsers[ASSET_BATCH_EVENT_MAX];
|
||||
|
||||
/** Fires once when all entries have finished (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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -117,6 +117,48 @@ errorret_t assetFileDispose(assetfile_t *file) {
|
||||
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,
|
||||
|
||||
@@ -92,6 +92,21 @@ errorret_t assetFileClose(assetfile_t *file);
|
||||
*/
|
||||
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;
|
||||
|
||||
@@ -21,6 +21,7 @@ void assetEntryInit(
|
||||
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);
|
||||
@@ -28,21 +29,39 @@ void assetEntryInit(
|
||||
entry->state = ASSET_ENTRY_STATE_NOT_STARTED;
|
||||
if(input) {
|
||||
entry->inputData = *input;
|
||||
entry->input = &entry->inputData;
|
||||
} else {
|
||||
memoryZero(&entry->inputData, sizeof(assetloaderinput_t));
|
||||
}
|
||||
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.");
|
||||
entry->wasLocked = true;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -57,6 +76,7 @@ void assetEntryStartLoading(
|
||||
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));
|
||||
@@ -67,10 +87,15 @@ void assetEntryStartLoading(
|
||||
|
||||
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();
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#pragma once
|
||||
#include "asset/loader/assetloading.h"
|
||||
#include "event/event.h"
|
||||
#include "util/ref.h"
|
||||
|
||||
typedef enum {
|
||||
@@ -19,27 +20,43 @@ typedef enum {
|
||||
ASSET_ENTRY_STATE_ERROR
|
||||
} assetentrystate_t;
|
||||
|
||||
typedef struct assetentry_s {
|
||||
// Filename and cache key
|
||||
/** 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];
|
||||
// What type of asset is this?
|
||||
assetloadertype_t type;
|
||||
// Data
|
||||
assetloaderoutput_t data;
|
||||
// What state is this asset entry in currently?
|
||||
assetentrystate_t state;
|
||||
// What is referencing this asset entry.
|
||||
ref_t refs;
|
||||
// True once assetEntryLock has been called at least once. The reaper only
|
||||
// collects entries that have been explicitly locked (and later unlocked to
|
||||
// zero). Entries that nobody has ever locked are left alone so raw-pointer
|
||||
// callers (tests, requireLoaded before locking) are not surprised.
|
||||
bool_t wasLocked;
|
||||
// Owned copy of the loader input. input points here when non-NULL.
|
||||
assetloaderinput_t inputData;
|
||||
// Pointer to inputData, or NULL if no input was provided.
|
||||
assetloaderinput_t *input;
|
||||
} assetentry_t;
|
||||
/**
|
||||
* 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
|
||||
@@ -86,6 +103,7 @@ 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.
|
||||
|
||||
@@ -13,13 +13,9 @@
|
||||
typedef struct assetentry_s assetentry_t;
|
||||
|
||||
typedef struct assetloading_s {
|
||||
// Protects entry pointer and entry->state from concurrent access.
|
||||
threadmutex_t mutex;
|
||||
// What type of asset is being loaded.
|
||||
assetloadertype_t type;
|
||||
// Referral back to the asset entry that will be kept alive after load done.
|
||||
assetentry_t *entry;
|
||||
// Information used during the load operation only.
|
||||
assetloaderloading_t loading;
|
||||
} assetloading_t;
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ errorret_t assetMeshLoaderAsync(assetloading_t *loading) {
|
||||
|
||||
assetmeshoutput_t *out = &loading->entry->data.mesh;
|
||||
assetfile_t *file = &loading->loading.mesh.file;
|
||||
assetmeshinputaxis_t axis = loading->entry->input->mesh;
|
||||
assetmeshinputaxis_t axis = loading->entry->inputData.mesh;
|
||||
|
||||
assetLoaderErrorChain(loading, assetFileInit(file, loading->entry->name, NULL, NULL));
|
||||
assetLoaderErrorChain(loading, assetFileOpen(file));
|
||||
|
||||
@@ -54,6 +54,7 @@ int assetTextureEOF(void *user) {
|
||||
|
||||
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){
|
||||
@@ -76,7 +77,7 @@ errorret_t assetTextureLoaderAsync(assetloading_t *loading) {
|
||||
|
||||
// Determine channels
|
||||
int channelsDesired;
|
||||
switch(loading->entry->input->texture) {
|
||||
switch(loading->entry->inputData.texture) {
|
||||
case TEXTURE_FORMAT_RGBA:
|
||||
channelsDesired = 4;
|
||||
break;
|
||||
@@ -122,6 +123,7 @@ errorret_t assetTextureLoaderAsync(assetloading_t *loading) {
|
||||
|
||||
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:
|
||||
@@ -146,7 +148,7 @@ errorret_t assetTextureLoaderSync(assetloading_t *loading) {
|
||||
(texture_t*)&loading->entry->data.texture,
|
||||
loading->loading.texture.width,
|
||||
loading->loading.texture.height,
|
||||
loading->entry->input->texture,
|
||||
loading->entry->inputData.texture,
|
||||
(texturedata_t){
|
||||
.rgbaColors = (color_t*)loading->loading.texture.data
|
||||
}
|
||||
@@ -161,5 +163,7 @@ errorret_t assetTextureLoaderSync(assetloading_t *loading) {
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
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();
|
||||
@@ -40,6 +41,7 @@ errorret_t assetTilesetLoaderAsync(assetloading_t *loading) {
|
||||
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:
|
||||
@@ -111,5 +113,7 @@ errorret_t assetTilesetLoaderSync(assetloading_t *loading) {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
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();
|
||||
@@ -45,6 +46,7 @@ errorret_t assetJsonLoaderAsync(assetloading_t *loading) {
|
||||
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:
|
||||
@@ -82,7 +84,10 @@ errorret_t assetJsonLoaderSync(assetloading_t *loading) {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
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();
|
||||
@@ -38,6 +39,7 @@ errorret_t assetLocaleLoaderAsync(assetloading_t *loading) {
|
||||
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:
|
||||
@@ -60,11 +62,14 @@ errorret_t assetLocaleLoaderSync(assetloading_t *loading) {
|
||||
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,
|
||||
|
||||
@@ -9,12 +9,14 @@
|
||||
#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();
|
||||
@@ -23,33 +25,21 @@ errorret_t assetScriptLoaderAsync(assetloading_t *loading) {
|
||||
assertNull(loading->loading.script.buffer, "Buffer already defined?");
|
||||
|
||||
assetfile_t *file = &loading->loading.script.file;
|
||||
assetLoaderErrorChain(loading, assetFileInit(file, loading->entry->name, NULL, NULL));
|
||||
assetLoaderErrorChain(loading, assetFileOpen(file));
|
||||
assetLoaderErrorChain(
|
||||
loading, assetFileInit(file, loading->entry->name, NULL, NULL)
|
||||
);
|
||||
|
||||
size_t capacity = ASSET_SCRIPT_CHUNK_SIZE;
|
||||
uint8_t *buffer = memoryAllocate(capacity + 1);
|
||||
size_t offset = 0;
|
||||
|
||||
while(1) {
|
||||
if(offset + ASSET_SCRIPT_CHUNK_SIZE > capacity) {
|
||||
size_t oldCapacity = capacity + 1;
|
||||
capacity += ASSET_SCRIPT_CHUNK_SIZE;
|
||||
memoryResize((void **)&buffer, oldCapacity, capacity + 1);
|
||||
}
|
||||
assetLoaderErrorChain(loading, assetFileRead(
|
||||
file, buffer + offset, ASSET_SCRIPT_CHUNK_SIZE
|
||||
));
|
||||
size_t chunk = (size_t)file->lastRead;
|
||||
offset += chunk;
|
||||
if(chunk == 0) break;
|
||||
}
|
||||
|
||||
buffer[offset] = '\0';
|
||||
assetLoaderErrorChain(loading, assetFileClose(file));
|
||||
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 = offset;
|
||||
loading->loading.script.size = size;
|
||||
loading->loading.script.state = ASSET_SCRIPT_LOADING_STATE_EXEC;
|
||||
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
|
||||
errorOk();
|
||||
@@ -58,6 +48,7 @@ errorret_t assetScriptLoaderAsync(assetloading_t *loading) {
|
||||
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:
|
||||
@@ -73,34 +64,58 @@ errorret_t assetScriptLoaderSync(assetloading_t *loading) {
|
||||
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(
|
||||
(const jerry_char_t *)buffer,
|
||||
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_t errVal = jerry_exception_value(result, false);
|
||||
jerry_value_t errStr = jerry_value_to_string(errVal);
|
||||
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];
|
||||
jerry_size_t len = jerry_string_to_buffer(
|
||||
errStr, JERRY_ENCODING_UTF8, (jerry_char_t *)buf, sizeof(buf) - 1
|
||||
);
|
||||
buf[len] = '\0';
|
||||
jerry_value_free(errStr);
|
||||
jerry_value_free(errVal);
|
||||
moduleBaseExceptionMessage(result, buf, sizeof(buf));
|
||||
jerry_value_free(result);
|
||||
assetLoaderErrorThrow(loading, "Script error in '%s': %s",
|
||||
loading->entry->name, buf
|
||||
assetLoaderErrorThrow(
|
||||
loading,
|
||||
"Script execution failed: %s: %s", loading->entry->name, buf
|
||||
);
|
||||
}
|
||||
|
||||
loading->entry->data.script = (assetscriptoutput_t)result;
|
||||
// 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();
|
||||
}
|
||||
@@ -108,9 +123,15 @@ errorret_t assetScriptLoaderSync(assetloading_t *loading) {
|
||||
errorret_t assetScriptDispose(assetentry_t *entry) {
|
||||
assertNotNull(entry, "Asset entry cannot be NULL");
|
||||
assertTrue(entry->type == ASSET_LOADER_TYPE_SCRIPT, "Invalid type.");
|
||||
if(entry->data.script != 0) {
|
||||
jerry_value_free((jerry_value_t)entry->data.script);
|
||||
entry->data.script = 0;
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -7,14 +7,18 @@
|
||||
|
||||
#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 uint32_t assetscriptoutput_t;
|
||||
typedef struct {
|
||||
void *nothing;
|
||||
} assetscriptloaderinput_t;
|
||||
|
||||
typedef scriptmodule_t assetscriptoutput_t;
|
||||
|
||||
typedef enum {
|
||||
ASSET_SCRIPT_LOADING_STATE_INITIAL,
|
||||
@@ -30,6 +34,27 @@ typedef struct {
|
||||
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);
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
engine_t ENGINE;
|
||||
|
||||
errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
||||
assertInit();
|
||||
memoryZero(&ENGINE, sizeof(engine_t));
|
||||
ENGINE.running = true;
|
||||
ENGINE.argc = argc;
|
||||
@@ -58,7 +59,7 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
||||
consolePrint("Engine initialized");
|
||||
|
||||
errorChain(scriptExecFile("init.js"));
|
||||
sceneSet(SCENE_TYPE_INITIAL);
|
||||
errorChain(sceneSet("testscene.js"));
|
||||
|
||||
errorOk();
|
||||
}
|
||||
@@ -75,7 +76,7 @@ errorret_t engineUpdate(void) {
|
||||
physicsManagerUpdate();
|
||||
errorChain(displayUpdate());
|
||||
errorChain(cutsceneUpdate());
|
||||
errorChain(sceneUpdate());
|
||||
sceneUpdate();
|
||||
errorChain(assetUpdate());
|
||||
|
||||
if(inputPressed(INPUT_ACTION_RAGEQUIT)) ENGINE.running = false;
|
||||
@@ -97,8 +98,8 @@ errorret_t engineDispose(void) {
|
||||
consoleDispose();
|
||||
errorChain(displayDispose());
|
||||
// errorChain(saveDispose());
|
||||
errorChain(assetDispose());
|
||||
errorChain(scriptDispose());
|
||||
errorChain(assetDispose());
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,5 @@
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
add_subdirectory(display)
|
||||
add_subdirectory(overworld)
|
||||
add_subdirectory(physics)
|
||||
add_subdirectory(script)
|
||||
add_subdirectory(trigger)
|
||||
@@ -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
|
||||
entityinteractable.c
|
||||
entityoverworld.c
|
||||
entityoverworldcamera.c
|
||||
entityoverworldtrigger.c
|
||||
entityplayer.c
|
||||
)
|
||||
@@ -1,49 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entityinteractable.h"
|
||||
#include "entity/entitymanager.h"
|
||||
|
||||
void entityInteractableInit(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
entityinteractable_t *inter = entityInteractableGet(entityId, componentId);
|
||||
inter->onInteract = NULL;
|
||||
inter->user = NULL;
|
||||
}
|
||||
|
||||
entityinteractable_t * entityInteractableGet(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
return componentGetData(entityId, componentId, COMPONENT_TYPE_INTERACTABLE);
|
||||
}
|
||||
|
||||
void entityInteractableSetCallback(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
void (*onInteract)(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
void *user
|
||||
),
|
||||
void *user
|
||||
) {
|
||||
entityinteractable_t *inter = entityInteractableGet(entityId, componentId);
|
||||
inter->onInteract = onInteract;
|
||||
inter->user = user;
|
||||
}
|
||||
|
||||
void entityInteractableTrigger(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
entityinteractable_t *inter = entityInteractableGet(entityId, componentId);
|
||||
if(inter->onInteract == NULL) return;
|
||||
inter->onInteract(entityId, componentId, inter->user);
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "entity/entitybase.h"
|
||||
|
||||
typedef struct {
|
||||
void (*onInteract)(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
void *user
|
||||
);
|
||||
void *user;
|
||||
} entityinteractable_t;
|
||||
|
||||
/**
|
||||
* Initializes the interactable component, clearing the callback and user pointer.
|
||||
*
|
||||
* @param entityId The owning entity.
|
||||
* @param componentId This component's ID.
|
||||
*/
|
||||
void entityInteractableInit(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns a pointer to the interactable component data.
|
||||
*
|
||||
* @param entityId The owning entity.
|
||||
* @param componentId This component's ID.
|
||||
* @return Pointer to the entityinteractable_t data.
|
||||
*/
|
||||
entityinteractable_t * entityInteractableGet(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the callback invoked when this interactable is triggered.
|
||||
*
|
||||
* @param entityId The owning entity.
|
||||
* @param componentId This component's ID.
|
||||
* @param onInteract Function called on interaction, or NULL to clear.
|
||||
* @param user Arbitrary pointer forwarded to the callback.
|
||||
*/
|
||||
void entityInteractableSetCallback(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
void (*onInteract)(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
void *user
|
||||
),
|
||||
void *user
|
||||
);
|
||||
|
||||
/**
|
||||
* Fires the interactable's callback if one is set.
|
||||
*
|
||||
* @param entityId The owning entity.
|
||||
* @param componentId This component's ID.
|
||||
*/
|
||||
void entityInteractableTrigger(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
@@ -1,55 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entityoverworld.h"
|
||||
#include "entity/entitymanager.h"
|
||||
#include "entity/component/display/entityrenderable.h"
|
||||
#include "display/shader/shaderunlit.h"
|
||||
#include "display/mesh/cube.h"
|
||||
|
||||
void entityOverworldInit(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
entityoverworld_t *ow = entityOverworldGet(entityId, componentId);
|
||||
ow->type = OVERWORLD_ENTITY_TYPE_NPC;
|
||||
ow->facing = FACING_DIR_DOWN;
|
||||
ow->renderCompId = entityGetComponent(entityId, COMPONENT_TYPE_RENDERABLE);
|
||||
if(ow->renderCompId != COMPONENT_ID_INVALID) {
|
||||
entityRenderableSetDraw(entityId, ow->renderCompId, entityOverworldDraw, NULL);
|
||||
}
|
||||
ow->physCompId = entityGetComponent(entityId, COMPONENT_TYPE_PHYSICS);
|
||||
}
|
||||
|
||||
entityoverworld_t * entityOverworldGet(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
return componentGetData(entityId, componentId, COMPONENT_TYPE_OVERWORLD);
|
||||
}
|
||||
|
||||
void entityOverworldSetType(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const entityoverworldtype_t type
|
||||
) {
|
||||
entityOverworldGet(entityId, componentId)->type = type;
|
||||
}
|
||||
|
||||
errorret_t entityOverworldDraw(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
void *user
|
||||
) {
|
||||
componentid_t owCompId = entityGetComponent(entityId, COMPONENT_TYPE_OVERWORLD);
|
||||
entityoverworld_t *ow = entityOverworldGet(entityId, owCompId);
|
||||
|
||||
color_t col = ow->type == OVERWORLD_ENTITY_TYPE_PLAYER ? COLOR_WHITE : COLOR_BLUE;
|
||||
errorChain(shaderSetColor(&SHADER_UNLIT, SHADER_UNLIT_COLOR, col));
|
||||
errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, NULL));
|
||||
return meshDraw(&CUBE_MESH_SIMPLE, 0, -1);
|
||||
}
|
||||
@@ -1,74 +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 "entity/entitybase.h"
|
||||
#include "overworld/facingdir.h"
|
||||
|
||||
typedef enum {
|
||||
OVERWORLD_ENTITY_TYPE_PLAYER = 0,
|
||||
OVERWORLD_ENTITY_TYPE_NPC = 1,
|
||||
} entityoverworldtype_t;
|
||||
|
||||
typedef struct {
|
||||
entityoverworldtype_t type;
|
||||
facingdir_t facing;
|
||||
componentid_t renderCompId;
|
||||
componentid_t physCompId;
|
||||
} entityoverworld_t;
|
||||
|
||||
/**
|
||||
* Initializes the overworld component, wiring up the draw callback if a
|
||||
* renderable component is already present on the entity.
|
||||
*
|
||||
* @param entityId The owning entity.
|
||||
* @param componentId This component's ID.
|
||||
*/
|
||||
void entityOverworldInit(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns a pointer to the overworld component data.
|
||||
*
|
||||
* @param entityId The owning entity.
|
||||
* @param componentId This component's ID.
|
||||
* @return Pointer to the entityoverworld_t data.
|
||||
*/
|
||||
entityoverworld_t * entityOverworldGet(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the overworld entity type.
|
||||
*
|
||||
* @param entityId The owning entity.
|
||||
* @param componentId This component's ID.
|
||||
* @param type The type to assign.
|
||||
*/
|
||||
void entityOverworldSetType(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const entityoverworldtype_t type
|
||||
);
|
||||
|
||||
/**
|
||||
* Draw callback registered on the renderable component.
|
||||
*
|
||||
* @param entityId The owning entity.
|
||||
* @param componentId The renderable component's ID.
|
||||
* @param user Unused.
|
||||
* @return Error result.
|
||||
*/
|
||||
errorret_t entityOverworldDraw(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
void *user
|
||||
);
|
||||
@@ -1,66 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entityoverworldcamera.h"
|
||||
#include "entity/entitymanager.h"
|
||||
#include "entity/component/display/entityposition.h"
|
||||
#include "entity/component/display/entitycamera.h"
|
||||
|
||||
void entityOverworldCameraInit(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
entityoverworldcamera_t *cam = entityOverworldCameraGet(entityId, componentId);
|
||||
cam->targetEntityId = ENTITY_ID_INVALID;
|
||||
cam->targetPosCompId = COMPONENT_ID_INVALID;
|
||||
glm_vec3_zero(cam->targetOffset);
|
||||
glm_vec3_zero(cam->eyeOffset);
|
||||
cam->scale = 1.0f;
|
||||
}
|
||||
|
||||
entityoverworldcamera_t * entityOverworldCameraGet(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
return componentGetData(entityId, componentId, COMPONENT_TYPE_OVERWORLD_CAMERA);
|
||||
}
|
||||
|
||||
void entityOverworldCameraSetTarget(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const entityid_t targetEntityId,
|
||||
const componentid_t targetPosCompId
|
||||
) {
|
||||
entityoverworldcamera_t *cam = entityOverworldCameraGet(entityId, componentId);
|
||||
cam->targetEntityId = targetEntityId;
|
||||
cam->targetPosCompId = targetPosCompId;
|
||||
}
|
||||
|
||||
errorret_t entityOverworldCameraRender(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
entityoverworldcamera_t *cam = entityOverworldCameraGet(entityId, componentId);
|
||||
|
||||
vec3 targetPos;
|
||||
entityPositionGetWorldPosition(
|
||||
cam->targetEntityId, cam->targetPosCompId, targetPos
|
||||
);
|
||||
|
||||
vec3 center = {
|
||||
targetPos[0] + cam->targetOffset[0],
|
||||
targetPos[1] + cam->targetOffset[1],
|
||||
targetPos[2] + cam->targetOffset[2]
|
||||
};
|
||||
|
||||
componentid_t posComp = entityGetComponent(entityId, COMPONENT_TYPE_POSITION);
|
||||
componentid_t camComp = entityGetComponent(entityId, COMPONENT_TYPE_CAMERA);
|
||||
entityCameraLookAtPixelPerfect(
|
||||
entityId, posComp, camComp, center, cam->eyeOffset, cam->scale
|
||||
);
|
||||
errorOk();
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "entity/entitybase.h"
|
||||
#include "error/error.h"
|
||||
|
||||
typedef struct {
|
||||
entityid_t targetEntityId;
|
||||
componentid_t targetPosCompId;
|
||||
vec3 targetOffset;
|
||||
vec3 eyeOffset;
|
||||
float_t scale;
|
||||
} entityoverworldcamera_t;
|
||||
|
||||
/**
|
||||
* Initializes the overworld camera component.
|
||||
*
|
||||
* @param entityId The owning entity.
|
||||
* @param componentId This component's ID.
|
||||
*/
|
||||
void entityOverworldCameraInit(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns a pointer to the overworld camera component data.
|
||||
*
|
||||
* @param entityId The owning entity.
|
||||
* @param componentId This component's ID.
|
||||
* @return Pointer to the entityoverworldcamera_t data.
|
||||
*/
|
||||
entityoverworldcamera_t * entityOverworldCameraGet(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the entity and position component the camera will follow.
|
||||
*
|
||||
* @param entityId The owning entity.
|
||||
* @param componentId This component's ID.
|
||||
* @param targetEntityId Entity to follow.
|
||||
* @param targetPosCompId Position component on the target entity.
|
||||
*/
|
||||
void entityOverworldCameraSetTarget(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const entityid_t targetEntityId,
|
||||
const componentid_t targetPosCompId
|
||||
);
|
||||
|
||||
/**
|
||||
* Render callback: updates the camera position to track the target entity.
|
||||
* Called automatically each frame via componentRenderAll.
|
||||
*
|
||||
* @param entityId The owning entity.
|
||||
* @param componentId This component's ID.
|
||||
* @return Error state.
|
||||
*/
|
||||
errorret_t entityOverworldCameraRender(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
@@ -1,83 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entityoverworldtrigger.h"
|
||||
#include "entity/entitymanager.h"
|
||||
#include "entity/component/display/entityposition.h"
|
||||
|
||||
static void entityOverworldTriggerUpdate(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
void *user
|
||||
) {
|
||||
entityoverworldtrigger_t *t = entityOverworldTriggerGet(entityId, componentId);
|
||||
|
||||
// Find the player position.
|
||||
vec3 playerPos;
|
||||
bool_t playerFound = false;
|
||||
for(entityid_t i = 0; i < ENTITY_COUNT_MAX; i++) {
|
||||
if((ENTITY_MANAGER.entities[i].state & ENTITY_STATE_ACTIVE) == 0) continue;
|
||||
if(entityGetComponent(i, COMPONENT_TYPE_PLAYER) == COMPONENT_ID_INVALID) continue;
|
||||
componentid_t posComp = entityGetComponent(i, COMPONENT_TYPE_POSITION);
|
||||
if(posComp == COMPONENT_ID_INVALID) continue;
|
||||
entityPositionGetWorldPosition(i, posComp, playerPos);
|
||||
playerFound = true;
|
||||
break;
|
||||
}
|
||||
|
||||
bool_t wasInside = t->playerInside;
|
||||
bool_t nowInside = playerFound && (
|
||||
playerPos[0] >= t->min[0] && playerPos[0] <= t->max[0] &&
|
||||
playerPos[1] >= t->min[1] && playerPos[1] <= t->max[1] &&
|
||||
playerPos[2] >= t->min[2] && playerPos[2] <= t->max[2]
|
||||
);
|
||||
t->playerInside = nowInside;
|
||||
|
||||
if(nowInside) {
|
||||
if(!wasInside && t->onEnter) t->onEnter(entityId, componentId, t->user);
|
||||
if(t->onStay) t->onStay(entityId, componentId, t->user);
|
||||
} else {
|
||||
if(wasInside && t->onExit) t->onExit(entityId, componentId, t->user);
|
||||
if(t->onOutside) t->onOutside(entityId, componentId, t->user);
|
||||
}
|
||||
}
|
||||
|
||||
void entityOverworldTriggerInit(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
entityoverworldtrigger_t *t = entityOverworldTriggerGet(entityId, componentId);
|
||||
glm_vec3_zero(t->min);
|
||||
glm_vec3_zero(t->max);
|
||||
t->playerInside = false;
|
||||
t->onEnter = NULL;
|
||||
t->onExit = NULL;
|
||||
t->onStay = NULL;
|
||||
t->onOutside = NULL;
|
||||
t->user = NULL;
|
||||
entityUpdateAdd(entityId, entityOverworldTriggerUpdate, componentId, NULL);
|
||||
}
|
||||
|
||||
entityoverworldtrigger_t * entityOverworldTriggerGet(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
return componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_OVERWORLD_TRIGGER
|
||||
);
|
||||
}
|
||||
|
||||
void entityOverworldTriggerSetBounds(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const vec3 min,
|
||||
const vec3 max
|
||||
) {
|
||||
entityoverworldtrigger_t *t = entityOverworldTriggerGet(entityId, componentId);
|
||||
glm_vec3_copy((float_t*)min, t->min);
|
||||
glm_vec3_copy((float_t*)max, t->max);
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "entity/entitybase.h"
|
||||
|
||||
typedef void (*entityoverworldtriggercallback_t)(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
void *user
|
||||
);
|
||||
|
||||
typedef struct {
|
||||
vec3 min;
|
||||
vec3 max;
|
||||
bool_t playerInside;
|
||||
entityoverworldtriggercallback_t onEnter;
|
||||
entityoverworldtriggercallback_t onExit;
|
||||
entityoverworldtriggercallback_t onStay;
|
||||
entityoverworldtriggercallback_t onOutside;
|
||||
void *user;
|
||||
} entityoverworldtrigger_t;
|
||||
|
||||
/**
|
||||
* Initializes the overworld trigger component and registers its update
|
||||
* callback with the entity manager. The trigger is entirely self-contained
|
||||
* after init — no scene-level wiring required.
|
||||
*/
|
||||
void entityOverworldTriggerInit(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns a pointer to the overworld trigger component data.
|
||||
*/
|
||||
entityoverworldtrigger_t * entityOverworldTriggerGet(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the trigger AABB bounds.
|
||||
*/
|
||||
void entityOverworldTriggerSetBounds(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const vec3 min,
|
||||
const vec3 max
|
||||
);
|
||||
@@ -1,103 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entityplayer.h"
|
||||
#include "entity/entitymanager.h"
|
||||
#include "entity/component/display/entityposition.h"
|
||||
#include "entity/component/physics/entityphysics.h"
|
||||
#include "entity/component/overworld/entityoverworld.h"
|
||||
#include "entity/component/overworld/entityinteractable.h"
|
||||
#include "input/input.h"
|
||||
|
||||
void entityPlayerInit(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
entityplayer_t *player = entityPlayerGet(entityId, componentId);
|
||||
player->speed = ENTITY_PLAYER_SPEED;
|
||||
player->runSpeed = ENTITY_PLAYER_RUN_SPEED;
|
||||
}
|
||||
|
||||
entityplayer_t * entityPlayerGet(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
return componentGetData(entityId, componentId, COMPONENT_TYPE_PLAYER);
|
||||
}
|
||||
|
||||
void entityPlayerUpdate(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
entityplayer_t *player = entityPlayerGet(entityId, componentId);
|
||||
|
||||
vec2 dir;
|
||||
inputAngle2D(
|
||||
INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT,
|
||||
INPUT_ACTION_UP, INPUT_ACTION_DOWN,
|
||||
dir
|
||||
);
|
||||
|
||||
float_t speed = (
|
||||
inputIsDown(INPUT_ACTION_CANCEL) ? player->runSpeed : player->speed
|
||||
);
|
||||
|
||||
componentid_t owCompId = entityGetComponent(entityId, COMPONENT_TYPE_OVERWORLD);
|
||||
entityoverworld_t *ow = entityOverworldGet(entityId, owCompId);
|
||||
|
||||
if(ow && glm_vec2_norm(dir) > 0.0f) {
|
||||
if(fabsf(dir[0]) >= fabsf(dir[1])) {
|
||||
ow->facing = dir[0] > 0.0f ? FACING_DIR_RIGHT : FACING_DIR_LEFT;
|
||||
} else {
|
||||
ow->facing = dir[1] > 0.0f ? FACING_DIR_DOWN : FACING_DIR_UP;
|
||||
}
|
||||
}
|
||||
|
||||
vec3 vel;
|
||||
entityPhysicsGetVelocity(entityId, ow->physCompId, vel);
|
||||
vel[0] = dir[0] * speed;
|
||||
vel[2] = dir[1] * speed;
|
||||
entityPhysicsSetVelocity(entityId, ow->physCompId, vel);
|
||||
|
||||
if(!inputPressed(INPUT_ACTION_ACCEPT)) return;
|
||||
|
||||
vec3 playerPos;
|
||||
componentid_t playerPosCompId = entityGetComponent(entityId, COMPONENT_TYPE_POSITION);
|
||||
if(playerPosCompId == COMPONENT_ID_INVALID) return;
|
||||
entityPositionGetWorldPosition(entityId, playerPosCompId, playerPos);
|
||||
|
||||
vec2 facingDir;
|
||||
facingDirToVec2(ow ? ow->facing : FACING_DIR_DOWN, facingDir);
|
||||
|
||||
for(entityid_t i = 0; i < ENTITY_COUNT_MAX; i++) {
|
||||
if((ENTITY_MANAGER.entities[i].state & ENTITY_STATE_ACTIVE) == 0) continue;
|
||||
if(i == entityId) continue;
|
||||
|
||||
componentid_t interComp = entityGetComponent(i, COMPONENT_TYPE_INTERACTABLE);
|
||||
if(interComp == COMPONENT_ID_INVALID) continue;
|
||||
|
||||
componentid_t posComp = entityGetComponent(i, COMPONENT_TYPE_POSITION);
|
||||
if(posComp == COMPONENT_ID_INVALID) continue;
|
||||
|
||||
vec3 targetPos;
|
||||
entityPositionGetWorldPosition(i, posComp, targetPos);
|
||||
|
||||
vec2 toTarget = {
|
||||
targetPos[0] - playerPos[0],
|
||||
targetPos[2] - playerPos[2],
|
||||
};
|
||||
float_t forward = glm_vec2_dot(facingDir, toTarget);
|
||||
if(forward <= 0.0f || forward > ENTITY_PLAYER_INTERACT_RANGE) continue;
|
||||
|
||||
float_t lateral = fabsf(
|
||||
facingDir[0] * toTarget[1] - facingDir[1] * toTarget[0]
|
||||
);
|
||||
if(lateral > ENTITY_PLAYER_INTERACT_LATERAL) continue;
|
||||
|
||||
entityInteractableTrigger(i, interComp);
|
||||
}
|
||||
}
|
||||
@@ -1,52 +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 "entity/entitybase.h"
|
||||
|
||||
#define ENTITY_PLAYER_SPEED 4.0f
|
||||
#define ENTITY_PLAYER_RUN_SPEED 8.0f
|
||||
#define ENTITY_PLAYER_INTERACT_RANGE 1.5f
|
||||
#define ENTITY_PLAYER_INTERACT_LATERAL 0.6f
|
||||
|
||||
typedef struct {
|
||||
float_t speed;
|
||||
float_t runSpeed;
|
||||
} entityplayer_t;
|
||||
|
||||
/**
|
||||
* Initializes the player component.
|
||||
*
|
||||
* @param entityId The owning entity.
|
||||
* @param componentId This component's ID.
|
||||
*/
|
||||
void entityPlayerInit(const entityid_t entityId, const componentid_t componentId);
|
||||
|
||||
/**
|
||||
* Returns a pointer to the player component data.
|
||||
*
|
||||
* @param entityId The owning entity.
|
||||
* @param componentId This component's ID.
|
||||
* @return Pointer to the entityplayer_t data.
|
||||
*/
|
||||
entityplayer_t * entityPlayerGet(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Reads input, moves the player, updates facing direction, and checks for
|
||||
* interactable entities in front of the player when accept is pressed.
|
||||
*
|
||||
* @param entityId The owning entity.
|
||||
* @param componentId This component's ID.
|
||||
*/
|
||||
void entityPlayerUpdate(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
@@ -1,7 +0,0 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
|
||||
|
||||
@@ -10,11 +10,6 @@
|
||||
#include "entity/component/display/entityrenderable.h"
|
||||
#include "entity/component/physics/entityphysics.h"
|
||||
#include "entity/component/trigger/entitytrigger.h"
|
||||
#include "entity/component/overworld/entityoverworld.h"
|
||||
#include "entity/component/overworld/entityoverworldtrigger.h"
|
||||
#include "entity/component/overworld/entityplayer.h"
|
||||
#include "entity/component/overworld/entityinteractable.h"
|
||||
#include "entity/component/overworld/entityoverworldcamera.h"
|
||||
|
||||
// Name (Uppercase)
|
||||
// Structure
|
||||
@@ -28,8 +23,3 @@ X(CAMERA, entitycamera_t, camera, entityCameraInit, NULL, NULL)
|
||||
X(RENDERABLE, entityrenderable_t, renderable, entityRenderableInit, entityRenderableDispose, NULL)
|
||||
X(PHYSICS, entityphysics_t, physics, entityPhysicsInit, entityPhysicsDispose, NULL)
|
||||
X(TRIGGER, entitytrigger_t, trigger, entityTriggerInit, NULL, NULL)
|
||||
X(OVERWORLD, entityoverworld_t, overworld, entityOverworldInit, NULL, NULL)
|
||||
X(PLAYER, entityplayer_t, player, entityPlayerInit, NULL, NULL)
|
||||
X(INTERACTABLE, entityinteractable_t, interactable, entityInteractableInit, NULL, NULL)
|
||||
X(OVERWORLD_CAMERA, entityoverworldcamera_t, overworldCamera, entityOverworldCameraInit, NULL, entityOverworldCameraRender)
|
||||
X(OVERWORLD_TRIGGER, entityoverworldtrigger_t, overworldTrigger, entityOverworldTriggerInit, NULL, NULL)
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
# 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
|
||||
scene.c
|
||||
scenerenderpipeline.c
|
||||
)
|
||||
|
||||
# Subdirectories
|
||||
add_subdirectory(initial)
|
||||
add_subdirectory(test)
|
||||
add_subdirectory(overworld)
|
||||
@@ -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
|
||||
initialscene.c
|
||||
)
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "initialscene.h"
|
||||
#include "console/console.h"
|
||||
#include "scene/scene.h"
|
||||
#include "script/script.h"
|
||||
#include "entity/entitymanager.h"
|
||||
#include "entity/entity.h"
|
||||
#include "entity/component/display/entityposition.h"
|
||||
#include "entity/component/display/entityrenderable.h"
|
||||
|
||||
void initialSceneInit(void) {
|
||||
consolePrint("Initial scene initialized");
|
||||
errorCatch(errorPrint(scriptExecFile("testentity.js")));
|
||||
}
|
||||
|
||||
errorret_t initialSceneUpdate(void) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void initialSceneDispose(void) {
|
||||
entityDispose(SCENE.data.initial.cubeEntityId);
|
||||
}
|
||||
@@ -1,18 +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 "entity/entitybase.h"
|
||||
|
||||
typedef struct {
|
||||
entityid_t cubeEntityId;
|
||||
} initialscene_t;
|
||||
|
||||
void initialSceneInit(void);
|
||||
errorret_t initialSceneUpdate(void);
|
||||
void initialSceneDispose(void);
|
||||
@@ -1,12 +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
|
||||
overworldground.c
|
||||
overworldnpc.c
|
||||
overworldplayer.c
|
||||
overworldscene.c
|
||||
)
|
||||
@@ -1,49 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "overworldground.h"
|
||||
#include "entity/entitymanager.h"
|
||||
#include "entity/component/physics/entityphysics.h"
|
||||
|
||||
#define OVERWORLD_GROUND_SIZE 20.0f
|
||||
|
||||
void overworldGroundAdd(overworldground_t *ground) {
|
||||
ground->entityId = entityManagerAdd();
|
||||
ground->posCompId = entityAddComponent(
|
||||
ground->entityId, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
entityAddComponent(ground->entityId, COMPONENT_TYPE_RENDERABLE);
|
||||
vec3 pos = { -OVERWORLD_GROUND_SIZE, 0.0f, -OVERWORLD_GROUND_SIZE };
|
||||
vec3 scale = { OVERWORLD_GROUND_SIZE * 2.0f, 1.0f, OVERWORLD_GROUND_SIZE * 2.0f };
|
||||
entityPositionSetLocalPosition(ground->entityId, ground->posCompId, pos);
|
||||
entityPositionSetLocalScale(ground->entityId, ground->posCompId, scale);
|
||||
|
||||
// Separate physics entity centered on the finite ground area.
|
||||
// The visual entity's position is its corner {-size, 0, -size}, not its
|
||||
// center, so a box collider on it would be misplaced. This entity sits at
|
||||
// the true center {0, -0.5, 0} with halfExtents matching the visual extent.
|
||||
ground->floorEntityId = entityManagerAdd();
|
||||
componentid_t floorPosComp = entityAddComponent(
|
||||
ground->floorEntityId, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
componentid_t floorPhysComp = entityAddComponent(
|
||||
ground->floorEntityId, COMPONENT_TYPE_PHYSICS
|
||||
);
|
||||
|
||||
vec3 floorPos = { 0.0f, -0.5f, 0.0f };
|
||||
entityPositionSetLocalPosition(ground->floorEntityId, floorPosComp, floorPos);
|
||||
entityPhysicsSetBodyType(ground->floorEntityId, floorPhysComp, PHYSICS_BODY_STATIC);
|
||||
physicsshape_t shape = {
|
||||
.type = PHYSICS_SHAPE_CUBE,
|
||||
.data.cube = { .halfExtents = {
|
||||
OVERWORLD_GROUND_SIZE,
|
||||
0.5f,
|
||||
OVERWORLD_GROUND_SIZE
|
||||
}}
|
||||
};
|
||||
entityPhysicsSetShape(ground->floorEntityId, floorPhysComp, shape);
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "entity/entitybase.h"
|
||||
|
||||
typedef struct {
|
||||
entityid_t entityId;
|
||||
componentid_t posCompId;
|
||||
entityid_t floorEntityId;
|
||||
} overworldground_t;
|
||||
|
||||
/**
|
||||
* Creates the ground entity and adds it to the world.
|
||||
*
|
||||
* @param ground The ground state to initialize.
|
||||
*/
|
||||
void overworldGroundAdd(overworldground_t *ground);
|
||||
@@ -1,47 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "overworldnpc.h"
|
||||
#include "entity/entitymanager.h"
|
||||
#include "entity/component/physics/entityphysics.h"
|
||||
#include "entity/component/trigger/entitytrigger.h"
|
||||
#include "entity/component/overworld/entityoverworld.h"
|
||||
#include "entity/component/overworld/entityinteractable.h"
|
||||
|
||||
void overworldNpcAdd(overworldnpc_t *npc, vec3 position) {
|
||||
npc->entityId = entityManagerAdd();
|
||||
npc->posCompId = entityAddComponent(npc->entityId, COMPONENT_TYPE_POSITION);
|
||||
entityAddComponent(npc->entityId, COMPONENT_TYPE_RENDERABLE);
|
||||
entityAddComponent(npc->entityId, COMPONENT_TYPE_PHYSICS);
|
||||
npc->overworldCompId = entityAddComponent(npc->entityId, COMPONENT_TYPE_OVERWORLD);
|
||||
npc->triggerCompId = entityAddComponent(npc->entityId, COMPONENT_TYPE_TRIGGER);
|
||||
npc->interactableCompId = entityAddComponent(
|
||||
npc->entityId, COMPONENT_TYPE_INTERACTABLE
|
||||
);
|
||||
|
||||
entityPositionSetLocalPosition(npc->entityId, npc->posCompId, position);
|
||||
|
||||
componentid_t physCompId = entityOverworldGet(npc->entityId, npc->overworldCompId)->physCompId;
|
||||
entityPhysicsSetBodyType(npc->entityId, physCompId, PHYSICS_BODY_STATIC);
|
||||
physicsshape_t shape = {
|
||||
.type = PHYSICS_SHAPE_CAPSULE,
|
||||
.data.capsule = { .radius = 0.4f, .halfHeight = 0.1f }
|
||||
};
|
||||
entityPhysicsSetShape(npc->entityId, physCompId, shape);
|
||||
|
||||
vec3 min = {
|
||||
position[0] - OVERWORLD_NPC_INTERACT_RANGE,
|
||||
position[1] - OVERWORLD_NPC_INTERACT_RANGE,
|
||||
position[2] - OVERWORLD_NPC_INTERACT_RANGE
|
||||
};
|
||||
vec3 max = {
|
||||
position[0] + OVERWORLD_NPC_INTERACT_RANGE,
|
||||
position[1] + OVERWORLD_NPC_INTERACT_RANGE,
|
||||
position[2] + OVERWORLD_NPC_INTERACT_RANGE
|
||||
};
|
||||
entityTriggerSetBounds(npc->entityId, npc->triggerCompId, min, max);
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "entity/entitybase.h"
|
||||
|
||||
#define OVERWORLD_NPC_INTERACT_RANGE 1.5f
|
||||
|
||||
typedef struct {
|
||||
entityid_t entityId;
|
||||
componentid_t posCompId;
|
||||
componentid_t overworldCompId;
|
||||
componentid_t triggerCompId;
|
||||
componentid_t interactableCompId;
|
||||
} overworldnpc_t;
|
||||
|
||||
/**
|
||||
* Creates the NPC entity at the given world position.
|
||||
*
|
||||
* @param npc The NPC state to initialize.
|
||||
* @param position World-space position to spawn the NPC at.
|
||||
*/
|
||||
void overworldNpcAdd(overworldnpc_t *npc, vec3 position);
|
||||
@@ -1,38 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "overworldplayer.h"
|
||||
#include "entity/entitymanager.h"
|
||||
#include "entity/component/physics/entityphysics.h"
|
||||
#include "entity/component/overworld/entityoverworld.h"
|
||||
#include "entity/component/overworld/entityplayer.h"
|
||||
|
||||
void overworldPlayerAdd(overworldplayer_t *player) {
|
||||
player->entityId = entityManagerAdd();
|
||||
player->posCompId = entityAddComponent(player->entityId, COMPONENT_TYPE_POSITION);
|
||||
entityAddComponent(player->entityId, COMPONENT_TYPE_RENDERABLE);
|
||||
entityAddComponent(player->entityId, COMPONENT_TYPE_PHYSICS);
|
||||
componentid_t owCompId = entityAddComponent(player->entityId, COMPONENT_TYPE_OVERWORLD);
|
||||
entityOverworldSetType(player->entityId, owCompId, OVERWORLD_ENTITY_TYPE_PLAYER);
|
||||
player->playerCompId = entityAddComponent(player->entityId, COMPONENT_TYPE_PLAYER);
|
||||
|
||||
vec3 pos = { 0.0f, 0.5f, 0.0f };
|
||||
entityPositionSetLocalPosition(player->entityId, player->posCompId, pos);
|
||||
|
||||
componentid_t physCompId = entityOverworldGet(player->entityId, owCompId)->physCompId;
|
||||
entityPhysicsSetBodyType(player->entityId, physCompId, PHYSICS_BODY_DYNAMIC);
|
||||
physicsshape_t shape = {
|
||||
.type = PHYSICS_SHAPE_CAPSULE,
|
||||
.data.capsule = { .radius = 0.4f, .halfHeight = 0.1f }
|
||||
};
|
||||
entityPhysicsSetShape(player->entityId, physCompId, shape);
|
||||
entityPhysicsGet(player->entityId, physCompId)->gravityScale = 1.0f;
|
||||
}
|
||||
|
||||
void overworldPlayerUpdate(overworldplayer_t *player) {
|
||||
entityPlayerUpdate(player->entityId, player->playerCompId);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "entity/entitybase.h"
|
||||
|
||||
typedef struct {
|
||||
entityid_t entityId;
|
||||
componentid_t posCompId;
|
||||
componentid_t playerCompId;
|
||||
} overworldplayer_t;
|
||||
|
||||
/**
|
||||
* Creates the player entity and adds it to the world.
|
||||
*
|
||||
* @param player The player state to initialize.
|
||||
*/
|
||||
void overworldPlayerAdd(overworldplayer_t *player);
|
||||
|
||||
/**
|
||||
* Updates the player entity, reading input and moving on the XZ plane.
|
||||
*
|
||||
* @param player The player state to update.
|
||||
*/
|
||||
void overworldPlayerUpdate(overworldplayer_t *player);
|
||||
@@ -1,173 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "overworldscene.h"
|
||||
#include "overworldplayer.h"
|
||||
#include "overworldground.h"
|
||||
#include "overworldnpc.h"
|
||||
#include "console/console.h"
|
||||
#include "entity/entitymanager.h"
|
||||
#include "entity/component/display/entityrenderable.h"
|
||||
#include "display/shader/shaderlist.h"
|
||||
#include "entity/component/overworld/entityinteractable.h"
|
||||
#include "entity/component/overworld/entityoverworldcamera.h"
|
||||
#include "entity/component/overworld/entityoverworldtrigger.h"
|
||||
#include "display/mesh/cube.h"
|
||||
#include "display/mesh/plane.h"
|
||||
#include "display/displaystate.h"
|
||||
#include "scene/scene.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
#define OVERWORLD (SCENE.data.overworld)
|
||||
|
||||
static void overworldSceneTestTriggerEnter(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
void *user
|
||||
) {
|
||||
consolePrint("Test trigger: player entered");
|
||||
}
|
||||
|
||||
static void overworldSceneTestTriggerExit(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
void *user
|
||||
) {
|
||||
consolePrint("Test trigger: player exited");
|
||||
}
|
||||
|
||||
static void overworldSceneNpcInteract(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
void *user
|
||||
) {
|
||||
consolePrint("NPC interacted with!");
|
||||
}
|
||||
|
||||
void overworldSceneConfigureShaderMaterial(
|
||||
const entityid_t entityId,
|
||||
const color_t color,
|
||||
mesh_t *mesh
|
||||
) {
|
||||
assertNotNull(mesh, "Mesh cannot be null");
|
||||
componentid_t renderComp = entityGetComponent(
|
||||
entityId, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
entityRenderableSetType(entityId, renderComp, ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL);
|
||||
entityrenderable_t *r = componentGetData(
|
||||
entityId, renderComp, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
r->data.material.shaderType = SHADER_LIST_SHADER_UNLIT;
|
||||
r->data.material.material.unlit.color = color;
|
||||
r->data.material.material.unlit.texture = NULL;
|
||||
r->data.material.state.flags = DISPLAY_STATE_FLAG_DEPTH_TEST;
|
||||
r->data.material.meshes[0] = mesh;
|
||||
r->data.material.meshOffsets[0] = 0;
|
||||
r->data.material.meshCounts[0] = -1;
|
||||
r->data.material.meshCount = 1;
|
||||
}
|
||||
|
||||
void overworldSceneConfigureSprite(
|
||||
const entityid_t entityId,
|
||||
const color_t color,
|
||||
texture_t *texture
|
||||
) {
|
||||
componentid_t renderComp = entityGetComponent(
|
||||
entityId, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
entityRenderableSetType(entityId, renderComp, ENTITY_RENDERABLE_TYPE_SPRITEBATCH);
|
||||
entityrenderable_t *r = componentGetData(
|
||||
entityId, renderComp, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
r->data.spritebatch.texture = texture;
|
||||
r->data.spritebatch.spriteCount = 1;
|
||||
|
||||
r->data.spritebatch.sprites[0].min[0] = -0.5f;
|
||||
r->data.spritebatch.sprites[0].min[1] = -0.0f;
|
||||
r->data.spritebatch.sprites[0].min[2] = -0.5f;
|
||||
|
||||
r->data.spritebatch.sprites[0].max[0] = 0.5f;
|
||||
r->data.spritebatch.sprites[0].max[1] = 0.0f;
|
||||
r->data.spritebatch.sprites[0].max[2] = 0.5f;
|
||||
|
||||
r->data.spritebatch.sprites[0].uvMin[0] = 0.0f;
|
||||
r->data.spritebatch.sprites[0].uvMin[1] = 0.0f;
|
||||
r->data.spritebatch.sprites[0].uvMax[0] = 1.0f;
|
||||
r->data.spritebatch.sprites[0].uvMax[1] = 1.0f;
|
||||
}
|
||||
|
||||
void overworldSceneInit(void) {
|
||||
consolePrint("Overworld scene initialized");
|
||||
|
||||
overworldGroundAdd(&OVERWORLD.ground);
|
||||
overworldSceneConfigureShaderMaterial(
|
||||
OVERWORLD.ground.entityId, COLOR_MAGENTA, &PLANE_MESH_SIMPLE
|
||||
);
|
||||
|
||||
overworldPlayerAdd(&OVERWORLD.player);
|
||||
overworldSceneConfigureSprite(OVERWORLD.player.entityId, COLOR_GREEN, NULL);
|
||||
|
||||
vec3 npcPos = { 3.0f, 0.5f, 3.0f };
|
||||
overworldNpcAdd(&OVERWORLD.npc, npcPos);
|
||||
overworldSceneConfigureSprite(OVERWORLD.npc.entityId, COLOR_BLUE, NULL);
|
||||
|
||||
OVERWORLD.cameraEntityId = entityManagerAdd();
|
||||
entityAddComponent(OVERWORLD.cameraEntityId, COMPONENT_TYPE_POSITION);
|
||||
entityAddComponent(OVERWORLD.cameraEntityId, COMPONENT_TYPE_CAMERA);
|
||||
OVERWORLD.cameraOverworldCompId = entityAddComponent(
|
||||
OVERWORLD.cameraEntityId, COMPONENT_TYPE_OVERWORLD_CAMERA
|
||||
);
|
||||
entityOverworldCameraSetTarget(
|
||||
OVERWORLD.cameraEntityId, OVERWORLD.cameraOverworldCompId,
|
||||
OVERWORLD.player.entityId, OVERWORLD.player.posCompId
|
||||
);
|
||||
entityoverworldcamera_t *camData = entityOverworldCameraGet(
|
||||
OVERWORLD.cameraEntityId, OVERWORLD.cameraOverworldCompId
|
||||
);
|
||||
glm_vec3_zero(camData->targetOffset);
|
||||
glm_vec3_copy((vec3){ 0.0f, 0.0f, 5.0f }, camData->eyeOffset);
|
||||
camData->scale = 32.0f;
|
||||
entityInteractableSetCallback(
|
||||
OVERWORLD.npc.entityId, OVERWORLD.npc.interactableCompId,
|
||||
overworldSceneNpcInteract, NULL
|
||||
);
|
||||
|
||||
OVERWORLD.testTriggerId = entityManagerAdd();
|
||||
OVERWORLD.testTriggerCompId = entityAddComponent(
|
||||
OVERWORLD.testTriggerId, COMPONENT_TYPE_OVERWORLD_TRIGGER
|
||||
);
|
||||
entityOverworldTriggerSetBounds(
|
||||
OVERWORLD.testTriggerId, OVERWORLD.testTriggerCompId,
|
||||
(vec3){ -2.0f, -1.0f, -2.0f },
|
||||
(vec3){ 2.0f, 1.0f, 2.0f }
|
||||
);
|
||||
entityoverworldtrigger_t *testTrigger = entityOverworldTriggerGet(
|
||||
OVERWORLD.testTriggerId, OVERWORLD.testTriggerCompId
|
||||
);
|
||||
testTrigger->onEnter = overworldSceneTestTriggerEnter;
|
||||
testTrigger->onExit = overworldSceneTestTriggerExit;
|
||||
}
|
||||
|
||||
errorret_t overworldSceneUpdate(void) {
|
||||
overworldPlayerUpdate(&OVERWORLD.player);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void overworldSceneDispose(void) {
|
||||
OVERWORLD.cameraEntityId = ENTITY_ID_INVALID;
|
||||
OVERWORLD.cameraOverworldCompId = COMPONENT_ID_INVALID;
|
||||
OVERWORLD.ground.entityId = ENTITY_ID_INVALID;
|
||||
OVERWORLD.ground.posCompId = COMPONENT_ID_INVALID;
|
||||
OVERWORLD.ground.floorEntityId = ENTITY_ID_INVALID;
|
||||
OVERWORLD.player.entityId = ENTITY_ID_INVALID;
|
||||
OVERWORLD.player.posCompId = COMPONENT_ID_INVALID;
|
||||
OVERWORLD.npc.entityId = ENTITY_ID_INVALID;
|
||||
OVERWORLD.npc.posCompId = COMPONENT_ID_INVALID;
|
||||
OVERWORLD.npc.overworldCompId = COMPONENT_ID_INVALID;
|
||||
OVERWORLD.npc.triggerCompId = COMPONENT_ID_INVALID;
|
||||
OVERWORLD.npc.interactableCompId = COMPONENT_ID_INVALID;
|
||||
}
|
||||
@@ -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 "error/error.h"
|
||||
#include "entity/entitybase.h"
|
||||
#include "display/color.h"
|
||||
#include "display/mesh/mesh.h"
|
||||
#include "display/texture/texture.h"
|
||||
#include "scene/overworld/overworldplayer.h"
|
||||
#include "scene/overworld/overworldground.h"
|
||||
#include "scene/overworld/overworldnpc.h"
|
||||
|
||||
typedef struct {
|
||||
entityid_t cameraEntityId;
|
||||
componentid_t cameraOverworldCompId;
|
||||
overworldplayer_t player;
|
||||
overworldground_t ground;
|
||||
overworldnpc_t npc;
|
||||
entityid_t testTriggerId;
|
||||
componentid_t testTriggerCompId;
|
||||
} overworldscene_t;
|
||||
|
||||
/**
|
||||
* Configures a SHADER_MATERIAL renderable on an entity with the unlit shader,
|
||||
* depth testing enabled, no blending, and a single mesh.
|
||||
*
|
||||
* @param entityId The entity to configure.
|
||||
* @param color Unlit diffuse color.
|
||||
* @param mesh Mesh to render.
|
||||
*/
|
||||
void overworldSceneConfigureShaderMaterial(
|
||||
const entityid_t entityId,
|
||||
const color_t color,
|
||||
mesh_t *mesh
|
||||
);
|
||||
|
||||
/**
|
||||
* Configures a SPRITEBATCH renderable on an entity as a single 1x1 billboard
|
||||
* sprite centered at the entity's origin.
|
||||
*
|
||||
* @param entityId The entity to configure.
|
||||
* @param color Per-vertex sprite tint color.
|
||||
* @param texture Texture to use, or NULL for untextured.
|
||||
*/
|
||||
void overworldSceneConfigureSprite(
|
||||
const entityid_t entityId,
|
||||
const color_t color,
|
||||
texture_t *texture
|
||||
);
|
||||
|
||||
/**
|
||||
* Initializes the overworld scene, spawning all entities and configuring
|
||||
* the camera, player, ground, and NPC.
|
||||
*/
|
||||
void overworldSceneInit(void);
|
||||
|
||||
/**
|
||||
* Updates the overworld scene each frame.
|
||||
*
|
||||
* @return Error state.
|
||||
*/
|
||||
errorret_t overworldSceneUpdate(void);
|
||||
|
||||
/**
|
||||
* Disposes the overworld scene, invalidating all entity and component IDs.
|
||||
*/
|
||||
void overworldSceneDispose(void);
|
||||
+92
-51
@@ -6,25 +6,18 @@
|
||||
#include "scene.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "log/log.h"
|
||||
#include "time/time.h"
|
||||
#include "display/screen/screen.h"
|
||||
#include "entity/entitymanager.h"
|
||||
#include "display/shader/shaderunlit.h"
|
||||
#include "display/display.h"
|
||||
#include "console/console.h"
|
||||
#include "util/string.h"
|
||||
#include "ui/ui.h"
|
||||
#include "scene/scenerenderpipeline.h"
|
||||
#include "entity/component.h"
|
||||
|
||||
scenefuncs_t SCENE_FUNCTIONS[SCENE_TYPE_COUNT] = {
|
||||
{ 0 },
|
||||
#define X(structName, varName, varNameUpper, initFunc, updateFunc, disposeFunc) \
|
||||
{ initFunc, updateFunc, disposeFunc },
|
||||
#include "scene/scenelist.h"
|
||||
#undef X
|
||||
};
|
||||
#include "asset/asset.h"
|
||||
#include "asset/loader/assetloader.h"
|
||||
#include "script/module/modulebase.h"
|
||||
#include "console/console.h"
|
||||
|
||||
scene_t SCENE;
|
||||
|
||||
@@ -33,39 +26,21 @@ errorret_t sceneInit(void) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t sceneUpdate(void) {
|
||||
// Handle scene change
|
||||
if(SCENE.nextType != SCENE_TYPE_NULL) {
|
||||
// Dispose current scene.
|
||||
if(SCENE.type != SCENE_TYPE_NULL) {
|
||||
if(SCENE_FUNCTIONS[SCENE.type].dispose) {
|
||||
SCENE_FUNCTIONS[SCENE.type].dispose();
|
||||
}
|
||||
void sceneUpdate(void) {
|
||||
if((SCENE.state & SCENE_STATE_LOADED) == 0) return;
|
||||
|
||||
if((SCENE.state & SCENE_STATE_INITIALIZED) == 0) {
|
||||
errorret_t ret = sceneCallScriptMethod(SCENE.jsInit, "init");
|
||||
if(errorIsNotOk(ret)) {
|
||||
errorCatch(errorPrint(ret));
|
||||
errorCatch(errorPrint(sceneDispose()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Init new scene
|
||||
SCENE.type = SCENE.nextType;
|
||||
SCENE.nextType = SCENE_TYPE_NULL;
|
||||
|
||||
if(SCENE.type != SCENE_TYPE_NULL) {
|
||||
if(SCENE_FUNCTIONS[SCENE.type].init) {
|
||||
SCENE_FUNCTIONS[SCENE.type].init();
|
||||
}
|
||||
}
|
||||
SCENE.state |= SCENE_STATE_INITIALIZED;
|
||||
}
|
||||
|
||||
// Update scene
|
||||
#ifdef DUSK_TIME_DYNAMIC
|
||||
if(TIME.dynamicUpdate) {
|
||||
errorOk();
|
||||
}
|
||||
#endif
|
||||
|
||||
if(SCENE.type != SCENE_TYPE_NULL && SCENE_FUNCTIONS[SCENE.type].update) {
|
||||
errorChain(SCENE_FUNCTIONS[SCENE.type].update());
|
||||
}
|
||||
|
||||
errorOk();
|
||||
errorCatch(errorPrint(sceneCallScriptMethod(SCENE.jsUpdate, "update")));
|
||||
}
|
||||
|
||||
errorret_t sceneRender(void) {
|
||||
@@ -103,21 +78,87 @@ errorret_t sceneRender(void) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void sceneSet(const scenetype_t type) {
|
||||
assertTrue(
|
||||
type > SCENE_TYPE_NULL && type < SCENE_TYPE_COUNT,
|
||||
"Invalid scene type"
|
||||
);
|
||||
SCENE.nextType = type;
|
||||
errorret_t sceneSet(const char_t *jsFile) {
|
||||
assertStrLenMin(jsFile, 1, "jsFile cannot be empty");
|
||||
|
||||
// Dispose any active scene before switching.
|
||||
errorChain(sceneDispose());
|
||||
|
||||
// // Lock the asset
|
||||
// assetloaderinput_t input;
|
||||
// memoryZero(&input, sizeof(input));
|
||||
// assetentry_t *entry = assetLock(jsFile, ASSET_LOADER_TYPE_SCRIPT, &input);
|
||||
// if(!entry) errorThrow("sceneSet: failed to lock '%s'", jsFile);
|
||||
|
||||
// // Wait for loaded.
|
||||
// errorret_t err = assetRequireLoaded(entry);
|
||||
// if(errorIsNotOk(err)) {
|
||||
// assetUnlockEntry(entry);
|
||||
// errorChain(err);
|
||||
// }
|
||||
|
||||
// // Any export?
|
||||
// if(entry->data.script.exportedModule == 0) {
|
||||
// assetUnlockEntry(entry);
|
||||
// errorThrow("sceneSet: '%s' produced no exports", jsFile);
|
||||
// }
|
||||
|
||||
// // Copy the exports reference before unlocking (entry may be reaped).
|
||||
// jerry_value_t exports = jerry_value_copy(entry->data.script.exportedModule);
|
||||
// assetUnlockEntry(entry);
|
||||
|
||||
// // Extract the three lifecycle callbacks.
|
||||
// SCENE.jsInit = sceneExtractFn(exports, "init");
|
||||
// SCENE.jsUpdate = sceneExtractFn(exports, "update");
|
||||
// SCENE.jsDispose = sceneExtractFn(exports, "dispose");
|
||||
// jerry_value_free(exports);
|
||||
|
||||
SCENE.state |= SCENE_STATE_LOADED;
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t sceneCallScriptMethod(jerry_value_t fn, const char_t *name) {
|
||||
assertStrLenMin(name, 1, "name cannot be empty");
|
||||
|
||||
if(!jerry_value_is_function(fn)) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
jerry_value_t ret = jerry_call(fn, jerry_undefined(), NULL, 0);
|
||||
if(jerry_value_is_exception(ret)) {
|
||||
char_t buf[256];
|
||||
moduleBaseExceptionMessage(ret, buf, sizeof(buf));
|
||||
errorThrow("Scene script exception in %s: %s", name, buf);
|
||||
}
|
||||
jerry_value_free(ret);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
jerry_value_t sceneExtractFn(jerry_value_t obj, const char_t *name) {
|
||||
jerry_value_t val = moduleBaseGetProp(obj, name);
|
||||
if(jerry_value_is_function(val)) return val;
|
||||
jerry_value_free(val);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
errorret_t sceneDispose(void) {
|
||||
if(SCENE.type != SCENE_TYPE_NULL) {
|
||||
if(SCENE_FUNCTIONS[SCENE.type].dispose) {
|
||||
SCENE_FUNCTIONS[SCENE.type].dispose();
|
||||
if((SCENE.state & SCENE_STATE_LOADED) == 0) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
if((SCENE.state & SCENE_STATE_INITIALIZED) != 0) {
|
||||
errorChain(sceneCallScriptMethod(SCENE.jsDispose, "dispose"));
|
||||
}
|
||||
SCENE.type = SCENE_TYPE_NULL;
|
||||
SCENE.nextType = SCENE_TYPE_NULL;
|
||||
|
||||
jerry_value_free(SCENE.jsDispose);
|
||||
jerry_value_free(SCENE.jsInit);
|
||||
jerry_value_free(SCENE.jsUpdate);
|
||||
|
||||
SCENE.jsDispose = jerry_undefined();
|
||||
SCENE.jsInit = jerry_undefined();
|
||||
SCENE.jsUpdate = jerry_undefined();
|
||||
|
||||
SCENE.state = 0;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
+45
-47
@@ -6,75 +6,73 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "asset/assetfile.h"
|
||||
#include "scene/initial/initialscene.h"
|
||||
#include "scene/test/testscene.h"
|
||||
#include "scene/overworld/overworldscene.h"
|
||||
#include "error/error.h"
|
||||
#include <jerryscript.h>
|
||||
|
||||
#define SCENE_EVENT_UPDATE_MAX 16
|
||||
|
||||
typedef enum {
|
||||
SCENE_TYPE_NULL,
|
||||
#define X(structName, varName, varNameUpper, initFunc, updateFunc, disposeFunc) \
|
||||
SCENE_TYPE_##varNameUpper,
|
||||
#include "scene/scenelist.h"
|
||||
#undef X
|
||||
SCENE_TYPE_COUNT
|
||||
} scenetype_t;
|
||||
|
||||
typedef union {
|
||||
#define X(structName, varName, varNameUpper, initFunc, updateFunc, disposeFunc) \
|
||||
structName varName;
|
||||
#include "scene/scenelist.h"
|
||||
#undef X
|
||||
} scenedata_t;
|
||||
#define SCENE_STATE_LOADED 0x01
|
||||
#define SCENE_STATE_INITIALIZED 0x02
|
||||
|
||||
typedef struct {
|
||||
void (*init)(void);
|
||||
errorret_t (*update)(void);
|
||||
void (*dispose)(void);
|
||||
} scenefuncs_t;
|
||||
|
||||
typedef struct {
|
||||
scenedata_t data;
|
||||
scenetype_t type;
|
||||
scenetype_t nextType;
|
||||
jerry_value_t jsInit;
|
||||
jerry_value_t jsUpdate;
|
||||
jerry_value_t jsDispose;
|
||||
uint8_t state;
|
||||
} scene_t;
|
||||
|
||||
extern scenefuncs_t SCENE_FUNCTIONS[SCENE_TYPE_COUNT];
|
||||
extern scene_t SCENE;
|
||||
|
||||
/**
|
||||
* Initializes the scene manager.
|
||||
*
|
||||
* @return Any error state that happened.
|
||||
* Initialises the scene manager.
|
||||
*/
|
||||
errorret_t sceneInit(void);
|
||||
|
||||
/**
|
||||
* Ticks the scene manager; may call the scene's update method.
|
||||
*
|
||||
* @return Any error state that happened.
|
||||
* Ticks the scene manager. Processes any pending scene transition, then
|
||||
* calls scriptSceneUpdate on the active scene.
|
||||
*/
|
||||
errorret_t sceneUpdate(void);
|
||||
void sceneUpdate(void);
|
||||
|
||||
/**
|
||||
* Renders the scene.
|
||||
*
|
||||
* @return Any error state that happened.
|
||||
* Renders the current scene (entities, render pipeline, UI).
|
||||
*/
|
||||
errorret_t sceneRender(void);
|
||||
|
||||
/**
|
||||
* Requests a scene change on the next safe opportunity.
|
||||
*
|
||||
* @param type The type of scene to change to.
|
||||
* Requests a scene transition. The pending script scene (set via
|
||||
* scriptSceneSetPending) will be activated at the start of the next
|
||||
* sceneUpdate call.
|
||||
*/
|
||||
void sceneSet(const scenetype_t type);
|
||||
// void sceneSet(void);
|
||||
|
||||
/**
|
||||
* Disposes of the current scene.
|
||||
* Loads a JS file as a module, extracts the .init / .update / .dispose
|
||||
* callbacks from its exports, and stores them ready for the scene loop.
|
||||
* Disposes any previously active scene first.
|
||||
*
|
||||
* @return Any error state that happened.
|
||||
* @param jsFile Asset path of the script (e.g. "testscene.js").
|
||||
*/
|
||||
errorret_t sceneSet(const char_t *jsFile);
|
||||
|
||||
/**
|
||||
* Calls a scene script method and logs any exception.
|
||||
*
|
||||
* @param fn The function to call. If not a function, this is a no-op.
|
||||
* @param name The name of the method (for logging purposes).
|
||||
* @return Error state (if any).
|
||||
*/
|
||||
errorret_t sceneCallScriptMethod(jerry_value_t fn, const char_t *name);
|
||||
|
||||
/**
|
||||
* Extracts a function from a scene module export. If the property doesn't
|
||||
* exist or isn't a function, returns undefined.
|
||||
*
|
||||
* @param obj The module exports object.
|
||||
* @param name The name of the property to extract.
|
||||
* @return The extracted function, or undefined.
|
||||
*/
|
||||
jerry_value_t sceneExtractFn(jerry_value_t obj, const char_t *name);
|
||||
|
||||
/**
|
||||
* Disposes the active scene immediately.
|
||||
*/
|
||||
errorret_t sceneDispose(void);
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#ifndef X
|
||||
#define X(structName, varName, varNameUpper, initFunc, updateFunc, disposeFunc) \
|
||||
((void))
|
||||
#endif
|
||||
|
||||
X(initialscene_t, initial, INITIAL, initialSceneInit, initialSceneUpdate, initialSceneDispose)
|
||||
X(testscene_t, test, TEST, testSceneInit, testSceneUpdate, testSceneDispose)
|
||||
X(overworldscene_t, overworld, OVERWORLD, overworldSceneInit, overworldSceneUpdate, overworldSceneDispose)
|
||||
@@ -1,110 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "testscene.h"
|
||||
#include "console/console.h"
|
||||
#include "entity/entitymanager.h"
|
||||
#include "input/input.h"
|
||||
|
||||
#define TEST_SCENE_ENTITY_MAX (ENTITY_COUNT_MAX - 1)
|
||||
|
||||
static entityid_t cameraEntityId;
|
||||
static componentid_t cameraCompId;
|
||||
static entityid_t testEntities[TEST_SCENE_ENTITY_MAX];
|
||||
static uint8_t testEntityCount = 0;
|
||||
|
||||
static void testSceneSpawnTestEntity(void) {
|
||||
if(testEntityCount >= TEST_SCENE_ENTITY_MAX) return;
|
||||
|
||||
entityid_t entity = entityManagerAdd();
|
||||
componentid_t posComp = entityAddComponent(entity, COMPONENT_TYPE_POSITION);
|
||||
entityAddComponent(entity, COMPONENT_TYPE_RENDERABLE);
|
||||
|
||||
const int32_t cols = 20;
|
||||
const float_t spacing = 1.5f;
|
||||
int32_t col = testEntityCount % cols;
|
||||
int32_t row = testEntityCount / cols;
|
||||
vec3 pos = {
|
||||
((float_t)col - (cols - 1) * 0.5f) * spacing,
|
||||
0.0f,
|
||||
(float_t)row * spacing
|
||||
};
|
||||
entityPositionSetLocalPosition(entity, posComp, pos);
|
||||
|
||||
testEntities[testEntityCount++] = entity;
|
||||
}
|
||||
|
||||
static void testSceneUpdateCamera(void) {
|
||||
if(testEntityCount == 0) return;
|
||||
|
||||
float_t minX = FLT_MAX, maxX = -FLT_MAX;
|
||||
float_t minZ = FLT_MAX, maxZ = -FLT_MAX;
|
||||
|
||||
for(entityid_t i = 0; i < testEntityCount; i++) {
|
||||
componentid_t posComp = entityGetComponent(
|
||||
testEntities[i], COMPONENT_TYPE_POSITION
|
||||
);
|
||||
if(posComp == COMPONENT_ID_INVALID) continue;
|
||||
vec3 pos;
|
||||
entityPositionGetLocalPosition(testEntities[i], posComp, pos);
|
||||
if(pos[0] < minX) minX = pos[0];
|
||||
if(pos[0] > maxX) maxX = pos[0];
|
||||
if(pos[2] < minZ) minZ = pos[2];
|
||||
if(pos[2] > maxZ) maxZ = pos[2];
|
||||
}
|
||||
|
||||
float_t centerX = (minX + maxX) * 0.5f;
|
||||
float_t centerZ = (minZ + maxZ) * 0.5f;
|
||||
float_t extentX = (maxX - minX) * 0.5f + 0.5f;
|
||||
float_t extentZ = (maxZ - minZ) * 0.5f + 0.5f;
|
||||
float_t extent = extentX > extentZ ? extentX : extentZ;
|
||||
float_t dist = extent * 1.5f + 2.0f;
|
||||
|
||||
vec3 target = { centerX, 0.0f, centerZ };
|
||||
vec3 eye = { centerX + dist, dist, centerZ + dist };
|
||||
vec3 up = { 0.0f, 1.0f, 0.0f };
|
||||
entityPositionLookAt(cameraEntityId, cameraCompId, eye, target, up);
|
||||
}
|
||||
|
||||
void testSceneInit(void) {
|
||||
consolePrint("Test scene initialized");
|
||||
testEntityCount = 0;
|
||||
|
||||
cameraEntityId = entityManagerAdd();
|
||||
cameraCompId = entityAddComponent(cameraEntityId, COMPONENT_TYPE_POSITION);
|
||||
entityAddComponent(cameraEntityId, COMPONENT_TYPE_CAMERA);
|
||||
|
||||
vec3 eye, target, up;
|
||||
glm_vec3_zero(target);
|
||||
glm_vec3_copy((vec3){ 3.0f, 3.0f, 3.0f }, eye);
|
||||
glm_vec3_copy((vec3){ 0.0f, 1.0f, 0.0f }, up);
|
||||
entityPositionLookAt(cameraEntityId, cameraCompId, eye, target, up);
|
||||
|
||||
for(int i = 0; i < 5; i++) testSceneSpawnTestEntity();
|
||||
}
|
||||
|
||||
errorret_t testSceneUpdate(void) {
|
||||
if(inputPressed(INPUT_ACTION_ACCEPT)) {
|
||||
for(int i = 0; i < 5; i++) testSceneSpawnTestEntity();
|
||||
}
|
||||
|
||||
if(inputPressed(INPUT_ACTION_CANCEL)) {
|
||||
for(int i = 0; i < 5 && testEntityCount > 0; i++) {
|
||||
entityDispose(testEntities[--testEntityCount]);
|
||||
}
|
||||
}
|
||||
|
||||
testSceneUpdateCamera();
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void testSceneDispose(void) {
|
||||
testEntityCount = 0;
|
||||
cameraEntityId = ENTITY_ID_INVALID;
|
||||
cameraCompId = COMPONENT_ID_INVALID;
|
||||
}
|
||||
@@ -6,4 +6,8 @@
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
modulebase.c
|
||||
modulelist.c
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(require)
|
||||
@@ -10,6 +10,7 @@
|
||||
#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"
|
||||
|
||||
@@ -89,6 +90,7 @@ moduleBaseFunction(moduleAssetUnlock) {
|
||||
|
||||
static void moduleAssetInit(void) {
|
||||
moduleAssetEntryInit();
|
||||
moduleAssetBatchInit();
|
||||
scriptProtoInit(&MODULE_ASSET_PROTO, "Asset", sizeof(uint8_t), NULL);
|
||||
scriptProtoDefineStaticFunc(&MODULE_ASSET_PROTO, "exists", moduleAssetExists);
|
||||
scriptProtoDefineStaticFunc(&MODULE_ASSET_PROTO, "lock", moduleAssetLock);
|
||||
@@ -134,5 +136,6 @@ static void moduleAssetInit(void) {
|
||||
|
||||
static void moduleAssetDispose(void) {
|
||||
scriptProtoDispose(&MODULE_ASSET_PROTO);
|
||||
moduleAssetBatchDispose();
|
||||
moduleAssetEntryDispose();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
/**
|
||||
* 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/asset/moduleassetentry.h"
|
||||
#include "asset/assetbatch.h"
|
||||
#include "asset/asset.h"
|
||||
#include "asset/loader/assetloader.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
static scriptproto_t MODULE_ASSET_BATCH_PROTO;
|
||||
|
||||
typedef struct {
|
||||
assetbatch_t *batch;
|
||||
} jsassetbatch_t;
|
||||
|
||||
static void moduleAssetBatchFree(void *ptr, jerry_object_native_info_t *info) {
|
||||
(void)info;
|
||||
jsassetbatch_t *b = (jsassetbatch_t *)ptr;
|
||||
if(b && b->batch) {
|
||||
assetBatchDispose(b->batch);
|
||||
memoryFree(b->batch);
|
||||
}
|
||||
memoryFree(ptr);
|
||||
}
|
||||
|
||||
static inline jsassetbatch_t *moduleAssetBatchSelf(
|
||||
const jerry_call_info_t *callInfo
|
||||
) {
|
||||
return (jsassetbatch_t *)scriptProtoGetValue(
|
||||
&MODULE_ASSET_BATCH_PROTO, callInfo->this_value
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* AssetBatch(descriptors[])
|
||||
*
|
||||
* 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`.
|
||||
*/
|
||||
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");
|
||||
}
|
||||
|
||||
/* path */
|
||||
jerry_value_t pathProp = moduleBaseGetProp(desc, "path");
|
||||
if(!jerry_value_is_string(pathProp)) {
|
||||
jerry_value_free(pathProp);
|
||||
jerry_value_free(desc);
|
||||
return moduleBaseThrow("AssetBatch: descriptor.path must be a string");
|
||||
}
|
||||
jerry_size_t pathLen = jerry_string_to_buffer(
|
||||
pathProp, JERRY_ENCODING_UTF8,
|
||||
(jerry_char_t *)paths[i], ASSET_FILE_NAME_MAX - 1
|
||||
);
|
||||
paths[i][pathLen] = '\0';
|
||||
jerry_value_free(pathProp);
|
||||
descs[i].path = paths[i];
|
||||
|
||||
/* type */
|
||||
jerry_value_t typeProp = moduleBaseGetProp(desc, "type");
|
||||
if(!jerry_value_is_number(typeProp)) {
|
||||
jerry_value_free(typeProp);
|
||||
jerry_value_free(desc);
|
||||
return moduleBaseThrow("AssetBatch: descriptor.type must be a number");
|
||||
}
|
||||
descs[i].type = (assetloadertype_t)moduleBaseValueInt(typeProp);
|
||||
jerry_value_free(typeProp);
|
||||
|
||||
/* input: accept format, input, or axis — whichever is present */
|
||||
memoryZero(&descs[i].input, sizeof(assetloaderinput_t));
|
||||
jerry_value_t inputProp = moduleBaseGetProp(desc, "format");
|
||||
if(jerry_value_is_undefined(inputProp)) {
|
||||
jerry_value_free(inputProp);
|
||||
inputProp = moduleBaseGetProp(desc, "input");
|
||||
}
|
||||
if(jerry_value_is_undefined(inputProp)) {
|
||||
jerry_value_free(inputProp);
|
||||
inputProp = moduleBaseGetProp(desc, "axis");
|
||||
}
|
||||
if(jerry_value_is_number(inputProp)) {
|
||||
int32_t v = moduleBaseValueInt(inputProp);
|
||||
switch(descs[i].type) {
|
||||
case ASSET_LOADER_TYPE_TEXTURE:
|
||||
descs[i].input.texture = (textureformat_t)v;
|
||||
break;
|
||||
case ASSET_LOADER_TYPE_MESH:
|
||||
descs[i].input.mesh = (assetmeshinputaxis_t)v;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
jerry_value_free(inputProp);
|
||||
jerry_value_free(desc);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/* ---- Properties ---------------------------------------------------------- */
|
||||
|
||||
moduleBaseFunction(moduleAssetBatchGetCount) {
|
||||
(void)args; (void)argc;
|
||||
jsassetbatch_t *b = moduleAssetBatchSelf(callInfo);
|
||||
if(!b || !b->batch) return jerry_number(0.0);
|
||||
return jerry_number((double)b->batch->count);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleAssetBatchGetIsLoaded) {
|
||||
(void)args; (void)argc;
|
||||
jsassetbatch_t *b = moduleAssetBatchSelf(callInfo);
|
||||
if(!b || !b->batch) return jerry_boolean(false);
|
||||
return jerry_boolean(assetBatchIsLoaded(b->batch));
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleAssetBatchGetHasError) {
|
||||
(void)args; (void)argc;
|
||||
jsassetbatch_t *b = moduleAssetBatchSelf(callInfo);
|
||||
if(!b || !b->batch) return jerry_boolean(false);
|
||||
return jerry_boolean(assetBatchHasError(b->batch));
|
||||
}
|
||||
|
||||
/* ---- Methods ------------------------------------------------------------- */
|
||||
|
||||
/* Blocks until every entry is loaded. Returns this for chaining. */
|
||||
moduleBaseFunction(moduleAssetBatchRequireLoaded) {
|
||||
(void)args; (void)argc;
|
||||
jsassetbatch_t *b = moduleAssetBatchSelf(callInfo);
|
||||
if(!b || !b->batch) {
|
||||
return moduleBaseThrow("AssetBatch.requireLoaded: batch already disposed");
|
||||
}
|
||||
errorret_t err = assetBatchRequireLoaded(b->batch);
|
||||
if(errorIsNotOk(err)) return moduleBaseThrowError(err);
|
||||
return jerry_value_copy(callInfo->this_value);
|
||||
}
|
||||
|
||||
/* Acquires an additional lock on every entry. Returns this for chaining. */
|
||||
moduleBaseFunction(moduleAssetBatchLock) {
|
||||
(void)args; (void)argc;
|
||||
jsassetbatch_t *b = moduleAssetBatchSelf(callInfo);
|
||||
if(!b || !b->batch) return jerry_undefined();
|
||||
assetBatchLock(b->batch);
|
||||
return jerry_value_copy(callInfo->this_value);
|
||||
}
|
||||
|
||||
/* Releases all locks and clears the batch. The object becomes invalid. */
|
||||
moduleBaseFunction(moduleAssetBatchUnlock) {
|
||||
(void)args; (void)argc;
|
||||
jsassetbatch_t *b = moduleAssetBatchSelf(callInfo);
|
||||
if(!b || !b->batch) return jerry_undefined();
|
||||
assetBatchDispose(b->batch);
|
||||
memoryFree(b->batch);
|
||||
b->batch = NULL;
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
/* Returns the AssetEntry at index i, adding an independent lock to it. */
|
||||
moduleBaseFunction(moduleAssetBatchEntry) {
|
||||
moduleBaseRequireArgs(1);
|
||||
jsassetbatch_t *b = moduleAssetBatchSelf(callInfo);
|
||||
if(!b || !b->batch) return jerry_undefined();
|
||||
uint32_t idx = (uint32_t)moduleBaseArgInt(0);
|
||||
if(idx >= (uint32_t)b->batch->count) return jerry_undefined();
|
||||
assetentry_t *entry = b->batch->entries[idx];
|
||||
if(!entry) return jerry_undefined();
|
||||
assetEntryLock(entry);
|
||||
jsassetentry_t e = { .entry = entry };
|
||||
return scriptProtoCreateValue(&MODULE_ASSET_ENTRY_PROTO, &e);
|
||||
}
|
||||
|
||||
/* ---- Event proxies ------------------------------------------------------- */
|
||||
|
||||
moduleBaseFunction(moduleAssetBatchGetOnLoaded) {
|
||||
jsassetbatch_t *b = moduleAssetBatchSelf(callInfo);
|
||||
if(!b || !b->batch) return jerry_undefined();
|
||||
return moduleEventProxyGetOrCreate(callInfo, &b->batch->onLoaded, "_onLoaded");
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleAssetBatchGetOnEntryLoaded) {
|
||||
jsassetbatch_t *b = moduleAssetBatchSelf(callInfo);
|
||||
if(!b || !b->batch) return jerry_undefined();
|
||||
return moduleEventProxyGetOrCreate(callInfo, &b->batch->onEntryLoaded, "_onEntryLoaded");
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleAssetBatchGetOnError) {
|
||||
jsassetbatch_t *b = moduleAssetBatchSelf(callInfo);
|
||||
if(!b || !b->batch) return jerry_undefined();
|
||||
return moduleEventProxyGetOrCreate(callInfo, &b->batch->onError, "_onError");
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleAssetBatchGetOnEntryError) {
|
||||
jsassetbatch_t *b = moduleAssetBatchSelf(callInfo);
|
||||
if(!b || !b->batch) return jerry_undefined();
|
||||
return moduleEventProxyGetOrCreate(callInfo, &b->batch->onEntryError, "_onEntryError");
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleAssetBatchToString) {
|
||||
jsassetbatch_t *b = moduleAssetBatchSelf(callInfo);
|
||||
if(!b || !b->batch) return jerry_string_sz("AssetBatch:invalid");
|
||||
char_t buf[32];
|
||||
snprintf(buf, sizeof(buf), "AssetBatch(%u)", (unsigned)b->batch->count);
|
||||
return jerry_string_sz(buf);
|
||||
}
|
||||
|
||||
/* ---- Init / Dispose ------------------------------------------------------ */
|
||||
|
||||
static void moduleAssetBatchInit(void) {
|
||||
scriptProtoInit(
|
||||
&MODULE_ASSET_BATCH_PROTO, "AssetBatch",
|
||||
sizeof(jsassetbatch_t), moduleAssetBatchCtor
|
||||
);
|
||||
MODULE_ASSET_BATCH_PROTO.info.free_cb = moduleAssetBatchFree;
|
||||
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_ASSET_BATCH_PROTO, "count",
|
||||
moduleAssetBatchGetCount, NULL
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_ASSET_BATCH_PROTO, "isLoaded",
|
||||
moduleAssetBatchGetIsLoaded, NULL
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_ASSET_BATCH_PROTO, "hasError",
|
||||
moduleAssetBatchGetHasError, NULL
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_ASSET_BATCH_PROTO, "requireLoaded",
|
||||
moduleAssetBatchRequireLoaded
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_ASSET_BATCH_PROTO, "lock",
|
||||
moduleAssetBatchLock
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_ASSET_BATCH_PROTO, "unlock",
|
||||
moduleAssetBatchUnlock
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_ASSET_BATCH_PROTO, "entry",
|
||||
moduleAssetBatchEntry
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_ASSET_BATCH_PROTO, "onLoaded",
|
||||
moduleAssetBatchGetOnLoaded, NULL
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_ASSET_BATCH_PROTO, "onEntryLoaded",
|
||||
moduleAssetBatchGetOnEntryLoaded, NULL
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_ASSET_BATCH_PROTO, "onError",
|
||||
moduleAssetBatchGetOnError, NULL
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_ASSET_BATCH_PROTO, "onEntryError",
|
||||
moduleAssetBatchGetOnEntryError, NULL
|
||||
);
|
||||
scriptProtoDefineToString(
|
||||
&MODULE_ASSET_BATCH_PROTO, moduleAssetBatchToString
|
||||
);
|
||||
}
|
||||
|
||||
static void moduleAssetBatchDispose(void) {
|
||||
scriptProtoDispose(&MODULE_ASSET_BATCH_PROTO);
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "script/module/display/moduletexture.h"
|
||||
#include "script/module/asset/moduleeventproxy.h"
|
||||
#include "asset/asset.h"
|
||||
#include "asset/loader/assetloader.h"
|
||||
#include "asset/loader/assetentry.h"
|
||||
@@ -106,6 +107,24 @@ moduleBaseFunction(moduleAssetEntryUnlock) {
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleAssetEntryGetOnLoaded) {
|
||||
jsassetentry_t *e = moduleAssetEntrySelf(callInfo);
|
||||
if(!e || !e->entry) return jerry_undefined();
|
||||
return moduleEventProxyGetOrCreate(callInfo, &e->entry->onLoaded, "_onLoaded");
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleAssetEntryGetOnUnloaded) {
|
||||
jsassetentry_t *e = moduleAssetEntrySelf(callInfo);
|
||||
if(!e || !e->entry) return jerry_undefined();
|
||||
return moduleEventProxyGetOrCreate(callInfo, &e->entry->onUnloaded, "_onUnloaded");
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleAssetEntryGetOnError) {
|
||||
jsassetentry_t *e = moduleAssetEntrySelf(callInfo);
|
||||
if(!e || !e->entry) return jerry_undefined();
|
||||
return moduleEventProxyGetOrCreate(callInfo, &e->entry->onError, "_onError");
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleAssetEntryToString) {
|
||||
jsassetentry_t *e = moduleAssetEntrySelf(callInfo);
|
||||
if(!e || !e->entry) return jerry_string_sz("AssetEntry:invalid");
|
||||
@@ -115,6 +134,7 @@ moduleBaseFunction(moduleAssetEntryToString) {
|
||||
}
|
||||
|
||||
static void moduleAssetEntryInit(void) {
|
||||
moduleEventProxyInit();
|
||||
scriptProtoInit(
|
||||
&MODULE_ASSET_ENTRY_PROTO, "AssetEntry",
|
||||
sizeof(jsassetentry_t), moduleAssetEntryCtor
|
||||
@@ -142,6 +162,18 @@ static void moduleAssetEntryInit(void) {
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_ASSET_ENTRY_PROTO, "unlock", moduleAssetEntryUnlock
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_ASSET_ENTRY_PROTO, "onLoaded",
|
||||
moduleAssetEntryGetOnLoaded, NULL
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_ASSET_ENTRY_PROTO, "onUnloaded",
|
||||
moduleAssetEntryGetOnUnloaded, NULL
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_ASSET_ENTRY_PROTO, "onError",
|
||||
moduleAssetEntryGetOnError, NULL
|
||||
);
|
||||
scriptProtoDefineToString(
|
||||
&MODULE_ASSET_ENTRY_PROTO, moduleAssetEntryToString
|
||||
);
|
||||
@@ -168,4 +200,5 @@ static void moduleAssetEntryInit(void) {
|
||||
|
||||
static void moduleAssetEntryDispose(void) {
|
||||
scriptProtoDispose(&MODULE_ASSET_ENTRY_PROTO);
|
||||
moduleEventProxyDispose();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
/**
|
||||
* 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 "event/event.h"
|
||||
|
||||
/**
|
||||
* Maximum number of JS subscriber slots per proxy. Must be >= the largest
|
||||
* event capacity used (ASSET_ENTRY_EVENT_MAX, ASSET_BATCH_EVENT_MAX, etc.).
|
||||
*/
|
||||
#define MODULE_EVENT_PROXY_MAX_SLOTS 4
|
||||
|
||||
static scriptproto_t MODULE_EVENT_PROXY_PROTO;
|
||||
|
||||
/** Native data stored on each EventProxy JS object. */
|
||||
typedef struct {
|
||||
event_t *event;
|
||||
jerry_value_t fns[MODULE_EVENT_PROXY_MAX_SLOTS];
|
||||
} jseventproxy_t;
|
||||
|
||||
/* ---- C trampolines (one per slot index) ---------------------------------- */
|
||||
|
||||
static void moduleEventProxyTrampoline0(void *params, void *user) {
|
||||
(void)params;
|
||||
jerry_value_t fn = (jerry_value_t)(uintptr_t)user;
|
||||
jerry_value_t ret = jerry_call(fn, jerry_undefined(), NULL, 0);
|
||||
jerry_value_free(ret);
|
||||
}
|
||||
|
||||
static void moduleEventProxyTrampoline1(void *params, void *user) {
|
||||
(void)params;
|
||||
jerry_value_t fn = (jerry_value_t)(uintptr_t)user;
|
||||
jerry_value_t ret = jerry_call(fn, jerry_undefined(), NULL, 0);
|
||||
jerry_value_free(ret);
|
||||
}
|
||||
|
||||
static void moduleEventProxyTrampoline2(void *params, void *user) {
|
||||
(void)params;
|
||||
jerry_value_t fn = (jerry_value_t)(uintptr_t)user;
|
||||
jerry_value_t ret = jerry_call(fn, jerry_undefined(), NULL, 0);
|
||||
jerry_value_free(ret);
|
||||
}
|
||||
|
||||
static void moduleEventProxyTrampoline3(void *params, void *user) {
|
||||
(void)params;
|
||||
jerry_value_t fn = (jerry_value_t)(uintptr_t)user;
|
||||
jerry_value_t ret = jerry_call(fn, jerry_undefined(), NULL, 0);
|
||||
jerry_value_free(ret);
|
||||
}
|
||||
|
||||
static eventcallback_t MODULE_EVENT_PROXY_TRAMPOLINES[MODULE_EVENT_PROXY_MAX_SLOTS] = {
|
||||
moduleEventProxyTrampoline0,
|
||||
moduleEventProxyTrampoline1,
|
||||
moduleEventProxyTrampoline2,
|
||||
moduleEventProxyTrampoline3,
|
||||
};
|
||||
|
||||
/* ---- GC free callback ---------------------------------------------------- */
|
||||
|
||||
static void moduleEventProxyFree(void *ptr, jerry_object_native_info_t *info) {
|
||||
(void)info;
|
||||
jseventproxy_t *ep = (jseventproxy_t *)ptr;
|
||||
if(ep) {
|
||||
for(uint32_t i = 0; i < MODULE_EVENT_PROXY_MAX_SLOTS; i++) {
|
||||
if(jerry_value_is_function(ep->fns[i])) {
|
||||
if(ep->event) eventUnsubscribe(ep->event, MODULE_EVENT_PROXY_TRAMPOLINES[i]);
|
||||
jerry_value_free(ep->fns[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
memoryFree(ptr);
|
||||
}
|
||||
|
||||
/* ---- Self helper --------------------------------------------------------- */
|
||||
|
||||
static inline jseventproxy_t *moduleEventProxySelf(
|
||||
const jerry_call_info_t *callInfo
|
||||
) {
|
||||
return (jseventproxy_t *)scriptProtoGetValue(
|
||||
&MODULE_EVENT_PROXY_PROTO, callInfo->this_value
|
||||
);
|
||||
}
|
||||
|
||||
/* ---- Slot get/set helpers ------------------------------------------------ */
|
||||
|
||||
static inline jerry_value_t moduleEventProxyGetSlot(
|
||||
const jerry_call_info_t *callInfo,
|
||||
const uint32_t slot
|
||||
) {
|
||||
jseventproxy_t *ep = moduleEventProxySelf(callInfo);
|
||||
if(!ep || !ep->event || slot >= ep->event->size) return jerry_null();
|
||||
return jerry_value_is_function(ep->fns[slot])
|
||||
? jerry_value_copy(ep->fns[slot])
|
||||
: jerry_null();
|
||||
}
|
||||
|
||||
static inline jerry_value_t moduleEventProxySetSlot(
|
||||
const jerry_call_info_t *callInfo,
|
||||
const jerry_value_t args[],
|
||||
const jerry_length_t argc,
|
||||
const uint32_t slot
|
||||
) {
|
||||
jseventproxy_t *ep = moduleEventProxySelf(callInfo);
|
||||
if(!ep || !ep->event || slot >= ep->event->size) return jerry_undefined();
|
||||
|
||||
if(jerry_value_is_function(ep->fns[slot])) {
|
||||
eventUnsubscribe(ep->event, MODULE_EVENT_PROXY_TRAMPOLINES[slot]);
|
||||
jerry_value_free(ep->fns[slot]);
|
||||
ep->fns[slot] = jerry_undefined();
|
||||
}
|
||||
|
||||
jerry_value_t val = (argc > 0) ? args[0] : jerry_undefined();
|
||||
if(jerry_value_is_function(val)) {
|
||||
ep->fns[slot] = jerry_value_copy(val);
|
||||
eventSubscribe(
|
||||
ep->event,
|
||||
MODULE_EVENT_PROXY_TRAMPOLINES[slot],
|
||||
(void *)(uintptr_t)ep->fns[slot]
|
||||
);
|
||||
}
|
||||
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
/* ---- Per-slot getter/setter pairs ---------------------------------------- */
|
||||
|
||||
moduleBaseFunction(moduleEventProxyGet0) {
|
||||
(void)args; (void)argc;
|
||||
return moduleEventProxyGetSlot(callInfo, 0);
|
||||
}
|
||||
moduleBaseFunction(moduleEventProxySet0) {
|
||||
return moduleEventProxySetSlot(callInfo, args, argc, 0);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEventProxyGet1) {
|
||||
(void)args; (void)argc;
|
||||
return moduleEventProxyGetSlot(callInfo, 1);
|
||||
}
|
||||
moduleBaseFunction(moduleEventProxySet1) {
|
||||
return moduleEventProxySetSlot(callInfo, args, argc, 1);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEventProxyGet2) {
|
||||
(void)args; (void)argc;
|
||||
return moduleEventProxyGetSlot(callInfo, 2);
|
||||
}
|
||||
moduleBaseFunction(moduleEventProxySet2) {
|
||||
return moduleEventProxySetSlot(callInfo, args, argc, 2);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEventProxyGet3) {
|
||||
(void)args; (void)argc;
|
||||
return moduleEventProxyGetSlot(callInfo, 3);
|
||||
}
|
||||
moduleBaseFunction(moduleEventProxySet3) {
|
||||
return moduleEventProxySetSlot(callInfo, args, argc, 3);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEventProxyGetLength) {
|
||||
(void)args; (void)argc;
|
||||
jseventproxy_t *ep = moduleEventProxySelf(callInfo);
|
||||
if(!ep || !ep->event) return jerry_number(0.0);
|
||||
return jerry_number((double)ep->event->size);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEventProxyToString) {
|
||||
(void)args; (void)argc;
|
||||
return jerry_string_sz("EventProxy");
|
||||
}
|
||||
|
||||
/* ---- Lazy-create helper (shared by all parent types) --------------------- */
|
||||
|
||||
/**
|
||||
* Returns the event proxy pinned at pinKey on callInfo->this_value.
|
||||
* Creates and pins it on first access. event must remain valid for the
|
||||
* lifetime of the parent JS object (it is stored by pointer, not copied).
|
||||
*/
|
||||
static inline jerry_value_t moduleEventProxyGetOrCreate(
|
||||
const jerry_call_info_t *callInfo,
|
||||
event_t *event,
|
||||
const char_t *pinKey
|
||||
) {
|
||||
jerry_value_t keyStr = jerry_string_sz(pinKey);
|
||||
jerry_value_t existing = jerry_object_get(callInfo->this_value, keyStr);
|
||||
if(!jerry_value_is_undefined(existing)) {
|
||||
jerry_value_free(keyStr);
|
||||
return existing;
|
||||
}
|
||||
jerry_value_free(existing);
|
||||
|
||||
jseventproxy_t ep;
|
||||
ep.event = event;
|
||||
for(uint32_t i = 0; i < MODULE_EVENT_PROXY_MAX_SLOTS; i++) {
|
||||
ep.fns[i] = jerry_undefined();
|
||||
}
|
||||
|
||||
jerry_value_t proxy = scriptProtoCreateValue(&MODULE_EVENT_PROXY_PROTO, &ep);
|
||||
jerry_object_set(callInfo->this_value, keyStr, proxy);
|
||||
jerry_value_free(keyStr);
|
||||
return proxy;
|
||||
}
|
||||
|
||||
/* ---- Init / Dispose ------------------------------------------------------ */
|
||||
|
||||
static void moduleEventProxyInit(void) {
|
||||
scriptProtoInit(
|
||||
&MODULE_EVENT_PROXY_PROTO, NULL,
|
||||
sizeof(jseventproxy_t), NULL
|
||||
);
|
||||
MODULE_EVENT_PROXY_PROTO.info.free_cb = moduleEventProxyFree;
|
||||
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_EVENT_PROXY_PROTO, "0",
|
||||
moduleEventProxyGet0, moduleEventProxySet0
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_EVENT_PROXY_PROTO, "1",
|
||||
moduleEventProxyGet1, moduleEventProxySet1
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_EVENT_PROXY_PROTO, "2",
|
||||
moduleEventProxyGet2, moduleEventProxySet2
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_EVENT_PROXY_PROTO, "3",
|
||||
moduleEventProxyGet3, moduleEventProxySet3
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_EVENT_PROXY_PROTO, "length",
|
||||
moduleEventProxyGetLength, NULL
|
||||
);
|
||||
scriptProtoDefineToString(
|
||||
&MODULE_EVENT_PROXY_PROTO, moduleEventProxyToString
|
||||
);
|
||||
}
|
||||
|
||||
static void moduleEventProxyDispose(void) {
|
||||
scriptProtoDispose(&MODULE_EVENT_PROXY_PROTO);
|
||||
}
|
||||
@@ -1,145 +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/overworld/entityinteractable.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
static scriptproto_t MODULE_INTERACTABLE_PROTO;
|
||||
|
||||
moduleBaseFunction(moduleInteractableCtor) {
|
||||
(void)callInfo; (void)args; (void)argc;
|
||||
return moduleBaseThrow("Interactable cannot be instantiated with new");
|
||||
}
|
||||
|
||||
static inline jscomponent_t *moduleInteractableSelf(
|
||||
const jerry_call_info_t *callInfo
|
||||
) {
|
||||
return (jscomponent_t *)scriptProtoGetValue(
|
||||
&MODULE_INTERACTABLE_PROTO, callInfo->this_value
|
||||
);
|
||||
}
|
||||
|
||||
static void moduleInteractableCb(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
void *user
|
||||
) {
|
||||
(void)entityId; (void)componentId;
|
||||
jerry_value_t fn = *((jerry_value_t *)user);
|
||||
jerry_value_t ret = jerry_call(fn, jerry_undefined(), NULL, 0);
|
||||
jerry_value_free(ret);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleInteractableGetEntity) {
|
||||
jscomponent_t *c = moduleInteractableSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
return jerry_number((double)c->entityId);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleInteractableGetId) {
|
||||
jscomponent_t *c = moduleInteractableSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
return jerry_number((double)c->componentId);
|
||||
}
|
||||
|
||||
/*
|
||||
* onInteract getter — reads back the pinned JS function stored on this._cb.
|
||||
*/
|
||||
moduleBaseFunction(moduleInteractableGetOnInteract) {
|
||||
(void)args; (void)argc;
|
||||
jerry_value_t key = jerry_string_sz("_cb");
|
||||
jerry_value_t val = jerry_object_get(callInfo->this_value, key);
|
||||
jerry_value_free(key);
|
||||
return val;
|
||||
}
|
||||
|
||||
/*
|
||||
* onInteract setter — pins the JS function on this._cb for GC safety, and
|
||||
* registers a C trampoline that calls it when the player interacts.
|
||||
* Passing null/undefined clears the callback.
|
||||
*/
|
||||
moduleBaseFunction(moduleInteractableSetOnInteract) {
|
||||
jscomponent_t *c = moduleInteractableSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
entityinteractable_t *d = entityInteractableGet(c->entityId, c->componentId);
|
||||
if(!d) return jerry_undefined();
|
||||
|
||||
/* Free previously stored JS reference */
|
||||
if(d->user) {
|
||||
jerry_value_free(*((jerry_value_t *)d->user));
|
||||
free(d->user);
|
||||
d->user = NULL;
|
||||
}
|
||||
|
||||
jerry_value_t pin = (argc > 0) ? args[0] : jerry_undefined();
|
||||
jerry_value_t pinKey = jerry_string_sz("_cb");
|
||||
|
||||
if(jerry_value_is_function(pin)) {
|
||||
jerry_value_t *stored = (jerry_value_t *)malloc(sizeof(jerry_value_t));
|
||||
*stored = jerry_value_copy(pin);
|
||||
entityInteractableSetCallback(
|
||||
c->entityId, c->componentId, moduleInteractableCb, stored
|
||||
);
|
||||
jerry_object_set(callInfo->this_value, pinKey, pin);
|
||||
} else {
|
||||
entityInteractableSetCallback(c->entityId, c->componentId, NULL, NULL);
|
||||
jerry_value_t undef = jerry_undefined();
|
||||
jerry_object_set(callInfo->this_value, pinKey, undef);
|
||||
jerry_value_free(undef);
|
||||
}
|
||||
|
||||
jerry_value_free(pinKey);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleInteractableTrigger) {
|
||||
(void)args; (void)argc;
|
||||
jscomponent_t *c = moduleInteractableSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
entityInteractableTrigger(c->entityId, c->componentId);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleInteractableToString) {
|
||||
jscomponent_t *c = moduleInteractableSelf(callInfo);
|
||||
if(!c) return jerry_string_sz("Interactable:invalid");
|
||||
char_t buf[32];
|
||||
snprintf(buf, sizeof(buf), "Interactable(%u)", (unsigned)c->componentId);
|
||||
return jerry_string_sz(buf);
|
||||
}
|
||||
|
||||
static void moduleInteractableInit(void) {
|
||||
scriptProtoInit(
|
||||
&MODULE_INTERACTABLE_PROTO, "Interactable",
|
||||
sizeof(jscomponent_t), moduleInteractableCtor
|
||||
);
|
||||
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_INTERACTABLE_PROTO, "entity", moduleInteractableGetEntity, NULL
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_INTERACTABLE_PROTO, "id", moduleInteractableGetId, NULL
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_INTERACTABLE_PROTO, "onInteract",
|
||||
moduleInteractableGetOnInteract, moduleInteractableSetOnInteract
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_INTERACTABLE_PROTO, "trigger", moduleInteractableTrigger
|
||||
);
|
||||
scriptProtoDefineToString(
|
||||
&MODULE_INTERACTABLE_PROTO, moduleInteractableToString
|
||||
);
|
||||
}
|
||||
|
||||
static void moduleInteractableDispose(void) {
|
||||
scriptProtoDispose(&MODULE_INTERACTABLE_PROTO);
|
||||
}
|
||||
@@ -7,15 +7,10 @@
|
||||
|
||||
#pragma once
|
||||
#include "script/module/entity/modulecomponent.h"
|
||||
#include "camera/modulecamera.h"
|
||||
#include "interactable/moduleinteractable.h"
|
||||
#include "overworld/moduleoverworld.h"
|
||||
#include "overworldcamera/moduleoverworldcamera.h"
|
||||
#include "overworldtrigger/moduleoverworldtrigger.h"
|
||||
#include "display/modulecamera.h"
|
||||
#include "display/moduleposition.h"
|
||||
#include "display/modulerenderable.h"
|
||||
#include "physics/modulephysics.h"
|
||||
#include "player/moduleplayer.h"
|
||||
#include "position/moduleposition.h"
|
||||
#include "renderable/modulerenderable.h"
|
||||
#include "trigger/moduletrigger.h"
|
||||
|
||||
/**
|
||||
@@ -29,18 +24,8 @@ static jerry_value_t moduleComponentListCreateInstance(
|
||||
switch(type) {
|
||||
case COMPONENT_TYPE_CAMERA:
|
||||
return scriptProtoCreateValue(&MODULE_CAMERA_PROTO, comp);
|
||||
case COMPONENT_TYPE_INTERACTABLE:
|
||||
return scriptProtoCreateValue(&MODULE_INTERACTABLE_PROTO, comp);
|
||||
case COMPONENT_TYPE_OVERWORLD:
|
||||
return scriptProtoCreateValue(&MODULE_OVERWORLD_PROTO, comp);
|
||||
case COMPONENT_TYPE_OVERWORLD_CAMERA:
|
||||
return scriptProtoCreateValue(&MODULE_OVERWORLD_CAMERA_PROTO, comp);
|
||||
case COMPONENT_TYPE_OVERWORLD_TRIGGER:
|
||||
return scriptProtoCreateValue(&MODULE_OVERWORLD_TRIGGER_PROTO, comp);
|
||||
case COMPONENT_TYPE_PHYSICS:
|
||||
return scriptProtoCreateValue(&MODULE_PHYSICS_PROTO, comp);
|
||||
case COMPONENT_TYPE_PLAYER:
|
||||
return scriptProtoCreateValue(&MODULE_PLAYER_PROTO, comp);
|
||||
case COMPONENT_TYPE_POSITION:
|
||||
return scriptProtoCreateValue(&MODULE_POSITION_PROTO, comp);
|
||||
case COMPONENT_TYPE_RENDERABLE:
|
||||
@@ -54,12 +39,7 @@ static jerry_value_t moduleComponentListCreateInstance(
|
||||
|
||||
static void moduleComponentListInit(void) {
|
||||
moduleCameraInit();
|
||||
moduleInteractableInit();
|
||||
moduleOverworldInit();
|
||||
moduleOverworldCameraInit();
|
||||
moduleOverworldTriggerInit();
|
||||
modulePhysicsInit();
|
||||
modulePlayerInit();
|
||||
modulePositionInit();
|
||||
moduleRenderableInit();
|
||||
moduleTriggerInit();
|
||||
@@ -69,11 +49,6 @@ static void moduleComponentListDispose(void) {
|
||||
moduleTriggerDispose();
|
||||
moduleRenderableDispose();
|
||||
modulePositionDispose();
|
||||
modulePlayerDispose();
|
||||
modulePhysicsDispose();
|
||||
moduleOverworldTriggerDispose();
|
||||
moduleOverworldCameraDispose();
|
||||
moduleOverworldDispose();
|
||||
moduleInteractableDispose();
|
||||
moduleCameraDispose();
|
||||
}
|
||||
|
||||
@@ -1,168 +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/overworld/entityoverworld.h"
|
||||
#include "overworld/facingdir.h"
|
||||
|
||||
static scriptproto_t MODULE_OVERWORLD_PROTO;
|
||||
|
||||
moduleBaseFunction(moduleOverworldCtor) {
|
||||
(void)callInfo; (void)args; (void)argc;
|
||||
return moduleBaseThrow("Overworld cannot be instantiated with new");
|
||||
}
|
||||
|
||||
static inline jscomponent_t *moduleOverworldSelf(
|
||||
const jerry_call_info_t *callInfo
|
||||
) {
|
||||
return (jscomponent_t *)scriptProtoGetValue(
|
||||
&MODULE_OVERWORLD_PROTO, callInfo->this_value
|
||||
);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldGetEntity) {
|
||||
jscomponent_t *c = moduleOverworldSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
return jerry_number((double)c->entityId);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldGetId) {
|
||||
jscomponent_t *c = moduleOverworldSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
return jerry_number((double)c->componentId);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldGetType) {
|
||||
jscomponent_t *c = moduleOverworldSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
entityoverworld_t *o = entityOverworldGet(c->entityId, c->componentId);
|
||||
if(!o) return jerry_undefined();
|
||||
return jerry_number((double)o->type);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldSetType) {
|
||||
moduleBaseRequireArgs(1);
|
||||
jscomponent_t *c = moduleOverworldSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
entityOverworldSetType(
|
||||
c->entityId, c->componentId,
|
||||
(entityoverworldtype_t)moduleBaseArgInt(0)
|
||||
);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldGetFacing) {
|
||||
jscomponent_t *c = moduleOverworldSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
entityoverworld_t *o = entityOverworldGet(c->entityId, c->componentId);
|
||||
if(!o) return jerry_undefined();
|
||||
return jerry_number((double)o->facing);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldSetFacing) {
|
||||
moduleBaseRequireArgs(1);
|
||||
jscomponent_t *c = moduleOverworldSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
entityoverworld_t *o = entityOverworldGet(c->entityId, c->componentId);
|
||||
if(!o) return jerry_undefined();
|
||||
o->facing = (facingdir_t)moduleBaseArgInt(0);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldGetRenderCompId) {
|
||||
jscomponent_t *c = moduleOverworldSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
entityoverworld_t *o = entityOverworldGet(c->entityId, c->componentId);
|
||||
if(!o) return jerry_undefined();
|
||||
return jerry_number((double)o->renderCompId);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldGetPhysCompId) {
|
||||
jscomponent_t *c = moduleOverworldSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
entityoverworld_t *o = entityOverworldGet(c->entityId, c->componentId);
|
||||
if(!o) return jerry_undefined();
|
||||
return jerry_number((double)o->physCompId);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldToString) {
|
||||
jscomponent_t *c = moduleOverworldSelf(callInfo);
|
||||
if(!c) return jerry_string_sz("Overworld:invalid");
|
||||
char_t buf[32];
|
||||
snprintf(buf, sizeof(buf), "Overworld(%u)", (unsigned)c->componentId);
|
||||
return jerry_string_sz(buf);
|
||||
}
|
||||
|
||||
static void moduleOverworldInit(void) {
|
||||
scriptProtoInit(
|
||||
&MODULE_OVERWORLD_PROTO, "Overworld",
|
||||
sizeof(jscomponent_t), moduleOverworldCtor
|
||||
);
|
||||
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_OVERWORLD_PROTO, "entity", moduleOverworldGetEntity, NULL
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_OVERWORLD_PROTO, "id", moduleOverworldGetId, NULL
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_OVERWORLD_PROTO, "type",
|
||||
moduleOverworldGetType, moduleOverworldSetType
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_OVERWORLD_PROTO, "facing",
|
||||
moduleOverworldGetFacing, moduleOverworldSetFacing
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_OVERWORLD_PROTO, "renderComponentId",
|
||||
moduleOverworldGetRenderCompId, NULL
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_OVERWORLD_PROTO, "physicsComponentId",
|
||||
moduleOverworldGetPhysCompId, NULL
|
||||
);
|
||||
scriptProtoDefineToString(&MODULE_OVERWORLD_PROTO, moduleOverworldToString);
|
||||
|
||||
jerry_value_t ctor = MODULE_OVERWORLD_PROTO.constructor;
|
||||
|
||||
struct { const char_t *name; int val; } types[] = {
|
||||
{ "PLAYER", OVERWORLD_ENTITY_TYPE_PLAYER },
|
||||
{ "NPC", OVERWORLD_ENTITY_TYPE_NPC },
|
||||
};
|
||||
for(int i = 0; i < 2; 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);
|
||||
}
|
||||
|
||||
struct { const char_t *name; int val; } dirs[] = {
|
||||
{ "FACING_DOWN", FACING_DIR_DOWN },
|
||||
{ "FACING_UP", FACING_DIR_UP },
|
||||
{ "FACING_LEFT", FACING_DIR_LEFT },
|
||||
{ "FACING_RIGHT", FACING_DIR_RIGHT },
|
||||
{ "FACING_SOUTH", FACING_DIR_SOUTH },
|
||||
{ "FACING_NORTH", FACING_DIR_NORTH },
|
||||
{ "FACING_WEST", FACING_DIR_WEST },
|
||||
{ "FACING_EAST", FACING_DIR_EAST },
|
||||
};
|
||||
for(int i = 0; i < 8; i++) {
|
||||
jerry_value_t k = jerry_string_sz(dirs[i].name);
|
||||
jerry_value_t v = jerry_number((double)dirs[i].val);
|
||||
jerry_object_set(ctor, k, v);
|
||||
jerry_value_free(v);
|
||||
jerry_value_free(k);
|
||||
}
|
||||
}
|
||||
|
||||
static void moduleOverworldDispose(void) {
|
||||
scriptProtoDispose(&MODULE_OVERWORLD_PROTO);
|
||||
}
|
||||
@@ -1,201 +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/overworld/entityoverworldcamera.h"
|
||||
|
||||
static scriptproto_t MODULE_OVERWORLD_CAMERA_PROTO;
|
||||
|
||||
moduleBaseFunction(moduleOverworldCameraCtor) {
|
||||
(void)callInfo; (void)args; (void)argc;
|
||||
return moduleBaseThrow("OverworldCamera cannot be instantiated with new");
|
||||
}
|
||||
|
||||
static inline jscomponent_t *moduleOverworldCameraSelf(
|
||||
const jerry_call_info_t *callInfo
|
||||
) {
|
||||
return (jscomponent_t *)scriptProtoGetValue(
|
||||
&MODULE_OVERWORLD_CAMERA_PROTO, callInfo->this_value
|
||||
);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldCameraGetEntity) {
|
||||
jscomponent_t *c = moduleOverworldCameraSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
return jerry_number((double)c->entityId);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldCameraGetId) {
|
||||
jscomponent_t *c = moduleOverworldCameraSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
return jerry_number((double)c->componentId);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldCameraGetTargetEntity) {
|
||||
jscomponent_t *c = moduleOverworldCameraSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
entityoverworldcamera_t *cam = entityOverworldCameraGet(c->entityId, c->componentId);
|
||||
if(!cam) return jerry_undefined();
|
||||
return jerry_number((double)cam->targetEntityId);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldCameraSetTargetEntity) {
|
||||
moduleBaseRequireArgs(1);
|
||||
jscomponent_t *c = moduleOverworldCameraSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
entityoverworldcamera_t *cam = entityOverworldCameraGet(c->entityId, c->componentId);
|
||||
if(!cam) return jerry_undefined();
|
||||
cam->targetEntityId = (entityid_t)moduleBaseArgInt(0);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldCameraGetTargetPosComp) {
|
||||
jscomponent_t *c = moduleOverworldCameraSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
entityoverworldcamera_t *cam = entityOverworldCameraGet(c->entityId, c->componentId);
|
||||
if(!cam) return jerry_undefined();
|
||||
return jerry_number((double)cam->targetPosCompId);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldCameraSetTargetPosComp) {
|
||||
moduleBaseRequireArgs(1);
|
||||
jscomponent_t *c = moduleOverworldCameraSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
entityoverworldcamera_t *cam = entityOverworldCameraGet(c->entityId, c->componentId);
|
||||
if(!cam) return jerry_undefined();
|
||||
cam->targetPosCompId = (componentid_t)moduleBaseArgInt(0);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldCameraGetTargetOffset) {
|
||||
jscomponent_t *c = moduleOverworldCameraSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
entityoverworldcamera_t *cam = entityOverworldCameraGet(c->entityId, c->componentId);
|
||||
if(!cam) return jerry_undefined();
|
||||
return moduleVec3Push(cam->targetOffset);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldCameraSetTargetOffset) {
|
||||
moduleBaseRequireArgs(1);
|
||||
jscomponent_t *c = moduleOverworldCameraSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
float_t *v = moduleVec3From(args[0]);
|
||||
if(!v) return moduleBaseThrow("OverworldCamera.targetOffset: expected Vec3");
|
||||
entityoverworldcamera_t *cam = entityOverworldCameraGet(c->entityId, c->componentId);
|
||||
if(!cam) return jerry_undefined();
|
||||
glm_vec3_copy(v, cam->targetOffset);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldCameraGetEyeOffset) {
|
||||
jscomponent_t *c = moduleOverworldCameraSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
entityoverworldcamera_t *cam = entityOverworldCameraGet(c->entityId, c->componentId);
|
||||
if(!cam) return jerry_undefined();
|
||||
return moduleVec3Push(cam->eyeOffset);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldCameraSetEyeOffset) {
|
||||
moduleBaseRequireArgs(1);
|
||||
jscomponent_t *c = moduleOverworldCameraSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
float_t *v = moduleVec3From(args[0]);
|
||||
if(!v) return moduleBaseThrow("OverworldCamera.eyeOffset: expected Vec3");
|
||||
entityoverworldcamera_t *cam = entityOverworldCameraGet(c->entityId, c->componentId);
|
||||
if(!cam) return jerry_undefined();
|
||||
glm_vec3_copy(v, cam->eyeOffset);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldCameraGetScale) {
|
||||
jscomponent_t *c = moduleOverworldCameraSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
entityoverworldcamera_t *cam = entityOverworldCameraGet(c->entityId, c->componentId);
|
||||
if(!cam) return jerry_undefined();
|
||||
return jerry_number((double)cam->scale);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldCameraSetScale) {
|
||||
moduleBaseRequireArgs(1);
|
||||
jscomponent_t *c = moduleOverworldCameraSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
entityoverworldcamera_t *cam = entityOverworldCameraGet(c->entityId, c->componentId);
|
||||
if(!cam) return jerry_undefined();
|
||||
cam->scale = moduleBaseArgFloat(0);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldCameraSetTarget) {
|
||||
moduleBaseRequireArgs(2);
|
||||
jscomponent_t *c = moduleOverworldCameraSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
entityOverworldCameraSetTarget(
|
||||
c->entityId, c->componentId,
|
||||
(entityid_t)moduleBaseArgInt(0),
|
||||
(componentid_t)moduleBaseArgInt(1)
|
||||
);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldCameraToString) {
|
||||
jscomponent_t *c = moduleOverworldCameraSelf(callInfo);
|
||||
if(!c) return jerry_string_sz("OverworldCamera:invalid");
|
||||
char_t buf[32];
|
||||
snprintf(buf, sizeof(buf), "OverworldCamera(%u)", (unsigned)c->componentId);
|
||||
return jerry_string_sz(buf);
|
||||
}
|
||||
|
||||
static void moduleOverworldCameraInit(void) {
|
||||
scriptProtoInit(
|
||||
&MODULE_OVERWORLD_CAMERA_PROTO, "OverworldCamera",
|
||||
sizeof(jscomponent_t), moduleOverworldCameraCtor
|
||||
);
|
||||
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_OVERWORLD_CAMERA_PROTO, "entity",
|
||||
moduleOverworldCameraGetEntity, NULL
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_OVERWORLD_CAMERA_PROTO, "id",
|
||||
moduleOverworldCameraGetId, NULL
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_OVERWORLD_CAMERA_PROTO, "targetEntity",
|
||||
moduleOverworldCameraGetTargetEntity, moduleOverworldCameraSetTargetEntity
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_OVERWORLD_CAMERA_PROTO, "targetPositionComponent",
|
||||
moduleOverworldCameraGetTargetPosComp, moduleOverworldCameraSetTargetPosComp
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_OVERWORLD_CAMERA_PROTO, "targetOffset",
|
||||
moduleOverworldCameraGetTargetOffset, moduleOverworldCameraSetTargetOffset
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_OVERWORLD_CAMERA_PROTO, "eyeOffset",
|
||||
moduleOverworldCameraGetEyeOffset, moduleOverworldCameraSetEyeOffset
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_OVERWORLD_CAMERA_PROTO, "scale",
|
||||
moduleOverworldCameraGetScale, moduleOverworldCameraSetScale
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_OVERWORLD_CAMERA_PROTO, "setTarget",
|
||||
moduleOverworldCameraSetTarget
|
||||
);
|
||||
scriptProtoDefineToString(
|
||||
&MODULE_OVERWORLD_CAMERA_PROTO, moduleOverworldCameraToString
|
||||
);
|
||||
}
|
||||
|
||||
static void moduleOverworldCameraDispose(void) {
|
||||
scriptProtoDispose(&MODULE_OVERWORLD_CAMERA_PROTO);
|
||||
}
|
||||
@@ -1,374 +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/overworld/entityoverworldtrigger.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
static scriptproto_t MODULE_OVERWORLD_TRIGGER_PROTO;
|
||||
|
||||
/**
|
||||
* Heap-allocated struct stored in entityoverworldtrigger_t.user.
|
||||
* Holds jerry_value_t copies of the four JS callback functions.
|
||||
*/
|
||||
typedef struct {
|
||||
jerry_value_t onEnter;
|
||||
jerry_value_t onExit;
|
||||
jerry_value_t onStay;
|
||||
jerry_value_t onOutside;
|
||||
} jsoverworldtriggercbs_t;
|
||||
|
||||
moduleBaseFunction(moduleOverworldTriggerCtor) {
|
||||
(void)callInfo; (void)args; (void)argc;
|
||||
return moduleBaseThrow("OverworldTrigger cannot be instantiated with new");
|
||||
}
|
||||
|
||||
static inline jscomponent_t *moduleOverworldTriggerSelf(
|
||||
const jerry_call_info_t *callInfo
|
||||
) {
|
||||
return (jscomponent_t *)scriptProtoGetValue(
|
||||
&MODULE_OVERWORLD_TRIGGER_PROTO, callInfo->this_value
|
||||
);
|
||||
}
|
||||
|
||||
/** Lazily allocates the callback struct and stores it in the trigger's user. */
|
||||
static jsoverworldtriggercbs_t *moduleOverworldTriggerGetCbs(
|
||||
entityoverworldtrigger_t *t
|
||||
) {
|
||||
if(t->user) return (jsoverworldtriggercbs_t *)t->user;
|
||||
jsoverworldtriggercbs_t *cbs = (jsoverworldtriggercbs_t *)malloc(
|
||||
sizeof(jsoverworldtriggercbs_t)
|
||||
);
|
||||
cbs->onEnter = jerry_undefined();
|
||||
cbs->onExit = jerry_undefined();
|
||||
cbs->onStay = jerry_undefined();
|
||||
cbs->onOutside = jerry_undefined();
|
||||
t->user = (void *)cbs;
|
||||
return cbs;
|
||||
}
|
||||
|
||||
static void moduleOverworldTriggerOnEnterCb(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
void *user
|
||||
) {
|
||||
(void)entityId; (void)componentId;
|
||||
jsoverworldtriggercbs_t *cbs = (jsoverworldtriggercbs_t *)user;
|
||||
if(!jerry_value_is_function(cbs->onEnter)) return;
|
||||
jerry_value_t ret = jerry_call(cbs->onEnter, jerry_undefined(), NULL, 0);
|
||||
jerry_value_free(ret);
|
||||
}
|
||||
|
||||
static void moduleOverworldTriggerOnExitCb(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
void *user
|
||||
) {
|
||||
(void)entityId; (void)componentId;
|
||||
jsoverworldtriggercbs_t *cbs = (jsoverworldtriggercbs_t *)user;
|
||||
if(!jerry_value_is_function(cbs->onExit)) return;
|
||||
jerry_value_t ret = jerry_call(cbs->onExit, jerry_undefined(), NULL, 0);
|
||||
jerry_value_free(ret);
|
||||
}
|
||||
|
||||
static void moduleOverworldTriggerOnStayCb(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
void *user
|
||||
) {
|
||||
(void)entityId; (void)componentId;
|
||||
jsoverworldtriggercbs_t *cbs = (jsoverworldtriggercbs_t *)user;
|
||||
if(!jerry_value_is_function(cbs->onStay)) return;
|
||||
jerry_value_t ret = jerry_call(cbs->onStay, jerry_undefined(), NULL, 0);
|
||||
jerry_value_free(ret);
|
||||
}
|
||||
|
||||
static void moduleOverworldTriggerOnOutsideCb(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
void *user
|
||||
) {
|
||||
(void)entityId; (void)componentId;
|
||||
jsoverworldtriggercbs_t *cbs = (jsoverworldtriggercbs_t *)user;
|
||||
if(!jerry_value_is_function(cbs->onOutside)) return;
|
||||
jerry_value_t ret = jerry_call(cbs->onOutside, jerry_undefined(), NULL, 0);
|
||||
jerry_value_free(ret);
|
||||
}
|
||||
|
||||
// ---- entity / id ----
|
||||
|
||||
moduleBaseFunction(moduleOverworldTriggerGetEntity) {
|
||||
jscomponent_t *c = moduleOverworldTriggerSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
return jerry_number((double)c->entityId);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldTriggerGetId) {
|
||||
jscomponent_t *c = moduleOverworldTriggerSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
return jerry_number((double)c->componentId);
|
||||
}
|
||||
|
||||
// ---- min / max ----
|
||||
|
||||
moduleBaseFunction(moduleOverworldTriggerGetMin) {
|
||||
jscomponent_t *c = moduleOverworldTriggerSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
entityoverworldtrigger_t *t = entityOverworldTriggerGet(c->entityId, c->componentId);
|
||||
if(!t) return jerry_undefined();
|
||||
return moduleVec3Push(t->min);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldTriggerSetMin) {
|
||||
moduleBaseRequireArgs(1);
|
||||
jscomponent_t *c = moduleOverworldTriggerSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
float_t *v = moduleVec3From(args[0]);
|
||||
if(!v) return moduleBaseThrow("OverworldTrigger.min: expected Vec3");
|
||||
entityoverworldtrigger_t *t = entityOverworldTriggerGet(c->entityId, c->componentId);
|
||||
if(!t) return jerry_undefined();
|
||||
glm_vec3_copy(v, t->min);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldTriggerGetMax) {
|
||||
jscomponent_t *c = moduleOverworldTriggerSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
entityoverworldtrigger_t *t = entityOverworldTriggerGet(c->entityId, c->componentId);
|
||||
if(!t) return jerry_undefined();
|
||||
return moduleVec3Push(t->max);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldTriggerSetMax) {
|
||||
moduleBaseRequireArgs(1);
|
||||
jscomponent_t *c = moduleOverworldTriggerSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
float_t *v = moduleVec3From(args[0]);
|
||||
if(!v) return moduleBaseThrow("OverworldTrigger.max: expected Vec3");
|
||||
entityoverworldtrigger_t *t = entityOverworldTriggerGet(c->entityId, c->componentId);
|
||||
if(!t) return jerry_undefined();
|
||||
glm_vec3_copy(v, t->max);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
// ---- playerInside ----
|
||||
|
||||
moduleBaseFunction(moduleOverworldTriggerGetPlayerInside) {
|
||||
jscomponent_t *c = moduleOverworldTriggerSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
entityoverworldtrigger_t *t = entityOverworldTriggerGet(c->entityId, c->componentId);
|
||||
if(!t) return jerry_undefined();
|
||||
return jerry_boolean(t->playerInside);
|
||||
}
|
||||
|
||||
// ---- setBounds ----
|
||||
|
||||
moduleBaseFunction(moduleOverworldTriggerSetBounds) {
|
||||
moduleBaseRequireArgs(2);
|
||||
jscomponent_t *c = moduleOverworldTriggerSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
float_t *minV = moduleVec3From(args[0]);
|
||||
float_t *maxV = moduleVec3From(args[1]);
|
||||
if(!minV) return moduleBaseThrow("OverworldTrigger.setBounds: expected Vec3 for min");
|
||||
if(!maxV) return moduleBaseThrow("OverworldTrigger.setBounds: expected Vec3 for max");
|
||||
entityOverworldTriggerSetBounds(c->entityId, c->componentId, minV, maxV);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
// ---- callback helpers ----
|
||||
|
||||
/*
|
||||
* Pins the JS function on this._onXxx for GC safety, stores a copied
|
||||
* jerry_value_t reference in the cbs struct for the C trampoline, and
|
||||
* wires up the C callback pointer. Passing null/undefined clears it.
|
||||
*/
|
||||
static void moduleOverworldTriggerSetCb(
|
||||
const jerry_call_info_t *callInfo,
|
||||
const jerry_value_t *newArgs,
|
||||
const jerry_length_t newArgc,
|
||||
jerry_value_t *slot,
|
||||
entityoverworldtriggercallback_t *cslot,
|
||||
entityoverworldtriggercallback_t trampolineFn,
|
||||
const char_t *pinKey
|
||||
) {
|
||||
jerry_value_free(*slot);
|
||||
jerry_value_t pin = (newArgc > 0) ? newArgs[0] : jerry_undefined();
|
||||
jerry_value_t keyVal = jerry_string_sz(pinKey);
|
||||
if(jerry_value_is_function(pin)) {
|
||||
*slot = jerry_value_copy(pin);
|
||||
*cslot = trampolineFn;
|
||||
jerry_object_set(callInfo->this_value, keyVal, pin);
|
||||
} else {
|
||||
*slot = jerry_undefined();
|
||||
*cslot = NULL;
|
||||
jerry_value_t undef = jerry_undefined();
|
||||
jerry_object_set(callInfo->this_value, keyVal, undef);
|
||||
jerry_value_free(undef);
|
||||
}
|
||||
jerry_value_free(keyVal);
|
||||
}
|
||||
|
||||
static jerry_value_t moduleOverworldTriggerGetPinnedCb(
|
||||
const jerry_call_info_t *callInfo,
|
||||
const char_t *pinKey
|
||||
) {
|
||||
jerry_value_t key = jerry_string_sz(pinKey);
|
||||
jerry_value_t val = jerry_object_get(callInfo->this_value, key);
|
||||
jerry_value_free(key);
|
||||
return val;
|
||||
}
|
||||
|
||||
// ---- onEnter ----
|
||||
|
||||
moduleBaseFunction(moduleOverworldTriggerGetOnEnter) {
|
||||
(void)args; (void)argc;
|
||||
return moduleOverworldTriggerGetPinnedCb(callInfo, "_onEnter");
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldTriggerSetOnEnter) {
|
||||
jscomponent_t *c = moduleOverworldTriggerSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
entityoverworldtrigger_t *t = entityOverworldTriggerGet(c->entityId, c->componentId);
|
||||
if(!t) return jerry_undefined();
|
||||
jsoverworldtriggercbs_t *cbs = moduleOverworldTriggerGetCbs(t);
|
||||
moduleOverworldTriggerSetCb(
|
||||
callInfo, args, argc,
|
||||
&cbs->onEnter, &t->onEnter,
|
||||
moduleOverworldTriggerOnEnterCb, "_onEnter"
|
||||
);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
// ---- onExit ----
|
||||
|
||||
moduleBaseFunction(moduleOverworldTriggerGetOnExit) {
|
||||
(void)args; (void)argc;
|
||||
return moduleOverworldTriggerGetPinnedCb(callInfo, "_onExit");
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldTriggerSetOnExit) {
|
||||
jscomponent_t *c = moduleOverworldTriggerSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
entityoverworldtrigger_t *t = entityOverworldTriggerGet(c->entityId, c->componentId);
|
||||
if(!t) return jerry_undefined();
|
||||
jsoverworldtriggercbs_t *cbs = moduleOverworldTriggerGetCbs(t);
|
||||
moduleOverworldTriggerSetCb(
|
||||
callInfo, args, argc,
|
||||
&cbs->onExit, &t->onExit,
|
||||
moduleOverworldTriggerOnExitCb, "_onExit"
|
||||
);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
// ---- onStay ----
|
||||
|
||||
moduleBaseFunction(moduleOverworldTriggerGetOnStay) {
|
||||
(void)args; (void)argc;
|
||||
return moduleOverworldTriggerGetPinnedCb(callInfo, "_onStay");
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldTriggerSetOnStay) {
|
||||
jscomponent_t *c = moduleOverworldTriggerSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
entityoverworldtrigger_t *t = entityOverworldTriggerGet(c->entityId, c->componentId);
|
||||
if(!t) return jerry_undefined();
|
||||
jsoverworldtriggercbs_t *cbs = moduleOverworldTriggerGetCbs(t);
|
||||
moduleOverworldTriggerSetCb(
|
||||
callInfo, args, argc,
|
||||
&cbs->onStay, &t->onStay,
|
||||
moduleOverworldTriggerOnStayCb, "_onStay"
|
||||
);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
// ---- onOutside ----
|
||||
|
||||
moduleBaseFunction(moduleOverworldTriggerGetOnOutside) {
|
||||
(void)args; (void)argc;
|
||||
return moduleOverworldTriggerGetPinnedCb(callInfo, "_onOutside");
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldTriggerSetOnOutside) {
|
||||
jscomponent_t *c = moduleOverworldTriggerSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
entityoverworldtrigger_t *t = entityOverworldTriggerGet(c->entityId, c->componentId);
|
||||
if(!t) return jerry_undefined();
|
||||
jsoverworldtriggercbs_t *cbs = moduleOverworldTriggerGetCbs(t);
|
||||
moduleOverworldTriggerSetCb(
|
||||
callInfo, args, argc,
|
||||
&cbs->onOutside, &t->onOutside,
|
||||
moduleOverworldTriggerOnOutsideCb, "_onOutside"
|
||||
);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleOverworldTriggerToString) {
|
||||
jscomponent_t *c = moduleOverworldTriggerSelf(callInfo);
|
||||
if(!c) return jerry_string_sz("OverworldTrigger:invalid");
|
||||
char_t buf[32];
|
||||
snprintf(buf, sizeof(buf), "OverworldTrigger(%u)", (unsigned)c->componentId);
|
||||
return jerry_string_sz(buf);
|
||||
}
|
||||
|
||||
static void moduleOverworldTriggerInit(void) {
|
||||
scriptProtoInit(
|
||||
&MODULE_OVERWORLD_TRIGGER_PROTO, "OverworldTrigger",
|
||||
sizeof(jscomponent_t), moduleOverworldTriggerCtor
|
||||
);
|
||||
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_OVERWORLD_TRIGGER_PROTO, "entity",
|
||||
moduleOverworldTriggerGetEntity, NULL
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_OVERWORLD_TRIGGER_PROTO, "id",
|
||||
moduleOverworldTriggerGetId, NULL
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_OVERWORLD_TRIGGER_PROTO, "min",
|
||||
moduleOverworldTriggerGetMin, moduleOverworldTriggerSetMin
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_OVERWORLD_TRIGGER_PROTO, "max",
|
||||
moduleOverworldTriggerGetMax, moduleOverworldTriggerSetMax
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_OVERWORLD_TRIGGER_PROTO, "playerInside",
|
||||
moduleOverworldTriggerGetPlayerInside, NULL
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_OVERWORLD_TRIGGER_PROTO, "setBounds",
|
||||
moduleOverworldTriggerSetBounds
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_OVERWORLD_TRIGGER_PROTO, "onEnter",
|
||||
moduleOverworldTriggerGetOnEnter, moduleOverworldTriggerSetOnEnter
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_OVERWORLD_TRIGGER_PROTO, "onExit",
|
||||
moduleOverworldTriggerGetOnExit, moduleOverworldTriggerSetOnExit
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_OVERWORLD_TRIGGER_PROTO, "onStay",
|
||||
moduleOverworldTriggerGetOnStay, moduleOverworldTriggerSetOnStay
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_OVERWORLD_TRIGGER_PROTO, "onOutside",
|
||||
moduleOverworldTriggerGetOnOutside, moduleOverworldTriggerSetOnOutside
|
||||
);
|
||||
scriptProtoDefineToString(
|
||||
&MODULE_OVERWORLD_TRIGGER_PROTO, moduleOverworldTriggerToString
|
||||
);
|
||||
}
|
||||
|
||||
static void moduleOverworldTriggerDispose(void) {
|
||||
scriptProtoDispose(&MODULE_OVERWORLD_TRIGGER_PROTO);
|
||||
}
|
||||
@@ -1,110 +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/overworld/entityplayer.h"
|
||||
|
||||
static scriptproto_t MODULE_PLAYER_PROTO;
|
||||
|
||||
moduleBaseFunction(modulePlayerCtor) {
|
||||
(void)callInfo; (void)args; (void)argc;
|
||||
return moduleBaseThrow("Player cannot be instantiated with new");
|
||||
}
|
||||
|
||||
static inline jscomponent_t *modulePlayerSelf(
|
||||
const jerry_call_info_t *callInfo
|
||||
) {
|
||||
return (jscomponent_t *)scriptProtoGetValue(
|
||||
&MODULE_PLAYER_PROTO, callInfo->this_value
|
||||
);
|
||||
}
|
||||
|
||||
moduleBaseFunction(modulePlayerGetEntity) {
|
||||
jscomponent_t *c = modulePlayerSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
return jerry_number((double)c->entityId);
|
||||
}
|
||||
|
||||
moduleBaseFunction(modulePlayerGetId) {
|
||||
jscomponent_t *c = modulePlayerSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
return jerry_number((double)c->componentId);
|
||||
}
|
||||
|
||||
moduleBaseFunction(modulePlayerGetSpeed) {
|
||||
jscomponent_t *c = modulePlayerSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
entityplayer_t *p = entityPlayerGet(c->entityId, c->componentId);
|
||||
if(!p) return jerry_undefined();
|
||||
return jerry_number((double)p->speed);
|
||||
}
|
||||
|
||||
moduleBaseFunction(modulePlayerSetSpeed) {
|
||||
moduleBaseRequireArgs(1);
|
||||
jscomponent_t *c = modulePlayerSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
entityplayer_t *p = entityPlayerGet(c->entityId, c->componentId);
|
||||
if(!p) return jerry_undefined();
|
||||
p->speed = moduleBaseArgFloat(0);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(modulePlayerGetRunSpeed) {
|
||||
jscomponent_t *c = modulePlayerSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
entityplayer_t *p = entityPlayerGet(c->entityId, c->componentId);
|
||||
if(!p) return jerry_undefined();
|
||||
return jerry_number((double)p->runSpeed);
|
||||
}
|
||||
|
||||
moduleBaseFunction(modulePlayerSetRunSpeed) {
|
||||
moduleBaseRequireArgs(1);
|
||||
jscomponent_t *c = modulePlayerSelf(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
entityplayer_t *p = entityPlayerGet(c->entityId, c->componentId);
|
||||
if(!p) return jerry_undefined();
|
||||
p->runSpeed = moduleBaseArgFloat(0);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(modulePlayerToString) {
|
||||
jscomponent_t *c = modulePlayerSelf(callInfo);
|
||||
if(!c) return jerry_string_sz("Player:invalid");
|
||||
char_t buf[32];
|
||||
snprintf(buf, sizeof(buf), "Player(%u)", (unsigned)c->componentId);
|
||||
return jerry_string_sz(buf);
|
||||
}
|
||||
|
||||
static void modulePlayerInit(void) {
|
||||
scriptProtoInit(
|
||||
&MODULE_PLAYER_PROTO, "Player",
|
||||
sizeof(jscomponent_t), modulePlayerCtor
|
||||
);
|
||||
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_PLAYER_PROTO, "entity", modulePlayerGetEntity, NULL
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_PLAYER_PROTO, "id", modulePlayerGetId, NULL
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_PLAYER_PROTO, "speed",
|
||||
modulePlayerGetSpeed, modulePlayerSetSpeed
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_PLAYER_PROTO, "runSpeed",
|
||||
modulePlayerGetRunSpeed, modulePlayerSetRunSpeed
|
||||
);
|
||||
scriptProtoDefineToString(&MODULE_PLAYER_PROTO, modulePlayerToString);
|
||||
}
|
||||
|
||||
static void modulePlayerDispose(void) {
|
||||
scriptProtoDispose(&MODULE_PLAYER_PROTO);
|
||||
}
|
||||
@@ -13,31 +13,17 @@
|
||||
|
||||
static scriptproto_t MODULE_VEC3_PROTO;
|
||||
|
||||
/**
|
||||
* Returns the native float[3] pointer from the Vec3 instance that owns
|
||||
* the current call (this_value). Returns NULL if not a Vec3.
|
||||
*/
|
||||
static inline float_t *moduleVec3Get(const jerry_call_info_t *callInfo) {
|
||||
float_t * moduleVec3Get(const jerry_call_info_t *callInfo) {
|
||||
return (float_t *)scriptProtoGetValue(
|
||||
&MODULE_VEC3_PROTO, callInfo->this_value
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the native float[3] pointer from any jerry value.
|
||||
* Returns NULL if the value is not a Vec3 instance.
|
||||
*/
|
||||
static inline float_t *moduleVec3From(const jerry_value_t val) {
|
||||
float_t * moduleVec3From(const jerry_value_t val) {
|
||||
return (float_t *)scriptProtoGetValue(&MODULE_VEC3_PROTO, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Vec3 JS object from a C vec3 array.
|
||||
*
|
||||
* @param v Source vec3 to copy.
|
||||
* @return A new Vec3 JS instance owning a copy of the data.
|
||||
*/
|
||||
static inline jerry_value_t moduleVec3Push(const vec3 v) {
|
||||
jerry_value_t moduleVec3Push(const vec3 v) {
|
||||
return scriptProtoCreateValue(&MODULE_VEC3_PROTO, v);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "modulelist.h"
|
||||
#include "script/module/asset/moduleasset.h"
|
||||
#include "script/module/console/moduleconsole.h"
|
||||
#include "script/module/display/modulecolor.h"
|
||||
#include "script/module/display/modulescreen.h"
|
||||
#include "script/module/engine/moduleengine.h"
|
||||
#include "script/module/entity/component/modulecomponentlist.h"
|
||||
#include "script/module/entity/modulecomponent.h"
|
||||
#include "script/module/entity/moduleentity.h"
|
||||
#include "script/module/input/moduleinput.h"
|
||||
#include "script/module/math/modulevec3.h"
|
||||
#include "script/module/require/modulerequire.h"
|
||||
#include "script/module/scene/modulescene.h"
|
||||
#include "script/module/system/modulesystem.h"
|
||||
|
||||
|
||||
void moduleListInit(void) {
|
||||
moduleTextureInit();
|
||||
moduleColorInit();
|
||||
moduleAssetInit();
|
||||
moduleConsoleInit();
|
||||
moduleScreenInit();
|
||||
moduleEngineInit();
|
||||
moduleVec3Init();
|
||||
moduleComponentInit();
|
||||
moduleEntityInit();
|
||||
moduleComponentListInit();
|
||||
moduleInputInit();
|
||||
moduleRequireInit();
|
||||
moduleSceneInit();
|
||||
moduleSystemInit();
|
||||
}
|
||||
|
||||
void moduleListDispose(void) {
|
||||
moduleSystemDispose();
|
||||
moduleSceneDispose();
|
||||
moduleRequireDispose();
|
||||
moduleInputDispose();
|
||||
moduleComponentListDispose();
|
||||
moduleEntityDispose();
|
||||
moduleComponentDispose();
|
||||
moduleVec3Dispose();
|
||||
moduleEngineDispose();
|
||||
moduleScreenDispose();
|
||||
moduleConsoleDispose();
|
||||
moduleAssetDispose();
|
||||
moduleColorDispose();
|
||||
moduleTextureDispose();
|
||||
}
|
||||
@@ -6,47 +6,14 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/asset/moduleasset.h"
|
||||
#include "script/module/console/moduleconsole.h"
|
||||
#include "script/module/display/modulecolor.h"
|
||||
#include "script/module/display/modulescreen.h"
|
||||
#include "script/module/engine/moduleengine.h"
|
||||
#include "script/module/entity/component/modulecomponentlist.h"
|
||||
#include "script/module/entity/modulecomponent.h"
|
||||
#include "script/module/entity/moduleentity.h"
|
||||
#include "script/module/input/moduleinput.h"
|
||||
#include "script/module/math/modulevec3.h"
|
||||
#include "script/module/scene/modulescene.h"
|
||||
#include "script/module/system/modulesystem.h"
|
||||
#include "dusk.h"
|
||||
|
||||
static void moduleListInit(void) {
|
||||
moduleTextureInit();
|
||||
moduleColorInit();
|
||||
moduleAssetInit();
|
||||
moduleConsoleInit();
|
||||
moduleScreenInit();
|
||||
moduleEngineInit();
|
||||
moduleVec3Init();
|
||||
moduleComponentInit();
|
||||
moduleEntityInit();
|
||||
moduleComponentListInit();
|
||||
moduleInputInit();
|
||||
moduleSceneInit();
|
||||
moduleSystemInit();
|
||||
}
|
||||
/**
|
||||
* Initializes all of the internal (C) script modules.
|
||||
*/
|
||||
void moduleListInit(void);
|
||||
|
||||
static void moduleListDispose(void) {
|
||||
moduleSystemDispose();
|
||||
moduleSceneDispose();
|
||||
moduleInputDispose();
|
||||
moduleComponentListDispose();
|
||||
moduleEntityDispose();
|
||||
moduleComponentDispose();
|
||||
moduleVec3Dispose();
|
||||
moduleEngineDispose();
|
||||
moduleScreenDispose();
|
||||
moduleConsoleDispose();
|
||||
moduleAssetDispose();
|
||||
moduleColorDispose();
|
||||
moduleTextureDispose();
|
||||
}
|
||||
/**
|
||||
* Disposes all of the internal (C) script modules.
|
||||
*/
|
||||
void moduleListDispose(void);
|
||||
@@ -5,5 +5,5 @@
|
||||
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
testscene.c
|
||||
modulerequire.c
|
||||
)
|
||||
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "modulerequire.h"
|
||||
#include "asset/asset.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
#include "assert/assert.h"
|
||||
#include "console/console.h"
|
||||
|
||||
jerry_value_t moduleRequireFunc(
|
||||
const jerry_call_info_t *callInfo,
|
||||
const jerry_value_t args[],
|
||||
const jerry_length_t argc
|
||||
) {
|
||||
assertNotNull(callInfo, "callInfo must not be null.");
|
||||
assertNotNull(args, "args must not be null.");
|
||||
|
||||
if(argc < 1 || !jerry_value_is_string(args[0])) {
|
||||
return moduleBaseThrow("Expected a string argument for module name.");
|
||||
}
|
||||
|
||||
// Is filename too long?
|
||||
if(
|
||||
jerry_string_size(args[0], JERRY_ENCODING_UTF8) >= ASSET_FILE_NAME_MAX
|
||||
) {
|
||||
return moduleBaseThrow("Module name too long.");
|
||||
}
|
||||
|
||||
// Get C string
|
||||
char_t moduleName[ASSET_FILE_NAME_MAX];
|
||||
moduleBaseToString(args[0], moduleName, sizeof(moduleName));
|
||||
|
||||
// Lock and load the asset.
|
||||
assetloaderinput_t input;
|
||||
input.script.nothing = NULL;
|
||||
|
||||
assetentry_t *entry = assetLock(
|
||||
moduleName,
|
||||
ASSET_LOADER_TYPE_SCRIPT,
|
||||
&input
|
||||
);
|
||||
|
||||
errorret_t err = assetRequireLoaded(entry);
|
||||
|
||||
if(errorIsNotOk(err)) {
|
||||
assetUnlockEntry(entry);
|
||||
return moduleBaseThrowError(err);
|
||||
}
|
||||
|
||||
// Now the module is loaded, copy it before unlocking.
|
||||
if(jerry_value_is_undefined(entry->data.script.exports)) {
|
||||
assetUnlockEntry(entry);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
jerry_value_t exportsCopy = jerry_value_copy(
|
||||
entry->data.script.exports
|
||||
);
|
||||
assetUnlockEntry(entry);// Frees entry->data.script.exports
|
||||
return exportsCopy;
|
||||
}
|
||||
|
||||
jerry_value_t moduleRequireAsyncFunc(
|
||||
const jerry_call_info_t *callInfo,
|
||||
const jerry_value_t args[],
|
||||
const jerry_length_t argc
|
||||
) {
|
||||
assertNotNull(callInfo, "callInfo must not be null.");
|
||||
assertNotNull(args, "args must not be null.");
|
||||
|
||||
// Filename, required
|
||||
if(argc < 1 || !jerry_value_is_string(args[0])) {
|
||||
return moduleBaseThrow(
|
||||
"requireAsync expects filename."
|
||||
);
|
||||
}
|
||||
|
||||
// Callback, optional.
|
||||
if(argc >= 2 && !jerry_value_is_function(args[1])) {
|
||||
return moduleBaseThrow(
|
||||
"requireAsync callback must be a function."
|
||||
);
|
||||
}
|
||||
|
||||
// Is filename too long?
|
||||
if(
|
||||
jerry_string_size(args[0], JERRY_ENCODING_UTF8) >= ASSET_FILE_NAME_MAX
|
||||
) {
|
||||
return moduleBaseThrow("Module name too long.");
|
||||
}
|
||||
|
||||
// Get C string
|
||||
char_t moduleName[ASSET_FILE_NAME_MAX];
|
||||
moduleBaseToString(args[0], moduleName, sizeof(moduleName));
|
||||
|
||||
// Lock asset
|
||||
assetloaderinput_t input;
|
||||
input.script.nothing = NULL;
|
||||
|
||||
assetentry_t *entry = assetLock(
|
||||
moduleName,
|
||||
ASSET_LOADER_TYPE_SCRIPT,
|
||||
&input
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
void moduleRequireInit(void) {
|
||||
moduleBaseDefineGlobalMethod("require", moduleRequireFunc);
|
||||
moduleBaseDefineGlobalMethod("requireAsync", moduleRequireAsyncFunc);
|
||||
}
|
||||
|
||||
void moduleRequireDispose(void) {
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 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"
|
||||
|
||||
/**
|
||||
* Initializes the require module.
|
||||
*/
|
||||
void moduleRequireInit(void);
|
||||
|
||||
/**
|
||||
* Disposes the require module.
|
||||
*/
|
||||
void moduleRequireDispose(void);
|
||||
@@ -7,48 +7,17 @@
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/module/asset/moduleassetbatch.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "scene/scene.h"
|
||||
#include "asset/asset.h"
|
||||
#include "asset/loader/assetloader.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
static scriptproto_t MODULE_SCENE_PROTO;
|
||||
|
||||
moduleBaseFunction(moduleSceneGetCurrent) {
|
||||
return jerry_number((double)SCENE.type);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleSceneSet) {
|
||||
moduleBaseRequireArgs(1);
|
||||
moduleBaseRequireNumber(0);
|
||||
const scenetype_t type = (scenetype_t)moduleBaseArgInt(0);
|
||||
if(type <= SCENE_TYPE_NULL || type >= SCENE_TYPE_COUNT) {
|
||||
return moduleBaseThrow("Scene.set: invalid scene type");
|
||||
}
|
||||
sceneSet(type);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
static void moduleSceneInit(void) {
|
||||
scriptProtoInit(&MODULE_SCENE_PROTO, "Scene", sizeof(uint8_t), NULL);
|
||||
|
||||
scriptProtoDefineStaticProp(
|
||||
&MODULE_SCENE_PROTO, "current", moduleSceneGetCurrent, NULL
|
||||
);
|
||||
scriptProtoDefineStaticFunc(
|
||||
&MODULE_SCENE_PROTO, "set", moduleSceneSet
|
||||
);
|
||||
|
||||
/* Scene.INITIAL, Scene.TEST, Scene.OVERWORLD, ... */
|
||||
jerry_value_t global = MODULE_SCENE_PROTO.prototype;
|
||||
#define X(structName, varName, varNameUpper, initFunc, updateFunc, disposeFunc) \
|
||||
do { \
|
||||
jerry_value_t _key = jerry_string_sz(#varNameUpper); \
|
||||
jerry_value_t _val = jerry_number((double)SCENE_TYPE_##varNameUpper); \
|
||||
jerry_object_set(global, _key, _val); \
|
||||
jerry_value_free(_val); \
|
||||
jerry_value_free(_key); \
|
||||
} while(0);
|
||||
#include "scene/scenelist.h"
|
||||
#undef X
|
||||
}
|
||||
|
||||
static void moduleSceneDispose(void) {
|
||||
|
||||
@@ -69,8 +69,26 @@ errorret_t scriptExecFile(const char_t *path) {
|
||||
|
||||
errorret_t scriptDispose(void) {
|
||||
if(!SCRIPT.initialized) errorOk();
|
||||
|
||||
// Make a long story short we need to dispose script assets here, because the
|
||||
// asset reaper isn't called until later.
|
||||
assetentry_t *entries[ASSET_ENTRY_COUNT_MAX];
|
||||
uint32_t count = assetGetEntriesOfType(entries, ASSET_LOADER_TYPE_SCRIPT);
|
||||
|
||||
// Release the locks
|
||||
for(size_t i = 0; i < count; i++) {
|
||||
assetUnlockEntry(entries[i]);
|
||||
assetRequireDisposed(entries[i]);
|
||||
}
|
||||
|
||||
assertTrue(
|
||||
assetGetEntriesOfType(entries, ASSET_LOADER_TYPE_SCRIPT) == 0,
|
||||
"All script assets should be disposed by now."
|
||||
);
|
||||
|
||||
moduleListDispose();
|
||||
jerry_cleanup();
|
||||
SCRIPT.initialized = false;
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -6,12 +6,8 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "scriptproto.h"
|
||||
|
||||
typedef struct {
|
||||
void *nothing;
|
||||
} testscene_t;
|
||||
|
||||
void testSceneInit(void);
|
||||
errorret_t testSceneUpdate(void);
|
||||
void testSceneDispose(void);
|
||||
jerry_value_t exports;
|
||||
} scriptmodule_t;
|
||||
@@ -8,6 +8,8 @@
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
#define MATH_PI M_PI
|
||||
|
||||
/**
|
||||
* Finds the next power of two greater than or equal to the given value.
|
||||
*
|
||||
|
||||
@@ -107,6 +107,27 @@ static void test_assertStrLenMin(void **state) {
|
||||
expect_assert_failure(assertStrLenMin("tiny", 10, "asserts"));
|
||||
}
|
||||
|
||||
static void test_assertIsMainThread(void **state) {
|
||||
// The group setup recorded this thread as main — assertIsMainThread must pass.
|
||||
assertIsMainThread("test thread is main, should not assert");
|
||||
|
||||
// assertNotMainThread must fail when called from the main thread.
|
||||
expect_assert_failure(assertNotMainThread("asserts"));
|
||||
}
|
||||
|
||||
static void test_assertNotMainThread(void **state) {
|
||||
// assertNotMainThread must fail because we are still on the main thread.
|
||||
expect_assert_failure(assertNotMainThread("asserts"));
|
||||
|
||||
// assertIsMainThread must pass.
|
||||
assertIsMainThread("test thread is main, should not assert");
|
||||
}
|
||||
|
||||
static int groupSetup(void **state) {
|
||||
assertInit();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
const struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test(test_assertTrueImpl),
|
||||
@@ -122,7 +143,9 @@ int main(void) {
|
||||
cmocka_unit_test(test_assertDeprecated),
|
||||
cmocka_unit_test(test_assertStrLenMax),
|
||||
cmocka_unit_test(test_assertStrLenMin),
|
||||
cmocka_unit_test(test_assertIsMainThread),
|
||||
cmocka_unit_test(test_assertNotMainThread),
|
||||
};
|
||||
|
||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||
return cmocka_run_group_tests(tests, groupSetup, NULL);
|
||||
}
|
||||
Vendored
+86
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* Descriptor for one entry in an `AssetBatch`.
|
||||
*
|
||||
* `format` — texture format constant (`Texture.FORMAT_*`). Alias for `input`
|
||||
* when the type is `Asset.TYPE_TEXTURE`.
|
||||
* `axis` — mesh axis constant (`Asset.MESH_AXIS_*`). Alias for `input`
|
||||
* when the type is `Asset.TYPE_MESH`.
|
||||
* `input` — generic numeric input for all other loader types.
|
||||
*/
|
||||
interface AssetBatchDescriptor {
|
||||
path: string;
|
||||
type: number;
|
||||
format?: number;
|
||||
axis?: number;
|
||||
input?: number;
|
||||
}
|
||||
|
||||
/** A group of asset entries locked and queued for loading together. */
|
||||
interface AssetBatch {
|
||||
/** Number of entries in the batch. */
|
||||
readonly count: number;
|
||||
/** `true` when every entry has reached `LOADED`. */
|
||||
readonly isLoaded: boolean;
|
||||
/** `true` if any entry is in an `ERROR` state. */
|
||||
readonly hasError: boolean;
|
||||
/**
|
||||
* Blocks until every entry is loaded.
|
||||
* Returns `this` for chaining.
|
||||
* @throws If any entry fails to load.
|
||||
*/
|
||||
requireLoaded(): this;
|
||||
/**
|
||||
* Acquires one additional lock on every entry.
|
||||
* Returns `this` for chaining.
|
||||
*/
|
||||
lock(): this;
|
||||
/**
|
||||
* Releases all locks and clears the batch.
|
||||
* After this call the object is invalid — do not use it again.
|
||||
*/
|
||||
unlock(): void;
|
||||
/**
|
||||
* Returns the `AssetEntry` at `index`, adding an independent lock.
|
||||
* The returned entry must be unlocked separately when no longer needed.
|
||||
* Returns `undefined` if `index` is out of range or the batch is disposed.
|
||||
*/
|
||||
entry(index: number): AssetEntry | undefined;
|
||||
|
||||
/**
|
||||
* Fires once when every entry has loaded successfully.
|
||||
* Subscribe with `onLoaded[0] = () => { ... }`.
|
||||
*/
|
||||
readonly onLoaded: AssetEventProxy;
|
||||
/**
|
||||
* Fires each time a single entry finishes loading.
|
||||
* Subscribe with `onEntryLoaded[0] = () => { ... }`.
|
||||
*/
|
||||
readonly onEntryLoaded: AssetEventProxy;
|
||||
/**
|
||||
* Fires once when all entries have finished but at least one errored.
|
||||
* Subscribe with `onError[0] = () => { ... }`.
|
||||
*/
|
||||
readonly onError: AssetEventProxy;
|
||||
/**
|
||||
* Fires each time a single entry transitions to an error state.
|
||||
* Subscribe with `onEntryError[0] = () => { ... }`.
|
||||
*/
|
||||
readonly onEntryError: AssetEventProxy;
|
||||
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
interface AssetBatchConstructor {
|
||||
/** Creates a batch from an array of descriptors. Works with or without `new`. */
|
||||
(descriptors: AssetBatchDescriptor[]): AssetBatch;
|
||||
new(descriptors: AssetBatchDescriptor[]): AssetBatch;
|
||||
}
|
||||
|
||||
declare var AssetBatch: AssetBatchConstructor;
|
||||
Vendored
+24
@@ -5,6 +5,24 @@
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* An array-like proxy over one of an asset entry's events.
|
||||
* Assign a function to a numbered slot to subscribe; assign `null` to
|
||||
* unsubscribe that slot.
|
||||
*
|
||||
* @example
|
||||
* entry.onLoaded[0] = () => Console.print('loaded!');
|
||||
* entry.onLoaded[0] = null; // unsubscribe
|
||||
*/
|
||||
interface AssetEventProxy {
|
||||
readonly length: number;
|
||||
0: (() => void) | null;
|
||||
1: (() => void) | null;
|
||||
2: (() => void) | null;
|
||||
3: (() => void) | null;
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A live reference to an entry in the asset cache.
|
||||
* Holds a lock that keeps the entry alive; the lock is released automatically
|
||||
@@ -26,6 +44,12 @@ interface AssetEntry {
|
||||
* is not yet loaded.
|
||||
*/
|
||||
readonly texture: Texture | undefined;
|
||||
/** Event proxy — subscribe up to 4 callbacks for when loading completes. */
|
||||
readonly onLoaded: AssetEventProxy;
|
||||
/** Event proxy — subscribe up to 4 callbacks for when the entry is disposed. */
|
||||
readonly onUnloaded: AssetEventProxy;
|
||||
/** Event proxy — subscribe up to 4 callbacks for when loading fails. */
|
||||
readonly onError: AssetEventProxy;
|
||||
/**
|
||||
* Blocks until the entry reaches `LOADED` (or `ERROR`).
|
||||
* Returns `this` for chaining.
|
||||
|
||||
Vendored
+4
@@ -14,6 +14,9 @@
|
||||
* { "compilerOptions": { "typeRoots": ["./types"] } }
|
||||
*/
|
||||
|
||||
// module system
|
||||
/// <reference path="./require.d.ts" />
|
||||
|
||||
// math
|
||||
/// <reference path="./math/vec3.d.ts" />
|
||||
|
||||
@@ -24,6 +27,7 @@
|
||||
|
||||
// asset
|
||||
/// <reference path="./asset/assetentry.d.ts" />
|
||||
/// <reference path="./asset/assetbatch.d.ts" />
|
||||
/// <reference path="./asset/asset.d.ts" />
|
||||
|
||||
// engine systems
|
||||
|
||||
Vendored
+69
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* CommonJS-style module loader. Accepts a single path or an array of paths.
|
||||
*
|
||||
* - Single string → returns that module's `exports`.
|
||||
* - Array of strings → returns an array of `exports` in the same order.
|
||||
*
|
||||
* Modules are cached after their first load. Subsequent calls with the same
|
||||
* resolved path return the cached exports without re-executing the file.
|
||||
*
|
||||
* Path rules: `"./foo"` / `"../foo"` resolve relative to the calling script's
|
||||
* directory; any other string resolves from the archive root. `.js` is
|
||||
* appended automatically when missing.
|
||||
*
|
||||
* @example
|
||||
* const NPC = require('./entities/NPC');
|
||||
* const [NPC, Item] = require(['./entities/NPC', './entities/Item']);
|
||||
*/
|
||||
declare function require(path: string): any;
|
||||
declare function require(paths: string[]): any[];
|
||||
|
||||
/**
|
||||
* Asynchronous module loader. Accepts a single path or an array of paths.
|
||||
* The asset file(s) are read in the background; once all are loaded and
|
||||
* evaluated, `callback` is invoked.
|
||||
*
|
||||
* - Single string → `callback(exports)` — first argument is the module's
|
||||
* `exports`, or `null` on load failure.
|
||||
* - Array of strings → `callback(exportsArray)` — first argument is an array
|
||||
* of `exports` values in the same order; failed entries are `null`.
|
||||
*
|
||||
* Cached modules resolve synchronously (callback fires on the same call).
|
||||
* Path rules are identical to `require`.
|
||||
*
|
||||
* @example
|
||||
* requireAsync('./entities/NPC', function(NPC) {
|
||||
* if(NPC) NPC.init();
|
||||
* });
|
||||
*
|
||||
* requireAsync(['./entities/NPC', './entities/Item'], function(mods) {
|
||||
* const [NPC, Item] = mods;
|
||||
* });
|
||||
*/
|
||||
declare function requireAsync(
|
||||
path: string,
|
||||
callback: (exports: any) => void
|
||||
): void;
|
||||
declare function requireAsync(
|
||||
paths: string[],
|
||||
callback: (exports: any[]) => void
|
||||
): void;
|
||||
|
||||
/**
|
||||
* The module object for the currently executing script.
|
||||
* Assign `module.exports` to control what `require()` returns to callers.
|
||||
*/
|
||||
declare var module: { exports: any };
|
||||
|
||||
/**
|
||||
* Shorthand for `module.exports`. Direct assignment (`exports = ...`) does
|
||||
* NOT update `module.exports`; use `module.exports = ...` for that.
|
||||
*/
|
||||
declare var exports: any;
|
||||
Vendored
+39
-13
@@ -5,23 +5,49 @@
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
/** Scene management — request scene transitions and query the active scene. */
|
||||
interface SceneNamespace {
|
||||
/** Type constant of the currently active scene, or 0 if none. */
|
||||
readonly current: number;
|
||||
|
||||
/**
|
||||
* Requests a scene transition. The change takes effect at the start of the
|
||||
* next safe update tick (current scene is disposed, new scene is initialized).
|
||||
/**
|
||||
* The object a JS scene module must export.
|
||||
* All methods are optional.
|
||||
*
|
||||
* @example
|
||||
* Scene.set(Scene.OVERWORLD);
|
||||
* // assets/scenes/game.js
|
||||
* var scene = {};
|
||||
* var batch = AssetBatch([{ path: 'tex/bg.png', type: Asset.TYPE_TEXTURE }]);
|
||||
*
|
||||
* scene.load = function() { return batch; };
|
||||
* scene.init = function() { Console.print('scene started'); };
|
||||
* scene.update = function() { /* per-frame logic *\/ };
|
||||
* scene.dispose = function() { batch.unlock(); };
|
||||
*
|
||||
* module.exports = scene;
|
||||
*/
|
||||
set(type: number): void;
|
||||
interface SceneObject {
|
||||
/** Return an AssetBatch to preload before init is called. Optional. */
|
||||
load?(): AssetBatch | undefined;
|
||||
/** Called when this scene becomes active. Optional. */
|
||||
init?(): void;
|
||||
/** Called every frame while this scene is active. Optional. */
|
||||
update?(): void;
|
||||
/** Called when the scene is replaced or the engine shuts down. Optional. */
|
||||
dispose?(): void;
|
||||
}
|
||||
|
||||
readonly INITIAL: number;
|
||||
readonly TEST: number;
|
||||
readonly OVERWORLD: number;
|
||||
/** Scene management. */
|
||||
interface SceneNamespace {
|
||||
/** `true` while a JS script scene is running. */
|
||||
readonly active: boolean;
|
||||
|
||||
/**
|
||||
* Loads the JS module at `path`, waits for any `AssetBatch` returned by
|
||||
* `scene.load()`, then activates the scene (calling `scene.init()`).
|
||||
* The previous scene's `dispose()` is called just before `init()`.
|
||||
*
|
||||
* Returns immediately — the transition is asynchronous.
|
||||
*
|
||||
* @example
|
||||
* Scene.set('assets/scenes/game.js');
|
||||
*/
|
||||
set(path: string): void;
|
||||
}
|
||||
|
||||
declare var Scene: SceneNamespace;
|
||||
|
||||
Vendored
+1
-1
@@ -12,7 +12,7 @@ interface SystemNamespace {
|
||||
* Compare against the `System.PLATFORM_*` constants.
|
||||
*
|
||||
* @example
|
||||
* if (System.platform === System.PLATFORM_PSP) { useLowResAssets(); }
|
||||
* if(System.platform === System.PLATFORM_PSP) { useLowResAssets(); }
|
||||
*/
|
||||
readonly platform: number;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user