WIP: start JerryScript integration (core engine + Component wrapper)
Ports the core scripting engine (scriptmanager, scriptproto, modulebase, moduleplatform) and a generic Component JS wrapper from the old we-ball branch, adapted to the current entitymanager_t/multi-scene architecture. Not yet buildable: moduleentity.c, modulescene, modulelist.h/.c, and the CMakeLists.txt for the new module subdirectories are still missing, and engine.c isn't wired up yet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,96 @@
|
|||||||
|
# Copyright (c) 2026 Dominic Masters
|
||||||
|
#
|
||||||
|
# This software is released under the MIT License.
|
||||||
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
# Turn things off we don't need
|
||||||
|
set(JERRY_CMDLINE OFF CACHE BOOL "" FORCE)
|
||||||
|
set(JERRY_EXT ON CACHE BOOL "" FORCE)
|
||||||
|
set(JERRY_DEBUGGER OFF CACHE BOOL "" FORCE)
|
||||||
|
set(JERRY_BUILTIN_DATE OFF CACHE BOOL "" FORCE)
|
||||||
|
set(ENABLE_LTO OFF CACHE BOOL "" FORCE)
|
||||||
|
|
||||||
|
# Fetch Jerry
|
||||||
|
include(FetchContent)
|
||||||
|
FetchContent_Declare(
|
||||||
|
jerryscript
|
||||||
|
GIT_REPOSITORY https://git.wish.moe/YourWishes/jerryscript
|
||||||
|
GIT_TAG float32-fix
|
||||||
|
)
|
||||||
|
FetchContent_MakeAvailable(jerryscript)
|
||||||
|
|
||||||
|
# Mark found
|
||||||
|
set(jerryscript_FOUND ON)
|
||||||
|
|
||||||
|
# Define targets
|
||||||
|
if(TARGET jerryscript-core)
|
||||||
|
set(JERRY_CORE_TARGET jerryscript-core)
|
||||||
|
elseif(TARGET jerry-core)
|
||||||
|
set(JERRY_CORE_TARGET jerry-core)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(TARGET jerryscript-ext)
|
||||||
|
set(JERRY_EXT_TARGET jerryscript-ext)
|
||||||
|
elseif(TARGET jerry-ext)
|
||||||
|
set(JERRY_EXT_TARGET jerry-ext)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(TARGET jerryscript-port-default)
|
||||||
|
set(JERRY_PORT_TARGET jerryscript-port-default)
|
||||||
|
elseif(TARGET jerry-port-default)
|
||||||
|
set(JERRY_PORT_TARGET jerry-port-default)
|
||||||
|
elseif(TARGET jerryscript-port)
|
||||||
|
set(JERRY_PORT_TARGET jerryscript-port)
|
||||||
|
elseif(TARGET jerry-port)
|
||||||
|
set(JERRY_PORT_TARGET jerry-port)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT JERRY_CORE_TARGET)
|
||||||
|
message(FATAL_ERROR "JerryScript core target not found")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT JERRY_EXT_TARGET)
|
||||||
|
message(FATAL_ERROR "JerryScript ext target not found")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT JERRY_PORT_TARGET)
|
||||||
|
message(FATAL_ERROR "JerryScript port target not found")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
foreach(tgt IN ITEMS
|
||||||
|
${JERRY_CORE_TARGET}
|
||||||
|
${JERRY_EXT_TARGET}
|
||||||
|
${JERRY_PORT_TARGET}
|
||||||
|
)
|
||||||
|
if(TARGET ${tgt})
|
||||||
|
set_property(TARGET ${tgt} PROPERTY INTERPROCEDURAL_OPTIMIZATION OFF)
|
||||||
|
target_compile_definitions(${JERRY_CORE_TARGET} PRIVATE
|
||||||
|
JERRY_NUMBER_TYPE_FLOAT64=0
|
||||||
|
JERRY_BUILTIN_DATE=0
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
# Export include dirs through the targets
|
||||||
|
target_include_directories(${JERRY_CORE_TARGET} INTERFACE
|
||||||
|
${jerryscript_SOURCE_DIR}/jerry-core/include
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(${JERRY_EXT_TARGET} INTERFACE
|
||||||
|
${jerryscript_SOURCE_DIR}/jerry-ext/include
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(${JERRY_PORT_TARGET} INTERFACE
|
||||||
|
${jerryscript_SOURCE_DIR}/jerry-port/default/include
|
||||||
|
)
|
||||||
|
|
||||||
|
# Suppress JerryScript-only warning
|
||||||
|
if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
|
||||||
|
target_compile_options(${JERRY_CORE_TARGET} PRIVATE
|
||||||
|
-Wno-error
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_library(jerryscript::core ALIAS ${JERRY_CORE_TARGET})
|
||||||
|
add_library(jerryscript::ext ALIAS ${JERRY_EXT_TARGET})
|
||||||
|
add_library(jerryscript::port ALIAS ${JERRY_PORT_TARGET})
|
||||||
@@ -32,6 +32,15 @@ if(NOT yyjson_FOUND)
|
|||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(NOT jerryscript_FOUND)
|
||||||
|
find_package(jerryscript REQUIRED)
|
||||||
|
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||||
|
jerryscript::core
|
||||||
|
jerryscript::ext
|
||||||
|
jerryscript::port
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(DUSK_BACKTRACE)
|
if(DUSK_BACKTRACE)
|
||||||
target_link_options(${DUSK_LIBRARY_TARGET_NAME} PUBLIC -rdynamic)
|
target_link_options(${DUSK_LIBRARY_TARGET_NAME} PUBLIC -rdynamic)
|
||||||
target_compile_definitions(${DUSK_BINARY_TARGET_NAME} PUBLIC
|
target_compile_definitions(${DUSK_BINARY_TARGET_NAME} PUBLIC
|
||||||
@@ -72,5 +81,6 @@ add_subdirectory(ui)
|
|||||||
add_subdirectory(network)
|
add_subdirectory(network)
|
||||||
add_subdirectory(physics)
|
add_subdirectory(physics)
|
||||||
add_subdirectory(save)
|
add_subdirectory(save)
|
||||||
|
add_subdirectory(script)
|
||||||
add_subdirectory(util)
|
add_subdirectory(util)
|
||||||
add_subdirectory(thread)
|
add_subdirectory(thread)
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Copyright (c) 2026 Dominic Masters
|
||||||
|
#
|
||||||
|
# This software is released under the MIT License.
|
||||||
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
|
PUBLIC
|
||||||
|
scriptmanager.c
|
||||||
|
scriptproto.c
|
||||||
|
)
|
||||||
|
|
||||||
|
add_subdirectory(module)
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "modulecomponent.h"
|
||||||
|
#include "script/module/modulebase.h"
|
||||||
|
#include "util/string.h"
|
||||||
|
|
||||||
|
scriptproto_t MODULE_COMPONENT_PROTO;
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleComponentGetType) {
|
||||||
|
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleComponentGet);
|
||||||
|
return jerry_number(h->type);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleComponentGetEntityId) {
|
||||||
|
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleComponentGet);
|
||||||
|
return jerry_number(h->entityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleComponentDisposeMethod) {
|
||||||
|
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleComponentGet);
|
||||||
|
componentDispose(h->mgr, h->entityId, h->componentId);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleComponentToString) {
|
||||||
|
modulecomponenthandle_t *h = moduleComponentGet(callInfo);
|
||||||
|
if(!h) return jerry_string_sz("Component(?)");
|
||||||
|
|
||||||
|
char_t buf[64];
|
||||||
|
stringFormat(
|
||||||
|
buf, sizeof(buf), "Component(type=%d, entityId=%d)", h->type, h->entityId
|
||||||
|
);
|
||||||
|
return jerry_string_sz(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void moduleComponentInit(void) {
|
||||||
|
scriptProtoInit(
|
||||||
|
&MODULE_COMPONENT_PROTO,
|
||||||
|
"Component",
|
||||||
|
sizeof(modulecomponenthandle_t),
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_COMPONENT_PROTO, "type", moduleComponentGetType, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_COMPONENT_PROTO, "entityId", moduleComponentGetEntityId, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineFunc(
|
||||||
|
&MODULE_COMPONENT_PROTO, "dispose", moduleComponentDisposeMethod
|
||||||
|
);
|
||||||
|
scriptProtoDefineToString(&MODULE_COMPONENT_PROTO, moduleComponentToString);
|
||||||
|
}
|
||||||
|
|
||||||
|
void moduleComponentDispose(void) {
|
||||||
|
}
|
||||||
|
|
||||||
|
jerry_value_t moduleComponentCreate(const modulecomponenthandle_t *handle) {
|
||||||
|
return scriptProtoCreateValue(&MODULE_COMPONENT_PROTO, handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
modulecomponenthandle_t *moduleComponentGet(
|
||||||
|
const jerry_call_info_t *callInfo
|
||||||
|
) {
|
||||||
|
return (modulecomponenthandle_t *)scriptProtoGetValue(
|
||||||
|
&MODULE_COMPONENT_PROTO, callInfo->this_value
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "script/scriptproto.h"
|
||||||
|
#include "entity/entitybase.h"
|
||||||
|
#include "entity/component.h"
|
||||||
|
#include <jerryscript.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Native data wrapped by a JS Component instance. Generic across every
|
||||||
|
* componenttype_t -- there are no per-type property wrappers yet (e.g.
|
||||||
|
* no `.position` on a POSITION component), just the type-agnostic
|
||||||
|
* operations every component supports.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
entitymanager_t *mgr;
|
||||||
|
entityid_t entityId;
|
||||||
|
componentid_t componentId;
|
||||||
|
componenttype_t type;
|
||||||
|
} modulecomponenthandle_t;
|
||||||
|
|
||||||
|
extern scriptproto_t MODULE_COMPONENT_PROTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the Component class. Has no constructor -- instances are
|
||||||
|
* only ever created via moduleComponentCreate(), e.g. from
|
||||||
|
* Entity.add()/getComponent().
|
||||||
|
*/
|
||||||
|
void moduleComponentInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes the Component class's script resources.
|
||||||
|
*/
|
||||||
|
void moduleComponentDispose(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a component handle as a new JS Component instance.
|
||||||
|
*
|
||||||
|
* @param handle The handle to copy into the new instance.
|
||||||
|
* @return The new JS object.
|
||||||
|
*/
|
||||||
|
jerry_value_t moduleComponentCreate(const modulecomponenthandle_t *handle);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal. Gets the native handle wrapped by a Component instance's
|
||||||
|
* `this` value.
|
||||||
|
*
|
||||||
|
* @param callInfo The JS call info, whose this_value is the instance.
|
||||||
|
* @return The wrapped handle, or NULL if this_value isn't a Component.
|
||||||
|
*/
|
||||||
|
modulecomponenthandle_t *moduleComponentGet(
|
||||||
|
const jerry_call_info_t *callInfo
|
||||||
|
);
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "script/scriptproto.h"
|
||||||
|
#include "entity/entitybase.h"
|
||||||
|
#include <jerryscript.h>
|
||||||
|
|
||||||
|
/** Native data wrapped by a JS Entity instance. */
|
||||||
|
typedef struct {
|
||||||
|
entitymanager_t *mgr;
|
||||||
|
entityid_t id;
|
||||||
|
} moduleentityhandle_t;
|
||||||
|
|
||||||
|
extern scriptproto_t MODULE_ENTITY_PROTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the Entity class (constructible via `new Entity()`, adding
|
||||||
|
* to the currently active scene's entity manager -- see
|
||||||
|
* sceneGetActive()) and every currently-registered component type name
|
||||||
|
* (see componentlist.h) as a global integer constant usable with
|
||||||
|
* `entity.add(TYPE)`/`entity.getComponent(TYPE)`.
|
||||||
|
*/
|
||||||
|
void moduleEntityInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes the Entity class's script resources.
|
||||||
|
*/
|
||||||
|
void moduleEntityDispose(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal. Gets the native handle wrapped by an Entity instance's
|
||||||
|
* `this` value.
|
||||||
|
*
|
||||||
|
* @param callInfo The JS call info, whose this_value is the instance.
|
||||||
|
* @return The wrapped handle, or NULL if this_value isn't an Entity.
|
||||||
|
*/
|
||||||
|
moduleentityhandle_t *moduleEntityGet(const jerry_call_info_t *callInfo);
|
||||||
@@ -0,0 +1,396 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "script/scriptmanager.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
#include "util/string.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a function for a module in JavaScript.
|
||||||
|
*
|
||||||
|
* @param name Name of the method (in C, not JS)
|
||||||
|
* @return A C function with that name, containing the standard JS sig.
|
||||||
|
*/
|
||||||
|
#define moduleBaseFunction(name) \
|
||||||
|
static jerry_value_t name( \
|
||||||
|
const jerry_call_info_t *callInfo, \
|
||||||
|
const jerry_value_t args[], \
|
||||||
|
const jerry_length_t argc)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the pointer to what is otherwise a prototype in the JerryScript
|
||||||
|
* code. This, for example, allows you to define a pointer in C and have
|
||||||
|
* it be a prototype for objects in JavaScript.
|
||||||
|
*
|
||||||
|
* @param object The JavaScript object to get the native pointer from.
|
||||||
|
* @param info The native info "prototype" struct used to get the
|
||||||
|
* pointer.
|
||||||
|
* @return The pointer to the proto (or NULL).
|
||||||
|
*/
|
||||||
|
static inline void *moduleBaseGetProto(
|
||||||
|
const jerry_value_t object,
|
||||||
|
const jerry_object_native_info_t *info
|
||||||
|
) {
|
||||||
|
assertNotNull(info, "Native info must not be null");
|
||||||
|
if(!jerry_value_is_object(object)) return NULL;
|
||||||
|
|
||||||
|
return (void *)jerry_object_get_native_ptr(object, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard JerryScript free callback.
|
||||||
|
*
|
||||||
|
* @param ptr The pointer to free.
|
||||||
|
* @param info The native info struct associated with the pointer.
|
||||||
|
*/
|
||||||
|
static inline void moduleBaseFreeProto(
|
||||||
|
void *ptr, jerry_object_native_info_t *info
|
||||||
|
) {
|
||||||
|
assertNotNull(ptr, "Pointer must not be null");
|
||||||
|
assertNotNull(info, "Native info must not be null");
|
||||||
|
memoryFree(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluates a script in the global scope.
|
||||||
|
*
|
||||||
|
* @param script The script to evaluate.
|
||||||
|
*/
|
||||||
|
static inline void moduleBaseEval(const char_t *script) {
|
||||||
|
assertNotNull(script, "Script must not be null");
|
||||||
|
|
||||||
|
jerry_value_t result = jerry_eval(
|
||||||
|
(const jerry_char_t *)script,
|
||||||
|
strlen(script),
|
||||||
|
JERRY_PARSE_NO_OPTS
|
||||||
|
);
|
||||||
|
jerry_value_free(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throw a type error from a module function.
|
||||||
|
*
|
||||||
|
* @param message The error message to throw.
|
||||||
|
* @return A JerryScript error value.
|
||||||
|
*/
|
||||||
|
static inline jerry_value_t moduleBaseThrow(const char_t *message) {
|
||||||
|
assertStrLenMin(message, 1, "Error message must not be empty");
|
||||||
|
return jerry_throw_sz(JERRY_ERROR_TYPE, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a C errorret_t into a JS exception, forwarding the error
|
||||||
|
* message so that try/catch in JS sees the real error text. Clears the
|
||||||
|
* C error state.
|
||||||
|
*
|
||||||
|
* @param err The errorret_t returned by a failing C function.
|
||||||
|
* @return A JerryScript error value carrying the C error message.
|
||||||
|
*/
|
||||||
|
static inline jerry_value_t moduleBaseThrowError(const errorret_t err) {
|
||||||
|
assertNotNull(err.state, "Error state must not be NULL");
|
||||||
|
assertNotNull(err.state->message, "Error message must not be NULL");
|
||||||
|
jerry_value_t jsErr = jerry_throw_sz(JERRY_ERROR_TYPE, err.state->message);
|
||||||
|
errorCatch(err);
|
||||||
|
return jsErr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert an argument is a number; return type error if not.
|
||||||
|
*/
|
||||||
|
#define moduleBaseRequireNumber(i) do { \
|
||||||
|
if(!jerry_value_is_number(args[(i)])) { \
|
||||||
|
return moduleBaseThrow("Expected number argument"); \
|
||||||
|
} \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert an argument is a string; return type error if not.
|
||||||
|
*/
|
||||||
|
#define moduleBaseRequireString(i) do { \
|
||||||
|
if(!jerry_value_is_string(args[(i)])) { \
|
||||||
|
return moduleBaseThrow("Expected string argument"); \
|
||||||
|
} \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert an argument is a function; return type error if not.
|
||||||
|
*/
|
||||||
|
#define moduleBaseRequireFunction(i) do { \
|
||||||
|
if(!jerry_value_is_function(args[(i)])) { \
|
||||||
|
return moduleBaseThrow("Expected function argument"); \
|
||||||
|
} \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert an argument is an object; return type error if not.
|
||||||
|
*/
|
||||||
|
#define moduleBaseRequireObject(i) do { \
|
||||||
|
if(!jerry_value_is_object(args[(i)])) { \
|
||||||
|
return moduleBaseThrow("Expected object argument"); \
|
||||||
|
} \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Require at least N arguments; throw a TypeError if fewer were
|
||||||
|
* provided.
|
||||||
|
*
|
||||||
|
* Example: moduleBaseRequireArgs(2);
|
||||||
|
*/
|
||||||
|
#define moduleBaseRequireArgs(n) do { \
|
||||||
|
if(argc < (jerry_length_t)(n)) { \
|
||||||
|
return moduleBaseThrow("Expected at least " #n " argument(s)"); \
|
||||||
|
} \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Declare a typed pointer from a getter and immediately return undefined
|
||||||
|
* if it is NULL. The named variable is available for the rest of the
|
||||||
|
* function.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* moduleBaseGetOrReturn(entitycamera_t, cam, moduleEntityCameraGet);
|
||||||
|
* return jerry_number(cam->nearClip);
|
||||||
|
*/
|
||||||
|
#define moduleBaseGetOrReturn(type, var, getter) \
|
||||||
|
type *var = (getter)(callInfo); \
|
||||||
|
if(!(var)) return jerry_undefined()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cast argument i to float_t. Call after validating the arg is a number.
|
||||||
|
*/
|
||||||
|
#define moduleBaseArgFloat(i) ((float_t)jerry_value_as_number(args[(i)]))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cast argument i to int32_t. Call after validating the arg is a number.
|
||||||
|
*/
|
||||||
|
#define moduleBaseArgInt(i) ((int32_t)jerry_value_as_number(args[(i)]))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read argument i as a boolean (true/false).
|
||||||
|
*/
|
||||||
|
#define moduleBaseArgBool(i) (jerry_value_is_true(args[(i)]))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read optional argument i as float_t. Returns def if the argument is
|
||||||
|
* missing or not a number.
|
||||||
|
*/
|
||||||
|
#define moduleBaseOptFloat(i, def) \
|
||||||
|
((jerry_length_t)(i) < argc && jerry_value_is_number(args[(i)]) \
|
||||||
|
? (float_t)jerry_value_as_number(args[(i)]) : (def))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read optional argument i as int32_t. Returns def if the argument is
|
||||||
|
* missing or not a number.
|
||||||
|
*/
|
||||||
|
#define moduleBaseOptInt(i, def) \
|
||||||
|
((jerry_length_t)(i) < argc && jerry_value_is_number(args[(i)]) \
|
||||||
|
? (int32_t)jerry_value_as_number(args[(i)]) : (def))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a global numeric constant.
|
||||||
|
*/
|
||||||
|
static inline void moduleBaseSetNumber(const char_t *name, double value) {
|
||||||
|
jerry_value_t global = jerry_current_realm();
|
||||||
|
jerry_value_t key = jerry_string_sz(name);
|
||||||
|
jerry_value_t val = jerry_number(value);
|
||||||
|
jerry_object_set(global, key, val);
|
||||||
|
jerry_value_free(val);
|
||||||
|
jerry_value_free(key);
|
||||||
|
jerry_value_free(global);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a global integer constant.
|
||||||
|
*/
|
||||||
|
static inline void moduleBaseSetInt(const char_t *name, int32_t value) {
|
||||||
|
moduleBaseSetNumber(name, (double)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a global JS value. Caller retains ownership of the value and must
|
||||||
|
* free it independently.
|
||||||
|
*/
|
||||||
|
static inline void moduleBaseSetValue(
|
||||||
|
const char_t *name, jerry_value_t value
|
||||||
|
) {
|
||||||
|
jerry_value_t global = jerry_current_realm();
|
||||||
|
jerry_value_t key = jerry_string_sz(name);
|
||||||
|
jerry_object_set(global, key, value);
|
||||||
|
jerry_value_free(key);
|
||||||
|
jerry_value_free(global);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap an engine-owned C pointer as a JS object (no GC free callback).
|
||||||
|
* Used for global singletons.
|
||||||
|
*/
|
||||||
|
static inline jerry_value_t moduleBaseWrapPointer(void *ptr) {
|
||||||
|
jerry_value_t obj = jerry_object();
|
||||||
|
jerry_object_set_native_ptr(obj, &JS_PTR_NATIVE_INFO, ptr);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a named global to a wrapped engine-owned C pointer. Combines
|
||||||
|
* moduleBaseWrapPointer and moduleBaseSetValue in one call.
|
||||||
|
*/
|
||||||
|
static inline void moduleBaseSetWrappedPointer(
|
||||||
|
const char_t *name, void *ptr
|
||||||
|
) {
|
||||||
|
jerry_value_t val = moduleBaseWrapPointer(ptr);
|
||||||
|
moduleBaseSetValue(name, val);
|
||||||
|
jerry_value_free(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unwrap a C pointer from a JS object created by moduleBaseWrapPointer.
|
||||||
|
* Returns NULL if the object does not carry a matching native pointer.
|
||||||
|
*/
|
||||||
|
static inline void *moduleBaseUnwrapPointer(jerry_value_t val) {
|
||||||
|
if(!jerry_value_is_object(val)) return NULL;
|
||||||
|
return jerry_object_get_native_ptr(val, &JS_PTR_NATIVE_INFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy a JerryScript string value into a C buffer (null-terminated).
|
||||||
|
*
|
||||||
|
* @param val Jerry string value.
|
||||||
|
* @param buf Output buffer.
|
||||||
|
* @param buflen Buffer capacity including the null terminator.
|
||||||
|
*/
|
||||||
|
static inline void moduleBaseToString(
|
||||||
|
jerry_value_t val,
|
||||||
|
char_t *buf,
|
||||||
|
jerry_size_t buflen
|
||||||
|
) {
|
||||||
|
jerry_size_t len = jerry_string_to_buffer(
|
||||||
|
val, JERRY_ENCODING_UTF8, (jerry_char_t *)buf, buflen - 1
|
||||||
|
);
|
||||||
|
buf[len] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a named property on a JS object with getter and optional
|
||||||
|
* setter.
|
||||||
|
*
|
||||||
|
* @param obj Target object (e.g. a prototype).
|
||||||
|
* @param name Property name.
|
||||||
|
* @param getter C getter handler.
|
||||||
|
* @param setter C setter handler, or NULL for read-only property.
|
||||||
|
*/
|
||||||
|
static inline void moduleBaseDefineProperty(
|
||||||
|
jerry_value_t obj,
|
||||||
|
const char_t *name,
|
||||||
|
jerry_external_handler_t getter,
|
||||||
|
jerry_external_handler_t setter
|
||||||
|
) {
|
||||||
|
jerry_property_descriptor_t desc;
|
||||||
|
memset(&desc, 0, sizeof(desc));
|
||||||
|
desc.flags = (uint16_t)(
|
||||||
|
JERRY_PROP_IS_GET_DEFINED |
|
||||||
|
JERRY_PROP_IS_ENUMERABLE_DEFINED | JERRY_PROP_IS_ENUMERABLE |
|
||||||
|
JERRY_PROP_IS_CONFIGURABLE_DEFINED | JERRY_PROP_IS_CONFIGURABLE
|
||||||
|
);
|
||||||
|
desc.getter = jerry_function_external(getter);
|
||||||
|
if(setter != NULL) {
|
||||||
|
desc.flags |= JERRY_PROP_IS_SET_DEFINED;
|
||||||
|
desc.setter = jerry_function_external(setter);
|
||||||
|
}
|
||||||
|
jerry_value_t key = jerry_string_sz(name);
|
||||||
|
jerry_value_t result = jerry_object_define_own_prop(obj, key, &desc);
|
||||||
|
jerry_value_free(result);
|
||||||
|
jerry_value_free(key);
|
||||||
|
jerry_value_free(desc.getter);
|
||||||
|
if(setter != NULL) jerry_value_free(desc.setter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a named method (C function) on a JS object.
|
||||||
|
*
|
||||||
|
* @param obj Target object (e.g. a prototype).
|
||||||
|
* @param name Method name.
|
||||||
|
* @param fn C handler function.
|
||||||
|
*/
|
||||||
|
static inline void moduleBaseDefineMethod(
|
||||||
|
jerry_value_t obj,
|
||||||
|
const char_t *name,
|
||||||
|
jerry_external_handler_t fn
|
||||||
|
) {
|
||||||
|
jerry_value_t key = jerry_string_sz(name);
|
||||||
|
jerry_value_t func = jerry_function_external(fn);
|
||||||
|
jerry_object_set(obj, key, func);
|
||||||
|
jerry_value_free(func);
|
||||||
|
jerry_value_free(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a named global function.
|
||||||
|
*
|
||||||
|
* @param name The name of the function as seen in JavaScript.
|
||||||
|
* @param fn The C handler function for the method.
|
||||||
|
*/
|
||||||
|
static inline void moduleBaseDefineGlobalMethod(
|
||||||
|
const char_t *name,
|
||||||
|
jerry_external_handler_t fn
|
||||||
|
) {
|
||||||
|
jerry_value_t global = jerry_current_realm();
|
||||||
|
moduleBaseDefineMethod(global, name, fn);
|
||||||
|
jerry_value_free(global);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format an error message from a JerryScript exception value. Caller
|
||||||
|
* must ensure buf is large enough.
|
||||||
|
*/
|
||||||
|
static inline void moduleBaseExceptionMessage(
|
||||||
|
jerry_value_t exception,
|
||||||
|
char_t *buf,
|
||||||
|
size_t buflen
|
||||||
|
) {
|
||||||
|
jerry_value_t errVal = jerry_exception_value(exception, false);
|
||||||
|
jerry_value_t errStr = jerry_value_to_string(errVal);
|
||||||
|
jerry_size_t len = jerry_string_to_buffer(
|
||||||
|
errStr, JERRY_ENCODING_UTF8, (jerry_char_t *)buf,
|
||||||
|
(jerry_size_t)(buflen - 1)
|
||||||
|
);
|
||||||
|
buf[len] = '\0';
|
||||||
|
jerry_value_free(errStr);
|
||||||
|
jerry_value_free(errVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a named property from a JS object. Caller must free the returned
|
||||||
|
* value.
|
||||||
|
*/
|
||||||
|
static inline jerry_value_t moduleBaseGetProp(
|
||||||
|
jerry_value_t obj, const char_t *name
|
||||||
|
) {
|
||||||
|
jerry_value_t key = jerry_string_sz(name);
|
||||||
|
jerry_value_t val = jerry_object_get(obj, key);
|
||||||
|
jerry_value_free(key);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cast a JS value to float_t. Use for non-args[] values (e.g. object
|
||||||
|
* properties). For args[], prefer moduleBaseArgFloat.
|
||||||
|
*/
|
||||||
|
static inline float_t moduleBaseValueFloat(jerry_value_t val) {
|
||||||
|
return (float_t)jerry_value_as_number(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cast a JS value to int32_t. Use for non-args[] values (e.g. object
|
||||||
|
* properties). For args[], prefer moduleBaseArgInt.
|
||||||
|
*/
|
||||||
|
static inline int32_t moduleBaseValueInt(jerry_value_t val) {
|
||||||
|
return (int32_t)jerry_value_as_number(val);
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "script/module/modulebase.h"
|
||||||
|
#include "script/module/moduleplatformplatform.h"
|
||||||
|
|
||||||
|
#ifndef DUSK_TARGET_SYSTEM
|
||||||
|
#error "DUSK_TARGET_SYSTEM must be defined"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define MODULE_PLATFORM_VALUE "var PLATFORM = '" DUSK_TARGET_SYSTEM "';\n"
|
||||||
|
|
||||||
|
static inline void modulePlatform(void) {
|
||||||
|
moduleBaseEval(MODULE_PLATFORM_VALUE);
|
||||||
|
|
||||||
|
#ifdef modulePlatformPlatform
|
||||||
|
modulePlatformPlatform();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "scriptmanager.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
#include "asset/asset.h"
|
||||||
|
#include "asset/assetfile.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
#include "util/string.h"
|
||||||
|
#include "scriptproto.h"
|
||||||
|
#include "script/module/modulelist.h"
|
||||||
|
|
||||||
|
scriptmanager_t SCRIPT_MANAGER;
|
||||||
|
|
||||||
|
const jerry_object_native_info_t JS_PTR_NATIVE_INFO = {
|
||||||
|
.free_cb = NULL,
|
||||||
|
.number_of_references = 0,
|
||||||
|
.offset_of_references = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
errorret_t scriptManagerInit(void) {
|
||||||
|
memoryZero(&SCRIPT_MANAGER, sizeof(scriptmanager_t));
|
||||||
|
|
||||||
|
jerry_init(JERRY_INIT_EMPTY);
|
||||||
|
|
||||||
|
moduleListInit();
|
||||||
|
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t scriptManagerExec(const char_t *script, jerry_value_t *resultOut) {
|
||||||
|
assertNotNull(script, "Script cannot be NULL");
|
||||||
|
|
||||||
|
jerry_value_t result = jerry_eval(
|
||||||
|
(const jerry_char_t *)script,
|
||||||
|
strlen(script),
|
||||||
|
JERRY_PARSE_NO_OPTS
|
||||||
|
);
|
||||||
|
|
||||||
|
if(jerry_value_is_exception(result)) {
|
||||||
|
jerry_value_t errVal = jerry_exception_value(result, false);
|
||||||
|
jerry_value_t errStr = jerry_value_to_string(errVal);
|
||||||
|
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);
|
||||||
|
jerry_value_free(result);
|
||||||
|
errorThrow("Failed to execute script: %s", buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(resultOut != NULL) {
|
||||||
|
*resultOut = result;
|
||||||
|
} else {
|
||||||
|
jerry_value_free(result);
|
||||||
|
}
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t scriptManagerExecFile(
|
||||||
|
const char_t *fname,
|
||||||
|
jerry_value_t *resultOut
|
||||||
|
) {
|
||||||
|
assertNotNull(fname, "Filename cannot be NULL");
|
||||||
|
|
||||||
|
assetfile_t file;
|
||||||
|
errorChain(assetFileInit(&file, fname, NULL, NULL));
|
||||||
|
|
||||||
|
uint8_t *buffer = NULL;
|
||||||
|
size_t size = 0;
|
||||||
|
errorChain(assetFileReadEntire(&file, &buffer, &size));
|
||||||
|
errorChain(assetFileDispose(&file));
|
||||||
|
|
||||||
|
char_t *src = (char_t *)memoryAllocate(size + 1);
|
||||||
|
memoryCopy(src, buffer, size);
|
||||||
|
src[size] = '\0';
|
||||||
|
memoryFree(buffer);
|
||||||
|
|
||||||
|
errorret_t ret = scriptManagerExec(src, resultOut);
|
||||||
|
memoryFree(src);
|
||||||
|
errorChain(ret);
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t scriptManagerCallGlobal(const char_t *name) {
|
||||||
|
assertNotNull(name, "Function name cannot be NULL");
|
||||||
|
|
||||||
|
jerry_value_t global = jerry_current_realm();
|
||||||
|
jerry_value_t key = scriptManagerGetGlobalKey(name);
|
||||||
|
jerry_value_t fn = jerry_object_get(global, key);
|
||||||
|
jerry_value_free(global);
|
||||||
|
|
||||||
|
if(!jerry_value_is_function(fn)) {
|
||||||
|
jerry_value_free(fn);
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
jerry_value_t result = jerry_call(fn, jerry_undefined(), NULL, 0);
|
||||||
|
jerry_value_free(fn);
|
||||||
|
|
||||||
|
if(jerry_value_is_exception(result)) {
|
||||||
|
errorret_t err = scriptManagerFormatException(
|
||||||
|
"Global function", name, result
|
||||||
|
);
|
||||||
|
jerry_value_free(result);
|
||||||
|
errorChain(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this was an `async function`, its work (including anything it
|
||||||
|
// awaited) isn't actually done yet - it just returned a pending
|
||||||
|
// promise. Drive both the JerryScript job queue and the asset system
|
||||||
|
// forward until it settles, so the caller can rely on the function
|
||||||
|
// being fully complete.
|
||||||
|
if(jerry_value_is_promise(result)) {
|
||||||
|
while(jerry_promise_state(result) == JERRY_PROMISE_STATE_PENDING) {
|
||||||
|
errorret_t updateErr = assetUpdate();
|
||||||
|
if(errorIsNotOk(updateErr)) {
|
||||||
|
jerry_value_free(result);
|
||||||
|
errorChain(updateErr);
|
||||||
|
}
|
||||||
|
jerry_value_t jobsResult = jerry_run_jobs();
|
||||||
|
jerry_value_free(jobsResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(jerry_promise_state(result) == JERRY_PROMISE_STATE_REJECTED) {
|
||||||
|
jerry_value_t rejectVal = jerry_promise_result(result);
|
||||||
|
errorret_t err = scriptManagerFormatValueError(
|
||||||
|
"Global async function", name, rejectVal
|
||||||
|
);
|
||||||
|
jerry_value_free(rejectVal);
|
||||||
|
jerry_value_free(result);
|
||||||
|
errorChain(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jerry_value_free(result);
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t scriptManagerDispose(void) {
|
||||||
|
scriptProtoDisposeAll();
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < SCRIPT_MANAGER.globalKeyCacheCount; i++) {
|
||||||
|
jerry_value_free(SCRIPT_MANAGER.globalKeyCache[i].key);
|
||||||
|
}
|
||||||
|
SCRIPT_MANAGER.globalKeyCacheCount = 0;
|
||||||
|
|
||||||
|
jerry_cleanup();
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t scriptManagerFormatValueError(
|
||||||
|
const char_t *context,
|
||||||
|
const char_t *name,
|
||||||
|
jerry_value_t value
|
||||||
|
) {
|
||||||
|
jerry_value_t errStr = jerry_value_to_string(value);
|
||||||
|
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);
|
||||||
|
errorThrow("%s '%s': %s", context, name, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t scriptManagerFormatException(
|
||||||
|
const char_t *context,
|
||||||
|
const char_t *name,
|
||||||
|
jerry_value_t exception
|
||||||
|
) {
|
||||||
|
jerry_value_t errVal = jerry_exception_value(exception, false);
|
||||||
|
errorret_t err = scriptManagerFormatValueError(context, name, errVal);
|
||||||
|
jerry_value_free(errVal);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
jerry_value_t scriptManagerGetGlobalKey(const char_t *name) {
|
||||||
|
for(uint8_t i = 0; i < SCRIPT_MANAGER.globalKeyCacheCount; i++) {
|
||||||
|
if(stringCompare(name, SCRIPT_MANAGER.globalKeyCache[i].name) == 0) {
|
||||||
|
return SCRIPT_MANAGER.globalKeyCache[i].key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jerry_value_t key = jerry_string_sz(name);
|
||||||
|
assertTrue(
|
||||||
|
SCRIPT_MANAGER.globalKeyCacheCount < SCRIPT_MANAGER_MAX_CACHED_GLOBAL_KEYS,
|
||||||
|
"Global function key cache is full."
|
||||||
|
);
|
||||||
|
SCRIPT_MANAGER.globalKeyCache[SCRIPT_MANAGER.globalKeyCacheCount].name = name;
|
||||||
|
SCRIPT_MANAGER.globalKeyCache[SCRIPT_MANAGER.globalKeyCacheCount].key = key;
|
||||||
|
SCRIPT_MANAGER.globalKeyCacheCount++;
|
||||||
|
return key;
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "error/error.h"
|
||||||
|
#include <jerryscript.h>
|
||||||
|
|
||||||
|
#define SCRIPT_MANAGER_MAX_EVENT_SUBSCRIPTIONS 64
|
||||||
|
|
||||||
|
/** Max distinct global function names cached by scriptManagerCallGlobal. */
|
||||||
|
#define SCRIPT_MANAGER_MAX_CACHED_GLOBAL_KEYS 8
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
struct {
|
||||||
|
const char_t *name;
|
||||||
|
jerry_value_t key;
|
||||||
|
} globalKeyCache[SCRIPT_MANAGER_MAX_CACHED_GLOBAL_KEYS];
|
||||||
|
uint8_t globalKeyCacheCount;
|
||||||
|
} scriptmanager_t;
|
||||||
|
|
||||||
|
extern scriptmanager_t SCRIPT_MANAGER;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Singleton native-info tag for engine-owned C pointers wrapped in JS
|
||||||
|
* objects. A single global instance ensures jerry_object_get_native_ptr()
|
||||||
|
* matches across all compilation units (including module headers).
|
||||||
|
*/
|
||||||
|
extern const jerry_object_native_info_t JS_PTR_NATIVE_INFO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the script manager (and the underlying JerryScript context).
|
||||||
|
*
|
||||||
|
* @return The error return value.
|
||||||
|
*/
|
||||||
|
errorret_t scriptManagerInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a JS string in the active script context.
|
||||||
|
*
|
||||||
|
* @param script The JS source to execute.
|
||||||
|
* @param result Optional out-parameter for the script's return value.
|
||||||
|
* Caller must call jerry_value_free() on it when done. Pass NULL to
|
||||||
|
* discard the return value.
|
||||||
|
* @return The error return value.
|
||||||
|
*/
|
||||||
|
errorret_t scriptManagerExec(const char_t *script, jerry_value_t *result);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a JS file in the active script context.
|
||||||
|
*
|
||||||
|
* @param fname The filename of the script to execute.
|
||||||
|
* @param result Optional out-parameter for the script's return value.
|
||||||
|
* Caller must call jerry_value_free() on it when done. Pass NULL to
|
||||||
|
* discard the return value.
|
||||||
|
* @return The error return value.
|
||||||
|
*/
|
||||||
|
errorret_t scriptManagerExecFile(
|
||||||
|
const char_t *fname,
|
||||||
|
jerry_value_t *result
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls a global JS function by name, if one is defined. Silently does
|
||||||
|
* nothing if the global isn't a function (e.g. the script never defined
|
||||||
|
* it).
|
||||||
|
*
|
||||||
|
* If the function returns a Promise (e.g. it's an `async function`), the
|
||||||
|
* JerryScript job queue is pumped (alongside assetUpdate(), so pending
|
||||||
|
* asset loads can actually progress) until it settles, and a rejection
|
||||||
|
* is surfaced as an error - callers can rely on the function's work,
|
||||||
|
* including anything it awaited, being complete by the time this
|
||||||
|
* returns.
|
||||||
|
*
|
||||||
|
* @param name The name of the global function to call.
|
||||||
|
* @return The error return value. An error is thrown if the JS function
|
||||||
|
* itself throws, or if its returned promise rejects.
|
||||||
|
*/
|
||||||
|
errorret_t scriptManagerCallGlobal(const char_t *name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispose of the script manager.
|
||||||
|
*
|
||||||
|
* @return The error return value.
|
||||||
|
*/
|
||||||
|
errorret_t scriptManagerDispose(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal. Formats a thrown JS value's string representation into an
|
||||||
|
* errorret_t (e.g. a promise rejection's value, which may not itself be
|
||||||
|
* an Error/exception value).
|
||||||
|
*
|
||||||
|
* @param context Short label for what was being called, e.g. "Global
|
||||||
|
* function".
|
||||||
|
* @param name The name of the function/global that produced value.
|
||||||
|
* @param value The JS value to format (not necessarily an exception).
|
||||||
|
* @return The error return value.
|
||||||
|
*/
|
||||||
|
errorret_t scriptManagerFormatValueError(
|
||||||
|
const char_t *context,
|
||||||
|
const char_t *name,
|
||||||
|
jerry_value_t value
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal. Formats a JerryScript exception value into an errorret_t.
|
||||||
|
*
|
||||||
|
* @param context Short label for what was being called, e.g. "Global
|
||||||
|
* function".
|
||||||
|
* @param name The name of the function/global that threw.
|
||||||
|
* @param exception The exception value (see jerry_value_is_exception()).
|
||||||
|
* @return The error return value.
|
||||||
|
*/
|
||||||
|
errorret_t scriptManagerFormatException(
|
||||||
|
const char_t *context,
|
||||||
|
const char_t *name,
|
||||||
|
jerry_value_t exception
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal. Returns a cached jerry_value_t string key for a global
|
||||||
|
* function name, creating and caching it on first use. Avoids
|
||||||
|
* re-allocating a JS string every call for names looked up every
|
||||||
|
* frame/tick (e.g. "update").
|
||||||
|
*
|
||||||
|
* @param name The global function name to get a cached key for.
|
||||||
|
* @return The cached (or newly created) JS string key.
|
||||||
|
*/
|
||||||
|
jerry_value_t scriptManagerGetGlobalKey(const char_t *name);
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "scriptproto.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
#include "script/module/modulebase.h"
|
||||||
|
|
||||||
|
#define SCRIPT_PROTO_REGISTRY_MAX 64
|
||||||
|
|
||||||
|
static scriptproto_t *SCRIPT_PROTO_REGISTRY[SCRIPT_PROTO_REGISTRY_MAX];
|
||||||
|
static size_t SCRIPT_PROTO_REGISTRY_COUNT = 0;
|
||||||
|
|
||||||
|
void scriptProtoInit(
|
||||||
|
scriptproto_t *proto,
|
||||||
|
const char_t *name,
|
||||||
|
const size_t size,
|
||||||
|
jerry_external_handler_t constructor
|
||||||
|
) {
|
||||||
|
assertNotNull(proto, "Script prototype struct must not be null");
|
||||||
|
memoryZero(proto, sizeof(scriptproto_t));
|
||||||
|
|
||||||
|
assertTrue(
|
||||||
|
SCRIPT_PROTO_REGISTRY_COUNT < SCRIPT_PROTO_REGISTRY_MAX,
|
||||||
|
"Script prototype registry capacity exceeded"
|
||||||
|
);
|
||||||
|
SCRIPT_PROTO_REGISTRY[SCRIPT_PROTO_REGISTRY_COUNT++] = proto;
|
||||||
|
|
||||||
|
proto->info = (jerry_object_native_info_t){
|
||||||
|
.free_cb = moduleBaseFreeProto,
|
||||||
|
.number_of_references = 0,
|
||||||
|
.offset_of_references = 0
|
||||||
|
};
|
||||||
|
proto->prototype = jerry_object();
|
||||||
|
proto->size = size;
|
||||||
|
|
||||||
|
if(constructor != NULL) {
|
||||||
|
proto->constructor = jerry_function_external(constructor);
|
||||||
|
jerry_value_t protoKey = jerry_string_sz("prototype");
|
||||||
|
jerry_object_set(proto->constructor, protoKey, proto->prototype);
|
||||||
|
jerry_value_free(protoKey);
|
||||||
|
jerry_value_t ctorKey = jerry_string_sz("constructor");
|
||||||
|
jerry_object_set(proto->prototype, ctorKey, proto->constructor);
|
||||||
|
jerry_value_free(ctorKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(name != NULL) {
|
||||||
|
jerry_value_t global = jerry_current_realm();
|
||||||
|
jerry_value_t key = jerry_string_sz(name);
|
||||||
|
jerry_value_t val;
|
||||||
|
if(proto->constructor) {
|
||||||
|
val = proto->constructor;
|
||||||
|
} else {
|
||||||
|
val = proto->prototype;
|
||||||
|
}
|
||||||
|
jerry_object_set(global, key, val);
|
||||||
|
jerry_value_free(key);
|
||||||
|
jerry_value_free(global);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void scriptProtoDefineProp(
|
||||||
|
scriptproto_t *proto,
|
||||||
|
const char_t *name,
|
||||||
|
jerry_external_handler_t getter,
|
||||||
|
jerry_external_handler_t setter
|
||||||
|
) {
|
||||||
|
assertNotNull(proto, "Script prototype struct must not be null");
|
||||||
|
assertStrLenMin(name, 1, "Property name must not be empty");
|
||||||
|
assertNotNull(getter, "Getter must not be null");
|
||||||
|
|
||||||
|
jerry_property_descriptor_t desc;
|
||||||
|
memoryZero(&desc, sizeof(desc));
|
||||||
|
desc.flags = (uint16_t)(
|
||||||
|
JERRY_PROP_IS_GET_DEFINED |
|
||||||
|
JERRY_PROP_IS_ENUMERABLE_DEFINED | JERRY_PROP_IS_ENUMERABLE |
|
||||||
|
JERRY_PROP_IS_CONFIGURABLE_DEFINED | JERRY_PROP_IS_CONFIGURABLE
|
||||||
|
);
|
||||||
|
|
||||||
|
desc.getter = jerry_function_external(getter);
|
||||||
|
|
||||||
|
if(setter != NULL) {
|
||||||
|
desc.flags |= JERRY_PROP_IS_SET_DEFINED;
|
||||||
|
desc.setter = jerry_function_external(setter);
|
||||||
|
}
|
||||||
|
|
||||||
|
jerry_value_t key = jerry_string_sz(name);
|
||||||
|
jerry_value_t result = jerry_object_define_own_prop(
|
||||||
|
proto->prototype, key, &desc
|
||||||
|
);
|
||||||
|
jerry_value_free(result);
|
||||||
|
jerry_value_free(key);
|
||||||
|
jerry_value_free(desc.getter);
|
||||||
|
if(setter != NULL) jerry_value_free(desc.setter);
|
||||||
|
}
|
||||||
|
|
||||||
|
void scriptProtoDefineFunc(
|
||||||
|
scriptproto_t *proto,
|
||||||
|
const char_t *name,
|
||||||
|
jerry_external_handler_t fn
|
||||||
|
) {
|
||||||
|
assertNotNull(proto, "Script prototype struct must not be null");
|
||||||
|
assertStrLenMin(name, 1, "Method name must not be empty");
|
||||||
|
assertNotNull(fn, "Function handler must not be null");
|
||||||
|
|
||||||
|
jerry_value_t key = jerry_string_sz(name);
|
||||||
|
jerry_value_t func = jerry_function_external(fn);
|
||||||
|
jerry_object_set(proto->prototype, key, func);
|
||||||
|
jerry_value_free(func);
|
||||||
|
jerry_value_free(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void scriptProtoDefineStaticProp(
|
||||||
|
scriptproto_t *proto,
|
||||||
|
const char_t *name,
|
||||||
|
jerry_external_handler_t getter,
|
||||||
|
jerry_external_handler_t setter
|
||||||
|
) {
|
||||||
|
assertNotNull(proto, "Script prototype struct must not be null");
|
||||||
|
assertStrLenMin(name, 1, "Property name must not be empty");
|
||||||
|
assertNotNull(getter, "Getter must not be null");
|
||||||
|
|
||||||
|
jerry_value_t target = (
|
||||||
|
proto->constructor ? proto->constructor : proto->prototype
|
||||||
|
);
|
||||||
|
|
||||||
|
jerry_property_descriptor_t desc;
|
||||||
|
memoryZero(&desc, sizeof(desc));
|
||||||
|
desc.flags = (uint16_t)(
|
||||||
|
JERRY_PROP_IS_GET_DEFINED |
|
||||||
|
JERRY_PROP_IS_ENUMERABLE_DEFINED | JERRY_PROP_IS_ENUMERABLE |
|
||||||
|
JERRY_PROP_IS_CONFIGURABLE_DEFINED | JERRY_PROP_IS_CONFIGURABLE
|
||||||
|
);
|
||||||
|
|
||||||
|
desc.getter = jerry_function_external(getter);
|
||||||
|
|
||||||
|
if(setter != NULL) {
|
||||||
|
desc.flags |= JERRY_PROP_IS_SET_DEFINED;
|
||||||
|
desc.setter = jerry_function_external(setter);
|
||||||
|
}
|
||||||
|
|
||||||
|
jerry_value_t key = jerry_string_sz(name);
|
||||||
|
jerry_value_t result = jerry_object_define_own_prop(target, key, &desc);
|
||||||
|
jerry_value_free(result);
|
||||||
|
jerry_value_free(key);
|
||||||
|
jerry_value_free(desc.getter);
|
||||||
|
if(setter != NULL) jerry_value_free(desc.setter);
|
||||||
|
}
|
||||||
|
|
||||||
|
void scriptProtoDefineStaticFunc(
|
||||||
|
scriptproto_t *proto,
|
||||||
|
const char_t *name,
|
||||||
|
jerry_external_handler_t fn
|
||||||
|
) {
|
||||||
|
assertNotNull(proto, "Script prototype struct must not be null");
|
||||||
|
assertStrLenMin(name, 1, "Method name must not be empty");
|
||||||
|
assertNotNull(fn, "Function handler must not be null");
|
||||||
|
|
||||||
|
jerry_value_t target = (
|
||||||
|
proto->constructor ? proto->constructor : proto->prototype
|
||||||
|
);
|
||||||
|
jerry_value_t key = jerry_string_sz(name);
|
||||||
|
jerry_value_t func = jerry_function_external(fn);
|
||||||
|
jerry_object_set(target, key, func);
|
||||||
|
jerry_value_free(func);
|
||||||
|
jerry_value_free(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
jerry_value_t scriptProtoCreateValue(
|
||||||
|
const scriptproto_t *proto,
|
||||||
|
const void *value
|
||||||
|
) {
|
||||||
|
assertNotNull(proto, "Script prototype struct must not be null");
|
||||||
|
assertNotNull(value, "Value pointer must not be null");
|
||||||
|
|
||||||
|
void *ptr = memoryAllocate(proto->size);
|
||||||
|
memoryCopy(ptr, value, proto->size);
|
||||||
|
jerry_value_t obj = jerry_object();
|
||||||
|
jerry_object_set_native_ptr(obj, &proto->info, ptr);
|
||||||
|
jerry_object_set_proto(obj, proto->prototype);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *scriptProtoGetValue(
|
||||||
|
const scriptproto_t *proto,
|
||||||
|
const jerry_value_t obj
|
||||||
|
) {
|
||||||
|
assertNotNull(proto, "Script prototype struct must not be null");
|
||||||
|
if(!jerry_value_is_object(obj)) return NULL;
|
||||||
|
return jerry_object_get_native_ptr(obj, &proto->info);
|
||||||
|
}
|
||||||
|
|
||||||
|
void scriptProtoDefineToString(
|
||||||
|
scriptproto_t *proto,
|
||||||
|
jerry_external_handler_t fn
|
||||||
|
) {
|
||||||
|
scriptProtoDefineFunc(proto, "toString", fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void scriptProtoDispose(scriptproto_t *proto) {
|
||||||
|
assertNotNull(proto, "Script prototype struct must not be null");
|
||||||
|
jerry_value_free(proto->prototype);
|
||||||
|
if(proto->constructor) jerry_value_free(proto->constructor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void scriptProtoDisposeAll(void) {
|
||||||
|
for(size_t i = 0; i < SCRIPT_PROTO_REGISTRY_COUNT; i++) {
|
||||||
|
scriptProtoDispose(SCRIPT_PROTO_REGISTRY[i]);
|
||||||
|
}
|
||||||
|
SCRIPT_PROTO_REGISTRY_COUNT = 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "dusk.h"
|
||||||
|
#include <jerryscript.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
jerry_object_native_info_t info;
|
||||||
|
jerry_value_t prototype;
|
||||||
|
jerry_value_t constructor;
|
||||||
|
size_t size;
|
||||||
|
} scriptproto_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize a JS class prototype.
|
||||||
|
*
|
||||||
|
* If name is non-NULL the class is registered as a global. When ctor is
|
||||||
|
* also non-NULL the global is the constructor function (enabling
|
||||||
|
* `new Name(...)`); otherwise the prototype object itself becomes the
|
||||||
|
* global.
|
||||||
|
*
|
||||||
|
* @param proto The struct to initialize.
|
||||||
|
* @param name JS global name, or NULL to skip global registration.
|
||||||
|
* @param size sizeof the C struct this class wraps.
|
||||||
|
* @param ctor Constructor handler, or NULL if the class has no
|
||||||
|
* constructor.
|
||||||
|
*/
|
||||||
|
void scriptProtoInit(
|
||||||
|
scriptproto_t *proto,
|
||||||
|
const char_t *name,
|
||||||
|
const size_t size,
|
||||||
|
jerry_external_handler_t ctor
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define an instance property with a getter and optional setter.
|
||||||
|
*
|
||||||
|
* @param proto The class prototype.
|
||||||
|
* @param name Property name.
|
||||||
|
* @param getter Getter handler (must not be NULL).
|
||||||
|
* @param setter Setter handler, or NULL for a read-only property.
|
||||||
|
*/
|
||||||
|
void scriptProtoDefineProp(
|
||||||
|
scriptproto_t *proto,
|
||||||
|
const char_t *name,
|
||||||
|
jerry_external_handler_t getter,
|
||||||
|
jerry_external_handler_t setter
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define an instance method on the class prototype.
|
||||||
|
*
|
||||||
|
* @param proto The class prototype.
|
||||||
|
* @param name Method name.
|
||||||
|
* @param fn C handler called when the method is invoked.
|
||||||
|
*/
|
||||||
|
void scriptProtoDefineFunc(
|
||||||
|
scriptproto_t *proto,
|
||||||
|
const char_t *name,
|
||||||
|
jerry_external_handler_t fn
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a static property on the class (e.g. Scene.current).
|
||||||
|
*
|
||||||
|
* Attaches to the constructor function when one exists, otherwise
|
||||||
|
* attaches directly to the prototype object (which is the global in
|
||||||
|
* that case).
|
||||||
|
*
|
||||||
|
* @param proto The class prototype.
|
||||||
|
* @param name Property name.
|
||||||
|
* @param getter Getter handler (must not be NULL).
|
||||||
|
* @param setter Setter handler, or NULL for a read-only property.
|
||||||
|
*/
|
||||||
|
void scriptProtoDefineStaticProp(
|
||||||
|
scriptproto_t *proto,
|
||||||
|
const char_t *name,
|
||||||
|
jerry_external_handler_t getter,
|
||||||
|
jerry_external_handler_t setter
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a static method on the class (e.g. Color.fromRGBA).
|
||||||
|
*
|
||||||
|
* Attaches to the constructor function when one exists, otherwise
|
||||||
|
* attaches directly to the prototype object (which is the global in
|
||||||
|
* that case).
|
||||||
|
*
|
||||||
|
* @param proto The class prototype.
|
||||||
|
* @param name Method name.
|
||||||
|
* @param fn C handler called when the static method is invoked.
|
||||||
|
*/
|
||||||
|
void scriptProtoDefineStaticFunc(
|
||||||
|
scriptproto_t *proto,
|
||||||
|
const char_t *name,
|
||||||
|
jerry_external_handler_t fn
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a JS instance wrapping a copy of a C value.
|
||||||
|
*
|
||||||
|
* @param proto The class prototype.
|
||||||
|
* @param value Pointer to the C value to copy into the new JS object.
|
||||||
|
* @return A new JS object with the class prototype and native pointer
|
||||||
|
* set.
|
||||||
|
*/
|
||||||
|
jerry_value_t scriptProtoCreateValue(
|
||||||
|
const scriptproto_t *proto,
|
||||||
|
const void *value
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unwrap the native C pointer from a JS object.
|
||||||
|
*
|
||||||
|
* @param proto The class prototype.
|
||||||
|
* @param obj The JS object to inspect.
|
||||||
|
* @return Pointer to the wrapped C value, or NULL if not an instance.
|
||||||
|
*/
|
||||||
|
void *scriptProtoGetValue(
|
||||||
|
const scriptproto_t *proto,
|
||||||
|
const jerry_value_t obj
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the toString() method on the class prototype.
|
||||||
|
*
|
||||||
|
* @param proto The class prototype.
|
||||||
|
* @param fn C handler called when toString() is invoked on an instance.
|
||||||
|
*/
|
||||||
|
void scriptProtoDefineToString(
|
||||||
|
scriptproto_t *proto,
|
||||||
|
jerry_external_handler_t fn
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release all JerryScript resources held by the prototype.
|
||||||
|
*
|
||||||
|
* @param proto The class prototype to dispose.
|
||||||
|
*/
|
||||||
|
void scriptProtoDispose(scriptproto_t *proto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes every prototype ever initialized via scriptProtoInit. Must be
|
||||||
|
* called before jerry_cleanup() - this JerryScript build fatally asserts
|
||||||
|
* during cleanup if any jerry_value_t handle is still held by the
|
||||||
|
* embedder, and every scriptproto_t retains its prototype/constructor
|
||||||
|
* handles for the lifetime of the module system.
|
||||||
|
*/
|
||||||
|
void scriptProtoDisposeAll(void);
|
||||||
Reference in New Issue
Block a user