diff --git a/assets/entities/cube.js b/assets/entities/cube.js index a19ed092..e40f3291 100644 --- a/assets/entities/cube.js +++ b/assets/entities/cube.js @@ -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; \ No newline at end of file +module = CubeEntity; \ No newline at end of file diff --git a/assets/scenes/cube.js b/assets/scenes/cube.js index 7aa43fee..f41b3d8c 100644 --- a/assets/scenes/cube.js +++ b/assets/scenes/cube.js @@ -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; diff --git a/src/dusk/entity/component.c b/src/dusk/entity/component.c index 88e9cea7..108c7c22 100644 --- a/src/dusk/entity/component.c +++ b/src/dusk/entity/component.c @@ -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 diff --git a/src/dusk/entity/component.h b/src/dusk/entity/component.h index 3d79f7d7..ab310786 100644 --- a/src/dusk/entity/component.h +++ b/src/dusk/entity/component.h @@ -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; diff --git a/src/dusk/scene/scene.c b/src/dusk/scene/scene.c index 323f23db..f83ebfe0 100644 --- a/src/dusk/scene/scene.c +++ b/src/dusk/scene/scene.c @@ -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; } diff --git a/src/dusk/script/module/display/modulecolor.h b/src/dusk/script/module/display/modulecolor.h index 068c3042..8fe2c44d 100644 --- a/src/dusk/script/module/display/modulecolor.h +++ b/src/dusk/script/module/display/modulecolor.h @@ -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); } diff --git a/src/dusk/script/module/display/modulescreen.h b/src/dusk/script/module/display/modulescreen.h index 56848d5c..5eb3104b 100644 --- a/src/dusk/script/module/display/modulescreen.h +++ b/src/dusk/script/module/display/modulescreen.h @@ -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"); } \ No newline at end of file diff --git a/src/dusk/script/module/entity/moduleentity.h b/src/dusk/script/module/entity/moduleentity.h index f9f0762e..8171dcde 100644 --- a/src/dusk/script/module/entity/moduleentity.h +++ b/src/dusk/script/module/entity/moduleentity.h @@ -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); } diff --git a/src/dusk/script/module/scene/modulescene.h b/src/dusk/script/module/scene/modulescene.h index 2fd3f868..a6923cd9 100644 --- a/src/dusk/script/module/scene/modulescene.h +++ b/src/dusk/script/module/scene/modulescene.h @@ -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 + ); } diff --git a/src/dusk/script/module/script/moduleinclude.h b/src/dusk/script/module/script/moduleinclude.h new file mode 100644 index 00000000..9b04920b --- /dev/null +++ b/src/dusk/script/module/script/moduleinclude.h @@ -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; +} diff --git a/src/dusk/script/module/script/modulescript.h b/src/dusk/script/module/script/modulescript.h index 82a4ca87..7d20281c 100644 --- a/src/dusk/script/module/script/modulescript.h +++ b/src/dusk/script/module/script/modulescript.h @@ -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); } diff --git a/src/dusk/script/scriptproto.c b/src/dusk/script/scriptproto.c index 73b3507d..3fc2067d 100644 --- a/src/dusk/script/scriptproto.c +++ b/src/dusk/script/scriptproto.c @@ -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); } diff --git a/src/dusk/script/scriptproto.h b/src/dusk/script/scriptproto.h index 45afb176..9a758ac9 100644 --- a/src/dusk/script/scriptproto.h +++ b/src/dusk/script/scriptproto.h @@ -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); \ No newline at end of file +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); diff --git a/tools/color/csv/__main__.py b/tools/color/csv/__main__.py index 86ba9d21..39c7f47a 100644 --- a/tools/color/csv/__main__.py +++ b/tools/color/csv/__main__.py @@ -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});", "};", "", ]