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()
|
||||
|
||||
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)
|
||||
target_link_options(${DUSK_LIBRARY_TARGET_NAME} PUBLIC -rdynamic)
|
||||
target_compile_definitions(${DUSK_BINARY_TARGET_NAME} PUBLIC
|
||||
@@ -72,5 +81,6 @@ add_subdirectory(ui)
|
||||
add_subdirectory(network)
|
||||
add_subdirectory(physics)
|
||||
add_subdirectory(save)
|
||||
add_subdirectory(script)
|
||||
add_subdirectory(util)
|
||||
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