Scene script code
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
|
||||||
|
|
||||||
const platformNames = {
|
const platformNames = {
|
||||||
[System.PLATFORM_LINUX]: 'Linux',
|
[System.PLATFORM_LINUX]: 'Linux',
|
||||||
[System.PLATFORM_KNULLI]: 'Knulli',
|
[System.PLATFORM_KNULLI]: 'Knulli',
|
||||||
@@ -9,3 +11,12 @@ const platformNames = {
|
|||||||
Console.print('Platform: ' + (platformNames[System.platform] || 'Unknown'));
|
Console.print('Platform: ' + (platformNames[System.platform] || 'Unknown'));
|
||||||
|
|
||||||
// Scene.set('testscene.js');
|
// 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();
|
||||||
|
// });
|
||||||
+12
-10
@@ -1,6 +1,8 @@
|
|||||||
var scene = {};
|
var scene = {
|
||||||
|
'test': 'teststring'
|
||||||
|
};
|
||||||
|
|
||||||
scene.batch = AssetBatch([
|
var assets = AssetBatch([
|
||||||
{ path: 'test.png', type: Asset.TYPE_TEXTURE, format: Texture.FORMAT_RGBA }
|
{ path: 'test.png', type: Asset.TYPE_TEXTURE, format: Texture.FORMAT_RGBA }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -11,12 +13,13 @@ var testPos;
|
|||||||
var testRenderable;
|
var testRenderable;
|
||||||
var texEntry;
|
var texEntry;
|
||||||
|
|
||||||
scene.load = function() {
|
scene.init = function() {
|
||||||
return scene.batch;
|
assets.lock();
|
||||||
|
assets.onLoaded[0] = scene.loaded;
|
||||||
};
|
};
|
||||||
|
|
||||||
scene.init = function() {
|
scene.loaded = function() {
|
||||||
texEntry = scene.batch.entry(0);
|
texEntry = assets.entry(0);
|
||||||
|
|
||||||
// Camera at (3, 3, 3) looking at origin
|
// Camera at (3, 3, 3) looking at origin
|
||||||
cam = Entity.create();
|
cam = Entity.create();
|
||||||
@@ -35,14 +38,13 @@ scene.init = function() {
|
|||||||
[0, 0, 1, 1, 0, 1, 1, 0]
|
[0, 0, 1, 1, 0, 1, 1, 0]
|
||||||
];
|
];
|
||||||
testPos.localPosition = new Vec3(0, 0, 0);
|
testPos.localPosition = new Vec3(0, 0, 0);
|
||||||
};
|
}
|
||||||
|
|
||||||
scene.dispose = function() {
|
scene.dispose = function() {
|
||||||
|
Console.print('Scene Dispose');
|
||||||
Entity.dispose(cam);
|
Entity.dispose(cam);
|
||||||
Entity.dispose(testEntity);
|
Entity.dispose(testEntity);
|
||||||
|
assets.unlock();
|
||||||
texEntry.unlock();
|
|
||||||
scene.batch.unlock();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = scene;
|
module.exports = scene;
|
||||||
|
|||||||
@@ -9,8 +9,15 @@
|
|||||||
#include "log/log.h"
|
#include "log/log.h"
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
|
#ifdef DUSK_THREAD_PTHREAD
|
||||||
|
#include "thread/thread.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef DUSK_ASSERTIONS_FAKED
|
#ifndef DUSK_ASSERTIONS_FAKED
|
||||||
|
void assertInit(void) {
|
||||||
|
assertMainThreadInit();
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef DUSK_TEST_ASSERT
|
#ifdef DUSK_TEST_ASSERT
|
||||||
void assertTrueImpl(
|
void assertTrueImpl(
|
||||||
const char *file,
|
const char *file,
|
||||||
@@ -132,4 +139,26 @@
|
|||||||
) {
|
) {
|
||||||
assertTrueImpl(file, line, stringCompare(a, b) == 0, message);
|
assertTrueImpl(file, line, stringCompare(a, b) == 0, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef DUSK_THREAD_PTHREAD
|
||||||
|
void assertMainThreadInit(void) {
|
||||||
|
THREAD_MAIN_THREAD_ID = pthread_self();
|
||||||
|
}
|
||||||
|
|
||||||
|
void assertIsMainThreadImpl(
|
||||||
|
const char *file,
|
||||||
|
const int32_t line,
|
||||||
|
const char *message
|
||||||
|
) {
|
||||||
|
assertTrueImpl(file, line, pthread_self() == THREAD_MAIN_THREAD_ID, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void assertNotMainThreadImpl(
|
||||||
|
const char *file,
|
||||||
|
const int32_t line,
|
||||||
|
const char *message
|
||||||
|
) {
|
||||||
|
assertTrueImpl(file, line, pthread_self() != THREAD_MAIN_THREAD_ID, message);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
@@ -17,6 +17,57 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef DUSK_ASSERTIONS_FAKED
|
#ifndef DUSK_ASSERTIONS_FAKED
|
||||||
|
/**
|
||||||
|
* Initializes the assert system. Must be the very first call in engine
|
||||||
|
* startup.
|
||||||
|
*/
|
||||||
|
void assertInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records the calling thread as the main (game) thread. Call once, early
|
||||||
|
* in engine startup, before any assertIsMainThread / assertNotMainThread
|
||||||
|
* checks are made.
|
||||||
|
*/
|
||||||
|
#ifdef DUSK_THREAD_PTHREAD
|
||||||
|
void assertMainThreadInit(void);
|
||||||
|
#else
|
||||||
|
#define assertMainThreadInit() ((void)0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the current thread is the main thread.
|
||||||
|
*
|
||||||
|
* @param message Message to throw against assertion failure.
|
||||||
|
*/
|
||||||
|
#ifdef DUSK_THREAD_PTHREAD
|
||||||
|
void assertIsMainThreadImpl(
|
||||||
|
const char *file,
|
||||||
|
const int32_t line,
|
||||||
|
const char *message
|
||||||
|
);
|
||||||
|
#define assertIsMainThread(message) \
|
||||||
|
assertIsMainThreadImpl(__FILE__, __LINE__, message)
|
||||||
|
#else
|
||||||
|
#define assertIsMainThread(message) ((void)0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the current thread is NOT the main thread.
|
||||||
|
*
|
||||||
|
* @param message Message to throw against assertion failure.
|
||||||
|
*/
|
||||||
|
#ifdef DUSK_THREAD_PTHREAD
|
||||||
|
void assertNotMainThreadImpl(
|
||||||
|
const char *file,
|
||||||
|
const int32_t line,
|
||||||
|
const char *message
|
||||||
|
);
|
||||||
|
#define assertNotMainThread(message) \
|
||||||
|
assertNotMainThreadImpl(__FILE__, __LINE__, message)
|
||||||
|
#else
|
||||||
|
#define assertNotMainThread(message) ((void)0)
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assert a given value to be true.
|
* Assert a given value to be true.
|
||||||
*
|
*
|
||||||
@@ -207,6 +258,10 @@
|
|||||||
|
|
||||||
#else
|
#else
|
||||||
// If assertions are faked, we define the macros to do nothing.
|
// 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 assertTrue(x, message) ((void)0)
|
||||||
#define assertFalse(x, message) ((void)0)
|
#define assertFalse(x, message) ((void)0)
|
||||||
#define assertUnreachable(message) ((void)0)
|
#define assertUnreachable(message) ((void)0)
|
||||||
|
|||||||
+31
-1
@@ -81,6 +81,7 @@ assetentry_t * assetGetEntry(
|
|||||||
errorret_t assetRequireLoaded(assetentry_t *entry) {
|
errorret_t assetRequireLoaded(assetentry_t *entry) {
|
||||||
assertNotNull(entry, "Entry cannot be NULL.");
|
assertNotNull(entry, "Entry cannot be NULL.");
|
||||||
assertTrue(entry->type != ASSET_LOADER_TYPE_NULL, "Invalid loader type.");
|
assertTrue(entry->type != ASSET_LOADER_TYPE_NULL, "Invalid loader type.");
|
||||||
|
assertIsMainThread("Currently only works on main thread.");
|
||||||
|
|
||||||
if(entry->state == ASSET_ENTRY_STATE_LOADED) {
|
if(entry->state == ASSET_ENTRY_STATE_LOADED) {
|
||||||
errorOk();
|
errorOk();
|
||||||
@@ -114,6 +115,7 @@ assetentry_t * assetLock(
|
|||||||
|
|
||||||
void assetUnlock(const char_t *name) {
|
void assetUnlock(const char_t *name) {
|
||||||
assertNotNull(name, "Name cannot be NULL.");
|
assertNotNull(name, "Name cannot be NULL.");
|
||||||
|
|
||||||
assetentry_t *entry = ASSET.entries;
|
assetentry_t *entry = ASSET.entries;
|
||||||
do {
|
do {
|
||||||
if(entry->type != ASSET_LOADER_TYPE_NULL && stringEquals(entry->name, name)) {
|
if(entry->type != ASSET_LOADER_TYPE_NULL && stringEquals(entry->name, name)) {
|
||||||
@@ -122,6 +124,7 @@ void assetUnlock(const char_t *name) {
|
|||||||
}
|
}
|
||||||
entry++;
|
entry++;
|
||||||
} while(entry < ASSET.entries + ASSET_ENTRY_COUNT_MAX);
|
} while(entry < ASSET.entries + ASSET_ENTRY_COUNT_MAX);
|
||||||
|
|
||||||
assertUnreachable("Asset entry not found for unlock.");
|
assertUnreachable("Asset entry not found for unlock.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,6 +134,8 @@ void assetUnlockEntry(assetentry_t *entry) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
errorret_t assetUpdate(void) {
|
errorret_t assetUpdate(void) {
|
||||||
|
assertIsMainThread("assetUpdate must be called from the main thread.");
|
||||||
|
|
||||||
// Determine how many available loading slots we have.
|
// Determine how many available loading slots we have.
|
||||||
assetloading_t *availableLoading[ASSET_LOADING_COUNT_MAX];
|
assetloading_t *availableLoading[ASSET_LOADING_COUNT_MAX];
|
||||||
uint8_t availableLoadingCount = 0;
|
uint8_t availableLoadingCount = 0;
|
||||||
@@ -294,6 +299,8 @@ errorret_t assetUpdate(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void assetUpdateAsync(thread_t *thread) {
|
void assetUpdateAsync(thread_t *thread) {
|
||||||
|
assertNotMainThread("assetUpdateAsync must not run on the main thread.");
|
||||||
|
|
||||||
while(!threadShouldStop(thread)) {
|
while(!threadShouldStop(thread)) {
|
||||||
// Walk over each asset
|
// Walk over each asset
|
||||||
assetloading_t *loading;
|
assetloading_t *loading;
|
||||||
@@ -350,11 +357,34 @@ void assetUpdateAsync(thread_t *thread) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
errorret_t assetDispose(void) {
|
errorret_t assetDispose(void) {
|
||||||
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
threadStop(&ASSET.loadThread);
|
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++) {
|
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.
|
// Cleanup zip file.
|
||||||
if(ASSET.zip != NULL) {
|
if(ASSET.zip != NULL) {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ void assetEntryInit(
|
|||||||
assertStrLenMax(name, ASSET_FILE_NAME_MAX - 1, "Name too long");
|
assertStrLenMax(name, ASSET_FILE_NAME_MAX - 1, "Name too long");
|
||||||
assertTrue(type != ASSET_LOADER_TYPE_NULL, "Invalid loader type.");
|
assertTrue(type != ASSET_LOADER_TYPE_NULL, "Invalid loader type.");
|
||||||
assertTrue(type < ASSET_LOADER_TYPE_COUNT, "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));
|
memoryZero(entry, sizeof(assetentry_t));
|
||||||
stringCopy(entry->name, name, ASSET_FILE_NAME_MAX);
|
stringCopy(entry->name, name, ASSET_FILE_NAME_MAX);
|
||||||
@@ -52,13 +53,16 @@ void assetEntryInit(
|
|||||||
void assetEntryLock(assetentry_t *entry) {
|
void assetEntryLock(assetentry_t *entry) {
|
||||||
assertNotNull(entry, "Entry cannot be NULL");
|
assertNotNull(entry, "Entry cannot be NULL");
|
||||||
assertTrue(entry->type != ASSET_LOADER_TYPE_NULL, "Invalid loader type.");
|
assertTrue(entry->type != ASSET_LOADER_TYPE_NULL, "Invalid loader type.");
|
||||||
|
|
||||||
entry->wasLocked = true;
|
entry->wasLocked = true;
|
||||||
|
|
||||||
refLock(&entry->refs);
|
refLock(&entry->refs);
|
||||||
}
|
}
|
||||||
|
|
||||||
void assetEntryUnlock(assetentry_t *entry) {
|
void assetEntryUnlock(assetentry_t *entry) {
|
||||||
assertNotNull(entry, "Entry cannot be NULL");
|
assertNotNull(entry, "Entry cannot be NULL");
|
||||||
assertTrue(entry->type != ASSET_LOADER_TYPE_NULL, "Invalid loader type.");
|
assertTrue(entry->type != ASSET_LOADER_TYPE_NULL, "Invalid loader type.");
|
||||||
|
|
||||||
refUnlock(&entry->refs);
|
refUnlock(&entry->refs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +77,7 @@ void assetEntryStartLoading(
|
|||||||
entry->state == ASSET_ENTRY_STATE_NOT_STARTED,
|
entry->state == ASSET_ENTRY_STATE_NOT_STARTED,
|
||||||
"Can only start loading from NOT_STARTED state."
|
"Can only start loading from NOT_STARTED state."
|
||||||
);
|
);
|
||||||
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
|
|
||||||
entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
|
entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
|
||||||
memoryZero(&loading->loading, sizeof(assetloaderloading_t));
|
memoryZero(&loading->loading, sizeof(assetloaderloading_t));
|
||||||
@@ -85,6 +90,11 @@ errorret_t assetEntryDispose(assetentry_t *entry) {
|
|||||||
assertNotNull(entry, "Entry cannot be NULL");
|
assertNotNull(entry, "Entry cannot be NULL");
|
||||||
assertTrue(entry->type != ASSET_LOADER_TYPE_NULL, "Invalid loader type.");
|
assertTrue(entry->type != ASSET_LOADER_TYPE_NULL, "Invalid loader type.");
|
||||||
assertTrue(entry->type < ASSET_LOADER_TYPE_COUNT, "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);
|
eventInvoke(&entry->onUnloaded, entry);
|
||||||
errorChain(ASSET_LOADER_CALLBACKS[entry->type].dispose(entry));
|
errorChain(ASSET_LOADER_CALLBACKS[entry->type].dispose(entry));
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ int assetTextureEOF(void *user) {
|
|||||||
|
|
||||||
errorret_t assetTextureLoaderAsync(assetloading_t *loading) {
|
errorret_t assetTextureLoaderAsync(assetloading_t *loading) {
|
||||||
assertNotNull(loading, "Loading cannot be NULL");
|
assertNotNull(loading, "Loading cannot be NULL");
|
||||||
|
assertNotMainThread("Should be called from an async thread.");
|
||||||
|
|
||||||
// Only care about loading pixels.
|
// Only care about loading pixels.
|
||||||
if(loading->loading.texture.state != ASSET_TEXTURE_LOADING_STATE_LOAD_PIXELS){
|
if(loading->loading.texture.state != ASSET_TEXTURE_LOADING_STATE_LOAD_PIXELS){
|
||||||
@@ -122,6 +123,7 @@ errorret_t assetTextureLoaderAsync(assetloading_t *loading) {
|
|||||||
|
|
||||||
errorret_t assetTextureLoaderSync(assetloading_t *loading) {
|
errorret_t assetTextureLoaderSync(assetloading_t *loading) {
|
||||||
assertNotNull(loading, "Loading cannot be NULL");
|
assertNotNull(loading, "Loading cannot be NULL");
|
||||||
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
|
|
||||||
switch(loading->loading.texture.state) {
|
switch(loading->loading.texture.state) {
|
||||||
case ASSET_TEXTURE_LOADING_STATE_INITIAL:
|
case ASSET_TEXTURE_LOADING_STATE_INITIAL:
|
||||||
@@ -161,5 +163,7 @@ errorret_t assetTextureLoaderSync(assetloading_t *loading) {
|
|||||||
|
|
||||||
errorret_t assetTextureDispose(assetentry_t *entry) {
|
errorret_t assetTextureDispose(assetentry_t *entry) {
|
||||||
assertNotNull(entry, "Asset entry cannot be NULL");
|
assertNotNull(entry, "Asset entry cannot be NULL");
|
||||||
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
|
|
||||||
return textureDispose(&entry->data.texture);
|
return textureDispose(&entry->data.texture);
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
errorret_t assetTilesetLoaderAsync(assetloading_t *loading) {
|
errorret_t assetTilesetLoaderAsync(assetloading_t *loading) {
|
||||||
assertNotNull(loading, "Loading cannot be NULL");
|
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) {
|
if(loading->loading.tileset.state != ASSET_TILESET_LOADING_STATE_READ_FILE) {
|
||||||
errorOk();
|
errorOk();
|
||||||
@@ -40,6 +41,7 @@ errorret_t assetTilesetLoaderAsync(assetloading_t *loading) {
|
|||||||
errorret_t assetTilesetLoaderSync(assetloading_t *loading) {
|
errorret_t assetTilesetLoaderSync(assetloading_t *loading) {
|
||||||
assertNotNull(loading, "Loading cannot be NULL");
|
assertNotNull(loading, "Loading cannot be NULL");
|
||||||
assertTrue(loading->type == ASSET_LOADER_TYPE_TILESET, "Invalid type.");
|
assertTrue(loading->type == ASSET_LOADER_TYPE_TILESET, "Invalid type.");
|
||||||
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
|
|
||||||
switch(loading->loading.tileset.state) {
|
switch(loading->loading.tileset.state) {
|
||||||
case ASSET_TILESET_LOADING_STATE_INITIAL:
|
case ASSET_TILESET_LOADING_STATE_INITIAL:
|
||||||
@@ -111,5 +113,7 @@ errorret_t assetTilesetLoaderSync(assetloading_t *loading) {
|
|||||||
errorret_t assetTilesetDispose(assetentry_t *entry) {
|
errorret_t assetTilesetDispose(assetentry_t *entry) {
|
||||||
assertNotNull(entry, "Entry cannot be NULL");
|
assertNotNull(entry, "Entry cannot be NULL");
|
||||||
assertTrue(entry->type == ASSET_LOADER_TYPE_TILESET, "Invalid type.");
|
assertTrue(entry->type == ASSET_LOADER_TYPE_TILESET, "Invalid type.");
|
||||||
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
errorret_t assetJsonLoaderAsync(assetloading_t *loading) {
|
errorret_t assetJsonLoaderAsync(assetloading_t *loading) {
|
||||||
assertNotNull(loading, "Loading cannot be NULL");
|
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) {
|
if(loading->loading.json.state != ASSET_JSON_LOADING_STATE_READ_FILE) {
|
||||||
errorOk();
|
errorOk();
|
||||||
@@ -45,6 +46,7 @@ errorret_t assetJsonLoaderAsync(assetloading_t *loading) {
|
|||||||
errorret_t assetJsonLoaderSync(assetloading_t *loading) {
|
errorret_t assetJsonLoaderSync(assetloading_t *loading) {
|
||||||
assertNotNull(loading, "Loading cannot be NULL");
|
assertNotNull(loading, "Loading cannot be NULL");
|
||||||
assertTrue(loading->type == ASSET_LOADER_TYPE_JSON, "Invalid type.");
|
assertTrue(loading->type == ASSET_LOADER_TYPE_JSON, "Invalid type.");
|
||||||
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
|
|
||||||
switch(loading->loading.json.state) {
|
switch(loading->loading.json.state) {
|
||||||
case ASSET_JSON_LOADING_STATE_INITIAL:
|
case ASSET_JSON_LOADING_STATE_INITIAL:
|
||||||
@@ -82,7 +84,10 @@ errorret_t assetJsonLoaderSync(assetloading_t *loading) {
|
|||||||
errorret_t assetJsonDispose(assetentry_t *entry) {
|
errorret_t assetJsonDispose(assetentry_t *entry) {
|
||||||
assertNotNull(entry, "Asset entry cannot be NULL");
|
assertNotNull(entry, "Asset entry cannot be NULL");
|
||||||
assertTrue(entry->type == ASSET_LOADER_TYPE_JSON, "Invalid type.");
|
assertTrue(entry->type == ASSET_LOADER_TYPE_JSON, "Invalid type.");
|
||||||
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
|
|
||||||
yyjson_doc_free(entry->data.json);
|
yyjson_doc_free(entry->data.json);
|
||||||
entry->data.json = NULL;
|
entry->data.json = NULL;
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
errorret_t assetLocaleLoaderAsync(assetloading_t *loading) {
|
errorret_t assetLocaleLoaderAsync(assetloading_t *loading) {
|
||||||
assertNotNull(loading, "Loading cannot be NULL");
|
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) {
|
if(loading->loading.locale.state != ASSET_LOCALE_LOADER_STATE_LOAD_HEADER) {
|
||||||
errorOk();
|
errorOk();
|
||||||
@@ -38,6 +39,7 @@ errorret_t assetLocaleLoaderAsync(assetloading_t *loading) {
|
|||||||
errorret_t assetLocaleLoaderSync(assetloading_t *loading) {
|
errorret_t assetLocaleLoaderSync(assetloading_t *loading) {
|
||||||
assertNotNull(loading, "Loading cannot be NULL");
|
assertNotNull(loading, "Loading cannot be NULL");
|
||||||
assertTrue(loading->type == ASSET_LOADER_TYPE_LOCALE, "Invalid type.");
|
assertTrue(loading->type == ASSET_LOADER_TYPE_LOCALE, "Invalid type.");
|
||||||
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
|
|
||||||
switch(loading->loading.locale.state) {
|
switch(loading->loading.locale.state) {
|
||||||
case ASSET_LOCALE_LOADER_STATE_INITIAL:
|
case ASSET_LOCALE_LOADER_STATE_INITIAL:
|
||||||
@@ -60,11 +62,14 @@ errorret_t assetLocaleLoaderSync(assetloading_t *loading) {
|
|||||||
errorret_t assetLocaleDispose(assetentry_t *entry) {
|
errorret_t assetLocaleDispose(assetentry_t *entry) {
|
||||||
assertNotNull(entry, "Asset entry cannot be NULL");
|
assertNotNull(entry, "Asset entry cannot be NULL");
|
||||||
assertTrue(entry->type == ASSET_LOADER_TYPE_LOCALE, "Invalid type.");
|
assertTrue(entry->type == ASSET_LOADER_TYPE_LOCALE, "Invalid type.");
|
||||||
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
|
|
||||||
assetlocalefile_t *localeFile = &entry->data.locale;
|
assetlocalefile_t *localeFile = &entry->data.locale;
|
||||||
errorChain(assetFileClose(&localeFile->file));
|
errorChain(assetFileClose(&localeFile->file));
|
||||||
return assetFileDispose(&localeFile->file);
|
return assetFileDispose(&localeFile->file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// These functions probably need some cleaning;
|
||||||
errorret_t assetLocaleParseHeader(
|
errorret_t assetLocaleParseHeader(
|
||||||
assetlocalefile_t *localeFile,
|
assetlocalefile_t *localeFile,
|
||||||
char_t *headerBuffer,
|
char_t *headerBuffer,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
errorret_t assetScriptLoaderAsync(assetloading_t *loading) {
|
errorret_t assetScriptLoaderAsync(assetloading_t *loading) {
|
||||||
assertNotNull(loading, "Loading cannot be NULL");
|
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) {
|
if(loading->loading.script.state != ASSET_SCRIPT_LOADING_STATE_READ_FILE) {
|
||||||
errorOk();
|
errorOk();
|
||||||
@@ -59,6 +60,7 @@ errorret_t assetScriptLoaderAsync(assetloading_t *loading) {
|
|||||||
errorret_t assetScriptLoaderSync(assetloading_t *loading) {
|
errorret_t assetScriptLoaderSync(assetloading_t *loading) {
|
||||||
assertNotNull(loading, "Loading cannot be NULL");
|
assertNotNull(loading, "Loading cannot be NULL");
|
||||||
assertTrue(loading->type == ASSET_LOADER_TYPE_SCRIPT, "Invalid type.");
|
assertTrue(loading->type == ASSET_LOADER_TYPE_SCRIPT, "Invalid type.");
|
||||||
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
|
|
||||||
switch(loading->loading.script.state) {
|
switch(loading->loading.script.state) {
|
||||||
case ASSET_SCRIPT_LOADING_STATE_INITIAL:
|
case ASSET_SCRIPT_LOADING_STATE_INITIAL:
|
||||||
@@ -77,11 +79,28 @@ errorret_t assetScriptLoaderSync(assetloading_t *loading) {
|
|||||||
uint8_t *buffer = loading->loading.script.buffer;
|
uint8_t *buffer = loading->loading.script.buffer;
|
||||||
assertNotNull(buffer, "Script buffer should have been loaded by now.");
|
assertNotNull(buffer, "Script buffer should have been loaded by now.");
|
||||||
|
|
||||||
jerry_value_t result = jerry_eval(
|
bool_t isModule = (
|
||||||
|
loading->entry->input != NULL &&
|
||||||
|
loading->entry->input->script.isModule
|
||||||
|
);
|
||||||
|
|
||||||
|
jerry_value_t result;
|
||||||
|
if(isModule) {
|
||||||
|
result = moduleRequireExecModule(
|
||||||
|
loading->entry->name,
|
||||||
|
(const jerry_char_t *)buffer,
|
||||||
|
loading->loading.script.size
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
moduleRequirePushDir(loading->entry->name);
|
||||||
|
result = jerry_eval(
|
||||||
(const jerry_char_t *)buffer,
|
(const jerry_char_t *)buffer,
|
||||||
loading->loading.script.size,
|
loading->loading.script.size,
|
||||||
JERRY_PARSE_NO_OPTS
|
JERRY_PARSE_NO_OPTS
|
||||||
);
|
);
|
||||||
|
moduleRequirePopDir();
|
||||||
|
}
|
||||||
|
|
||||||
memoryFree(buffer);
|
memoryFree(buffer);
|
||||||
loading->loading.script.buffer = NULL;
|
loading->loading.script.buffer = NULL;
|
||||||
|
|
||||||
@@ -109,9 +128,12 @@ errorret_t assetScriptLoaderSync(assetloading_t *loading) {
|
|||||||
errorret_t assetScriptDispose(assetentry_t *entry) {
|
errorret_t assetScriptDispose(assetentry_t *entry) {
|
||||||
assertNotNull(entry, "Asset entry cannot be NULL");
|
assertNotNull(entry, "Asset entry cannot be NULL");
|
||||||
assertTrue(entry->type == ASSET_LOADER_TYPE_SCRIPT, "Invalid type.");
|
assertTrue(entry->type == ASSET_LOADER_TYPE_SCRIPT, "Invalid type.");
|
||||||
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
|
|
||||||
if(entry->data.script != 0) {
|
if(entry->data.script != 0) {
|
||||||
jerry_value_free((jerry_value_t)entry->data.script);
|
jerry_value_free((jerry_value_t)entry->data.script);
|
||||||
entry->data.script = 0;
|
entry->data.script = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
engine_t ENGINE;
|
engine_t ENGINE;
|
||||||
|
|
||||||
errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
||||||
|
assertInit();
|
||||||
memoryZero(&ENGINE, sizeof(engine_t));
|
memoryZero(&ENGINE, sizeof(engine_t));
|
||||||
ENGINE.running = true;
|
ENGINE.running = true;
|
||||||
ENGINE.argc = argc;
|
ENGINE.argc = argc;
|
||||||
@@ -58,6 +59,7 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
|||||||
consolePrint("Engine initialized");
|
consolePrint("Engine initialized");
|
||||||
|
|
||||||
errorChain(scriptExecFile("init.js"));
|
errorChain(scriptExecFile("init.js"));
|
||||||
|
errorChain(sceneSet("testscene.js"));
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
@@ -74,7 +76,7 @@ errorret_t engineUpdate(void) {
|
|||||||
physicsManagerUpdate();
|
physicsManagerUpdate();
|
||||||
errorChain(displayUpdate());
|
errorChain(displayUpdate());
|
||||||
errorChain(cutsceneUpdate());
|
errorChain(cutsceneUpdate());
|
||||||
errorChain(sceneUpdate());
|
sceneUpdate();
|
||||||
errorChain(assetUpdate());
|
errorChain(assetUpdate());
|
||||||
|
|
||||||
if(inputPressed(INPUT_ACTION_RAGEQUIT)) ENGINE.running = false;
|
if(inputPressed(INPUT_ACTION_RAGEQUIT)) ENGINE.running = false;
|
||||||
|
|||||||
+99
-8
@@ -14,6 +14,10 @@
|
|||||||
#include "ui/ui.h"
|
#include "ui/ui.h"
|
||||||
#include "scene/scenerenderpipeline.h"
|
#include "scene/scenerenderpipeline.h"
|
||||||
#include "entity/component.h"
|
#include "entity/component.h"
|
||||||
|
#include "asset/asset.h"
|
||||||
|
#include "asset/loader/assetloader.h"
|
||||||
|
#include "script/module/modulebase.h"
|
||||||
|
#include "console/console.h"
|
||||||
|
|
||||||
scene_t SCENE;
|
scene_t SCENE;
|
||||||
|
|
||||||
@@ -22,8 +26,21 @@ errorret_t sceneInit(void) {
|
|||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
errorret_t sceneUpdate(void) {
|
void sceneUpdate(void) {
|
||||||
errorOk();
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
SCENE.state |= SCENE_STATE_INITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorCatch(errorPrint(sceneCallScriptMethod(SCENE.jsUpdate, "update")));
|
||||||
}
|
}
|
||||||
|
|
||||||
errorret_t sceneRender(void) {
|
errorret_t sceneRender(void) {
|
||||||
@@ -61,14 +78,88 @@ errorret_t sceneRender(void) {
|
|||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
void sceneSet(void) {
|
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));
|
||||||
|
input.script.isModule = true;
|
||||||
|
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 == 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((jerry_value_t)entry->data.script);
|
||||||
|
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) {
|
errorret_t sceneDispose(void) {
|
||||||
// if(SCENE.active) {
|
if((SCENE.state & SCENE_STATE_LOADED) == 0) {
|
||||||
// scriptSceneDispose();
|
errorOk();
|
||||||
// SCENE.active = false;
|
}
|
||||||
// }
|
|
||||||
// SCENE.transitioning = false;
|
if((SCENE.state & SCENE_STATE_INITIALIZED) != 0) {
|
||||||
|
errorChain(sceneCallScriptMethod(SCENE.jsDispose, "dispose"));
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|||||||
+37
-2
@@ -7,9 +7,16 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "error/error.h"
|
#include "error/error.h"
|
||||||
|
#include <jerryscript.h>
|
||||||
|
|
||||||
|
#define SCENE_STATE_LOADED 0x01
|
||||||
|
#define SCENE_STATE_INITIALIZED 0x02
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
void *nothing;
|
jerry_value_t jsInit;
|
||||||
|
jerry_value_t jsUpdate;
|
||||||
|
jerry_value_t jsDispose;
|
||||||
|
uint8_t state;
|
||||||
} scene_t;
|
} scene_t;
|
||||||
|
|
||||||
extern scene_t SCENE;
|
extern scene_t SCENE;
|
||||||
@@ -23,7 +30,7 @@ errorret_t sceneInit(void);
|
|||||||
* Ticks the scene manager. Processes any pending scene transition, then
|
* Ticks the scene manager. Processes any pending scene transition, then
|
||||||
* calls scriptSceneUpdate on the active scene.
|
* calls scriptSceneUpdate on the active scene.
|
||||||
*/
|
*/
|
||||||
errorret_t sceneUpdate(void);
|
void sceneUpdate(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the current scene (entities, render pipeline, UI).
|
* Renders the current scene (entities, render pipeline, UI).
|
||||||
@@ -37,6 +44,34 @@ errorret_t sceneRender(void);
|
|||||||
*/
|
*/
|
||||||
// void sceneSet(void);
|
// void sceneSet(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @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.
|
* Disposes the active scene immediately.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -7,28 +7,621 @@
|
|||||||
|
|
||||||
#include "modulerequire.h"
|
#include "modulerequire.h"
|
||||||
#include "asset/asset.h"
|
#include "asset/asset.h"
|
||||||
#include "asset/assetfile.h"
|
#include "util/memory.h"
|
||||||
|
#include "util/string.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
#include "console/console.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Constants
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#define REQUIRE_CACHE_MAX 32
|
||||||
|
#define REQUIRE_DIR_STACK_MAX 16
|
||||||
|
#define REQUIRE_SEG_MAX 12
|
||||||
|
#define REQUIRE_ASYNC_MULTI_MAX 16
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Static state
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char_t path[ASSET_FILE_NAME_MAX];
|
||||||
|
jerry_value_t exports;
|
||||||
|
} requirecacheentry_t;
|
||||||
|
|
||||||
|
static requirecacheentry_t g_cache[REQUIRE_CACHE_MAX];
|
||||||
|
static uint16_t g_cacheCount;
|
||||||
|
|
||||||
|
static char_t g_dirStack[REQUIRE_DIR_STACK_MAX][ASSET_FILE_NAME_MAX];
|
||||||
|
static uint16_t g_dirDepth;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Async context types (forward-declared so items can back-pointer to ctx)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
typedef struct requireasyncmulti_s requireasyncmulti_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
requireasyncmulti_t *ctx;
|
||||||
|
uint16_t index;
|
||||||
|
assetentry_t *entry;
|
||||||
|
char_t resolvedPath[ASSET_FILE_NAME_MAX];
|
||||||
|
} requireasyncmultiitem_t;
|
||||||
|
|
||||||
|
struct requireasyncmulti_s {
|
||||||
|
jerry_value_t jscallback;
|
||||||
|
jerry_value_t resultArray;
|
||||||
|
uint16_t remaining;
|
||||||
|
requireasyncmultiitem_t items[REQUIRE_ASYNC_MULTI_MAX];
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
jerry_value_t jscallback;
|
||||||
|
assetentry_t *entry;
|
||||||
|
char_t resolvedPath[ASSET_FILE_NAME_MAX];
|
||||||
|
} requireasynctask_t;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Path resolution
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
static void requireResolvePath(
|
||||||
|
const char_t *path,
|
||||||
|
char_t *out,
|
||||||
|
const size_t outLen
|
||||||
|
) {
|
||||||
|
// Build the raw combined path (base dir + requested path if relative).
|
||||||
|
char_t combined[ASSET_FILE_NAME_MAX];
|
||||||
|
bool_t isRelative = (
|
||||||
|
path[0] == '.' && (
|
||||||
|
path[1] == '/' ||
|
||||||
|
(path[1] == '.' && path[2] == '/')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if(isRelative && g_dirDepth > 0) {
|
||||||
|
stringFormat(combined, sizeof(combined), "%s/%s",
|
||||||
|
g_dirStack[g_dirDepth - 1], path);
|
||||||
|
} else {
|
||||||
|
stringCopy(combined, path, sizeof(combined));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalise: split on '/', resolve '.' and '..'.
|
||||||
|
char_t seg[REQUIRE_SEG_MAX][ASSET_FILE_NAME_MAX];
|
||||||
|
int32_t segCnt = 0;
|
||||||
|
const char_t *p = combined;
|
||||||
|
|
||||||
|
while (*p) {
|
||||||
|
while (*p == '/') p++;
|
||||||
|
if(!*p) break;
|
||||||
|
const char_t *start = p;
|
||||||
|
while (*p && *p != '/') p++;
|
||||||
|
size_t len = (size_t)(p - start);
|
||||||
|
|
||||||
|
if(len == 1 && start[0] == '.') {
|
||||||
|
// skip current-dir marker
|
||||||
|
} else if(len == 2 && start[0] == '.' && start[1] == '.') {
|
||||||
|
if(segCnt > 0) segCnt--;
|
||||||
|
} else if(segCnt < REQUIRE_SEG_MAX) {
|
||||||
|
size_t copy = len < ASSET_FILE_NAME_MAX - 1 ? len : ASSET_FILE_NAME_MAX - 1;
|
||||||
|
memoryCopy(seg[segCnt], start, copy);
|
||||||
|
seg[segCnt][copy] = '\0';
|
||||||
|
segCnt++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reconstruct normalised path.
|
||||||
|
size_t pos = 0;
|
||||||
|
out[0] = '\0';
|
||||||
|
for(int32_t i = 0; i < segCnt; i++) {
|
||||||
|
if(i > 0 && pos < outLen - 1) {
|
||||||
|
out[pos++] = '/';
|
||||||
|
out[pos] = '\0';
|
||||||
|
}
|
||||||
|
size_t slen = strlen(seg[i]);
|
||||||
|
size_t avail = outLen - pos - 1;
|
||||||
|
if(slen > avail) slen = avail;
|
||||||
|
memoryCopy(out + pos, seg[i], slen);
|
||||||
|
pos += slen;
|
||||||
|
out[pos] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append .js extension when absent.
|
||||||
|
if(!stringEndsWith(out, ".js")) {
|
||||||
|
size_t curLen = strlen(out);
|
||||||
|
size_t avail = outLen - curLen - 1;
|
||||||
|
size_t extLen = avail < 3u ? avail : 3u;
|
||||||
|
memoryCopy(out + curLen, ".js", extLen);
|
||||||
|
out[curLen + extLen] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Directory stack
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void moduleRequirePushDir(const char_t *filePath) {
|
||||||
|
assertTrue(g_dirDepth < REQUIRE_DIR_STACK_MAX, "Require dir stack overflow.");
|
||||||
|
char_t *lastSlash = stringFindLastChar(filePath, '/');
|
||||||
|
if(lastSlash) {
|
||||||
|
size_t dirLen = (size_t)(lastSlash - filePath);
|
||||||
|
if(dirLen > ASSET_FILE_NAME_MAX - 1) dirLen = ASSET_FILE_NAME_MAX - 1;
|
||||||
|
memoryCopy(g_dirStack[g_dirDepth], filePath, dirLen);
|
||||||
|
g_dirStack[g_dirDepth][dirLen] = '\0';
|
||||||
|
} else {
|
||||||
|
g_dirStack[g_dirDepth][0] = '\0';
|
||||||
|
}
|
||||||
|
g_dirDepth++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void moduleRequirePopDir(void) {
|
||||||
|
assertTrue(g_dirDepth > 0, "Require dir stack underflow.");
|
||||||
|
g_dirDepth--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Module cache
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
static bool_t requireCacheHas(const char_t *resolvedPath) {
|
||||||
|
for(uint16_t i = 0; i < g_cacheCount; i++) {
|
||||||
|
if(stringEquals(g_cache[i].path, resolvedPath)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a new reference on cache hit, jerry_undefined() on miss.
|
||||||
|
static jerry_value_t requireCacheLookup(const char_t *resolvedPath) {
|
||||||
|
for(uint16_t i = 0; i < g_cacheCount; i++) {
|
||||||
|
if(stringEquals(g_cache[i].path, resolvedPath)) {
|
||||||
|
return jerry_value_copy(g_cache[i].exports);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stores an additional reference to exports in the cache.
|
||||||
|
static void requireCacheStore(
|
||||||
|
const char_t *resolvedPath,
|
||||||
|
jerry_value_t exports
|
||||||
|
) {
|
||||||
|
if(g_cacheCount >= REQUIRE_CACHE_MAX) return;
|
||||||
|
stringCopy(g_cache[g_cacheCount].path, resolvedPath, ASSET_FILE_NAME_MAX);
|
||||||
|
g_cache[g_cacheCount].exports = jerry_value_copy(exports);
|
||||||
|
g_cacheCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Module execution
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
jerry_value_t moduleRequireExecModule(
|
||||||
|
const char_t *path,
|
||||||
|
const jerry_char_t *src,
|
||||||
|
const size_t size
|
||||||
|
) {
|
||||||
|
jerry_value_t global = jerry_current_realm();
|
||||||
|
jerry_value_t modKey = jerry_string_sz("module");
|
||||||
|
jerry_value_t expKey = jerry_string_sz("exports");
|
||||||
|
|
||||||
|
// Save existing module/exports globals so nested modules can be restored.
|
||||||
|
jerry_value_t oldModule = jerry_object_get(global, modKey);
|
||||||
|
jerry_value_t oldExports = jerry_object_get(global, expKey);
|
||||||
|
|
||||||
|
// Create fresh: module = { exports: {} }
|
||||||
|
jerry_value_t moduleObj = jerry_object();
|
||||||
|
jerry_value_t emptyExports = jerry_object();
|
||||||
|
jerry_object_set(moduleObj, expKey, emptyExports);
|
||||||
|
jerry_value_free(emptyExports);
|
||||||
|
|
||||||
|
jerry_object_set(global, modKey, moduleObj);
|
||||||
|
|
||||||
|
// Also expose exports as a top-level shorthand.
|
||||||
|
jerry_value_t freshExports = jerry_object_get(moduleObj, expKey);
|
||||||
|
jerry_object_set(global, expKey, freshExports);
|
||||||
|
jerry_value_free(freshExports);
|
||||||
|
jerry_value_free(moduleObj);
|
||||||
|
|
||||||
|
// Push this module's directory so nested require() calls resolve correctly.
|
||||||
|
moduleRequirePushDir(path);
|
||||||
|
|
||||||
|
jerry_value_t evalResult = jerry_eval(src, size, JERRY_PARSE_NO_OPTS);
|
||||||
|
|
||||||
|
moduleRequirePopDir();
|
||||||
|
|
||||||
|
// Collect module.exports (the script may have replaced the object entirely).
|
||||||
|
jerry_value_t result;
|
||||||
|
if(jerry_value_is_exception(evalResult)) {
|
||||||
|
result = evalResult;
|
||||||
|
} else {
|
||||||
|
jerry_value_free(evalResult);
|
||||||
|
jerry_value_t currentModule = jerry_object_get(global, modKey);
|
||||||
|
result = jerry_object_get(currentModule, expKey);
|
||||||
|
jerry_value_free(currentModule);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore caller's module/exports globals.
|
||||||
|
jerry_object_set(global, modKey, oldModule);
|
||||||
|
jerry_object_set(global, expKey, oldExports);
|
||||||
|
jerry_value_free(oldModule);
|
||||||
|
jerry_value_free(oldExports);
|
||||||
|
jerry_value_free(modKey);
|
||||||
|
jerry_value_free(expKey);
|
||||||
|
jerry_value_free(global);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Callback error logging
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
static void requireLogCallbackException(
|
||||||
|
jerry_value_t ret,
|
||||||
|
const char_t *modulePath
|
||||||
|
) {
|
||||||
|
if(!jerry_value_is_exception(ret)) return;
|
||||||
|
char_t buf[256];
|
||||||
|
moduleBaseExceptionMessage(ret, buf, sizeof(buf));
|
||||||
|
consolePrint("requireAsync callback error (in '%s'): %s", modulePath, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Async callbacks — single path
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
static void requireAsyncOnLoaded(void *params, void *user) {
|
||||||
|
assetentry_t *entry = (assetentry_t *)params;
|
||||||
|
requireasynctask_t *task = (requireasynctask_t *)user;
|
||||||
|
|
||||||
|
jerry_value_t exports = jerry_value_copy((jerry_value_t)entry->data.script);
|
||||||
|
|
||||||
|
if(!requireCacheHas(task->resolvedPath)) {
|
||||||
|
requireCacheStore(task->resolvedPath, exports);
|
||||||
|
}
|
||||||
|
|
||||||
|
jerry_value_t callArgs[1] = { exports };
|
||||||
|
jerry_value_t ret = jerry_call(task->jscallback, jerry_undefined(), callArgs, 1);
|
||||||
|
requireLogCallbackException(ret, task->resolvedPath);
|
||||||
|
jerry_value_free(ret);
|
||||||
|
jerry_value_free(exports);
|
||||||
|
jerry_value_free(task->jscallback);
|
||||||
|
assetUnlockEntry(task->entry);
|
||||||
|
memoryFree(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void requireAsyncOnError(void *params, void *user) {
|
||||||
|
(void)params;
|
||||||
|
requireasynctask_t *task = (requireasynctask_t *)user;
|
||||||
|
|
||||||
|
consolePrint("requireAsync: failed to load '%s'", task->resolvedPath);
|
||||||
|
|
||||||
|
jerry_value_t jsnull = jerry_null();
|
||||||
|
jerry_value_t callArgs[1] = { jsnull };
|
||||||
|
jerry_value_t ret = jerry_call(task->jscallback, jerry_undefined(), callArgs, 1);
|
||||||
|
requireLogCallbackException(ret, task->resolvedPath);
|
||||||
|
jerry_value_free(ret);
|
||||||
|
jerry_value_free(jsnull);
|
||||||
|
jerry_value_free(task->jscallback);
|
||||||
|
assetUnlockEntry(task->entry);
|
||||||
|
memoryFree(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Async callbacks — multi-path
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
static void requireAsyncMultiFinish(requireasyncmulti_t *ctx) {
|
||||||
|
jerry_value_t callArgs[1] = { ctx->resultArray };
|
||||||
|
jerry_value_t ret = jerry_call(ctx->jscallback, jerry_undefined(), callArgs, 1);
|
||||||
|
requireLogCallbackException(ret, "multi-path requireAsync");
|
||||||
|
jerry_value_free(ret);
|
||||||
|
jerry_value_free(ctx->resultArray);
|
||||||
|
jerry_value_free(ctx->jscallback);
|
||||||
|
memoryFree(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void requireAsyncMultiOnLoaded(void *params, void *user) {
|
||||||
|
assetentry_t *entry = (assetentry_t *)params;
|
||||||
|
requireasyncmultiitem_t *item = (requireasyncmultiitem_t *)user;
|
||||||
|
requireasyncmulti_t *ctx = item->ctx;
|
||||||
|
|
||||||
|
jerry_value_t exports = jerry_value_copy((jerry_value_t)entry->data.script);
|
||||||
|
|
||||||
|
if(!requireCacheHas(item->resolvedPath)) {
|
||||||
|
requireCacheStore(item->resolvedPath, exports);
|
||||||
|
}
|
||||||
|
|
||||||
|
jerry_object_set_index(ctx->resultArray, (uint32_t)item->index, exports);
|
||||||
|
jerry_value_free(exports);
|
||||||
|
assetUnlockEntry(entry);
|
||||||
|
item->entry = NULL;
|
||||||
|
|
||||||
|
ctx->remaining--;
|
||||||
|
if(ctx->remaining == 0) requireAsyncMultiFinish(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void requireAsyncMultiOnError(void *params, void *user) {
|
||||||
|
(void)params;
|
||||||
|
requireasyncmultiitem_t *item = (requireasyncmultiitem_t *)user;
|
||||||
|
consolePrint("requireAsync: failed to load '%s'", item->resolvedPath);
|
||||||
|
requireasyncmulti_t *ctx = item->ctx;
|
||||||
|
|
||||||
|
jerry_value_t jsnull = jerry_null();
|
||||||
|
jerry_object_set_index(ctx->resultArray, (uint32_t)item->index, jsnull);
|
||||||
|
jerry_value_free(jsnull);
|
||||||
|
assetUnlockEntry(item->entry);
|
||||||
|
item->entry = NULL;
|
||||||
|
|
||||||
|
ctx->remaining--;
|
||||||
|
if(ctx->remaining == 0) requireAsyncMultiFinish(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Sync load helper — resolves, checks cache, locks, and blocks until loaded.
|
||||||
|
// Returns an owned jerry_value_t on success, or an exception on failure.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
static jerry_value_t requireLoad(const char_t *resolved) {
|
||||||
|
// Fast path: return cached exports.
|
||||||
|
jerry_value_t cached = requireCacheLookup(resolved);
|
||||||
|
if(!jerry_value_is_undefined(cached)) return cached;
|
||||||
|
jerry_value_free(cached);
|
||||||
|
|
||||||
|
assetloaderinput_t input;
|
||||||
|
memoryZero(&input, sizeof(input));
|
||||||
|
input.script.isModule = true;
|
||||||
|
|
||||||
|
assetentry_t *entry = assetLock(resolved, ASSET_LOADER_TYPE_SCRIPT, &input);
|
||||||
|
if(!entry) return moduleBaseThrow("require: failed to lock asset");
|
||||||
|
|
||||||
|
errorret_t err = assetRequireLoaded(entry);
|
||||||
|
if(errorIsNotOk(err)) {
|
||||||
|
assetUnlockEntry(entry);
|
||||||
|
return moduleBaseThrowError(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(entry->data.script == 0) {
|
||||||
|
assetUnlockEntry(entry);
|
||||||
|
return moduleBaseThrow("require: module produced no exports");
|
||||||
|
}
|
||||||
|
|
||||||
|
jerry_value_t exports = jerry_value_copy((jerry_value_t)entry->data.script);
|
||||||
|
requireCacheStore(resolved, exports);
|
||||||
|
assetUnlockEntry(entry);
|
||||||
|
return exports;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// JS require()
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
jerry_value_t moduleRequireFunc(
|
jerry_value_t moduleRequireFunc(
|
||||||
const jerry_call_info_t *callInfo,
|
const jerry_call_info_t *callInfo,
|
||||||
const jerry_value_t args[],
|
const jerry_value_t args[],
|
||||||
const jerry_length_t argc
|
const jerry_length_t argc
|
||||||
) {
|
) {
|
||||||
return jerry_undefined();
|
(void)callInfo;
|
||||||
|
if(argc < 1) return moduleBaseThrow("require: expected path argument");
|
||||||
|
|
||||||
|
// Multi-path: require(['a', 'b', ...])
|
||||||
|
if(jerry_value_is_array(args[0])) {
|
||||||
|
uint32_t count = jerry_array_length(args[0]);
|
||||||
|
jerry_value_t result = jerry_array(count);
|
||||||
|
|
||||||
|
for(uint32_t i = 0; i < count; i++) {
|
||||||
|
jerry_value_t pathVal = jerry_object_get_index(args[0], i);
|
||||||
|
if(!jerry_value_is_string(pathVal)) {
|
||||||
|
jerry_value_free(pathVal);
|
||||||
|
jerry_value_free(result);
|
||||||
|
return moduleBaseThrow("require: each path must be a string");
|
||||||
|
}
|
||||||
|
char_t rawPath[ASSET_FILE_NAME_MAX];
|
||||||
|
moduleBaseToString(pathVal, rawPath, sizeof(rawPath));
|
||||||
|
jerry_value_free(pathVal);
|
||||||
|
|
||||||
|
char_t resolved[ASSET_FILE_NAME_MAX];
|
||||||
|
requireResolvePath(rawPath, resolved, sizeof(resolved));
|
||||||
|
|
||||||
|
jerry_value_t exports = requireLoad(resolved);
|
||||||
|
if(jerry_value_is_exception(exports)) {
|
||||||
|
jerry_value_free(result);
|
||||||
|
return exports;
|
||||||
|
}
|
||||||
|
jerry_object_set_index(result, i, exports);
|
||||||
|
jerry_value_free(exports);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single path: require('./foo')
|
||||||
|
if(!jerry_value_is_string(args[0])) {
|
||||||
|
return moduleBaseThrow("require: path must be a string or array of strings");
|
||||||
|
}
|
||||||
|
|
||||||
|
char_t rawPath[ASSET_FILE_NAME_MAX];
|
||||||
|
moduleBaseToString(args[0], rawPath, sizeof(rawPath));
|
||||||
|
|
||||||
|
char_t resolved[ASSET_FILE_NAME_MAX];
|
||||||
|
requireResolvePath(rawPath, resolved, sizeof(resolved));
|
||||||
|
|
||||||
|
return requireLoad(resolved);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// JS requireAsync()
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
jerry_value_t moduleRequireAsyncFunc(
|
jerry_value_t moduleRequireAsyncFunc(
|
||||||
const jerry_call_info_t *callInfo,
|
const jerry_call_info_t *callInfo,
|
||||||
const jerry_value_t args[],
|
const jerry_value_t args[],
|
||||||
const jerry_length_t argc
|
const jerry_length_t argc
|
||||||
) {
|
) {
|
||||||
|
(void)callInfo;
|
||||||
|
if(argc < 2) {
|
||||||
|
return moduleBaseThrow("requireAsync: expected (path, callback)");
|
||||||
|
}
|
||||||
|
if(!jerry_value_is_function(args[1])) {
|
||||||
|
return moduleBaseThrow("requireAsync: second argument must be a function");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Single path -------------------------------------------------------
|
||||||
|
if(jerry_value_is_string(args[0])) {
|
||||||
|
char_t rawPath[ASSET_FILE_NAME_MAX];
|
||||||
|
moduleBaseToString(args[0], rawPath, sizeof(rawPath));
|
||||||
|
|
||||||
|
char_t resolved[ASSET_FILE_NAME_MAX];
|
||||||
|
requireResolvePath(rawPath, resolved, sizeof(resolved));
|
||||||
|
|
||||||
|
// Cache hit: invoke callback synchronously.
|
||||||
|
jerry_value_t cached = requireCacheLookup(resolved);
|
||||||
|
if(!jerry_value_is_undefined(cached)) {
|
||||||
|
jerry_value_t callArgs[1] = { cached };
|
||||||
|
jerry_value_t ret = jerry_call(args[1], jerry_undefined(), callArgs, 1);
|
||||||
|
jerry_value_free(ret);
|
||||||
|
jerry_value_free(cached);
|
||||||
return jerry_undefined();
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
jerry_value_free(cached);
|
||||||
|
|
||||||
|
assetloaderinput_t input;
|
||||||
|
memoryZero(&input, sizeof(input));
|
||||||
|
input.script.isModule = true;
|
||||||
|
assetentry_t *entry = assetLock(resolved, ASSET_LOADER_TYPE_SCRIPT, &input);
|
||||||
|
|
||||||
|
if(!entry) {
|
||||||
|
jerry_value_t jsnull = jerry_null();
|
||||||
|
jerry_value_t callArgs[1] = { jsnull };
|
||||||
|
jerry_value_t ret = jerry_call(args[1], jerry_undefined(), callArgs, 1);
|
||||||
|
jerry_value_free(ret);
|
||||||
|
jerry_value_free(jsnull);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Already loaded (e.g. locked previously and still alive).
|
||||||
|
if(entry->state == ASSET_ENTRY_STATE_LOADED && entry->data.script != 0) {
|
||||||
|
jerry_value_t exports = jerry_value_copy((jerry_value_t)entry->data.script);
|
||||||
|
requireCacheStore(resolved, exports);
|
||||||
|
jerry_value_t callArgs[1] = { exports };
|
||||||
|
jerry_value_t ret = jerry_call(args[1], jerry_undefined(), callArgs, 1);
|
||||||
|
jerry_value_free(ret);
|
||||||
|
jerry_value_free(exports);
|
||||||
|
assetUnlockEntry(entry);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
requireasynctask_t *task =
|
||||||
|
(requireasynctask_t *)memoryAllocate(sizeof(requireasynctask_t));
|
||||||
|
task->jscallback = jerry_value_copy(args[1]);
|
||||||
|
task->entry = entry;
|
||||||
|
stringCopy(task->resolvedPath, resolved, ASSET_FILE_NAME_MAX);
|
||||||
|
|
||||||
|
eventSubscribe(&entry->onLoaded, requireAsyncOnLoaded, task);
|
||||||
|
eventSubscribe(&entry->onError, requireAsyncOnError, task);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Multi-path --------------------------------------------------------
|
||||||
|
if(jerry_value_is_array(args[0])) {
|
||||||
|
uint32_t count = jerry_array_length(args[0]);
|
||||||
|
if(count == 0 || count > (uint32_t)REQUIRE_ASYNC_MULTI_MAX) {
|
||||||
|
return moduleBaseThrow("requireAsync: path array out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
requireasyncmulti_t *ctx =
|
||||||
|
(requireasyncmulti_t *)memoryAllocate(sizeof(requireasyncmulti_t));
|
||||||
|
ctx->jscallback = jerry_value_copy(args[1]);
|
||||||
|
ctx->resultArray = jerry_array(count);
|
||||||
|
ctx->remaining = (uint16_t)count;
|
||||||
|
|
||||||
|
for(uint32_t i = 0; i < count; i++) {
|
||||||
|
ctx->items[i].ctx = ctx;
|
||||||
|
ctx->items[i].index = (uint16_t)i;
|
||||||
|
ctx->items[i].entry = NULL;
|
||||||
|
|
||||||
|
jerry_value_t pathVal = jerry_object_get_index(args[0], i);
|
||||||
|
if(!jerry_value_is_string(pathVal)) {
|
||||||
|
jerry_value_free(pathVal);
|
||||||
|
jerry_value_t jsnull = jerry_null();
|
||||||
|
jerry_object_set_index(ctx->resultArray, i, jsnull);
|
||||||
|
jerry_value_free(jsnull);
|
||||||
|
ctx->remaining--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char_t rawPath[ASSET_FILE_NAME_MAX];
|
||||||
|
moduleBaseToString(pathVal, rawPath, sizeof(rawPath));
|
||||||
|
jerry_value_free(pathVal);
|
||||||
|
|
||||||
|
char_t resolved[ASSET_FILE_NAME_MAX];
|
||||||
|
requireResolvePath(rawPath, resolved, sizeof(resolved));
|
||||||
|
stringCopy(ctx->items[i].resolvedPath, resolved, ASSET_FILE_NAME_MAX);
|
||||||
|
|
||||||
|
// Cache hit.
|
||||||
|
jerry_value_t cached = requireCacheLookup(resolved);
|
||||||
|
if(!jerry_value_is_undefined(cached)) {
|
||||||
|
jerry_object_set_index(ctx->resultArray, i, cached);
|
||||||
|
jerry_value_free(cached);
|
||||||
|
ctx->remaining--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
jerry_value_free(cached);
|
||||||
|
|
||||||
|
assetloaderinput_t input;
|
||||||
|
memoryZero(&input, sizeof(input));
|
||||||
|
input.script.isModule = true;
|
||||||
|
assetentry_t *entry = assetLock(resolved, ASSET_LOADER_TYPE_SCRIPT, &input);
|
||||||
|
|
||||||
|
if(!entry) {
|
||||||
|
jerry_value_t jsnull = jerry_null();
|
||||||
|
jerry_object_set_index(ctx->resultArray, i, jsnull);
|
||||||
|
jerry_value_free(jsnull);
|
||||||
|
ctx->remaining--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Already loaded.
|
||||||
|
if(entry->state == ASSET_ENTRY_STATE_LOADED && entry->data.script != 0) {
|
||||||
|
jerry_value_t exports = jerry_value_copy((jerry_value_t)entry->data.script);
|
||||||
|
requireCacheStore(resolved, exports);
|
||||||
|
jerry_object_set_index(ctx->resultArray, i, exports);
|
||||||
|
jerry_value_free(exports);
|
||||||
|
assetUnlockEntry(entry);
|
||||||
|
ctx->remaining--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->items[i].entry = entry;
|
||||||
|
eventSubscribe(&entry->onLoaded, requireAsyncMultiOnLoaded, &ctx->items[i]);
|
||||||
|
eventSubscribe(&entry->onError, requireAsyncMultiOnError, &ctx->items[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// All resolved immediately.
|
||||||
|
if(ctx->remaining == 0) requireAsyncMultiFinish(ctx);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
return moduleBaseThrow("requireAsync: path must be a string or array of strings");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Init / Dispose
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
void moduleRequireInit(void) {
|
void moduleRequireInit(void) {
|
||||||
|
memoryZero(g_cache, sizeof(g_cache));
|
||||||
|
g_cacheCount = 0;
|
||||||
|
g_dirDepth = 0;
|
||||||
moduleBaseDefineGlobalMethod("require", moduleRequireFunc);
|
moduleBaseDefineGlobalMethod("require", moduleRequireFunc);
|
||||||
moduleBaseDefineGlobalMethod("requireAsync", moduleRequireAsyncFunc);
|
moduleBaseDefineGlobalMethod("requireAsync", moduleRequireAsyncFunc);
|
||||||
}
|
}
|
||||||
|
|
||||||
void moduleRequireDispose(void) {
|
void moduleRequireDispose(void) {
|
||||||
|
for(uint16_t i = 0; i < g_cacheCount; i++) {
|
||||||
|
jerry_value_free(g_cache[i].exports);
|
||||||
|
}
|
||||||
|
memoryZero(g_cache, sizeof(g_cache));
|
||||||
|
g_cacheCount = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,6 +96,8 @@ bool_t threadShouldStop(thread_t *thread) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DUSK_THREAD_PTHREAD
|
#ifdef DUSK_THREAD_PTHREAD
|
||||||
|
pthread_t THREAD_MAIN_THREAD_ID = 0;
|
||||||
|
|
||||||
void * threadHandler(thread_t *thread) {
|
void * threadHandler(thread_t *thread) {
|
||||||
assertNotNull(thread, "Thread cannot be NULL.");
|
assertNotNull(thread, "Thread cannot be NULL.");
|
||||||
|
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ void threadStop(thread_t *thread);
|
|||||||
bool_t threadShouldStop(thread_t *thread);
|
bool_t threadShouldStop(thread_t *thread);
|
||||||
|
|
||||||
#ifdef DUSK_THREAD_PTHREAD
|
#ifdef DUSK_THREAD_PTHREAD
|
||||||
|
extern pthread_t THREAD_MAIN_THREAD_ID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the thread's lifecycle for pthreads.
|
* Handles the thread's lifecycle for pthreads.
|
||||||
* @param thread Pointer to the thread structure to handle.
|
* @param thread Pointer to the thread structure to handle.
|
||||||
|
|||||||
@@ -107,6 +107,27 @@ static void test_assertStrLenMin(void **state) {
|
|||||||
expect_assert_failure(assertStrLenMin("tiny", 10, "asserts"));
|
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) {
|
int main(void) {
|
||||||
const struct CMUnitTest tests[] = {
|
const struct CMUnitTest tests[] = {
|
||||||
cmocka_unit_test(test_assertTrueImpl),
|
cmocka_unit_test(test_assertTrueImpl),
|
||||||
@@ -122,7 +143,9 @@ int main(void) {
|
|||||||
cmocka_unit_test(test_assertDeprecated),
|
cmocka_unit_test(test_assertDeprecated),
|
||||||
cmocka_unit_test(test_assertStrLenMax),
|
cmocka_unit_test(test_assertStrLenMax),
|
||||||
cmocka_unit_test(test_assertStrLenMin),
|
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
+1
-1
@@ -12,7 +12,7 @@ interface SystemNamespace {
|
|||||||
* Compare against the `System.PLATFORM_*` constants.
|
* Compare against the `System.PLATFORM_*` constants.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* if (System.platform === System.PLATFORM_PSP) { useLowResAssets(); }
|
* if(System.platform === System.PLATFORM_PSP) { useLowResAssets(); }
|
||||||
*/
|
*/
|
||||||
readonly platform: number;
|
readonly platform: number;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user