2 Commits

Author SHA1 Message Date
YourWishes 45d8fda0e4 require() working as I like 2026-06-05 18:49:10 -05:00
YourWishes a9e664492f First round of asset refactoring 2026-06-05 13:18:08 -05:00
28 changed files with 491 additions and 869 deletions
+7
View File
@@ -10,6 +10,13 @@ const platformNames = {
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) {
+15
View File
@@ -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--;
}
}
+4 -4
View File
@@ -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) {
+28 -22
View File
@@ -9,13 +9,16 @@
#include "log/log.h"
#include "util/string.h"
#include "util/memory.h"
#ifdef DUSK_THREAD_PTHREAD
#include "thread/thread.h"
pthread_t ASSERT_MAIN_THREAD_ID = 0;
#endif
#ifndef DUSK_ASSERTIONS_FAKED
void assertInit(void) {
assertMainThreadInit();
#ifdef DUSK_THREAD_PTHREAD
ASSERT_MAIN_THREAD_ID = pthread_self();
#endif
}
#ifdef DUSK_TEST_ASSERT
@@ -140,25 +143,28 @@
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
) {
#ifdef DUSK_THREAD_PTHREAD
assertTrueImpl(
file, line, pthread_self() == ASSERT_MAIN_THREAD_ID, message
);
#endif
}
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
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
+46 -46
View File
@@ -16,6 +16,11 @@
#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
@@ -23,51 +28,6 @@
*/
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.
*
@@ -172,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.
*
@@ -256,6 +238,22 @@
#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)
@@ -270,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)
+56 -8
View File
@@ -78,6 +78,24 @@ 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.");
@@ -103,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,
@@ -265,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) {
@@ -280,11 +333,6 @@ errorret_t assetUpdate(void) {
continue;
}
if(!entry->wasLocked) {
entry++;
continue;
}
if(entry->refs.count > 0) {
entry++;
continue;
+21
View File
@@ -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.
*
+42
View File
@@ -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,
+15
View File
@@ -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;
+2 -3
View File
@@ -29,7 +29,8 @@ 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);
@@ -54,8 +55,6 @@ 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);
}
-14
View File
@@ -26,26 +26,12 @@ typedef enum {
typedef struct assetentry_s assetentry_t;
struct assetentry_s {
// Filename and cache key
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;
/**
* Fired once when loading completes successfully (params = assetentry_t *).
* Always invoked on the main thread.
-4
View File
@@ -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));
@@ -77,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;
@@ -148,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
}
@@ -25,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();
@@ -76,51 +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.");
bool_t isModule = (
loading->entry->input != NULL &&
loading->entry->input->script.isModule
// Get the current global script realm
jerry_value_t global = jerry_current_realm();
// Replace globalThis.module with a new `module = {}`
jerry_value_t oldModule = jerry_object_get_sz(global, "module");
jerry_value_t module = jerry_object();
jerry_object_set_sz(global, "module", module);
// Eval the script, we handle failure later down the code.
jerry_value_t result = jerry_eval(
buffer,
loading->loading.script.size,
JERRY_PARSE_NO_OPTS
);
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,
loading->loading.script.size,
JERRY_PARSE_NO_OPTS
);
moduleRequirePopDir();
}
// 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();
}
@@ -130,9 +125,12 @@ errorret_t assetScriptDispose(assetentry_t *entry) {
assertTrue(entry->type == ASSET_LOADER_TYPE_SCRIPT, "Invalid type.");
assertIsMainThread("Must be called from the main thread.");
if(entry->data.script != 0) {
jerry_value_free((jerry_value_t)entry->data.script);
entry->data.script = 0;
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,19 +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;
/**
* Pass isModule = true to evaluate in isolated module scope.
* The loaded result (entry->data.script) will be module.exports rather than
* the script's return value.
*/
typedef struct { bool_t isModule; } 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,
@@ -35,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);
+1 -1
View File
@@ -98,8 +98,8 @@ errorret_t engineDispose(void) {
consoleDispose();
errorChain(displayDispose());
// errorChain(saveDispose());
errorChain(assetDispose());
errorChain(scriptDispose());
errorChain(assetDispose());
errorOk();
}
+24 -25
View File
@@ -84,35 +84,34 @@ errorret_t sceneSet(const char_t *jsFile) {
// 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);
// // 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);
}
// // 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);
}
// // 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((jerry_value_t)entry->data.script);
assetUnlockEntry(entry);
// // 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);
// // 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;
+1
View File
@@ -6,6 +6,7 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
modulebase.c
modulelist.c
)
# Subdirs
+56
View File
@@ -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();
}
+9 -45
View File
@@ -6,50 +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/require/modulerequire.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();
moduleRequireInit();
moduleSceneInit();
moduleSystemInit();
}
/**
* Initializes all of the internal (C) script modules.
*/
void moduleListInit(void);
static void moduleListDispose(void) {
moduleSystemDispose();
moduleSceneDispose();
moduleRequireDispose();
moduleInputDispose();
moduleComponentListDispose();
moduleEntityDispose();
moduleComponentDispose();
moduleVec3Dispose();
moduleEngineDispose();
moduleScreenDispose();
moduleConsoleDispose();
moduleAssetDispose();
moduleColorDispose();
moduleTextureDispose();
}
/**
* Disposes all of the internal (C) script modules.
*/
void moduleListDispose(void);
+41 -586
View File
@@ -11,617 +11,72 @@
#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(
const jerry_call_info_t *callInfo,
const jerry_value_t args[],
const jerry_length_t argc
) {
(void)callInfo;
if(argc < 1) return moduleBaseThrow("require: expected path argument");
assertNotNull(callInfo, "callInfo must not be null.");
assertNotNull(args, "args must not be null.");
// 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;
if(argc < 1 || !jerry_value_is_string(args[0])) {
return moduleBaseThrow("Expected a string argument for module name.");
}
// Single path: require('./foo')
if(!jerry_value_is_string(args[0])) {
return moduleBaseThrow("require: path must be a string or array of strings");
// Is filename too long?
if(
jerry_string_size(args[0], JERRY_ENCODING_UTF8) >= ASSET_FILE_NAME_MAX
) {
return moduleBaseThrow("Module name too long.");
}
char_t rawPath[ASSET_FILE_NAME_MAX];
moduleBaseToString(args[0], rawPath, sizeof(rawPath));
// Get C string
char_t moduleName[ASSET_FILE_NAME_MAX];
moduleBaseToString(args[0], moduleName, sizeof(moduleName));
char_t resolved[ASSET_FILE_NAME_MAX];
requireResolvePath(rawPath, resolved, sizeof(resolved));
// Lock and load the asset.
assetloaderinput_t input;
input.script.nothing = NULL;
return requireLoad(resolved);
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;
}
// ---------------------------------------------------------------------------
// JS requireAsync()
// ---------------------------------------------------------------------------
jerry_value_t moduleRequireAsyncFunc(
const jerry_call_info_t *callInfo,
const jerry_value_t args[],
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();
}
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");
return jerry_undefined();
}
// ---------------------------------------------------------------------------
// Init / Dispose
// ---------------------------------------------------------------------------
void moduleRequireInit(void) {
memoryZero(g_cache, sizeof(g_cache));
g_cacheCount = 0;
g_dirDepth = 0;
moduleBaseDefineGlobalMethod("require", moduleRequireFunc);
moduleBaseDefineGlobalMethod("require", moduleRequireFunc);
moduleBaseDefineGlobalMethod("requireAsync", moduleRequireAsyncFunc);
}
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;
}
+2 -37
View File
@@ -9,46 +9,11 @@
#include "script/module/modulebase.h"
/**
* Initializes the require() module system: creates the module cache and
* registers the global require() and requireAsync() functions.
* Initializes the require module.
*/
void moduleRequireInit(void);
/**
* Disposes of the require() module system, releasing the module cache.
* Disposes the require module.
*/
void moduleRequireDispose(void);
/**
* Records the directory of the script about to be evaluated so that relative
* require() paths can be resolved correctly. Call before jerry_eval; pair each
* call with moduleRequirePopDir().
*
* @param filePath Full asset path of the script (e.g. "scripts/Main.js").
*/
void moduleRequirePushDir(const char_t *filePath);
/**
* Pops the most recently pushed directory off the resolution stack.
*/
void moduleRequirePopDir(void);
/**
* Evaluates a script source in an isolated module scope.
*
* Sets up fresh module/exports globals, pushes the directory for nested
* require() calls, evaluates the source, then restores everything.
*
* On success returns module.exports (caller owns the reference).
* On eval error returns the exception value (caller must check with
* jerry_value_is_exception and free appropriately).
*
* @param path Full asset path — used only for directory resolution.
* @param src Source bytes.
* @param size Byte count of src (not including any null terminator).
*/
jerry_value_t moduleRequireExecModule(
const char_t *path,
const jerry_char_t *src,
const size_t size
);
+18
View File
@@ -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();
}
+13
View File
@@ -0,0 +1,13 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "scriptproto.h"
typedef struct {
jerry_value_t exports;
} scriptmodule_t;
-2
View File
@@ -96,8 +96,6 @@ bool_t threadShouldStop(thread_t *thread) {
}
#ifdef DUSK_THREAD_PTHREAD
pthread_t THREAD_MAIN_THREAD_ID = 0;
void * threadHandler(thread_t *thread) {
assertNotNull(thread, "Thread cannot be NULL.");
-2
View File
@@ -89,8 +89,6 @@ void threadStop(thread_t *thread);
bool_t threadShouldStop(thread_t *thread);
#ifdef DUSK_THREAD_PTHREAD
extern pthread_t THREAD_MAIN_THREAD_ID;
/**
* Handles the thread's lifecycle for pthreads.
* @param thread Pointer to the thread structure to handle.
+2
View File
@@ -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.
*