More cleanup

This commit is contained in:
2026-04-29 22:39:47 -05:00
parent 61f69af35a
commit ffed626447
14 changed files with 473 additions and 247 deletions
+18 -31
View File
@@ -1,35 +1,22 @@
var Cube = {
create: function() {
var e = Entity.create();
e.add(COMPONENT_TYPE_POSITION);
e.position.x = 0;
e.position.y = 0;
e.position.z = 0;
e.add(COMPONENT_TYPE_MESH);
e.add(COMPONENT_TYPE_MATERIAL);
// e.material.setColor(Color.black());
function CubeEntity() {
Entity.call(this);
this.add(POSITION);
this.position.x = 0;
this.position.y = 0;
this.position.z = 0;
this.add(MESH);
this.add(MATERIAL);
}
print(Color);
print(Color.prototype);
Object.assign(CubeEntity.prototype, Entity.prototype);
return {
_e: e,
update: Cube.update,
dispose: Cube.dispose
};
},
update: function() {
var speed = 3.0;
var dx = inputAxis(INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT);
var dz = inputAxis(INPUT_ACTION_UP, INPUT_ACTION_DOWN);
this._e.position.x += dx * speed * TIME.delta;
this._e.position.z += dz * speed * TIME.delta;
},
dispose: function() {
this._e.dispose();
}
CubeEntity.prototype.update = function() {
var speed = 3.0;
var dx = inputAxis(INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT);
var dz = inputAxis(INPUT_ACTION_UP, INPUT_ACTION_DOWN);
this.position.x += dx * speed * TIME.delta;
this.position.z += dz * speed * TIME.delta;
this.material.setColor(Color.rainbow());
};
Cube;
module = CubeEntity;
+19 -21
View File
@@ -1,28 +1,26 @@
var Cube = include('entities/cube.js');
var cam;
var cube;
function CubeScene() {
this.cam = new Entity();
this.cam.add(POSITION);
this.cam.position.x = 3;
this.cam.position.y = 3;
this.cam.position.z = 3;
this.cam.position.lookAt(0, 0, 0);
this.cam.add(CAMERA);
var SceneCube = {
init: function() {
cam = Entity.create();
cam.add(COMPONENT_TYPE_POSITION);
cam.position.x = 3;
cam.position.y = 3;
cam.position.z = 3;
cam.position.lookAt(0, 0, 0);
cam.add(COMPONENT_TYPE_CAMERA);
cube = Cube.create();
},
this.cube = new Cube();
}
update: function() {
cube.update();
},
Object.assign(CubeScene, Scene.prototype);
dispose: function() {
cam.dispose();
cube.dispose();
}
CubeScene.prototype.update = function() {
this.cube.update();
};
SceneCube;
CubeScene.prototype.dispose = function() {
this.cam.dispose();
this.cube.dispose();
};
module = CubeScene;
+1 -1
View File
@@ -13,7 +13,7 @@ componentdefinition_t COMPONENT_DEFINITIONS[] = {
[COMPONENT_TYPE_NULL] = { 0 },
#define X(enumName, type, field, iMethod, dMethod) \
[COMPONENT_TYPE_##enumName] = { .init = iMethod, .dispose = dMethod },
[COMPONENT_TYPE_##enumName] = { .name = #field, .init = iMethod, .dispose = dMethod },
#include "componentlist.h"
#undef X
+1
View File
@@ -20,6 +20,7 @@ typedef union {
} componentdata_t;
typedef struct {
const char_t *name;
void (*init)(const entityid_t, const componentid_t);
void (*dispose)(const entityid_t, const componentid_t);
} componentdefinition_t;
+15 -7
View File
@@ -167,19 +167,27 @@ errorret_t sceneSetImmediate(const char_t *scene) {
);
if(scene != NULL) {
jerry_value_t sceneObj = SCENE_SCRIPT_REF_NONE;
jerry_value_t sceneClass = SCENE_SCRIPT_REF_NONE;
errorChain(scriptContextExecFile(
&SCRIPT_MANAGER.mainContext, scene, &sceneObj
&SCRIPT_MANAGER.mainContext, scene, &sceneClass
));
if(!jerry_value_is_object(sceneObj)) {
if(sceneObj != SCENE_SCRIPT_REF_NONE) jerry_value_free(sceneObj);
errorThrow("Scene '%s' must return an object", scene);
if(!jerry_value_is_function(sceneClass)) {
if(sceneClass != SCENE_SCRIPT_REF_NONE) jerry_value_free(sceneClass);
errorThrow("Scene '%s' must export a constructor function", scene);
}
jerry_value_t sceneObj = jerry_construct(sceneClass, NULL, 0);
jerry_value_free(sceneClass);
if(jerry_value_is_exception(sceneObj)) {
char_t errMsg[512];
moduleBaseExceptionMessage(sceneObj, errMsg, sizeof(errMsg));
jerry_value_free(sceneObj);
errorThrow("Scene '%s' constructor threw: %s", scene, errMsg);
}
SCENE.scriptRef = sceneObj;
errorChain(moduleSceneCall("init"));
SCENE.sceneActive = true;
}
+25 -11
View File
@@ -12,7 +12,7 @@
#include "script/scriptproto.h"
// Define the prototype.
scriptproto_t MODULE_COLOR_PROTO;
static scriptproto_t MODULE_COLOR_PROTO;
// Getters
moduleBaseFunction(moduleColorGetR) {
@@ -88,12 +88,25 @@ moduleBaseFunction(moduleColorSetA) {
return jerry_undefined();
}
moduleBaseFunction(moduleColorToString) {
color_t *color = (color_t*)scriptProtoGetValue(
&MODULE_COLOR_PROTO, callInfo->this_value
);
if(!color) return jerry_undefined();
char_t buf[32];
stringFormat(buf, sizeof(buf), "[ %d, %d, %d, %d ]",
(int32_t)color->r, (int32_t)color->g,
(int32_t)color->b, (int32_t)color->a
);
return jerry_string_sz(buf);
}
// Constructor
static jerry_value_t moduleColorMakeObject(color_t color) {
return scriptProtoCreateValue(&MODULE_COLOR_PROTO, &color);
}
moduleBaseFunction(moduleColorRGBA) {
moduleBaseFunction(moduleColorConstructor) {
color_t c;
if(argc > 0) {
@@ -158,21 +171,22 @@ moduleBaseFunction(moduleColorRainbow) {
// Root Module
static void moduleColor(void) {
scriptProtoInit(&MODULE_COLOR_PROTO, sizeof(color_t));
scriptProtoInit(
&MODULE_COLOR_PROTO,
"Color",
sizeof(color_t),
moduleColorConstructor
);
#define X(x, g, s) \
scriptProtoDefineProperty(&MODULE_COLOR_PROTO, #x, g, s);
scriptProtoDefineProp(&MODULE_COLOR_PROTO, #x, g, s);
X(r, moduleColorGetR, moduleColorSetR);
X(g, moduleColorGetG, moduleColorSetG);
X(b, moduleColorGetB, moduleColorSetB);
X(a, moduleColorGetA, moduleColorSetA);
#undef X
scriptProtoDefineMethod(&MODULE_COLOR_PROTO, "rgba", moduleColorRGBA);
scriptProtoDefineMethod(
&MODULE_COLOR_PROTO,"rainbow", moduleColorRainbow
);
scriptProtoCreateGlobal(&MODULE_COLOR_PROTO, "Color");
moduleBaseEval("Color.prototype.black = function() { print('test'); };");
scriptProtoDefineStaticFunc(&MODULE_COLOR_PROTO, "rainbow", moduleColorRainbow);
scriptProtoDefineToString(&MODULE_COLOR_PROTO, moduleColorToString);
moduleBaseEval(COLOR_SCRIPT);
}
@@ -45,21 +45,14 @@ moduleBaseFunction(moduleScreenSetBackground) {
}
static void moduleScreen(void) {
// Define the prototype
scriptProtoInit(&MODULE_SCREEN_PROTO, sizeof(screen_t));
scriptProtoInit(&MODULE_SCREEN_PROTO, "Screen", sizeof(screen_t), NULL);
#define X(x, g, s) \
scriptProtoDefineProperty(&MODULE_SCREEN_PROTO, #x, g, s);
// Setup the getters and setters
scriptProtoDefineProp(&MODULE_SCREEN_PROTO, #x, g, s);
X(width, moduleScreenGetWidth, NULL)
X(height, moduleScreenGetHeight, NULL)
X(aspect, moduleScreenGetAspect, NULL)
X(background, moduleScreenGetBackground, moduleScreenSetBackground)
#undef X
// Create global Screen object
scriptProtoCreateGlobal(&MODULE_SCREEN_PROTO, "Screen");
}
+100 -66
View File
@@ -7,81 +7,89 @@
#pragma once
#include "script/module/modulebase.h"
#include "script/scriptproto.h"
#include "entity/entity.h"
#include "entity/entitymanager.h"
#include "component/moduleentityposition.h"
#include "component/moduleentitycamera.h"
#include "component/moduleentitymesh.h"
#include "component/moduleentitymaterial.h"
#include "component/moduleentityphysics.h"
// ---- component type constants script ----
typedef struct {
entityid_t id;
} entityscript_t;
#define X(enumName, type, field, init, dispose) \
"var COMPONENT_TYPE_" #enumName " = \"" #field "\";\n"
static const char_t *COMPONENT_TYPE_SCRIPT =
#include "entity/componentlist.h"
;
#undef X
static scriptproto_t MODULE_ENTITY_PROTO;
// ---- Entity base class script ----
static const char_t *ENTITY_SCRIPT =
"var Entity = {};\n"
"Entity.POSITION = COMPONENT_TYPE_POSITION;\n"
"Entity.CAMERA = COMPONENT_TYPE_CAMERA;\n"
"Entity.MESH = COMPONENT_TYPE_MESH;\n"
"Entity.MATERIAL = COMPONENT_TYPE_MATERIAL;\n"
"Entity.PHYSICS = COMPONENT_TYPE_PHYSICS;\n"
"\n"
"var _addFns = {};\n"
"_addFns[COMPONENT_TYPE_POSITION] = entityPositionAdd;\n"
"_addFns[COMPONENT_TYPE_CAMERA] = entityCameraAdd;\n"
"_addFns[COMPONENT_TYPE_MESH] = entityMeshAdd;\n"
"_addFns[COMPONENT_TYPE_MATERIAL] = entityMaterialAdd;\n"
"_addFns[COMPONENT_TYPE_PHYSICS] = entityPhysicsAdd;\n"
"\n"
"Entity.create = function() {\n"
" var self = Object.create(Entity);\n"
" self.id = entityAdd();\n"
" return self;\n"
"};\n"
"\n"
"Entity.add = function(componentType) {\n"
" var fn = _addFns[componentType];\n"
" if(!fn) throw new Error('unknown component type: ' + String(componentType));\n"
" this[componentType] = fn(this.id);\n"
" return this[componentType];\n"
"};\n"
"\n"
"Entity.dispose = function() {\n"
" entityRemove(this.id);\n"
"};\n"
;
// ---- module functions ----
/**
* Allocates a new entity and returns its id as a JS number.
*/
moduleBaseFunction(moduleEntityAdd) {
return jerry_number((double)entityManagerAdd());
}
/**
* Disposes the entity with the given id.
* Arg 0: entity id (number).
*/
moduleBaseFunction(moduleEntityRemove) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
entityDispose((entityid_t)jerry_value_as_number(args[0]));
moduleBaseFunction(moduleEntityConstructor) {
entityscript_t *inst = (entityscript_t*)memoryAllocate(sizeof(entityscript_t));
inst->id = entityManagerAdd();
jerry_object_set_native_ptr(callInfo->this_value, &MODULE_ENTITY_PROTO.info, inst);
return jerry_undefined();
}
moduleBaseFunction(moduleEntityAddComponent) {
entityscript_t *inst = (entityscript_t*)scriptProtoGetValue(
&MODULE_ENTITY_PROTO, callInfo->this_value
);
if(!inst) return moduleBaseThrow("Entity.add: invalid entity");
moduleBaseRequireArgs(1);
if(!jerry_value_is_object(args[0])) {
return moduleBaseThrow("Entity.add: expected a component type object");
}
jerry_value_t nameKey = jerry_string_sz("name");
jerry_value_t nameJsVal = jerry_object_get(args[0], nameKey);
jerry_value_free(nameKey);
if(!jerry_value_is_string(nameJsVal)) {
jerry_value_free(nameJsVal);
return moduleBaseThrow("Entity.add: component type must have a .name property");
}
char_t nameStr[64];
moduleBaseToString(nameJsVal, nameStr, sizeof(nameStr));
jerry_value_free(nameJsVal);
jerry_external_handler_t addFn = NULL;
const char_t *propName = NULL;
if(stringCompare(nameStr, "POSITION") == 0) {
addFn = moduleEntityPositionAdd; propName = "position";
} else if(stringCompare(nameStr, "CAMERA") == 0) {
addFn = moduleEntityCameraAdd; propName = "camera";
} else if(stringCompare(nameStr, "MESH") == 0) {
addFn = moduleEntityMeshAdd; propName = "mesh";
} else if(stringCompare(nameStr, "MATERIAL") == 0) {
addFn = moduleEntityMaterialAdd; propName = "material";
} else if(stringCompare(nameStr, "PHYSICS") == 0) {
addFn = moduleEntityPhysicsAdd; propName = "physics";
}
if(!addFn) return moduleBaseThrow("Entity.add: unknown component type");
jerry_value_t idVal = jerry_number((double)inst->id);
jerry_value_t handle = addFn(callInfo, &idVal, 1);
jerry_value_free(idVal);
jerry_value_t propKey = jerry_string_sz(propName);
jerry_object_set(callInfo->this_value, propKey, handle);
jerry_value_free(propKey);
return handle;
}
moduleBaseFunction(moduleEntityDisposeMethod) {
entityscript_t *inst = (entityscript_t*)scriptProtoGetValue(
&MODULE_ENTITY_PROTO, callInfo->this_value
);
if(!inst) return jerry_undefined();
entityDispose(inst->id);
return jerry_undefined();
}
/**
* Registers all entity and component modules, component type constants, and
* the Entity base object into the JS realm.
*/
static void moduleEntity(void) {
moduleEntityPosition();
moduleEntityCamera();
@@ -89,9 +97,35 @@ static void moduleEntity(void) {
moduleEntityMaterial();
moduleEntityPhysics();
moduleBaseFunctionRegister("entityAdd", moduleEntityAdd);
moduleBaseFunctionRegister("entityRemove", moduleEntityRemove);
scriptProtoInit(
&MODULE_ENTITY_PROTO,
"Entity",
sizeof(entityscript_t),
moduleEntityConstructor
);
moduleBaseEval(COMPONENT_TYPE_SCRIPT);
moduleBaseEval(ENTITY_SCRIPT);
scriptProtoDefineFunc(&MODULE_ENTITY_PROTO, "add", moduleEntityAddComponent);
scriptProtoDefineFunc(&MODULE_ENTITY_PROTO, "dispose", moduleEntityDisposeMethod);
// Register component type objects as globals and as Entity.POSITION etc.
// Each object has a .name property with the uppercase enum name, e.g. "POSITION".
jerry_value_t global = jerry_current_realm();
#define X(enumName, type, field, init, dispose) { \
jerry_value_t ctObj = jerry_object(); \
jerry_value_t nk = jerry_string_sz("name"); \
jerry_value_t nv = jerry_string_sz(#enumName); \
jerry_object_set(ctObj, nk, nv); \
jerry_value_free(nv); \
jerry_value_free(nk); \
jerry_value_t gk = jerry_string_sz(#enumName); \
jerry_object_set(global, gk, ctObj); \
jerry_value_free(gk); \
jerry_value_t ek = jerry_string_sz(#enumName); \
jerry_object_set(MODULE_ENTITY_PROTO.constructor, ek, ctObj); \
jerry_value_free(ek); \
jerry_value_free(ctObj); \
}
#include "entity/componentlist.h"
#undef X
jerry_value_free(global);
}
+44 -9
View File
@@ -7,20 +7,40 @@
#pragma once
#include "script/module/modulebase.h"
#include "script/scriptproto.h"
#include "scene/scene.h"
static scriptproto_t MODULE_SCENE_PROTO;
// Default no-op lifecycle methods — JS scene instances override these.
moduleBaseFunction(moduleSceneDefaultUpdate) {
return jerry_undefined();
}
moduleBaseFunction(moduleSceneDefaultDispose) {
return jerry_undefined();
}
// Constructor — returns undefined so JerryScript uses `this`, which already
// has Scene.prototype set. Scenes carry no C struct; all state lives in JS.
moduleBaseFunction(moduleSceneConstructor) {
return jerry_undefined();
}
// Static: Scene.set(name)
moduleBaseFunction(moduleSceneSet) {
moduleBaseRequireArgs(1);
moduleBaseRequireString(0);
char_t name[ASSET_FILE_PATH_MAX];
moduleBaseToString(args[0], name, sizeof(name));
if(name[0] == '\0') return moduleBaseThrow("Scene.set: Scene name cannot be empty");
if(name[0] == '\0') return moduleBaseThrow("Scene.set: name cannot be empty");
sceneSet(name);
return jerry_undefined();
}
// Static: Scene.current (getter)
moduleBaseFunction(moduleSceneGetCurrent) {
if(SCENE.sceneCurrent[0] == '\0') return jerry_undefined();
return jerry_string_sz(SCENE.sceneCurrent);
@@ -32,13 +52,15 @@ static void moduleSceneReset(void) {
SCENE.scriptRef = SCENE_SCRIPT_REF_NONE;
}
jerry_value_t obj = jerry_object();
moduleBaseDefineMethod(obj, "set", moduleSceneSet);
moduleBaseDefineProperty(obj, "current", moduleSceneGetCurrent, NULL);
moduleBaseSetValue("Scene", obj);
jerry_value_free(obj);
// Drop the 'module' global reference to the scene class so JerryScript's
// GC can collect it and all associated closures.
jerry_value_t global = jerry_current_realm();
jerry_value_t key = jerry_string_sz("module");
jerry_value_t undef = jerry_undefined();
jerry_object_set(global, key, undef);
jerry_value_free(undef);
jerry_value_free(key);
jerry_value_free(global);
}
static errorret_t moduleSceneCall(const char_t *method) {
@@ -72,5 +94,18 @@ static errorret_t moduleSceneCall(const char_t *method) {
}
static void moduleScene(void) {
moduleSceneReset();
scriptProtoInit(
&MODULE_SCENE_PROTO,
"Scene",
sizeof(uint8_t),
moduleSceneConstructor
);
scriptProtoDefineFunc(&MODULE_SCENE_PROTO, "update", moduleSceneDefaultUpdate);
scriptProtoDefineFunc(&MODULE_SCENE_PROTO, "dispose", moduleSceneDefaultDispose);
scriptProtoDefineStaticFunc(&MODULE_SCENE_PROTO, "set", moduleSceneSet);
scriptProtoDefineStaticProp(
&MODULE_SCENE_PROTO, "current", moduleSceneGetCurrent, NULL
);
}
@@ -0,0 +1,59 @@
/**
* 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"
moduleBaseFunction(moduleInclude) {
if(argc < 1 || !jerry_value_is_string(args[0])) {
return moduleBaseThrow("Expected string filename");
}
char_t filename[1024];
moduleBaseToString(args[0], filename, sizeof(filename));
if(filename[0] == '\0') {
return moduleBaseThrow("Filename cannot be empty");
}
size_t len = strlen(filename);
if(len < 3 || stringCompare(&filename[len - 3], ".js") != 0) {
return moduleBaseThrow("include: filename must end with .js");
}
char_t buffer[1024];
stringCopy(buffer, filename, sizeof(buffer));
// Save and reset 'export' so the included module gets a clean undefined
// default. Saving lets nested includes each have their own export scope.
jerry_value_t global = jerry_current_realm();
jerry_value_t moduleKey = jerry_string_sz("module");
jerry_value_t prevModule = jerry_object_get(global, moduleKey);
jerry_value_t undef = jerry_undefined();
jerry_object_set(global, moduleKey, undef);
jerry_value_free(undef);
jerry_value_t result = 0;
errorret_t err = scriptContextExecFile(scriptContextCurrent, buffer, &result);
if(result != 0) jerry_value_free(result);
// Capture whatever the module assigned to 'module', then restore the
// caller's value so nested includes don't clobber each other.
jerry_value_t moduleVal = jerry_object_get(global, moduleKey);
jerry_object_set(global, moduleKey, prevModule);
jerry_value_free(prevModule);
jerry_value_free(moduleKey);
jerry_value_free(global);
if(err.code != ERROR_OK) {
jerry_value_free(moduleVal);
errorCatch(errorPrint(err));
return moduleBaseThrow("Failed to include script file");
}
return moduleVal;
}
+2 -36
View File
@@ -7,6 +7,7 @@
#pragma once
#include "script/module/modulebase.h"
#include "script/module/script/moduleinclude.h"
#include "console/console.h"
moduleBaseFunction(moduleScriptPrint) {
@@ -35,42 +36,7 @@ moduleBaseFunction(moduleScriptPrint) {
return jerry_undefined();
}
moduleBaseFunction(moduleScriptInclude) {
if(argc < 1 || !jerry_value_is_string(args[0])) {
return moduleBaseThrow("Expected string filename");
}
char_t filename[1024];
moduleBaseToString(args[0], filename, sizeof(filename));
if(filename[0] == '\0') {
return moduleBaseThrow("Filename cannot be empty");
}
char_t buffer[1024];
stringCopy(buffer, filename, sizeof(buffer));
size_t len = strlen(buffer);
if(len < 3 || stringCompare(&buffer[len - 3], ".js") != 0) {
if(len + 3 >= sizeof(buffer)) {
return moduleBaseThrow("Filename too long to append .js");
}
stringCopy(&buffer[len], ".js", 4);
}
jerry_value_t result = 0;
errorret_t err = scriptContextExecFile(scriptContextCurrent, buffer, &result);
if(err.code != ERROR_OK) {
if(result != 0) jerry_value_free(result);
errorCatch(errorPrint(err));
return moduleBaseThrow("Failed to include script file");
}
if(result == 0) return jerry_undefined();
return result;
}
static void moduleScript(void) {
moduleBaseFunctionRegister("print", moduleScriptPrint);
moduleBaseFunctionRegister("include", moduleScriptInclude);
moduleBaseFunctionRegister("include", moduleInclude);
}
+94 -20
View File
@@ -1,6 +1,6 @@
/**
* Copyright (c) 2026 Dominic Masters
*
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
@@ -10,7 +10,12 @@
#include "util/memory.h"
#include "script/module/modulebase.h"
void scriptProtoInit(scriptproto_t *proto, const size_t size) {
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));
@@ -19,13 +24,35 @@ void scriptProtoInit(scriptproto_t *proto, const size_t size) {
.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 scriptProtoDefineProperty(
void scriptProtoDefineProp(
scriptproto_t *proto,
const char_t *name,
jerry_external_handler_t getter,
@@ -51,16 +78,14 @@ void scriptProtoDefineProperty(
}
jerry_value_t key = jerry_string_sz(name);
jerry_value_t result = jerry_object_define_own_prop(
proto->prototype, key, &desc
);
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 scriptProtoDefineMethod(
void scriptProtoDefineFunc(
scriptproto_t *proto,
const char_t *name,
jerry_external_handler_t fn
@@ -76,24 +101,56 @@ void scriptProtoDefineMethod(
jerry_value_free(key);
}
void scriptProtoCreateGlobal(
void scriptProtoDefineStaticProp(
scriptproto_t *proto,
const char_t *name
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, "Global object name must not be empty");
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 global = jerry_current_realm();
jerry_value_t key = jerry_string_sz(name);
jerry_object_set(global, key, proto->prototype);
jerry_value_t result = jerry_object_define_own_prop(target, key, &desc);
jerry_value_free(result);
jerry_value_free(key);
jerry_value_free(global);
jerry_value_free(desc.getter);
if(setter != NULL) jerry_value_free(desc.setter);
}
void * scriptProtoGetValue(const scriptproto_t *proto, const jerry_value_t obj){
void scriptProtoDefineStaticFunc(
scriptproto_t *proto,
const char_t *name,
jerry_external_handler_t fn
) {
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);
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(
@@ -102,16 +159,33 @@ jerry_value_t scriptProtoCreateValue(
) {
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, ptr, &proto->info);
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);
}
+92 -35
View File
@@ -1,6 +1,6 @@
/**
* Copyright (c) 2026 Dominic Masters
*
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
@@ -12,26 +12,38 @@
typedef struct {
jerry_object_native_info_t info;
jerry_value_t prototype;
jerry_value_t constructor;
size_t size;
} scriptproto_t;
/**
* Initialize a script prototype struct.
*
* @param proto The script prototype struct to initialize.
* @param size Size of the struct that this proto represents.
* 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 size_t size);
void scriptProtoInit(
scriptproto_t *proto,
const char_t *name,
const size_t size,
jerry_external_handler_t ctor
);
/**
* Define a property on a prototype with the provided getter and setter.
*
* @param proto The prototype to define the property on.
* @param name The name of the property.
* @param getter Function to call for getting.
* @param setter Function to call for setting (or NULL for read-only).
* 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 scriptProtoDefineProperty(
void scriptProtoDefineProp(
scriptproto_t *proto,
const char_t *name,
jerry_external_handler_t getter,
@@ -39,35 +51,58 @@ void scriptProtoDefineProperty(
);
/**
* Define a method on a prototype.
*
* @param proto The prototype to define the method on.
* @param name The name of the method.
* @param fn The C function to call when the method is invoked in JS.
* 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 scriptProtoDefineMethod(
void scriptProtoDefineFunc(
scriptproto_t *proto,
const char_t *name,
jerry_external_handler_t fn
);
/**
* Defines a global object with the provided name and prototype.
*
* @param proto The prototype struct to use for the global object.
* @param name The name of the global object to create.
* 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 scriptProtoCreateGlobal(
void scriptProtoDefineStaticProp(
scriptproto_t *proto,
const char_t *name
const char_t *name,
jerry_external_handler_t getter,
jerry_external_handler_t setter
);
/**
* Create a JS object based on a C value using the provided prototype.
*
* @param proto The prototype.
* @param value The pointer to the C value to create a JS object for.
* @return A JS object wrapping the provided C value.
* 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,
@@ -75,9 +110,31 @@ jerry_value_t scriptProtoCreateValue(
);
/**
* Gets the native C struct pointer from a JS object using the prototype info.
*
* @param proto The prototype struct containing the native info.
* @param obj The JS object to get the native value from.
* 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);
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);
+1 -1
View File
@@ -62,7 +62,7 @@ for name, (r, g, b, a) in colors.items():
]
js += [
f"Color.{name.lower()} = function() {{",
f" return Color.rgba({r8}, {g8}, {b8}, {a8});",
f" return new Color({r8}, {g8}, {b8}, {a8});",
"};",
"",
]