require() working as I like
This commit is contained in:
@@ -10,6 +10,13 @@ const platformNames = {
|
|||||||
|
|
||||||
Console.print('Platform: ' + (platformNames[System.platform] || 'Unknown'));
|
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');
|
// Scene.set('testscene.js');
|
||||||
// Console.print('Loading scene...');
|
// Console.print('Loading scene...');
|
||||||
// requireAsync('./testscene.js', function(scene) {
|
// requireAsync('./testscene.js', function(scene) {
|
||||||
|
|||||||
@@ -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--;
|
||||||
|
}
|
||||||
|
}
|
||||||
+56
-8
@@ -78,6 +78,24 @@ assetentry_t * assetGetEntry(
|
|||||||
return NULL;
|
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) {
|
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.");
|
||||||
@@ -103,6 +121,43 @@ errorret_t assetRequireLoaded(assetentry_t *entry) {
|
|||||||
errorOk();
|
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(
|
assetentry_t * assetLock(
|
||||||
const char_t *name,
|
const char_t *name,
|
||||||
const assetloadertype_t type,
|
const assetloadertype_t type,
|
||||||
@@ -265,9 +320,7 @@ errorret_t assetUpdate(void) {
|
|||||||
} while(loading < ASSET.loading + ASSET_LOADING_COUNT_MAX);
|
} while(loading < ASSET.loading + ASSET_LOADING_COUNT_MAX);
|
||||||
|
|
||||||
|
|
||||||
// Reap entries that have no external locks (refs.count == 0) AND have been
|
// Reap unused entries.
|
||||||
// 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.
|
|
||||||
entry = ASSET.entries;
|
entry = ASSET.entries;
|
||||||
do {
|
do {
|
||||||
if(entry->state != ASSET_ENTRY_STATE_LOADED) {
|
if(entry->state != ASSET_ENTRY_STATE_LOADED) {
|
||||||
@@ -280,11 +333,6 @@ errorret_t assetUpdate(void) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!entry->wasLocked) {
|
|
||||||
entry++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(entry->refs.count > 0) {
|
if(entry->refs.count > 0) {
|
||||||
entry++;
|
entry++;
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -68,6 +68,18 @@ assetentry_t * assetGetEntry(
|
|||||||
assetloaderinput_t *input
|
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
|
* 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
|
* 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);
|
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.
|
* Updates the asset system.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -25,33 +25,21 @@ errorret_t assetScriptLoaderAsync(assetloading_t *loading) {
|
|||||||
assertNull(loading->loading.script.buffer, "Buffer already defined?");
|
assertNull(loading->loading.script.buffer, "Buffer already defined?");
|
||||||
|
|
||||||
assetfile_t *file = &loading->loading.script.file;
|
assetfile_t *file = &loading->loading.script.file;
|
||||||
assetLoaderErrorChain(loading, assetFileInit(file, loading->entry->name, NULL, NULL));
|
assetLoaderErrorChain(
|
||||||
assetLoaderErrorChain(loading, assetFileOpen(file));
|
loading, assetFileInit(file, loading->entry->name, NULL, NULL)
|
||||||
|
);
|
||||||
|
|
||||||
size_t capacity = ASSET_SCRIPT_CHUNK_SIZE;
|
uint8_t *buffer = NULL;
|
||||||
uint8_t *buffer = memoryAllocate(capacity + 1);
|
size_t size = 0;
|
||||||
size_t offset = 0;
|
assetLoaderErrorChain(loading, assetFileReadEntire(file, &buffer, &size));
|
||||||
|
|
||||||
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));
|
|
||||||
assetLoaderErrorChain(loading, assetFileDispose(file));
|
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.buffer = buffer;
|
||||||
loading->loading.script.size = offset;
|
loading->loading.script.size = size;
|
||||||
loading->loading.script.state = ASSET_SCRIPT_LOADING_STATE_EXEC;
|
loading->loading.script.state = ASSET_SCRIPT_LOADING_STATE_EXEC;
|
||||||
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
|
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
|
||||||
errorOk();
|
errorOk();
|
||||||
@@ -76,51 +64,58 @@ errorret_t assetScriptLoaderSync(assetloading_t *loading) {
|
|||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get read buffer
|
||||||
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.");
|
||||||
|
|
||||||
bool_t isModule = (
|
// Get the current global script realm
|
||||||
loading->entry->input != NULL &&
|
jerry_value_t global = jerry_current_realm();
|
||||||
loading->entry->input->script.isModule
|
|
||||||
|
// 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;
|
// Free the read buffer
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
memoryFree(buffer);
|
memoryFree(buffer);
|
||||||
loading->loading.script.buffer = NULL;
|
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)) {
|
if(jerry_value_is_exception(result)) {
|
||||||
jerry_value_t errVal = jerry_exception_value(result, false);
|
jerry_value_free(module);
|
||||||
jerry_value_t errStr = jerry_value_to_string(errVal);
|
|
||||||
|
loading->entry->data.script.exports = jerry_undefined();
|
||||||
|
loading->entry->state = ASSET_ENTRY_STATE_ERROR;
|
||||||
|
|
||||||
|
// Get error string
|
||||||
char_t buf[256];
|
char_t buf[256];
|
||||||
jerry_size_t len = jerry_string_to_buffer(
|
moduleBaseExceptionMessage(result, buf, sizeof(buf));
|
||||||
errStr, JERRY_ENCODING_UTF8, (jerry_char_t *)buf, sizeof(buf) - 1
|
|
||||||
);
|
|
||||||
buf[len] = '\0';
|
|
||||||
jerry_value_free(errStr);
|
|
||||||
jerry_value_free(errVal);
|
|
||||||
jerry_value_free(result);
|
jerry_value_free(result);
|
||||||
assetLoaderErrorThrow(loading, "Script error in '%s': %s",
|
assetLoaderErrorThrow(
|
||||||
loading->entry->name, buf
|
loading,
|
||||||
|
"Script execution failed: %s: %s", loading->entry->name, buf
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get module.exports
|
||||||
|
jerry_value_t exports = jerry_object_get_sz(module, "exports");
|
||||||
|
jerry_value_free(result);
|
||||||
|
jerry_value_free(module);
|
||||||
|
|
||||||
loading->entry->data.script = (assetscriptoutput_t)result;
|
// Store the exports.
|
||||||
|
loading->entry->data.script.exports = exports;
|
||||||
loading->entry->state = ASSET_ENTRY_STATE_LOADED;
|
loading->entry->state = ASSET_ENTRY_STATE_LOADED;
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
@@ -130,9 +125,12 @@ errorret_t assetScriptDispose(assetentry_t *entry) {
|
|||||||
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.");
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
|
|
||||||
if(entry->data.script != 0) {
|
if(
|
||||||
jerry_value_free((jerry_value_t)entry->data.script);
|
entry->data.script.exports != 0 &&
|
||||||
entry->data.script = 0;
|
!jerry_value_is_undefined(entry->data.script.exports)
|
||||||
|
) {
|
||||||
|
jerry_value_free(entry->data.script.exports);
|
||||||
|
entry->data.script.exports = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
|
|||||||
@@ -7,19 +7,18 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "asset/assetfile.h"
|
#include "asset/assetfile.h"
|
||||||
|
#include "script/scriptmodule.h"
|
||||||
|
|
||||||
#define ASSET_SCRIPT_CHUNK_SIZE 1024
|
#define ASSET_SCRIPT_CHUNK_SIZE 1024
|
||||||
|
|
||||||
typedef struct assetloading_s assetloading_t;
|
typedef struct assetloading_s assetloading_t;
|
||||||
typedef struct assetentry_s assetentry_t;
|
typedef struct assetentry_s assetentry_t;
|
||||||
|
|
||||||
/**
|
typedef struct {
|
||||||
* Pass isModule = true to evaluate in isolated module scope.
|
void *nothing;
|
||||||
* The loaded result (entry->data.script) will be module.exports rather than
|
} assetscriptloaderinput_t;
|
||||||
* the script's return value.
|
|
||||||
*/
|
typedef scriptmodule_t assetscriptoutput_t;
|
||||||
typedef struct { bool_t isModule; } assetscriptloaderinput_t;
|
|
||||||
typedef uint32_t assetscriptoutput_t;
|
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
ASSET_SCRIPT_LOADING_STATE_INITIAL,
|
ASSET_SCRIPT_LOADING_STATE_INITIAL,
|
||||||
@@ -35,6 +34,27 @@ typedef struct {
|
|||||||
size_t size;
|
size_t size;
|
||||||
} assetscriptloaderloading_t;
|
} 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);
|
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);
|
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);
|
errorret_t assetScriptDispose(assetentry_t *entry);
|
||||||
|
|||||||
+24
-25
@@ -84,35 +84,34 @@ errorret_t sceneSet(const char_t *jsFile) {
|
|||||||
// Dispose any active scene before switching.
|
// Dispose any active scene before switching.
|
||||||
errorChain(sceneDispose());
|
errorChain(sceneDispose());
|
||||||
|
|
||||||
// Lock the asset
|
// // Lock the asset
|
||||||
assetloaderinput_t input;
|
// assetloaderinput_t input;
|
||||||
memoryZero(&input, sizeof(input));
|
// memoryZero(&input, sizeof(input));
|
||||||
input.script.isModule = true;
|
// assetentry_t *entry = assetLock(jsFile, ASSET_LOADER_TYPE_SCRIPT, &input);
|
||||||
assetentry_t *entry = assetLock(jsFile, ASSET_LOADER_TYPE_SCRIPT, &input);
|
// if(!entry) errorThrow("sceneSet: failed to lock '%s'", jsFile);
|
||||||
if(!entry) errorThrow("sceneSet: failed to lock '%s'", jsFile);
|
|
||||||
|
|
||||||
// Wait for loaded.
|
// // Wait for loaded.
|
||||||
errorret_t err = assetRequireLoaded(entry);
|
// errorret_t err = assetRequireLoaded(entry);
|
||||||
if(errorIsNotOk(err)) {
|
// if(errorIsNotOk(err)) {
|
||||||
assetUnlockEntry(entry);
|
// assetUnlockEntry(entry);
|
||||||
errorChain(err);
|
// errorChain(err);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Any export?
|
// // Any export?
|
||||||
if(entry->data.script == 0) {
|
// if(entry->data.script.exportedModule == 0) {
|
||||||
assetUnlockEntry(entry);
|
// assetUnlockEntry(entry);
|
||||||
errorThrow("sceneSet: '%s' produced no exports", jsFile);
|
// errorThrow("sceneSet: '%s' produced no exports", jsFile);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Copy the exports reference before unlocking (entry may be reaped).
|
// // Copy the exports reference before unlocking (entry may be reaped).
|
||||||
jerry_value_t exports = jerry_value_copy((jerry_value_t)entry->data.script);
|
// jerry_value_t exports = jerry_value_copy(entry->data.script.exportedModule);
|
||||||
assetUnlockEntry(entry);
|
// assetUnlockEntry(entry);
|
||||||
|
|
||||||
// Extract the three lifecycle callbacks.
|
// // Extract the three lifecycle callbacks.
|
||||||
SCENE.jsInit = sceneExtractFn(exports, "init");
|
// SCENE.jsInit = sceneExtractFn(exports, "init");
|
||||||
SCENE.jsUpdate = sceneExtractFn(exports, "update");
|
// SCENE.jsUpdate = sceneExtractFn(exports, "update");
|
||||||
SCENE.jsDispose = sceneExtractFn(exports, "dispose");
|
// SCENE.jsDispose = sceneExtractFn(exports, "dispose");
|
||||||
jerry_value_free(exports);
|
// jerry_value_free(exports);
|
||||||
|
|
||||||
SCENE.state |= SCENE_STATE_LOADED;
|
SCENE.state |= SCENE_STATE_LOADED;
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
PUBLIC
|
PUBLIC
|
||||||
modulebase.c
|
modulebase.c
|
||||||
|
modulelist.c
|
||||||
)
|
)
|
||||||
|
|
||||||
# Subdirs
|
# Subdirs
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "modulelist.h"
|
||||||
|
#include "script/module/asset/moduleasset.h"
|
||||||
|
#include "script/module/console/moduleconsole.h"
|
||||||
|
#include "script/module/display/modulecolor.h"
|
||||||
|
#include "script/module/display/modulescreen.h"
|
||||||
|
#include "script/module/engine/moduleengine.h"
|
||||||
|
#include "script/module/entity/component/modulecomponentlist.h"
|
||||||
|
#include "script/module/entity/modulecomponent.h"
|
||||||
|
#include "script/module/entity/moduleentity.h"
|
||||||
|
#include "script/module/input/moduleinput.h"
|
||||||
|
#include "script/module/math/modulevec3.h"
|
||||||
|
#include "script/module/require/modulerequire.h"
|
||||||
|
#include "script/module/scene/modulescene.h"
|
||||||
|
#include "script/module/system/modulesystem.h"
|
||||||
|
|
||||||
|
|
||||||
|
void moduleListInit(void) {
|
||||||
|
moduleTextureInit();
|
||||||
|
moduleColorInit();
|
||||||
|
moduleAssetInit();
|
||||||
|
moduleConsoleInit();
|
||||||
|
moduleScreenInit();
|
||||||
|
moduleEngineInit();
|
||||||
|
moduleVec3Init();
|
||||||
|
moduleComponentInit();
|
||||||
|
moduleEntityInit();
|
||||||
|
moduleComponentListInit();
|
||||||
|
moduleInputInit();
|
||||||
|
moduleRequireInit();
|
||||||
|
moduleSceneInit();
|
||||||
|
moduleSystemInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void moduleListDispose(void) {
|
||||||
|
moduleSystemDispose();
|
||||||
|
moduleSceneDispose();
|
||||||
|
moduleRequireDispose();
|
||||||
|
moduleInputDispose();
|
||||||
|
moduleComponentListDispose();
|
||||||
|
moduleEntityDispose();
|
||||||
|
moduleComponentDispose();
|
||||||
|
moduleVec3Dispose();
|
||||||
|
moduleEngineDispose();
|
||||||
|
moduleScreenDispose();
|
||||||
|
moduleConsoleDispose();
|
||||||
|
moduleAssetDispose();
|
||||||
|
moduleColorDispose();
|
||||||
|
moduleTextureDispose();
|
||||||
|
}
|
||||||
@@ -6,50 +6,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "script/module/asset/moduleasset.h"
|
#include "dusk.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"
|
|
||||||
|
|
||||||
static void moduleListInit(void) {
|
/**
|
||||||
moduleTextureInit();
|
* Initializes all of the internal (C) script modules.
|
||||||
moduleColorInit();
|
*/
|
||||||
moduleAssetInit();
|
void moduleListInit(void);
|
||||||
moduleConsoleInit();
|
|
||||||
moduleScreenInit();
|
|
||||||
moduleEngineInit();
|
|
||||||
moduleVec3Init();
|
|
||||||
moduleComponentInit();
|
|
||||||
moduleEntityInit();
|
|
||||||
moduleComponentListInit();
|
|
||||||
moduleInputInit();
|
|
||||||
moduleRequireInit();
|
|
||||||
moduleSceneInit();
|
|
||||||
moduleSystemInit();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void moduleListDispose(void) {
|
/**
|
||||||
moduleSystemDispose();
|
* Disposes all of the internal (C) script modules.
|
||||||
moduleSceneDispose();
|
*/
|
||||||
moduleRequireDispose();
|
void moduleListDispose(void);
|
||||||
moduleInputDispose();
|
|
||||||
moduleComponentListDispose();
|
|
||||||
moduleEntityDispose();
|
|
||||||
moduleComponentDispose();
|
|
||||||
moduleVec3Dispose();
|
|
||||||
moduleEngineDispose();
|
|
||||||
moduleScreenDispose();
|
|
||||||
moduleConsoleDispose();
|
|
||||||
moduleAssetDispose();
|
|
||||||
moduleColorDispose();
|
|
||||||
moduleTextureDispose();
|
|
||||||
}
|
|
||||||
@@ -11,617 +11,72 @@
|
|||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include "console/console.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
|
||||||
) {
|
) {
|
||||||
(void)callInfo;
|
assertNotNull(callInfo, "callInfo must not be null.");
|
||||||
if(argc < 1) return moduleBaseThrow("require: expected path argument");
|
assertNotNull(args, "args must not be null.");
|
||||||
|
|
||||||
// Multi-path: require(['a', 'b', ...])
|
if(argc < 1 || !jerry_value_is_string(args[0])) {
|
||||||
if(jerry_value_is_array(args[0])) {
|
return moduleBaseThrow("Expected a string argument for module name.");
|
||||||
uint32_t count = jerry_array_length(args[0]);
|
}
|
||||||
jerry_value_t result = jerry_array(count);
|
|
||||||
|
// Is filename too long?
|
||||||
for(uint32_t i = 0; i < count; i++) {
|
if(
|
||||||
jerry_value_t pathVal = jerry_object_get_index(args[0], i);
|
jerry_string_size(args[0], JERRY_ENCODING_UTF8) >= ASSET_FILE_NAME_MAX
|
||||||
if(!jerry_value_is_string(pathVal)) {
|
) {
|
||||||
jerry_value_free(pathVal);
|
return moduleBaseThrow("Module name too long.");
|
||||||
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')
|
// Get C string
|
||||||
if(!jerry_value_is_string(args[0])) {
|
char_t moduleName[ASSET_FILE_NAME_MAX];
|
||||||
return moduleBaseThrow("require: path must be a string or array of strings");
|
moduleBaseToString(args[0], moduleName, sizeof(moduleName));
|
||||||
|
|
||||||
|
// Lock and load the asset.
|
||||||
|
assetloaderinput_t input;
|
||||||
|
input.script.nothing = NULL;
|
||||||
|
|
||||||
|
assetentry_t *entry = assetLock(
|
||||||
|
moduleName,
|
||||||
|
ASSET_LOADER_TYPE_SCRIPT,
|
||||||
|
&input
|
||||||
|
);
|
||||||
|
|
||||||
|
errorret_t err = assetRequireLoaded(entry);
|
||||||
|
|
||||||
|
if(errorIsNotOk(err)) {
|
||||||
|
assetUnlockEntry(entry);
|
||||||
|
return moduleBaseThrowError(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
char_t rawPath[ASSET_FILE_NAME_MAX];
|
// Now the module is loaded, copy it before unlocking.
|
||||||
moduleBaseToString(args[0], rawPath, sizeof(rawPath));
|
if(jerry_value_is_undefined(entry->data.script.exports)) {
|
||||||
|
assetUnlockEntry(entry);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
char_t resolved[ASSET_FILE_NAME_MAX];
|
jerry_value_t exportsCopy = jerry_value_copy(
|
||||||
requireResolvePath(rawPath, resolved, sizeof(resolved));
|
entry->data.script.exports
|
||||||
|
);
|
||||||
return requireLoad(resolved);
|
assetUnlockEntry(entry);// Frees entry->data.script.exports
|
||||||
|
return exportsCopy;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// 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;
|
return jerry_undefined();
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Init / Dispose
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
void moduleRequireInit(void) {
|
void moduleRequireInit(void) {
|
||||||
memoryZero(g_cache, sizeof(g_cache));
|
moduleBaseDefineGlobalMethod("require", moduleRequireFunc);
|
||||||
g_cacheCount = 0;
|
|
||||||
g_dirDepth = 0;
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,46 +9,11 @@
|
|||||||
#include "script/module/modulebase.h"
|
#include "script/module/modulebase.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the require() module system: creates the module cache and
|
* Initializes the require module.
|
||||||
* registers the global require() and requireAsync() functions.
|
|
||||||
*/
|
*/
|
||||||
void moduleRequireInit(void);
|
void moduleRequireInit(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disposes of the require() module system, releasing the module cache.
|
* Disposes the require module.
|
||||||
*/
|
*/
|
||||||
void moduleRequireDispose(void);
|
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
|
|
||||||
);
|
|
||||||
@@ -69,8 +69,26 @@ errorret_t scriptExecFile(const char_t *path) {
|
|||||||
|
|
||||||
errorret_t scriptDispose(void) {
|
errorret_t scriptDispose(void) {
|
||||||
if(!SCRIPT.initialized) errorOk();
|
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();
|
moduleListDispose();
|
||||||
jerry_cleanup();
|
jerry_cleanup();
|
||||||
SCRIPT.initialized = false;
|
SCRIPT.initialized = false;
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
Reference in New Issue
Block a user