From e61914bad4ac35a71d2b352c2b0f3218c013add8 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Thu, 30 Jul 2026 11:19:08 -0500 Subject: [PATCH] Finish JerryScript Entity/Scene wiring; remove JSON serialize/deserialize Register Entity, the generic Component wrapper, and Scene as JerryScript classes (modulelist.c ties them together, plus their .d.ts stubs), so scenes/entities can be built from script going forward instead of the JSON scene format. With scripting now the intended authoring path, drop the JSON serialize/deserialize machinery entirely: componentdefinition_t's serialize/deserialize callbacks, every component's *Serialize/ *Deserialize function, entitySerialize/entityDeserialize, sceneSerialize/sceneDeserialize, and the "prefabs/.json"/ "scenes/.json" asset fallback in entityPrefabResolveAndApply/ scenePrefabResolveAndApply (C-coded prefabs only now). physicsshape.c and its test are removed outright since they only existed for this. game.c's test scene now comes from the existing overworldSceneCreate() C builder instead of loading the now-deleted assets/scenes/test.json. Co-Authored-By: Claude Sonnet 5 --- CLAUDE.md | 83 +++++++++- assets/scenes/test.json | 66 -------- src/dusk/engine/engine.c | 3 + src/dusk/entity/component.c | 51 +----- src/dusk/entity/component.h | 88 +--------- .../entity/component/display/entitycamera.c | 97 ----------- .../entity/component/display/entitycamera.h | 42 ----- .../entity/component/display/entityposition.c | 103 ------------ .../entity/component/display/entityposition.h | 60 ------- .../component/display/entityrenderable.c | 145 ----------------- .../component/display/entityrenderable.h | 45 ------ .../entity/component/physics/entityphysics.c | 101 ------------ .../entity/component/physics/entityphysics.h | 43 ----- .../entity/component/trigger/entitytrigger.c | 37 ----- .../entity/component/trigger/entitytrigger.h | 39 ----- src/dusk/entity/componentlist.h | 23 +-- src/dusk/entity/entity.c | 91 ----------- src/dusk/entity/entity.h | 63 +------- src/dusk/entity/entityprefab.c | 18 +-- src/dusk/entity/entityprefab.h | 10 +- src/dusk/physics/CMakeLists.txt | 1 - src/dusk/physics/physicsshape.c | 131 --------------- src/dusk/physics/physicsshape.h | 33 ---- src/dusk/scene/scene.c | 143 ---------------- src/dusk/scene/scene.h | 71 -------- src/dusk/scene/sceneprefab.c | 16 +- src/dusk/scene/sceneprefab.h | 10 +- src/dusk/script/module/CMakeLists.txt | 12 ++ src/dusk/script/module/entity/CMakeLists.txt | 11 ++ .../module/entity/component/CMakeLists.txt | 9 ++ src/dusk/script/module/entity/moduleentity.c | 145 +++++++++++++++++ src/dusk/script/module/modulelist.c | 25 +++ src/dusk/script/module/modulelist.h | 20 +++ src/dusk/script/module/scene/CMakeLists.txt | 9 ++ src/dusk/script/module/scene/modulescene.c | 91 +++++++++++ src/dusk/script/module/scene/modulescene.h | 41 +++++ .../component/overworld/entityinteractable.c | 88 ---------- .../component/overworld/entityinteractable.h | 43 ----- .../entity/component/overworld/entityplayer.c | 38 ----- .../entity/component/overworld/entityplayer.h | 38 ----- src/duskrpg/entity/gamecomponentlist.h | 7 +- .../prefab/entityprefaboverworldcamera.h | 3 +- .../entity/prefab/entityprefabplayer.h | 4 +- src/duskrpg/game/game.c | 25 +-- test/entity/test_entityposition.c | 45 ------ test/entity/test_entitytrigger.c | 51 ------ test/physics/CMakeLists.txt | 1 - test/physics/test_physicsshape.c | 153 ------------------ test/scene/test_scene.c | 78 --------- types/entity/component/component.d.ts | 36 +++++ types/entity/entity.d.ts | 36 +++++ types/index.d.ts | 8 + types/scene/scene.d.ts | 27 ++++ 53 files changed, 576 insertions(+), 2081 deletions(-) delete mode 100644 assets/scenes/test.json delete mode 100644 src/dusk/physics/physicsshape.c create mode 100644 src/dusk/script/module/CMakeLists.txt create mode 100644 src/dusk/script/module/entity/CMakeLists.txt create mode 100644 src/dusk/script/module/entity/component/CMakeLists.txt create mode 100644 src/dusk/script/module/entity/moduleentity.c create mode 100644 src/dusk/script/module/modulelist.c create mode 100644 src/dusk/script/module/modulelist.h create mode 100644 src/dusk/script/module/scene/CMakeLists.txt create mode 100644 src/dusk/script/module/scene/modulescene.c create mode 100644 src/dusk/script/module/scene/modulescene.h delete mode 100644 test/physics/test_physicsshape.c create mode 100644 types/entity/component/component.d.ts create mode 100644 types/entity/entity.d.ts create mode 100644 types/index.d.ts create mode 100644 types/scene/scene.d.ts diff --git a/CLAUDE.md b/CLAUDE.md index 8f18689f..e8edc700 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -188,21 +188,85 @@ simply left undefined — the core guards calls with `#ifdef`. ## Adding a new entity component 1. Create `src/dusk/entity/component//entityMyComp.h/.c` with struct `entityMyComp_t`, `entityMyCompInit()`, and optionally - `entityMyCompDispose()`. -2. Add the include to `src/dusk/entity/componentlist.h` header block. -3. Add a row to `src/dusk/entity/componentlist.h`: + `entityMyCompDispose()`, `entityMyCompRender()`. +2. Add the include to `src/dusk/entity/componentlist.h` header block + (or `src/duskrpg/entity/gamecomponentlist.h` for a game-specific + component, appended after the engine's inbuilt ones). +3. Add a row: ```c X(MYCOMP, entityMyComp_t, myComp, entityMyCompInit, NULL, NULL) ``` - This auto-generates the enum, union field, and definition entry. + Params are `(enumName, type, field, init, dispose, render)` — pass + `NULL` for any callback the component doesn't need. This + auto-generates the enum, union field, and definition entry. 4. If JS-facing, create the script module and `.d.ts` (see below). +Entities/components/scenes have no JSON serialize/deserialize path — +that was removed in favor of building scenes from C-coded prefabs +(below) or from JerryScript (`Entity`/`Component`/`Scene`, see +"Adding a new script (JS) module"). + +--- + +## Adding a new entity/scene prefab +Entity prefabs (`src/dusk/entity/entityprefab.h`) and scene prefabs +(`src/dusk/scene/sceneprefab.h`) follow the same pattern: +1. Write an apply function: `errorret_t entityPrefabXxxApply(mgr, + entityId)` (or `errorret_t scenePrefabXxxApply(sceneId)`), building + up the entity/scene with the normal component/entity APIs. +2. Add an entry to the sentinel-terminated `ENTITY_PREFABS[]` (in + `src/dusk/entity/entityprefablist.h`, or a game-specific list it + includes) or `SCENE_PREFABS[]`: + ```c + { .name = "MY_PREFAB", .extends = "", .apply = entityPrefabXxxApply } + ``` + `extends` names another prefab to apply first (recurses through + `entityPrefabResolveAndApply`/`scenePrefabResolveAndApply`), or `""` + for none. Do not add an enum or count field — the array is iterated + until `.name[0] == '\0'`. +3. `entityPrefabResolveAndApply`/`scenePrefabResolveAndApply` only + resolve names against the C-coded registry above — there is no JSON + asset fallback. Throws if no prefab with that name is registered. + +--- + +## Adding a new cutscene item type +1. Create `src/duskrpg/cutscene/item//cutsceneMyItem.h/.c` + with a data struct (e.g. `cutscenemyitem_t`) and + `cutsceneMyItemStart(item, data)` / `cutsceneMyItemUpdate(item, + data)` (the latter returns `true` once the item has completed). Add + a matching `cutscenemyitemdata_t` runtime-data struct only if the + item needs per-run state across ticks (most don't). +2. Add `CUTSCENE_ITEM_TYPE_MY_ITEM` to the enum and a union member to + `cutsceneitem_t` (and `cutsceneitemdata_t` if it has runtime data) in + `src/duskrpg/cutscene/item/cutsceneitem.h`. +3. Register the `{ start, update }` pair in `CUTSCENE_ITEM_CALLBACKS[]` + in `cutsceneitem.c`. +4. Add an authoring macro to `src/duskrpg/cutscene/cutscene.h`: + ```c + #define CUTSCENE_MY_ITEM(ARGS...) \ + { .type = CUTSCENE_ITEM_TYPE_MY_ITEM, .myItem = { ARGS } } + ``` + used inside a `CUTSCENE(NAME, SIZE, PAUSE_TYPE, ...)` block. + --- ## Adding a new script (JS) module +Dusk embeds JerryScript (`src/dusk/script/`, fetched via +`cmake/modules/Findjerryscript.cmake`). Today only `Entity`, the +generic `Component` wrapper, and `Scene` are registered (see +`src/dusk/script/module/modulelist.c`) — no per-component-type typed +wrappers exist yet (e.g. no `.position` on a `POSITION` component); +`entity.add(TYPE)`/`entity.getComponent(TYPE)` always return the +generic `Component`. + 1. Create `src/dusk/script/module//moduleMyMod.h/.c`. - Declare `extern scriptproto_t MODULE_MYMOD_PROTO;` in the header. - - Use `moduleBaseFunction(name)` to define JS-callable functions. + - Use `moduleBaseFunction(name)` to define JS-callable functions — + these are the one exception to "no `static` in `.c` files": the + macro itself expands to a `static jerry_value_t name(...)` + JerryScript external-handler trampoline, never called by name + from other C files, so it isn't declared in the `.h`. - Register props/funcs in `moduleMyModInit()` with `scriptProtoDefineProp` / `scriptProtoDefineFunc` / `scriptProtoDefineStaticFunc`. @@ -210,9 +274,12 @@ simply left undefined — the core guards calls with `#ifdef`. `src/dusk/script/module/modulelist.c` and call `moduleMyModInit()` in `moduleListInit()` (and `Dispose` in `moduleListDispose()`). -3. For component modules also register in - `src/dusk/script/module/entity/component/modulecomponentlist.c` - so `entity.add()` returns the typed wrapper. +3. For a component module that adds a *typed* wrapper for a specific + component type, create + `src/dusk/script/module/entity/component/modulecomponentlist.c` (it + doesn't exist yet — the first such module creates it) so + `entity.add()` can return the typed wrapper instead of the generic + `Component`. 4. Create `types//mymod.d.ts` and add a `/// ` line to `types/index.d.ts`. diff --git a/assets/scenes/test.json b/assets/scenes/test.json deleted file mode 100644 index fe6641ef..00000000 --- a/assets/scenes/test.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "extend": "OVERWORLDSCENE", - "entities": [ - { - "components": [ - { "type": "POSITION", "x": 0, "y": 1, "z": 0, "parent": "camera" } - ] - }, - { - "name": "testArea", - "components": [ - { "type": "POSITION", "x": 2, "y": 1, "z": 0 }, - { - "type": "TRIGGER", - "collideMask": 2, - "shape": { - "type": "CUBE", - "halfExtentX": 1.5, "halfExtentY": 1.5, "halfExtentZ": 1.5 - } - } - ] - }, - { - "name": "npc", - "components": [ - { "type": "POSITION", "x": -3, "y": 1, "z": 0 }, - { - "type": "RENDERABLE", - "renderType": "SHADER_MATERIAL", - "priority": 0, - "shaderType": "UNLIT", - "color": { "r": 80, "g": 255, "b": 120, "a": 255 }, - "displayState": { "cull": false, "depthTest": true, "blend": false } - }, - { "type": "PHYSICS", "bodyType": "STATIC" }, - { "type": "INTERACTABLE", "kind": "ITEM_PICKUP", "pickupId": 1 } - ] - }, - { - "components": [ - { - "type": "POSITION", - "x": 0, "y": -0.1, "z": 0, - "scale": { "x": 20, "y": 0.2, "z": 20 } - }, - { - "type": "RENDERABLE", - "renderType": "SHADER_MATERIAL", - "priority": 0, - "shaderType": "UNLIT", - "color": { "r": 128, "g": 128, "b": 128, "a": 255 }, - "displayState": { "cull": false, "depthTest": true, "blend": false } - }, - { - "type": "PHYSICS", - "bodyType": "STATIC", - "shape": { - "type": "PLANE", - "normalX": 0, "normalY": 1, "normalZ": 0, - "distance": 0 - } - } - ] - } - ] -} diff --git a/src/dusk/engine/engine.c b/src/dusk/engine/engine.c index fa7d47eb..f571b7a0 100644 --- a/src/dusk/engine/engine.c +++ b/src/dusk/engine/engine.c @@ -13,6 +13,7 @@ #include "display/display.h" #include "scene/scene.h" #include "asset/asset.h" +#include "script/scriptmanager.h" #include "ui/ui.h" #include "assert/assert.h" #include "network/network.h" @@ -39,6 +40,7 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) { errorChain(systemInit()); errorChain(inputInit()); errorChain(assetInit()); + errorChain(scriptManagerInit()); errorChain(saveInit()); errorChain(saveSettingsLoad()); errorChain(localeManagerInit()); @@ -101,6 +103,7 @@ errorret_t engineDispose(void) { consoleDispose(); errorChain(displayDispose()); errorChain(saveDispose()); + errorChain(scriptManagerDispose()); errorChain(assetDispose()); errorOk(); diff --git a/src/dusk/entity/component.c b/src/dusk/entity/component.c index ebf475a3..58512710 100644 --- a/src/dusk/entity/component.c +++ b/src/dusk/entity/component.c @@ -12,15 +12,13 @@ componentdefinition_t COMPONENT_DEFINITIONS[] = { [COMPONENT_TYPE_NULL] = { 0 }, - #define X(enm, type, field, iMethod, dMethod, rMethod, sMethod, dsMethod) \ + #define X(enm, type, field, iMethod, dMethod, rMethod) \ [COMPONENT_TYPE_##enm] = { \ .enumName = #enm, \ .name = #field, \ .init = iMethod, \ .dispose = dMethod, \ - .render = rMethod, \ - .serialize = sMethod, \ - .deserialize = dsMethod \ + .render = rMethod \ }, #include "componentlist.h" @@ -152,48 +150,3 @@ void componentDispose( cmp->type = COMPONENT_TYPE_NULL; } - -errorret_t componentSerialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - const componenttype_t type, - yyjson_mut_doc *doc, - yyjson_mut_val *json -) { - assertNotNull(mgr, "Entity manager cannot be null"); - assertNotNull(doc, "JSON document cannot be null"); - assertNotNull(json, "JSON object cannot be null"); - assertTrue(entityId < ENTITY_COUNT_MAX, "Entity ID OOB"); - assertTrue(componentId < ENTITY_COMPONENT_COUNT_MAX, "Component ID OOB"); - assertTrue(type < COMPONENT_TYPE_COUNT, "Component type OOB"); - assertTrue(type != COMPONENT_TYPE_NULL, "Cannot serialize null component"); - - if(!COMPONENT_DEFINITIONS[type].serialize) errorOk(); - errorChain( - COMPONENT_DEFINITIONS[type].serialize(mgr, entityId, componentId, doc, - json) - ); - errorOk(); -} - -errorret_t componentDeserialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - const componenttype_t type, - yyjson_val *json -) { - assertNotNull(mgr, "Entity manager cannot be null"); - assertNotNull(json, "JSON object cannot be null"); - assertTrue(entityId < ENTITY_COUNT_MAX, "Entity ID OOB"); - assertTrue(componentId < ENTITY_COMPONENT_COUNT_MAX, "Component ID OOB"); - assertTrue(type < COMPONENT_TYPE_COUNT, "Component type OOB"); - assertTrue(type != COMPONENT_TYPE_NULL, "Cannot deserialize null component"); - - if(!COMPONENT_DEFINITIONS[type].deserialize) errorOk(); - errorChain( - COMPONENT_DEFINITIONS[type].deserialize(mgr, entityId, componentId, json) - ); - errorOk(); -} diff --git a/src/dusk/entity/component.h b/src/dusk/entity/component.h index b56769e4..a1ee156a 100644 --- a/src/dusk/entity/component.h +++ b/src/dusk/entity/component.h @@ -8,17 +8,14 @@ #pragma once #include "entitybase.h" #include "error/error.h" -#include "yyjson.h" -#define X(enumName, type, field, init, dispose, render, serialize, \ - deserialize) \ +#define X(enumName, type, field, init, dispose, render) \ // do nothing #include "componentlist.h" #undef X typedef union { - #define X(enumName, type, field, init, dispose, render, serialize, \ - deserialize) type field; + #define X(enumName, type, field, init, dispose, render) type field; #include "componentlist.h" #undef X } componentdata_t; @@ -50,55 +47,18 @@ typedef errorret_t (*componentcallbackerror_t)( const componentid_t componentId ); -/** - * Callback signature for a component's serialize hook. - * - * @param mgr The entity manager that owns the entity. - * @param entityId The entity ID. - * @param componentId The component ID. - * @param doc The mutable JSON document to allocate values from. - * @param json The JSON object to write the component's fields into. - * @return Error state. - */ -typedef errorret_t (*componentserializecallback_t)( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_mut_doc *doc, - yyjson_mut_val *json -); - -/** - * Callback signature for a component's deserialize hook. - * - * @param mgr The entity manager that owns the entity. - * @param entityId The entity ID. - * @param componentId The component ID. - * @param json The JSON object to read the component's fields from. - * @return Error state. - */ -typedef errorret_t (*componentdeserializecallback_t)( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_val *json -); - typedef struct { const char_t *enumName; const char_t *name; componentcallback_t init; componentcallback_t dispose; componentcallbackerror_t render; - componentserializecallback_t serialize; - componentdeserializecallback_t deserialize; } componentdefinition_t; typedef enum { COMPONENT_TYPE_NULL, - #define X(enumName, type, field, init, dispose, render, serialize, \ - deserialize) \ + #define X(enumName, type, field, init, dispose, render) \ COMPONENT_TYPE_##enumName, #include "componentlist.h" #undef X @@ -195,45 +155,3 @@ void componentDispose( * @return Error state. */ errorret_t componentRenderAll(entitymanager_t *mgr); - -/** - * Writes a component's data into the given JSON object, if the component - * type defines a serialize callback. No-op (returns success) for - * components whose definition has serialize == NULL. - * - * @param mgr The entity manager that owns the entity. - * @param entityId The entity ID. - * @param componentId The component ID. - * @param type The type of the component to serialize. - * @param doc The mutable JSON document to allocate values from. - * @param json The JSON object to write the component's fields into. - * @return Error state. - */ -errorret_t componentSerialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - const componenttype_t type, - yyjson_mut_doc *doc, - yyjson_mut_val *json -); - -/** - * Reads a component's data from the given JSON object, if the component - * type defines a deserialize callback. No-op (returns success) for - * components whose definition has deserialize == NULL. - * - * @param mgr The entity manager that owns the entity. - * @param entityId The entity ID. - * @param componentId The component ID. - * @param type The type of the component to deserialize. - * @param json The JSON object to read the component's fields from. - * @return Error state. - */ -errorret_t componentDeserialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - const componenttype_t type, - yyjson_val *json -); diff --git a/src/dusk/entity/component/display/entitycamera.c b/src/dusk/entity/component/display/entitycamera.c index 95dce2a6..06697054 100644 --- a/src/dusk/entity/component/display/entitycamera.c +++ b/src/dusk/entity/component/display/entitycamera.c @@ -9,8 +9,6 @@ #include "entity/entity.h" #include "entity/component/display/entityposition.h" #include "display/screen/screen.h" -#include "util/string.h" -#include "assert/assert.h" void entityCameraInit( entitymanager_t *mgr, @@ -140,98 +138,3 @@ void entityCameraLookAtPixelPerfect( vec3 up = { 0.0f, 0.0f, -1.0f }; entityPositionLookAt(mgr, ent, posComp, eye, (float_t *)point, up); } - -errorret_t entityCameraSerialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_mut_doc *doc, - yyjson_mut_val *json -) { - entitycamera_t *cam = componentGetData( - mgr, entityId, componentId, COMPONENT_TYPE_CAMERA - ); - - const char_t *projTypeName; - switch(cam->projType) { - case ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE: - projTypeName = "PERSPECTIVE"; - break; - case ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED: - projTypeName = "PERSPECTIVE_FLIPPED"; - break; - case ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC: - projTypeName = "ORTHOGRAPHIC"; - break; - default: - assertUnreachable("Unknown camera projection type"); - } - yyjson_mut_obj_add_str(doc, json, "projType", projTypeName); - yyjson_mut_obj_add_real(doc, json, "nearClip", cam->nearClip); - yyjson_mut_obj_add_real(doc, json, "farClip", cam->farClip); - - if(cam->projType == ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) { - yyjson_mut_obj_add_real(doc, json, "left", cam->orthographic.left); - yyjson_mut_obj_add_real(doc, json, "right", cam->orthographic.right); - yyjson_mut_obj_add_real(doc, json, "top", cam->orthographic.top); - yyjson_mut_obj_add_real(doc, json, "bottom", cam->orthographic.bottom); - } else { - yyjson_mut_obj_add_real(doc, json, "fov", cam->perspective.fov); - } - - errorOk(); -} - -errorret_t entityCameraDeserialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_val *json -) { - entitycamera_t *cam = componentGetData( - mgr, entityId, componentId, COMPONENT_TYPE_CAMERA - ); - - yyjson_val *projTypeVal = yyjson_obj_get(json, "projType"); - if(projTypeVal) { - const char_t *projTypeName = yyjson_get_str(projTypeVal); - if(stringEquals(projTypeName, "ORTHOGRAPHIC")) { - cam->projType = ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC; - } else if(stringEquals(projTypeName, "PERSPECTIVE_FLIPPED")) { - cam->projType = ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED; - } else if(stringEquals(projTypeName, "PERSPECTIVE")) { - cam->projType = ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE; - } else { - errorThrow("Unknown camera projection type '%s'", projTypeName); - } - } - - yyjson_val *v; - if((v = yyjson_obj_get(json, "nearClip"))) { - cam->nearClip = (float_t)yyjson_get_num(v); - } - if((v = yyjson_obj_get(json, "farClip"))) { - cam->farClip = (float_t)yyjson_get_num(v); - } - - if(cam->projType == ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) { - if((v = yyjson_obj_get(json, "left"))) { - cam->orthographic.left = (float_t)yyjson_get_num(v); - } - if((v = yyjson_obj_get(json, "right"))) { - cam->orthographic.right = (float_t)yyjson_get_num(v); - } - if((v = yyjson_obj_get(json, "top"))) { - cam->orthographic.top = (float_t)yyjson_get_num(v); - } - if((v = yyjson_obj_get(json, "bottom"))) { - cam->orthographic.bottom = (float_t)yyjson_get_num(v); - } - } else { - if((v = yyjson_obj_get(json, "fov"))) { - cam->perspective.fov = (float_t)yyjson_get_num(v); - } - } - - errorOk(); -} diff --git a/src/dusk/entity/component/display/entitycamera.h b/src/dusk/entity/component/display/entitycamera.h index 983d0a90..5a52c568 100644 --- a/src/dusk/entity/component/display/entitycamera.h +++ b/src/dusk/entity/component/display/entitycamera.h @@ -8,7 +8,6 @@ #pragma once #include "entity/entitybase.h" #include "error/error.h" -#include "yyjson.h" typedef enum { ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE, @@ -121,44 +120,3 @@ void entityCameraLookAtPixelPerfect( const vec3 eyeOffset, const float_t scale ); - -/** - * Serializes the camera's projection type ("projType": "PERSPECTIVE" / - * "PERSPECTIVE_FLIPPED" / "ORTHOGRAPHIC"), "nearClip", "farClip", and - * whichever projection-specific fields apply ("fov" for the perspective - * types, "left"/"right"/"top"/"bottom" for orthographic) into the given - * JSON object. - * - * @param mgr The entity manager that owns the entity. - * @param entityId The entity ID. - * @param componentId The component ID. - * @param doc The mutable JSON document to allocate values from. - * @param json The JSON object to write the component's fields into. - * @return Error state. - */ -errorret_t entityCameraSerialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_mut_doc *doc, - yyjson_mut_val *json -); - -/** - * Reads "projType", "nearClip", "farClip", and the projection-specific - * fields (see entityCameraSerialize()) from the given JSON object and - * applies whichever are present, leaving the rest at their current - * values. - * - * @param mgr The entity manager that owns the entity. - * @param entityId The entity ID. - * @param componentId The component ID. - * @param json The JSON object to read the component's fields from. - * @return Error state. - */ -errorret_t entityCameraDeserialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_val *json -); diff --git a/src/dusk/entity/component/display/entityposition.c b/src/dusk/entity/component/display/entityposition.c index 7c068a77..bddd61d6 100644 --- a/src/dusk/entity/component/display/entityposition.c +++ b/src/dusk/entity/component/display/entityposition.c @@ -667,106 +667,3 @@ void entityPositionEnsureWorld(entitymanager_t *mgr, entityposition_t *pos) { pos->flags &= ~ENTITY_POSITION_FLAG_WORLD_DIRTY; } - -errorret_t entityPositionSerialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_mut_doc *doc, - yyjson_mut_val *json -) { - vec3 position, rotation, scale; - entityPositionGetLocalPosition(mgr, entityId, componentId, position); - entityPositionGetLocalRotation(mgr, entityId, componentId, rotation); - entityPositionGetLocalScale(mgr, entityId, componentId, scale); - - yyjson_mut_obj_add_real(doc, json, "x", position[0]); - yyjson_mut_obj_add_real(doc, json, "y", position[1]); - yyjson_mut_obj_add_real(doc, json, "z", position[2]); - - yyjson_mut_val *rot = yyjson_mut_obj(doc); - yyjson_mut_obj_add_val(doc, json, "rotation", rot); - yyjson_mut_obj_add_real(doc, rot, "x", rotation[0]); - yyjson_mut_obj_add_real(doc, rot, "y", rotation[1]); - yyjson_mut_obj_add_real(doc, rot, "z", rotation[2]); - - yyjson_mut_val *scl = yyjson_mut_obj(doc); - yyjson_mut_obj_add_val(doc, json, "scale", scl); - yyjson_mut_obj_add_real(doc, scl, "x", scale[0]); - yyjson_mut_obj_add_real(doc, scl, "y", scale[1]); - yyjson_mut_obj_add_real(doc, scl, "z", scale[2]); - - errorOk(); -} - -errorret_t entityPositionDeserialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_val *json -) { - yyjson_val *xVal = yyjson_obj_get(json, "x"); - yyjson_val *yVal = yyjson_obj_get(json, "y"); - yyjson_val *zVal = yyjson_obj_get(json, "z"); - if(xVal || yVal || zVal) { - vec3 position; - entityPositionGetLocalPosition(mgr, entityId, componentId, position); - if(xVal) position[0] = (float_t)yyjson_get_num(xVal); - if(yVal) position[1] = (float_t)yyjson_get_num(yVal); - if(zVal) position[2] = (float_t)yyjson_get_num(zVal); - entityPositionSetLocalPosition(mgr, entityId, componentId, position); - } - - yyjson_val *rot = yyjson_obj_get(json, "rotation"); - if(rot) { - vec3 rotation; - entityPositionGetLocalRotation(mgr, entityId, componentId, rotation); - yyjson_val *v; - if((v = yyjson_obj_get(rot, "x"))) rotation[0] = (float_t)yyjson_get_num(v); - if((v = yyjson_obj_get(rot, "y"))) rotation[1] = (float_t)yyjson_get_num(v); - if((v = yyjson_obj_get(rot, "z"))) rotation[2] = (float_t)yyjson_get_num(v); - entityPositionSetLocalRotation(mgr, entityId, componentId, rotation); - } - - yyjson_val *scl = yyjson_obj_get(json, "scale"); - if(scl) { - vec3 scale; - entityPositionGetLocalScale(mgr, entityId, componentId, scale); - yyjson_val *v; - if((v = yyjson_obj_get(scl, "x"))) scale[0] = (float_t)yyjson_get_num(v); - if((v = yyjson_obj_get(scl, "y"))) scale[1] = (float_t)yyjson_get_num(v); - if((v = yyjson_obj_get(scl, "z"))) scale[2] = (float_t)yyjson_get_num(v); - entityPositionSetLocalScale(mgr, entityId, componentId, scale); - } - - yyjson_val *lookAt = yyjson_obj_get(json, "lookAt"); - if(lookAt) { - yyjson_val *targetXVal = yyjson_obj_get(lookAt, "x"); - yyjson_val *targetYVal = yyjson_obj_get(lookAt, "y"); - yyjson_val *targetZVal = yyjson_obj_get(lookAt, "z"); - assertTrue( - targetXVal && targetYVal && targetZVal, - "Position JSON 'lookAt' missing 'x'/'y'/'z' field" - ); - vec3 target = { - (float_t)yyjson_get_num(targetXVal), - (float_t)yyjson_get_num(targetYVal), - (float_t)yyjson_get_num(targetZVal) - }; - - vec3 up = { 0.0f, 1.0f, 0.0f }; - yyjson_val *upVal = yyjson_obj_get(lookAt, "up"); - if(upVal) { - yyjson_val *v; - if((v = yyjson_obj_get(upVal, "x"))) up[0] = (float_t)yyjson_get_num(v); - if((v = yyjson_obj_get(upVal, "y"))) up[1] = (float_t)yyjson_get_num(v); - if((v = yyjson_obj_get(upVal, "z"))) up[2] = (float_t)yyjson_get_num(v); - } - - vec3 eye; - entityPositionGetLocalPosition(mgr, entityId, componentId, eye); - entityPositionLookAt(mgr, entityId, componentId, eye, target, up); - } - - errorOk(); -} diff --git a/src/dusk/entity/component/display/entityposition.h b/src/dusk/entity/component/display/entityposition.h index fb7cc5bc..8cf763c1 100644 --- a/src/dusk/entity/component/display/entityposition.h +++ b/src/dusk/entity/component/display/entityposition.h @@ -8,7 +8,6 @@ #pragma once #include "entity/entitybase.h" #include "error/error.h" -#include "yyjson.h" /** Maximum number of child position components this node can track. */ #define ENTITY_POSITION_CHILDREN_MAX 8 @@ -459,62 +458,3 @@ void entityPositionEnsureLocal(entityposition_t *pos); * @param pos The position component to update. */ void entityPositionEnsureWorld(entitymanager_t *mgr, entityposition_t *pos); - -/** - * Serializes the local position as "x"/"y"/"z", and the local rotation - * and scale as nested "rotation"/"scale" objects (each with "x"/"y"/"z"), - * into the given JSON object. Does NOT write a "parent" field itself -- - * the parent link (see entityPositionSetParent()) is a cross-entity - * reference that only sceneSerialize() has enough context (every - * entity's name) to resolve, so it writes "parent" onto this same JSON - * object as a sibling field after calling this function. - * - * @param mgr The entity manager that owns the entity. - * @param entityId The entity ID. - * @param componentId The component ID. - * @param doc The mutable JSON document to allocate values from. - * @param json The JSON object to write the component's fields into. - * @return Error state. - */ -errorret_t entityPositionSerialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_mut_doc *doc, - yyjson_mut_val *json -); - -/** - * Reads the local position ("x"/"y"/"z") and the local rotation/scale - * (nested "rotation"/"scale" objects, each with "x"/"y"/"z") from the - * given JSON object and applies whichever fields are present, leaving - * the rest at their current values. - * - * Also supports a nested "lookAt" object as an authoring convenience for - * orienting the entity, in place of an explicit "rotation": "x"/"y"/"z" - * (required, the world-space point to face) plus an optional nested - * "up" ({"x","y","z"}, defaults to {0,1,0}). The eye point is whatever - * the local position resolves to after applying "x"/"y"/"z" above (its - * current value if none of those were given). If both "rotation" and - * "lookAt" are present, "lookAt" wins since it is applied afterward. Not - * emitted by entityPositionSerialize() -- lookAt is a one-time input - * convenience, not persisted state; it round-trips through "rotation". - * - * Does NOT read a "parent" field itself, for the same reason - * entityPositionSerialize() doesn't write one -- resolving a "parent" - * name to an entityId requires sceneDeserialize()'s scene-wide view, so - * it calls entityPositionSetParent() directly once every entity in the - * scene exists. - * - * @param mgr The entity manager that owns the entity. - * @param entityId The entity ID. - * @param componentId The component ID. - * @param json The JSON object to read the component's fields from. - * @return Error state. - */ -errorret_t entityPositionDeserialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_val *json -); diff --git a/src/dusk/entity/component/display/entityrenderable.c b/src/dusk/entity/component/display/entityrenderable.c index 5a764cc9..03af2edf 100644 --- a/src/dusk/entity/component/display/entityrenderable.c +++ b/src/dusk/entity/component/display/entityrenderable.c @@ -12,7 +12,6 @@ #include "display/display.h" #include "display/mesh/cube.h" #include "util/memory.h" -#include "util/string.h" #include "assert/assert.h" void entityRenderableInit( @@ -165,147 +164,3 @@ errorret_t entityRenderableDrawCustom( ) { return custom->draw(mgr, entityId, componentId, custom->drawUser); } - -errorret_t entityRenderableSerialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_mut_doc *doc, - yyjson_mut_val *json -) { - entityrenderable_t *r = componentGetData( - mgr, entityId, componentId, COMPONENT_TYPE_RENDERABLE - ); - - const char_t *renderTypeName; - switch(r->type) { - case ENTITY_RENDERABLE_TYPE_CUSTOM: - renderTypeName = "CUSTOM"; - break; - case ENTITY_RENDERABLE_TYPE_SPRITEBATCH: - renderTypeName = "SPRITEBATCH"; - break; - case ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL: - renderTypeName = "SHADER_MATERIAL"; - break; - default: - assertUnreachable("Unknown renderable type"); - } - yyjson_mut_obj_add_str(doc, json, "renderType", renderTypeName); - yyjson_mut_obj_add_int(doc, json, "priority", r->priority); - - if(r->type != ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL) errorOk(); - - const entityrenderablematerial_t *m = &r->data.material; - - const char_t *shaderTypeName; - switch(m->shaderType) { - case SHADER_LIST_SHADER_UNLIT: - shaderTypeName = "UNLIT"; - break; - default: - errorThrow("Cannot serialize unknown shaderType %d", m->shaderType); - } - yyjson_mut_obj_add_str(doc, json, "shaderType", shaderTypeName); - - yyjson_mut_val *color = yyjson_mut_obj(doc); - yyjson_mut_obj_add_val(doc, json, "color", color); - yyjson_mut_obj_add_int(doc, color, "r", m->material.unlit.color.r); - yyjson_mut_obj_add_int(doc, color, "g", m->material.unlit.color.g); - yyjson_mut_obj_add_int(doc, color, "b", m->material.unlit.color.b); - yyjson_mut_obj_add_int(doc, color, "a", m->material.unlit.color.a); - - yyjson_mut_val *displayState = yyjson_mut_obj(doc); - yyjson_mut_obj_add_val(doc, json, "displayState", displayState); - yyjson_mut_obj_add_bool( - doc, displayState, "cull", (m->state.flags & DISPLAY_STATE_FLAG_CULL) != 0 - ); - yyjson_mut_obj_add_bool( - doc, displayState, "depthTest", - (m->state.flags & DISPLAY_STATE_FLAG_DEPTH_TEST) != 0 - ); - yyjson_mut_obj_add_bool( - doc, displayState, "blend", - (m->state.flags & DISPLAY_STATE_FLAG_BLEND) != 0 - ); - - errorOk(); -} - -errorret_t entityRenderableDeserialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_val *json -) { - entityrenderable_t *r = componentGetData( - mgr, entityId, componentId, COMPONENT_TYPE_RENDERABLE - ); - - yyjson_val *renderTypeVal = yyjson_obj_get(json, "renderType"); - if(renderTypeVal) { - const char_t *renderTypeName = yyjson_get_str(renderTypeVal); - if(stringEquals(renderTypeName, "CUSTOM")) { - r->type = ENTITY_RENDERABLE_TYPE_CUSTOM; - } else if(stringEquals(renderTypeName, "SPRITEBATCH")) { - r->type = ENTITY_RENDERABLE_TYPE_SPRITEBATCH; - } else if(stringEquals(renderTypeName, "SHADER_MATERIAL")) { - r->type = ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL; - } else { - errorThrow("Unknown renderable renderType '%s'", renderTypeName); - } - } - - yyjson_val *priorityVal = yyjson_obj_get(json, "priority"); - if(priorityVal) r->priority = (int8_t)yyjson_get_int(priorityVal); - - if(r->type != ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL) errorOk(); - - entityrenderablematerial_t *m = &r->data.material; - - yyjson_val *shaderTypeVal = yyjson_obj_get(json, "shaderType"); - if(shaderTypeVal) { - const char_t *shaderTypeName = yyjson_get_str(shaderTypeVal); - if(stringEquals(shaderTypeName, "UNLIT")) { - m->shaderType = SHADER_LIST_SHADER_UNLIT; - } else { - errorThrow("Unknown renderable shaderType '%s'", shaderTypeName); - } - } - - yyjson_val *color = yyjson_obj_get(json, "color"); - if(color) { - yyjson_val *v; - if((v = yyjson_obj_get(color, "r"))) { - m->material.unlit.color.r = (colorchannel8_t)yyjson_get_int(v); - } - if((v = yyjson_obj_get(color, "g"))) { - m->material.unlit.color.g = (colorchannel8_t)yyjson_get_int(v); - } - if((v = yyjson_obj_get(color, "b"))) { - m->material.unlit.color.b = (colorchannel8_t)yyjson_get_int(v); - } - if((v = yyjson_obj_get(color, "a"))) { - m->material.unlit.color.a = (colorchannel8_t)yyjson_get_int(v); - } - } - - yyjson_val *displayState = yyjson_obj_get(json, "displayState"); - if(displayState) { - yyjson_val *v; - if((v = yyjson_obj_get(displayState, "cull"))) { - if(yyjson_get_bool(v)) m->state.flags |= DISPLAY_STATE_FLAG_CULL; - else m->state.flags &= ~DISPLAY_STATE_FLAG_CULL; - } - if((v = yyjson_obj_get(displayState, "depthTest"))) { - if(yyjson_get_bool(v)) m->state.flags |= DISPLAY_STATE_FLAG_DEPTH_TEST; - else m->state.flags &= ~DISPLAY_STATE_FLAG_DEPTH_TEST; - } - if((v = yyjson_obj_get(displayState, "blend"))) { - if(yyjson_get_bool(v)) m->state.flags |= DISPLAY_STATE_FLAG_BLEND; - else m->state.flags &= ~DISPLAY_STATE_FLAG_BLEND; - } - } - - errorOk(); -} diff --git a/src/dusk/entity/component/display/entityrenderable.h b/src/dusk/entity/component/display/entityrenderable.h index 2a559eac..7c0b2d15 100644 --- a/src/dusk/entity/component/display/entityrenderable.h +++ b/src/dusk/entity/component/display/entityrenderable.h @@ -12,7 +12,6 @@ #include "display/spritebatch/spritebatch.h" #include "display/displaystate.h" #include "error/error.h" -#include "yyjson.h" #define ENTITY_RENDERABLE_SPRITEBATCH_SPRITES_MAX 64 #define ENTITY_RENDERABLE_MESHES_MAX 8 @@ -213,47 +212,3 @@ errorret_t entityRenderableDrawCustom( const componentid_t componentId, const entityrenderablecustom_t *custom ); - -/** - * Serializes the renderable's "renderType" - * ("CUSTOM"/"SPRITEBATCH"/"SHADER_MATERIAL") and "priority" into the - * given JSON object. For ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL, also - * serializes "shaderType" (currently only "UNLIT"), its "color" - * ({"r","g","b","a"}, 0-255), and "displayState" (an object of - * "cull"/"depthTest"/"blend" booleans). Meshes, spritebatch - * textures/sprites, and the custom draw callback are asset-loaded or - * runtime-only and are not serialized. - * - * @param mgr The entity manager that owns the entity. - * @param entityId The entity ID. - * @param componentId The component ID. - * @param doc The mutable JSON document to allocate values from. - * @param json The JSON object to write the component's fields into. - * @return Error state. - */ -errorret_t entityRenderableSerialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_mut_doc *doc, - yyjson_mut_val *json -); - -/** - * Reads "renderType", "priority", and (for SHADER_MATERIAL) - * "shaderType"/"color"/"displayState" (see entityRenderableSerialize()) - * from the given JSON object and applies whichever are present, leaving - * the rest at their current values. - * - * @param mgr The entity manager that owns the entity. - * @param entityId The entity ID. - * @param componentId The component ID. - * @param json The JSON object to read the component's fields from. - * @return Error state. - */ -errorret_t entityRenderableDeserialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_val *json -); diff --git a/src/dusk/entity/component/physics/entityphysics.c b/src/dusk/entity/component/physics/entityphysics.c index 0f157163..e5909d01 100644 --- a/src/dusk/entity/component/physics/entityphysics.c +++ b/src/dusk/entity/component/physics/entityphysics.c @@ -8,8 +8,6 @@ #include "entityphysics.h" #include "entity/entitymanager.h" #include "util/memory.h" -#include "util/string.h" -#include "assert/assert.h" void entityPhysicsInit( entitymanager_t *mgr, @@ -135,102 +133,3 @@ uint32_t entityPhysicsGetCollideMask( entityphysics_t *phys = entityPhysicsGet(mgr, entityId, componentId); return phys->collideMask; } - -errorret_t entityPhysicsSerialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_mut_doc *doc, - yyjson_mut_val *json -) { - entityphysics_t *phys = entityPhysicsGet(mgr, entityId, componentId); - - const char_t *bodyTypeName; - switch(phys->type) { - case PHYSICS_BODY_STATIC: - bodyTypeName = "STATIC"; - break; - case PHYSICS_BODY_DYNAMIC: - bodyTypeName = "DYNAMIC"; - break; - case PHYSICS_BODY_KINEMATIC: - bodyTypeName = "KINEMATIC"; - break; - default: - assertUnreachable("Unknown physics body type"); - } - yyjson_mut_obj_add_str(doc, json, "bodyType", bodyTypeName); - - errorChain(physicsShapeSerialize(doc, json, &phys->shape)); - - yyjson_mut_obj_add_real(doc, json, "velocityX", phys->velocity[0]); - yyjson_mut_obj_add_real(doc, json, "velocityY", phys->velocity[1]); - yyjson_mut_obj_add_real(doc, json, "velocityZ", phys->velocity[2]); - yyjson_mut_obj_add_real(doc, json, "gravityScale", phys->gravityScale); - yyjson_mut_obj_add_bool(doc, json, "onGround", phys->onGround); - yyjson_mut_obj_add_uint(doc, json, "collideMask", phys->collideMask); - - errorOk(); -} - -errorret_t entityPhysicsDeserialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_val *json -) { - entityphysics_t *phys = entityPhysicsGet(mgr, entityId, componentId); - - yyjson_val *bodyTypeVal = yyjson_obj_get(json, "bodyType"); - if(bodyTypeVal) { - const char_t *bodyTypeName = yyjson_get_str(bodyTypeVal); - if(stringEquals(bodyTypeName, "STATIC")) { - phys->type = PHYSICS_BODY_STATIC; - } else if(stringEquals(bodyTypeName, "DYNAMIC")) { - phys->type = PHYSICS_BODY_DYNAMIC; - } else if(stringEquals(bodyTypeName, "KINEMATIC")) { - phys->type = PHYSICS_BODY_KINEMATIC; - } else { - errorThrow("Unknown physics body type '%s'", bodyTypeName); - } - } - - yyjson_val *shape = yyjson_obj_get(json, "shape"); - if(shape) { - physicsshape_t s; - errorChain(physicsShapeDeserialize(shape, &s)); - entityPhysicsSetShape(mgr, entityId, componentId, s); - } - - yyjson_val *v; - vec3 velocity; - entityPhysicsGetVelocity(mgr, entityId, componentId, velocity); - bool_t velocityChanged = false; - if((v = yyjson_obj_get(json, "velocityX"))) { - velocity[0] = (float_t)yyjson_get_num(v); - velocityChanged = true; - } - if((v = yyjson_obj_get(json, "velocityY"))) { - velocity[1] = (float_t)yyjson_get_num(v); - velocityChanged = true; - } - if((v = yyjson_obj_get(json, "velocityZ"))) { - velocity[2] = (float_t)yyjson_get_num(v); - velocityChanged = true; - } - if(velocityChanged) { - entityPhysicsSetVelocity(mgr, entityId, componentId, velocity); - } - - if((v = yyjson_obj_get(json, "gravityScale"))) { - phys->gravityScale = (float_t)yyjson_get_num(v); - } - if((v = yyjson_obj_get(json, "onGround"))) { - phys->onGround = yyjson_get_bool(v); - } - if((v = yyjson_obj_get(json, "collideMask"))) { - phys->collideMask = (uint32_t)yyjson_get_uint(v); - } - - errorOk(); -} diff --git a/src/dusk/entity/component/physics/entityphysics.h b/src/dusk/entity/component/physics/entityphysics.h index 2046996c..7bd28c22 100644 --- a/src/dusk/entity/component/physics/entityphysics.h +++ b/src/dusk/entity/component/physics/entityphysics.h @@ -10,7 +10,6 @@ #include "physics/physicsshape.h" #include "physics/physicsbodytype.h" #include "error/error.h" -#include "yyjson.h" typedef struct { physicsbodytype_t type; @@ -210,45 +209,3 @@ uint32_t entityPhysicsGetCollideMask( const entityid_t entityId, const componentid_t componentId ); - -/** - * Serializes the physics body into the given JSON object: "bodyType" - * ("STATIC"/"DYNAMIC"/"KINEMATIC"), "shape" (an object with its own - * "type" of "CUBE"/"SPHERE"/"CAPSULE"/"PLANE" plus that shape's fields), - * "velocity" ({"x","y","z"}), "gravityScale", "onGround", and - * "collideMask". Fails if the body's shape is PHYSICS_SHAPE_CUSTOM -- - * custom shapes carry a callback and opaque userData pointer that cannot - * be represented in JSON. - * - * @param mgr The entity manager that owns the entity. - * @param entityId The entity ID. - * @param componentId The component ID. - * @param doc The mutable JSON document to allocate values from. - * @param json The JSON object to write the component's fields into. - * @return Error state. - */ -errorret_t entityPhysicsSerialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_mut_doc *doc, - yyjson_mut_val *json -); - -/** - * Reads "bodyType", "shape", "velocity", "gravityScale", and "onGround" - * (see entityPhysicsSerialize()) from the given JSON object and applies - * whichever are present, leaving the rest at their current values. - * - * @param mgr The entity manager that owns the entity. - * @param entityId The entity ID. - * @param componentId The component ID. - * @param json The JSON object to read the component's fields from. - * @return Error state. - */ -errorret_t entityPhysicsDeserialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_val *json -); diff --git a/src/dusk/entity/component/trigger/entitytrigger.c b/src/dusk/entity/component/trigger/entitytrigger.c index 0b722477..40edf1b9 100644 --- a/src/dusk/entity/component/trigger/entitytrigger.c +++ b/src/dusk/entity/component/trigger/entitytrigger.c @@ -8,7 +8,6 @@ #include "entitytrigger.h" #include "entity/entitymanager.h" #include "util/memory.h" -#include "assert/assert.h" void entityTriggerInit( entitymanager_t *mgr, @@ -104,39 +103,3 @@ bool_t entityTriggerIsOccupyingEntity( } return false; } - -errorret_t entityTriggerSerialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_mut_doc *doc, - yyjson_mut_val *json -) { - entitytrigger_t *trig = entityTriggerGet(mgr, entityId, componentId); - errorChain(physicsShapeSerialize(doc, json, &trig->shape)); - yyjson_mut_obj_add_uint(doc, json, "collideMask", trig->collideMask); - errorOk(); -} - -errorret_t entityTriggerDeserialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_val *json -) { - yyjson_val *shape = yyjson_obj_get(json, "shape"); - if(shape) { - physicsshape_t s; - errorChain(physicsShapeDeserialize(shape, &s)); - entityTriggerSetShape(mgr, entityId, componentId, s); - } - - yyjson_val *v = yyjson_obj_get(json, "collideMask"); - if(v) { - entityTriggerSetCollideMask( - mgr, entityId, componentId, (uint32_t)yyjson_get_uint(v) - ); - } - - errorOk(); -} diff --git a/src/dusk/entity/component/trigger/entitytrigger.h b/src/dusk/entity/component/trigger/entitytrigger.h index a0963b8b..d4d37f9e 100644 --- a/src/dusk/entity/component/trigger/entitytrigger.h +++ b/src/dusk/entity/component/trigger/entitytrigger.h @@ -9,7 +9,6 @@ #include "entity/entitybase.h" #include "physics/physicsshape.h" #include "error/error.h" -#include "yyjson.h" /** Maximum number of entities a single trigger can track at once. */ #define ENTITY_TRIGGER_OCCUPANTS_MAX 8 @@ -225,41 +224,3 @@ bool_t entityTriggerIsOccupyingEntity( const componentid_t componentId, const entityid_t otherEntityId ); - -/** - * Serializes the trigger's shape (see physicsShapeSerialize()) and - * "collideMask" into the given JSON object. Occupants and event - * subscribers are runtime state and are not serialized. - * - * @param mgr The entity manager that owns the entity. - * @param entityId The entity ID. - * @param componentId The component ID. - * @param doc The mutable JSON document to allocate values from. - * @param json The JSON object to write the component's fields into. - * @return Error state. - */ -errorret_t entityTriggerSerialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_mut_doc *doc, - yyjson_mut_val *json -); - -/** - * Reads "shape" and "collideMask" (see entityTriggerSerialize()) from the - * given JSON object and applies whichever are present, leaving the rest - * at their current values. - * - * @param mgr The entity manager that owns the entity. - * @param entityId The entity ID. - * @param componentId The component ID. - * @param json The JSON object to read the component's fields from. - * @return Error state. - */ -errorret_t entityTriggerDeserialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_val *json -); diff --git a/src/dusk/entity/componentlist.h b/src/dusk/entity/componentlist.h index 3e7036c1..07eb6eb1 100644 --- a/src/dusk/entity/componentlist.h +++ b/src/dusk/entity/componentlist.h @@ -18,25 +18,14 @@ // Init function (optional) // Dispose function (optional) // Render function (optional) -// Serialize function (optional) -// Deserialize function (optional) -X(POSITION, entityposition_t, position, entityPositionInit, NULL, NULL, - entityPositionSerialize, entityPositionDeserialize) -X(CAMERA, entitycamera_t, camera, entityCameraInit, NULL, NULL, - entityCameraSerialize, entityCameraDeserialize) +X(POSITION, entityposition_t, position, entityPositionInit, NULL, NULL) +X(CAMERA, entitycamera_t, camera, entityCameraInit, NULL, NULL) X(RENDERABLE, entityrenderable_t, renderable, - entityRenderableInit, entityRenderableDispose, NULL, - entityRenderableSerialize, entityRenderableDeserialize) -X(PHYSICS, entityphysics_t, physics, entityPhysicsInit, NULL, NULL, - entityPhysicsSerialize, entityPhysicsDeserialize) -X(TRIGGER, entitytrigger_t, trigger, entityTriggerInit, NULL, NULL, - entityTriggerSerialize, entityTriggerDeserialize) -// No serialize/deserialize: the animation's keyframes are caller-owned -// (see animationInit()), not data this component owns, so there's -// nothing meaningful to persist to/from JSON. -X(ANIMATION, entityanimation_t, animation, entityAnimationInit, NULL, NULL, - NULL, NULL) + entityRenderableInit, entityRenderableDispose, NULL) +X(PHYSICS, entityphysics_t, physics, entityPhysicsInit, NULL, NULL) +X(TRIGGER, entitytrigger_t, trigger, entityTriggerInit, NULL, NULL) +X(ANIMATION, entityanimation_t, animation, entityAnimationInit, NULL, NULL) // Game-specific components #include "entity/gamecomponentlist.h" diff --git a/src/dusk/entity/entity.c b/src/dusk/entity/entity.c index f922d80a..9bf3f740 100644 --- a/src/dusk/entity/entity.c +++ b/src/dusk/entity/entity.c @@ -7,7 +7,6 @@ #include "entitymanager.h" #include "component/display/entityposition.h" -#include "entityprefab.h" #include "util/memory.h" #include "util/string.h" #include "assert/assert.h" @@ -214,93 +213,3 @@ void entityDisposeRemove( return; } } - -errorret_t entitySerialize( - entitymanager_t *mgr, - const entityid_t entityId, - yyjson_mut_doc *doc, - yyjson_mut_val *json -) { - assertNotNull(mgr, "Entity manager cannot be null"); - assertNotNull(doc, "JSON document cannot be null"); - assertNotNull(json, "JSON object cannot be null"); - assertTrue(entityId < ENTITY_COUNT_MAX, "Entity ID OOB"); - - const char_t *name = mgr->entities[entityId].name; - if(name[0] != '\0') yyjson_mut_obj_add_strcpy(doc, json, "name", name); - - yyjson_mut_val *components = yyjson_mut_arr(doc); - yyjson_mut_obj_add_val(doc, json, "components", components); - - for(componenttype_t type = 1; type < COMPONENT_TYPE_COUNT; type++) { - componentid_t componentId = entityGetComponent(mgr, entityId, type); - if(componentId == COMPONENT_ID_INVALID) continue; - - yyjson_mut_val *component = yyjson_mut_obj(doc); - yyjson_mut_arr_add_val(components, component); - yyjson_mut_obj_add_str( - doc, component, "type", COMPONENT_DEFINITIONS[type].enumName - ); - - errorChain( - componentSerialize(mgr, entityId, componentId, type, doc, component) - ); - } - - errorOk(); -} - -errorret_t entityDeserialize( - entitymanager_t *mgr, - const entityid_t entityId, - yyjson_val *json -) { - assertNotNull(mgr, "Entity manager cannot be null"); - assertNotNull(json, "JSON object cannot be null"); - assertTrue(entityId < ENTITY_COUNT_MAX, "Entity ID OOB"); - - yyjson_val *nameVal = yyjson_obj_get(json, "name"); - if(nameVal) entitySetName(mgr, entityId, yyjson_get_str(nameVal)); - - yyjson_val *extendVal = yyjson_obj_get(json, "extend"); - if(extendVal) { - errorChain(entityPrefabResolveAndApply( - mgr, entityId, yyjson_get_str(extendVal) - )); - } - - yyjson_val *prefabVal = yyjson_obj_get(json, "prefab"); - if(prefabVal) { - errorChain(entityPrefabResolveAndApply( - mgr, entityId, yyjson_get_str(prefabVal) - )); - } - - yyjson_val *components = yyjson_obj_get(json, "components"); - if(!components) errorOk(); - - size_t idx, max; - yyjson_val *component; - yyjson_arr_foreach(components, idx, max, component) { - yyjson_val *typeVal = yyjson_obj_get(component, "type"); - assertNotNull(typeVal, "Component JSON entry missing 'type' field"); - const char_t *typeName = yyjson_get_str(typeVal); - - componenttype_t type = COMPONENT_TYPE_NULL; - for(componenttype_t i = 1; i < COMPONENT_TYPE_COUNT; i++) { - if(!stringEquals(COMPONENT_DEFINITIONS[i].enumName, typeName)) continue; - type = i; - break; - } - if(type == COMPONENT_TYPE_NULL) { - errorThrow("Unknown component type '%s'", typeName); - } - - componentid_t componentId = entityAddComponent(mgr, entityId, type); - errorChain( - componentDeserialize(mgr, entityId, componentId, type, component) - ); - } - - errorOk(); -} diff --git a/src/dusk/entity/entity.h b/src/dusk/entity/entity.h index 523b7d26..adb6c41e 100644 --- a/src/dusk/entity/entity.h +++ b/src/dusk/entity/entity.h @@ -8,7 +8,6 @@ #pragma once #include "component.h" #include "error/error.h" -#include "yyjson.h" #define ENTITY_STATE_ACTIVE (1 << 0) @@ -76,8 +75,8 @@ componentid_t entityGetComponent( /** * Sets an entity's name. Names are optional -- an entity's name is empty * by default (entityInit zeroes it) -- but are required to make the - * entity referenceable by name, e.g. as a parent in POSITION's "parent" - * JSON field (see entityPositionSetParent()). + * entity referenceable by name, e.g. as a parent via + * entityPositionSetParent(). * * @param mgr The entity manager that owns the entity. * @param entityId The ID of the entity to name. @@ -201,61 +200,3 @@ void entityDisposeRemove( const entityid_t entityId, const entitycallback_t callback ); - -/** - * Serializes an entity's name (if set) and components into the given - * JSON object, writing an optional "name" string and a "components" - * array of { "type": , ...fields } entries. Only component - * types the entity actually has are written; each entry's extra fields - * come from that component type's serialize callback (see - * componentSerialize()). "name" is omitted entirely for unnamed - * entities. - * - * @param mgr The entity manager that owns the entity. - * @param entityId The ID of the entity to serialize. - * @param doc The mutable JSON document to allocate values from. - * @param json The JSON object to write the entity's "name"/"components" - * keys into. - * @return Error state. - */ -errorret_t entitySerialize( - entitymanager_t *mgr, - const entityid_t entityId, - yyjson_mut_doc *doc, - yyjson_mut_val *json -); - -/** - * Deserializes an entity's optional "name", "extend"/"prefab", and its - * components from the given JSON object's "components" array. - * - * If present, "extend" and then "prefab" are each resolved and applied - * as a prefab (see entityPrefabResolveAndApply()) before "components" is - * processed -- both keys do the same thing (resolve a named prefab, - * C-coded first then a "prefabs/.json" asset, and apply it to this - * entity); "extend" is the conventional key when this JSON is itself a - * prefab definition inheriting from a parent, "prefab" is the - * conventional key when a plain entity wants to be defined by a prefab. - * A prefab-added component still counts as already added, so listing the - * same component type again in "components" throws (see - * entityAddComponent()). - * - * For each "components" entry, adds a component of the type named by its - * "type" field (matched against COMPONENT_DEFINITIONS[].enumName) and - * hands the entry to that component type's deserialize callback (see - * componentDeserialize()). No-op for "components" if json has no such - * array. - * - * @param mgr The entity manager that owns the entity. - * @param entityId The ID of the entity to deserialize into. Must not - * already have any of the components named in json (including any a - * resolved "extend"/"prefab" prefab already added). - * @param json The JSON object to read the entity's "name"/"extend"/ - * "prefab"/"components" keys from. - * @return Error state. - */ -errorret_t entityDeserialize( - entitymanager_t *mgr, - const entityid_t entityId, - yyjson_val *json -); diff --git a/src/dusk/entity/entityprefab.c b/src/dusk/entity/entityprefab.c index c4eb1957..46181a74 100644 --- a/src/dusk/entity/entityprefab.c +++ b/src/dusk/entity/entityprefab.c @@ -6,11 +6,8 @@ */ #include "entityprefab.h" -#include "entity.h" -#include "asset/asset.h" #include "util/string.h" #include "assert/assert.h" -#include "yyjson.h" // Pulls in every prefab's real header (entityprefablist.h's one-time // #include lines) exactly once -- each header's #pragma once then makes @@ -55,18 +52,5 @@ errorret_t entityPrefabResolveAndApply( errorOk(); } - char_t assetPath[ASSET_FILE_NAME_MAX]; - stringFormat(assetPath, ASSET_FILE_NAME_MAX - 1, "prefabs/%s.json", name); - - assetentry_t *entry = assetLock(assetPath, ASSET_LOADER_TYPE_JSON, NULL); - errorret_t ret = assetRequireLoaded(entry); - if(errorIsOk(ret)) { - ret = entityDeserialize( - mgr, entityId, yyjson_doc_get_root(entry->data.json) - ); - } - assetUnlockEntry(entry); - errorChain(ret); - - errorOk(); + errorThrow("Unknown entity prefab '%s'", name); } diff --git a/src/dusk/entity/entityprefab.h b/src/dusk/entity/entityprefab.h index 77b2e42d..9486723f 100644 --- a/src/dusk/entity/entityprefab.h +++ b/src/dusk/entity/entityprefab.h @@ -56,12 +56,10 @@ errorret_t entityPrefabInit( ); /** - * Resolves a prefab by name and applies it to the given entity. First - * searches the C-coded ENTITY_PREFABS[] registry (see - * entityprefablist.h) for a matching name; if none match, falls back to - * loading "prefabs/.json" as a JSON asset and applying it the same - * way an entity's own JSON definition is applied (see entityDeserialize() - * and its "prefab"/"extend" keys). + * Resolves a prefab by name and applies it to the given entity, by + * searching the C-coded ENTITY_PREFABS[] registry (see + * entityprefablist.h) for a matching name. Throws if no prefab with that + * name is registered. * * @param mgr The entity manager that owns the entity. * @param entityId The ID of the entity to apply the prefab to. diff --git a/src/dusk/physics/CMakeLists.txt b/src/dusk/physics/CMakeLists.txt index cf11a8fa..34d5578d 100644 --- a/src/dusk/physics/CMakeLists.txt +++ b/src/dusk/physics/CMakeLists.txt @@ -8,7 +8,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC physicsworld.c physicstest.c - physicsshape.c physicsshapemesh.c triggersystem.c ) diff --git a/src/dusk/physics/physicsshape.c b/src/dusk/physics/physicsshape.c deleted file mode 100644 index eabfe94a..00000000 --- a/src/dusk/physics/physicsshape.c +++ /dev/null @@ -1,131 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "physicsshape.h" -#include "assert/assert.h" -#include "util/string.h" - -errorret_t physicsShapeSerialize( - yyjson_mut_doc *doc, - yyjson_mut_val *parentJson, - const physicsshape_t *shape -) { - yyjson_mut_val *shapeJson = yyjson_mut_obj(doc); - yyjson_mut_obj_add_val(doc, parentJson, "shape", shapeJson); - - switch(shape->type) { - case PHYSICS_SHAPE_CUBE: - yyjson_mut_obj_add_str(doc, shapeJson, "type", "CUBE"); - yyjson_mut_obj_add_real( - doc, shapeJson, "halfExtentX", shape->data.cube.halfExtents[0] - ); - yyjson_mut_obj_add_real( - doc, shapeJson, "halfExtentY", shape->data.cube.halfExtents[1] - ); - yyjson_mut_obj_add_real( - doc, shapeJson, "halfExtentZ", shape->data.cube.halfExtents[2] - ); - break; - case PHYSICS_SHAPE_SPHERE: - yyjson_mut_obj_add_str(doc, shapeJson, "type", "SPHERE"); - yyjson_mut_obj_add_real( - doc, shapeJson, "radius", shape->data.sphere.radius - ); - break; - case PHYSICS_SHAPE_CAPSULE: - yyjson_mut_obj_add_str(doc, shapeJson, "type", "CAPSULE"); - yyjson_mut_obj_add_real( - doc, shapeJson, "radius", shape->data.capsule.radius - ); - yyjson_mut_obj_add_real( - doc, shapeJson, "halfHeight", shape->data.capsule.halfHeight - ); - break; - case PHYSICS_SHAPE_PLANE: - yyjson_mut_obj_add_str(doc, shapeJson, "type", "PLANE"); - yyjson_mut_obj_add_real( - doc, shapeJson, "normalX", shape->data.plane.normal[0] - ); - yyjson_mut_obj_add_real( - doc, shapeJson, "normalY", shape->data.plane.normal[1] - ); - yyjson_mut_obj_add_real( - doc, shapeJson, "normalZ", shape->data.plane.normal[2] - ); - yyjson_mut_obj_add_real( - doc, shapeJson, "distance", shape->data.plane.distance - ); - break; - case PHYSICS_SHAPE_CUSTOM: - errorThrow( - "Cannot serialize a PHYSICS_SHAPE_CUSTOM shape (callback/userData " - "are not representable in JSON)" - ); - default: - assertUnreachable("Unknown physics shape type"); - } - - errorOk(); -} - -errorret_t physicsShapeDeserialize( - yyjson_val *shapeJson, - physicsshape_t *outShape -) { - yyjson_val *typeVal = yyjson_obj_get(shapeJson, "type"); - assertNotNull(typeVal, "Physics shape JSON missing 'type' field"); - const char_t *typeName = yyjson_get_str(typeVal); - yyjson_val *v; - - if(stringEquals(typeName, "CUBE")) { - physicsshape_t s = { .type = PHYSICS_SHAPE_CUBE }; - if((v = yyjson_obj_get(shapeJson, "halfExtentX"))) { - s.data.cube.halfExtents[0] = (float_t)yyjson_get_num(v); - } - if((v = yyjson_obj_get(shapeJson, "halfExtentY"))) { - s.data.cube.halfExtents[1] = (float_t)yyjson_get_num(v); - } - if((v = yyjson_obj_get(shapeJson, "halfExtentZ"))) { - s.data.cube.halfExtents[2] = (float_t)yyjson_get_num(v); - } - *outShape = s; - } else if(stringEquals(typeName, "SPHERE")) { - physicsshape_t s = { .type = PHYSICS_SHAPE_SPHERE }; - if((v = yyjson_obj_get(shapeJson, "radius"))) { - s.data.sphere.radius = (float_t)yyjson_get_num(v); - } - *outShape = s; - } else if(stringEquals(typeName, "CAPSULE")) { - physicsshape_t s = { .type = PHYSICS_SHAPE_CAPSULE }; - if((v = yyjson_obj_get(shapeJson, "radius"))) { - s.data.capsule.radius = (float_t)yyjson_get_num(v); - } - if((v = yyjson_obj_get(shapeJson, "halfHeight"))) { - s.data.capsule.halfHeight = (float_t)yyjson_get_num(v); - } - *outShape = s; - } else if(stringEquals(typeName, "PLANE")) { - physicsshape_t s = { .type = PHYSICS_SHAPE_PLANE }; - if((v = yyjson_obj_get(shapeJson, "normalX"))) { - s.data.plane.normal[0] = (float_t)yyjson_get_num(v); - } - if((v = yyjson_obj_get(shapeJson, "normalY"))) { - s.data.plane.normal[1] = (float_t)yyjson_get_num(v); - } - if((v = yyjson_obj_get(shapeJson, "normalZ"))) { - s.data.plane.normal[2] = (float_t)yyjson_get_num(v); - } - if((v = yyjson_obj_get(shapeJson, "distance"))) { - s.data.plane.distance = (float_t)yyjson_get_num(v); - } - *outShape = s; - } else { - errorThrow("Unknown physics shape type '%s'", typeName); - } - - errorOk(); -} diff --git a/src/dusk/physics/physicsshape.h b/src/dusk/physics/physicsshape.h index 21c4ca74..52f56f0f 100644 --- a/src/dusk/physics/physicsshape.h +++ b/src/dusk/physics/physicsshape.h @@ -8,7 +8,6 @@ #pragma once #include "dusk.h" #include "error/error.h" -#include "yyjson.h" typedef enum { PHYSICS_SHAPE_CUBE, @@ -109,35 +108,3 @@ typedef struct physicsshape_t { physicshapetype_t type; physicsshapedata_t data; } physicsshape_t; - -/** - * Serializes a shape descriptor into a nested "shape" object on the given - * parent JSON object: {"type": "CUBE"/"SPHERE"/"CAPSULE"/"PLANE", ...that - * shape's own fields}. Fails if shape->type is PHYSICS_SHAPE_CUSTOM -- - * custom shapes carry a callback and opaque userData pointer that cannot - * be represented in JSON. - * - * @param doc The mutable JSON document to allocate values from. - * @param parentJson The JSON object to write the "shape" key into. - * @param shape The shape descriptor to serialize. - * @return Error state. - */ -errorret_t physicsShapeSerialize( - yyjson_mut_doc *doc, - yyjson_mut_val *parentJson, - const physicsshape_t *shape -); - -/** - * Deserializes a shape descriptor from a "shape" JSON object (see - * physicsShapeSerialize()). Requires a "type" field; any other field left - * absent defaults to zero on the returned shape. - * - * @param shapeJson The "shape" JSON object to read from. - * @param outShape Destination shape descriptor, fully overwritten. - * @return Error state. - */ -errorret_t physicsShapeDeserialize( - yyjson_val *shapeJson, - physicsshape_t *outShape -); diff --git a/src/dusk/scene/scene.c b/src/dusk/scene/scene.c index d7bd8751..19731e80 100644 --- a/src/dusk/scene/scene.c +++ b/src/dusk/scene/scene.c @@ -6,7 +6,6 @@ #include "scene.h" #include "assert/assert.h" #include "util/memory.h" -#include "util/string.h" #include "time/time.h" #include "display/screen/screen.h" #include "display/shader/shaderunlit.h" @@ -15,7 +14,6 @@ #include "entity/component/display/entityposition.h" #include "entity/component/display/entityrenderable.h" #include "physics/triggersystem.h" -#include "sceneprefab.h" #include "ui/ui.h" #include "console/console.h" @@ -194,144 +192,3 @@ errorret_t sceneDispose(void) { } errorOk(); } - -errorret_t sceneSerialize( - const sceneid_t id, - yyjson_mut_doc *doc, - yyjson_mut_val *json -) { - assertTrue(id < SCENE_COUNT_MAX, "Scene ID OOB"); - assertTrue(SCENE_MANAGER.scenes[id].used, "Scene is not in use"); - assertNotNull(doc, "JSON document cannot be null"); - assertNotNull(json, "JSON object cannot be null"); - - entitymanager_t *mgr = &SCENE_MANAGER.scenes[id].entities; - - yyjson_mut_val *entities = yyjson_mut_arr(doc); - yyjson_mut_obj_add_val(doc, json, "entities", entities); - - for(entityid_t i = 0; i < ENTITY_COUNT_MAX; i++) { - if(!(mgr->entities[i].state & ENTITY_STATE_ACTIVE)) continue; - - yyjson_mut_val *entity = yyjson_mut_obj(doc); - yyjson_mut_arr_add_val(entities, entity); - - errorChain(entitySerialize(mgr, i, doc, entity)); - - // entityPositionSerialize() doesn't know about sibling entities' names, - // so the "parent" cross-reference is written here instead, onto the - // POSITION object entitySerialize() just built. - componentid_t posComp = entityGetComponent(mgr, i, COMPONENT_TYPE_POSITION); - if(posComp == COMPONENT_ID_INVALID) continue; - entityid_t parentEntityId = entityPositionGetParent(mgr, i, posComp); - if(parentEntityId == ENTITY_ID_INVALID) continue; - const char_t *parentName = entityGetName(mgr, parentEntityId); - if(parentName[0] == '\0') continue; - - yyjson_mut_val *components = yyjson_mut_obj_get(entity, "components"); - size_t compIdx, compMax; - yyjson_mut_val *component; - yyjson_mut_arr_foreach(components, compIdx, compMax, component) { - yyjson_mut_val *typeVal = yyjson_mut_obj_get(component, "type"); - if(!stringEquals(yyjson_mut_get_str(typeVal), "POSITION")) continue; - yyjson_mut_obj_add_strcpy(doc, component, "parent", parentName); - break; - } - } - - errorOk(); -} - -errorret_t sceneDeserialize( - const sceneid_t id, - yyjson_val *json -) { - assertTrue(id < SCENE_COUNT_MAX, "Scene ID OOB"); - assertTrue(SCENE_MANAGER.scenes[id].used, "Scene is not in use"); - assertNotNull(json, "JSON object cannot be null"); - - entitymanager_t *mgr = &SCENE_MANAGER.scenes[id].entities; - - yyjson_val *extendVal = yyjson_obj_get(json, "extend"); - if(extendVal) { - errorChain(scenePrefabResolveAndApply(id, yyjson_get_str(extendVal))); - } - - yyjson_val *prefabVal = yyjson_obj_get(json, "prefab"); - if(prefabVal) { - errorChain(scenePrefabResolveAndApply(id, yyjson_get_str(prefabVal))); - } - - yyjson_val *entities = yyjson_obj_get(json, "entities"); - if(!entities) errorOk(); - - entityid_t indexToEntityId[ENTITY_COUNT_MAX]; - size_t idx, max; - yyjson_val *entity; - yyjson_arr_foreach(entities, idx, max, entity) { - entityid_t entityId = entityManagerAdd(mgr); - indexToEntityId[idx] = entityId; - errorChain(entityDeserialize(mgr, entityId, entity)); - } - - // Second pass: every entity now exists (and is findable by name), so - // POSITION's "parent" references (see entityPositionDeserialize()) can - // be resolved regardless of which order parent/child appeared in. - yyjson_arr_foreach(entities, idx, max, entity) { - errorChain(sceneDeserializeResolveParent( - mgr, indexToEntityId[idx], entity - )); - } - - errorOk(); -} - -errorret_t sceneDeserializeResolveParent( - entitymanager_t *mgr, - const entityid_t entityId, - yyjson_val *entityJson -) { - yyjson_val *components = yyjson_obj_get(entityJson, "components"); - if(!components) errorOk(); - - yyjson_val *positionObj = NULL; - size_t idx, max; - yyjson_val *component; - yyjson_arr_foreach(components, idx, max, component) { - yyjson_val *typeVal = yyjson_obj_get(component, "type"); - if(!stringEquals(yyjson_get_str(typeVal), "POSITION")) continue; - positionObj = component; - break; - } - if(!positionObj) errorOk(); - - yyjson_val *parentVal = yyjson_obj_get(positionObj, "parent"); - if(!parentVal) errorOk(); - - const char_t *parentName = yyjson_get_str(parentVal); - entityid_t parentEntityId = entityFindByName(mgr, parentName); - if(parentEntityId == ENTITY_ID_INVALID || parentEntityId == entityId) { - errorThrow("POSITION 'parent' references unknown entity '%s'", parentName); - } - - componentid_t componentId = entityGetComponent( - mgr, entityId, COMPONENT_TYPE_POSITION - ); - componentid_t parentComponentId = entityGetComponent( - mgr, parentEntityId, COMPONENT_TYPE_POSITION - ); - if( - componentId == COMPONENT_ID_INVALID || - parentComponentId == COMPONENT_ID_INVALID - ) { - errorThrow( - "POSITION 'parent' reference '%s' requires both entities to have a " - "POSITION component", parentName - ); - } - - entityPositionSetParent( - mgr, entityId, componentId, parentEntityId, parentComponentId - ); - errorOk(); -} diff --git a/src/dusk/scene/scene.h b/src/dusk/scene/scene.h index 096cc475..83e47320 100644 --- a/src/dusk/scene/scene.h +++ b/src/dusk/scene/scene.h @@ -10,7 +10,6 @@ #include "entity/entitymanager.h" #include "physics/physicsworld.h" #include "error/error.h" -#include "yyjson.h" typedef struct { bool_t used; @@ -121,73 +120,3 @@ errorret_t sceneRender(void); * @return An error if the dispose failed, or errorOk() if it succeeded. */ errorret_t sceneDispose(void); - -/** - * Serializes a scene's active entities into the given JSON object, - * writing an "entities" array of objects (see entitySerialize()). Also - * writes a "parent" name reference onto any entity's POSITION object - * whose entityPositionGetParent() resolves to a named entity (silently - * omitted if the parent has no name -- see entitySetName()). - * - * @param id The ID of the scene to serialize. - * @param doc The mutable JSON document to allocate values from. - * @param json The JSON object to write the scene's "entities" key into. - * @return Error state. - */ -errorret_t sceneSerialize( - const sceneid_t id, - yyjson_mut_doc *doc, - yyjson_mut_val *json -); - -/** - * Deserializes a scene's optional "extend"/"prefab" and its entities from - * the given JSON object. - * - * If present, "extend" and then "prefab" are each resolved and applied as - * a scene prefab (see scenePrefabResolveAndApply()) before "entities" is - * processed -- both keys do the same thing (resolve a named scene - * prefab, C-coded first then a "scenes/.json" asset, and apply it - * to this scene); "extend" is the conventional key when this JSON is - * itself a scene prefab definition inheriting from a parent, "prefab" is - * the conventional key when a plain scene wants to be defined by a - * prefab. Since a scene prefab just spawns more entities, there's no - * collision risk the way there is for entity components -- this scene's - * own "entities" are simply added on top of whatever the prefab spawned. - * - * Then creates one new entity per "entities" array entry (see - * entityDeserialize()), and resolves every entity's POSITION "parent" - * name reference (see sceneDeserializeResolveParent()) now that all - * entities exist and are named. No-op for "entities" if json has no such - * array. - * - * @param id The ID of the scene to deserialize into. - * @param json The JSON object to read the scene's "extend"/"prefab"/ - * "entities" keys from. - * @return Error state. - */ -errorret_t sceneDeserialize( - const sceneid_t id, - yyjson_val *json -); - -/** - * Internal. Resolves a single entity's POSITION "parent" reference (a - * name string, see entityPositionDeserialize()), if present, into a real - * parent link via entityPositionSetParent(). Called by sceneDeserialize() - * once every entity in the scene has been created and named, since - * resolving a name to an entityId requires entityFindByName() to be able - * to see the whole scene. No-op if entityJson has no POSITION component - * or its POSITION has no "parent" field. - * - * @param mgr The entity manager the entity belongs to. - * @param entityId The entity ID to resolve the parent reference for. - * @param entityJson The entity's own JSON object (as found in the - * scene's "entities" array). - * @return Error state. - */ -errorret_t sceneDeserializeResolveParent( - entitymanager_t *mgr, - const entityid_t entityId, - yyjson_val *entityJson -); diff --git a/src/dusk/scene/sceneprefab.c b/src/dusk/scene/sceneprefab.c index 695946fa..d5795e7b 100644 --- a/src/dusk/scene/sceneprefab.c +++ b/src/dusk/scene/sceneprefab.c @@ -6,11 +6,8 @@ */ #include "sceneprefab.h" -#include "scene.h" -#include "asset/asset.h" #include "util/string.h" #include "assert/assert.h" -#include "yyjson.h" // Pulls in every scene prefab's real header (sceneprefablist.h's one-time // #include lines) exactly once -- each header's #pragma once then makes @@ -53,16 +50,5 @@ errorret_t scenePrefabResolveAndApply( errorOk(); } - char_t assetPath[ASSET_FILE_NAME_MAX]; - stringFormat(assetPath, ASSET_FILE_NAME_MAX - 1, "scenes/%s.json", name); - - assetentry_t *entry = assetLock(assetPath, ASSET_LOADER_TYPE_JSON, NULL); - errorret_t ret = assetRequireLoaded(entry); - if(errorIsOk(ret)) { - ret = sceneDeserialize(sceneId, yyjson_doc_get_root(entry->data.json)); - } - assetUnlockEntry(entry); - errorChain(ret); - - errorOk(); + errorThrow("Unknown scene prefab '%s'", name); } diff --git a/src/dusk/scene/sceneprefab.h b/src/dusk/scene/sceneprefab.h index 0e766f0d..7991ec5d 100644 --- a/src/dusk/scene/sceneprefab.h +++ b/src/dusk/scene/sceneprefab.h @@ -50,12 +50,10 @@ errorret_t scenePrefabInit( ); /** - * Resolves a scene prefab by name and applies it to the given scene. - * First searches the C-coded SCENE_PREFABS[] registry (see - * sceneprefablist.h) for a matching name; if none match, falls back to - * loading "scenes/.json" as a JSON asset and applying it the same - * way a scene's own JSON definition is applied (see sceneDeserialize() - * and its "prefab"/"extend" keys). + * Resolves a scene prefab by name and applies it to the given scene, by + * searching the C-coded SCENE_PREFABS[] registry (see + * sceneprefablist.h) for a matching name. Throws if no prefab with that + * name is registered. * * @param sceneId The ID of the scene to apply the prefab to. * @param name The scene prefab name to resolve. diff --git a/src/dusk/script/module/CMakeLists.txt b/src/dusk/script/module/CMakeLists.txt new file mode 100644 index 00000000..d9aecb8a --- /dev/null +++ b/src/dusk/script/module/CMakeLists.txt @@ -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 + modulelist.c +) + +add_subdirectory(entity) +add_subdirectory(scene) diff --git a/src/dusk/script/module/entity/CMakeLists.txt b/src/dusk/script/module/entity/CMakeLists.txt new file mode 100644 index 00000000..7e9a8f69 --- /dev/null +++ b/src/dusk/script/module/entity/CMakeLists.txt @@ -0,0 +1,11 @@ +# 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 + moduleentity.c +) + +add_subdirectory(component) diff --git a/src/dusk/script/module/entity/component/CMakeLists.txt b/src/dusk/script/module/entity/component/CMakeLists.txt new file mode 100644 index 00000000..60460869 --- /dev/null +++ b/src/dusk/script/module/entity/component/CMakeLists.txt @@ -0,0 +1,9 @@ +# 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 + modulecomponent.c +) diff --git a/src/dusk/script/module/entity/moduleentity.c b/src/dusk/script/module/entity/moduleentity.c new file mode 100644 index 00000000..8cb8fc2d --- /dev/null +++ b/src/dusk/script/module/entity/moduleentity.c @@ -0,0 +1,145 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "moduleentity.h" +#include "script/module/modulebase.h" +#include "script/module/entity/component/modulecomponent.h" +#include "scene/scene.h" +#include "entity/entitymanager.h" +#include "entity/entity.h" +#include "entity/component.h" +#include "util/string.h" + +scriptproto_t MODULE_ENTITY_PROTO; + +moduleBaseFunction(moduleEntityConstructor) { + sceneid_t sceneId = sceneGetActive(); + if(sceneId == SCENE_ID_INVALID) { + return moduleBaseThrow("Entity: no active scene"); + } + + moduleentityhandle_t *inst = (moduleentityhandle_t *)memoryAllocate( + sizeof(moduleentityhandle_t) + ); + inst->mgr = sceneGetEntities(sceneId); + inst->id = entityManagerAdd(inst->mgr); + + jerry_object_set_native_ptr( + callInfo->this_value, &MODULE_ENTITY_PROTO.info, inst + ); + return jerry_undefined(); +} + +moduleBaseFunction(moduleEntityGetId) { + moduleBaseGetOrReturn(moduleentityhandle_t, inst, moduleEntityGet); + return jerry_number(inst->id); +} + +moduleBaseFunction(moduleEntityGetName) { + moduleBaseGetOrReturn(moduleentityhandle_t, inst, moduleEntityGet); + return jerry_string_sz(entityGetName(inst->mgr, inst->id)); +} + +moduleBaseFunction(moduleEntitySetName) { + moduleBaseRequireArgs(1); moduleBaseRequireString(0); + moduleBaseGetOrReturn(moduleentityhandle_t, inst, moduleEntityGet); + + char_t name[ENTITY_NAME_MAX]; + moduleBaseToString(args[0], name, sizeof(name)); + entitySetName(inst->mgr, inst->id, name); + return jerry_undefined(); +} + +moduleBaseFunction(moduleEntityAddComponent) { + moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); + componenttype_t type = (componenttype_t)moduleBaseArgInt(0); + if(type <= COMPONENT_TYPE_NULL || type >= COMPONENT_TYPE_COUNT) { + return moduleBaseThrow("Entity.add: invalid component type"); + } + + moduleBaseGetOrReturn(moduleentityhandle_t, inst, moduleEntityGet); + + componentid_t id = entityAddComponent(inst->mgr, inst->id, type); + modulecomponenthandle_t h = { + .mgr = inst->mgr, .entityId = inst->id, .componentId = id, .type = type + }; + return moduleComponentCreate(&h); +} + +moduleBaseFunction(moduleEntityGetComponentMethod) { + moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); + componenttype_t type = (componenttype_t)moduleBaseArgInt(0); + if(type <= COMPONENT_TYPE_NULL || type >= COMPONENT_TYPE_COUNT) { + return moduleBaseThrow("Entity.getComponent: invalid component type"); + } + + moduleBaseGetOrReturn(moduleentityhandle_t, inst, moduleEntityGet); + + componentid_t id = entityGetComponent(inst->mgr, inst->id, type); + if(id == COMPONENT_ID_INVALID) return jerry_undefined(); + + modulecomponenthandle_t h = { + .mgr = inst->mgr, .entityId = inst->id, .componentId = id, .type = type + }; + return moduleComponentCreate(&h); +} + +moduleBaseFunction(moduleEntityDisposeMethod) { + moduleBaseGetOrReturn(moduleentityhandle_t, inst, moduleEntityGet); + entityDisposeDeep(inst->mgr, inst->id); + return jerry_undefined(); +} + +moduleBaseFunction(moduleEntityToString) { + moduleentityhandle_t *inst = moduleEntityGet(callInfo); + if(!inst) return jerry_string_sz("Entity(?)"); + + char_t buf[32]; + stringFormat(buf, sizeof(buf), "Entity(id=%d)", inst->id); + return jerry_string_sz(buf); +} + +void moduleEntityInit(void) { + scriptProtoInit( + &MODULE_ENTITY_PROTO, + "Entity", + sizeof(moduleentityhandle_t), + moduleEntityConstructor + ); + + scriptProtoDefineToString(&MODULE_ENTITY_PROTO, moduleEntityToString); + + scriptProtoDefineFunc(&MODULE_ENTITY_PROTO, "add", moduleEntityAddComponent); + scriptProtoDefineFunc( + &MODULE_ENTITY_PROTO, "getComponent", moduleEntityGetComponentMethod + ); + scriptProtoDefineFunc( + &MODULE_ENTITY_PROTO, "dispose", moduleEntityDisposeMethod + ); + + scriptProtoDefineProp(&MODULE_ENTITY_PROTO, "id", moduleEntityGetId, NULL); + scriptProtoDefineProp( + &MODULE_ENTITY_PROTO, "name", moduleEntityGetName, moduleEntitySetName + ); + + // Register every currently-registered component type (see + // componentlist.h) as a JS global integer constant usable with + // Entity.add()/getComponent(). + #define X(enumName, type, field, init, dispose, render) \ + moduleBaseSetInt(#enumName, COMPONENT_TYPE_##enumName); + #include "entity/componentlist.h" + #undef X +} + +void moduleEntityDispose(void) { +} + +moduleentityhandle_t *moduleEntityGet(const jerry_call_info_t *callInfo) { + return (moduleentityhandle_t *)scriptProtoGetValue( + &MODULE_ENTITY_PROTO, callInfo->this_value + ); +} diff --git a/src/dusk/script/module/modulelist.c b/src/dusk/script/module/modulelist.c new file mode 100644 index 00000000..6cce6ec6 --- /dev/null +++ b/src/dusk/script/module/modulelist.c @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "modulelist.h" +#include "script/module/moduleplatform.h" +#include "script/module/entity/component/modulecomponent.h" +#include "script/module/entity/moduleentity.h" +#include "script/module/scene/modulescene.h" + +void moduleListInit(void) { + modulePlatform(); + moduleComponentInit(); + moduleEntityInit(); + moduleSceneInit(); +} + +void moduleListDispose(void) { + moduleSceneDispose(); + moduleEntityDispose(); + moduleComponentDispose(); +} diff --git a/src/dusk/script/module/modulelist.h b/src/dusk/script/module/modulelist.h new file mode 100644 index 00000000..30cf2a26 --- /dev/null +++ b/src/dusk/script/module/modulelist.h @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once + +/** + * Registers every script module (Component, Entity, Scene, the platform + * globals). Called once by scriptManagerInit(). + */ +void moduleListInit(void); + +/** + * Disposes every script module's resources. Called once by + * scriptManagerDispose(), before jerry_cleanup(). + */ +void moduleListDispose(void); diff --git a/src/dusk/script/module/scene/CMakeLists.txt b/src/dusk/script/module/scene/CMakeLists.txt new file mode 100644 index 00000000..4ef416e3 --- /dev/null +++ b/src/dusk/script/module/scene/CMakeLists.txt @@ -0,0 +1,9 @@ +# 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 + modulescene.c +) diff --git a/src/dusk/script/module/scene/modulescene.c b/src/dusk/script/module/scene/modulescene.c new file mode 100644 index 00000000..7591123f --- /dev/null +++ b/src/dusk/script/module/scene/modulescene.c @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "modulescene.h" +#include "script/module/modulebase.h" +#include "scene/scene.h" +#include "util/string.h" + +scriptproto_t MODULE_SCENE_PROTO; + +moduleBaseFunction(moduleSceneConstructor) { + modulescenehandle_t *inst = (modulescenehandle_t *)memoryAllocate( + sizeof(modulescenehandle_t) + ); + inst->id = sceneCreate(); + + jerry_object_set_native_ptr( + callInfo->this_value, &MODULE_SCENE_PROTO.info, inst + ); + return jerry_undefined(); +} + +moduleBaseFunction(moduleSceneGetId) { + moduleBaseGetOrReturn(modulescenehandle_t, inst, moduleSceneGet); + return jerry_number(inst->id); +} + +moduleBaseFunction(moduleSceneSetActiveMethod) { + moduleBaseGetOrReturn(modulescenehandle_t, inst, moduleSceneGet); + sceneSetActive(inst->id); + return jerry_undefined(); +} + +moduleBaseFunction(moduleSceneDisposeMethod) { + moduleBaseGetOrReturn(modulescenehandle_t, inst, moduleSceneGet); + sceneDestroy(inst->id); + return jerry_undefined(); +} + +moduleBaseFunction(moduleSceneGetActiveStatic) { + sceneid_t activeId = sceneGetActive(); + if(activeId == SCENE_ID_INVALID) return jerry_undefined(); + + modulescenehandle_t h = { .id = activeId }; + return scriptProtoCreateValue(&MODULE_SCENE_PROTO, &h); +} + +moduleBaseFunction(moduleSceneToString) { + modulescenehandle_t *inst = moduleSceneGet(callInfo); + if(!inst) return jerry_string_sz("Scene(?)"); + + char_t buf[32]; + stringFormat(buf, sizeof(buf), "Scene(id=%d)", inst->id); + return jerry_string_sz(buf); +} + +void moduleSceneInit(void) { + scriptProtoInit( + &MODULE_SCENE_PROTO, + "Scene", + sizeof(modulescenehandle_t), + moduleSceneConstructor + ); + + scriptProtoDefineToString(&MODULE_SCENE_PROTO, moduleSceneToString); + + scriptProtoDefineProp(&MODULE_SCENE_PROTO, "id", moduleSceneGetId, NULL); + scriptProtoDefineFunc( + &MODULE_SCENE_PROTO, "setActive", moduleSceneSetActiveMethod + ); + scriptProtoDefineFunc( + &MODULE_SCENE_PROTO, "dispose", moduleSceneDisposeMethod + ); + + scriptProtoDefineStaticFunc( + &MODULE_SCENE_PROTO, "getActive", moduleSceneGetActiveStatic + ); +} + +void moduleSceneDispose(void) { +} + +modulescenehandle_t *moduleSceneGet(const jerry_call_info_t *callInfo) { + return (modulescenehandle_t *)scriptProtoGetValue( + &MODULE_SCENE_PROTO, callInfo->this_value + ); +} diff --git a/src/dusk/script/module/scene/modulescene.h b/src/dusk/script/module/scene/modulescene.h new file mode 100644 index 00000000..6aaf18a7 --- /dev/null +++ b/src/dusk/script/module/scene/modulescene.h @@ -0,0 +1,41 @@ +/** + * 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 "scene/scenebase.h" +#include + +/** Native data wrapped by a JS Scene instance. */ +typedef struct { + sceneid_t id; +} modulescenehandle_t; + +extern scriptproto_t MODULE_SCENE_PROTO; + +/** + * Registers the Scene class. `new Scene()` creates a new, empty scene + * (see sceneCreate()) -- it is not made active automatically, call + * `.setActive()` for that. `Entity`/`Component` always operate on + * whichever scene is currently active (see sceneGetActive()), not on a + * specific Scene instance. + */ +void moduleSceneInit(void); + +/** + * Disposes the Scene class's script resources. + */ +void moduleSceneDispose(void); + +/** + * Internal. Gets the native handle wrapped by a Scene 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 Scene. + */ +modulescenehandle_t *moduleSceneGet(const jerry_call_info_t *callInfo); diff --git a/src/duskrpg/entity/component/overworld/entityinteractable.c b/src/duskrpg/entity/component/overworld/entityinteractable.c index 188b084b..14297c0e 100644 --- a/src/duskrpg/entity/component/overworld/entityinteractable.c +++ b/src/duskrpg/entity/component/overworld/entityinteractable.c @@ -156,91 +156,3 @@ errorret_t entityInteractableTryInteract( errorOk(); } - -errorret_t entityInteractableSerialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_mut_doc *doc, - yyjson_mut_val *json -) { - entityinteractable_t *interactable = entityInteractableGet( - mgr, entityId, componentId - ); - yyjson_mut_obj_add_bool(doc, json, "enabled", interactable->enabled); - - const char_t *typeName; - switch(interactable->type) { - case ENTITY_INTERACTABLE_TYPE_ITEM_PICKUP: - typeName = "ITEM_PICKUP"; - break; - case ENTITY_INTERACTABLE_TYPE_CUTSCENE: - typeName = "CUTSCENE"; - break; - case ENTITY_INTERACTABLE_TYPE_FUNCTION: - typeName = "FUNCTION"; - break; - default: - assertUnreachable("Unknown interactable type"); - } - // "kind", not "type" -- the JSON object's own "type" key is already the - // outer component-type discriminator ("INTERACTABLE"); this is this - // interactable's own sub-type. - yyjson_mut_obj_add_str(doc, json, "kind", typeName); - - if(interactable->type == ENTITY_INTERACTABLE_TYPE_ITEM_PICKUP) { - yyjson_mut_obj_add_uint( - doc, json, "pickupId", interactable->data.itemPickup.pickupId - ); - if(interactable->data.itemPickup.message[0] != '\0') { - yyjson_mut_obj_add_str( - doc, json, "message", interactable->data.itemPickup.message - ); - } - } - - errorOk(); -} - -errorret_t entityInteractableDeserialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_val *json -) { - yyjson_val *v; - if((v = yyjson_obj_get(json, "enabled"))) { - entityInteractableSetEnabled( - mgr, entityId, componentId, yyjson_get_bool(v) - ); - } - - if((v = yyjson_obj_get(json, "kind"))) { - const char_t *typeName = yyjson_get_str(v); - if(stringEquals(typeName, "ITEM_PICKUP")) { - yyjson_val *pickupIdVal = yyjson_obj_get(json, "pickupId"); - itempickupid_t pickupId = pickupIdVal - ? (itempickupid_t)yyjson_get_uint(pickupIdVal) : 0; - yyjson_val *messageVal = yyjson_obj_get(json, "message"); - const char_t *message = messageVal ? yyjson_get_str(messageVal) : NULL; - entityInteractableSetItemPickup( - mgr, entityId, componentId, pickupId, message - ); - } else if(stringEquals(typeName, "CUTSCENE")) { - errorThrow( - "Cannot deserialize a CUTSCENE interactable -- its cutscene_t " - "pointer is runtime-only and must be set via " - "entityInteractableSetCutscene()" - ); - } else if(stringEquals(typeName, "FUNCTION")) { - errorThrow( - "Cannot deserialize a FUNCTION interactable -- its callback is " - "runtime-only and must be set via entityInteractableSetFunction()" - ); - } else { - errorThrow("Unknown interactable type '%s'", typeName); - } - } - - errorOk(); -} diff --git a/src/duskrpg/entity/component/overworld/entityinteractable.h b/src/duskrpg/entity/component/overworld/entityinteractable.h index c212edc7..d69cb2ac 100644 --- a/src/duskrpg/entity/component/overworld/entityinteractable.h +++ b/src/duskrpg/entity/component/overworld/entityinteractable.h @@ -8,7 +8,6 @@ #pragma once #include "entity/entitybase.h" #include "error/error.h" -#include "yyjson.h" typedef struct cutscene_s cutscene_t; @@ -231,45 +230,3 @@ errorret_t entityInteractableTryInteract( const componentid_t componentId, const entityid_t interactorEntityId ); - -/** - * Serializes the interactable's "enabled", "kind" - * ("ITEM_PICKUP"/"CUTSCENE"/"FUNCTION" -- named "kind" rather than "type" - * since the JSON object's "type" key is already the outer - * component-type discriminator, "INTERACTABLE"), and (for ITEM_PICKUP - * only) "pickupId" and (if non-empty) "message" into the given JSON - * object. FUNCTION callbacks are runtime-only (a C function pointer) and - * are not serialized. - * - * @param mgr The entity manager that owns the entity. - * @param entityId The entity ID. - * @param componentId The component ID. - * @param doc The mutable JSON document to allocate values from. - * @param json The JSON object to write the component's fields into. - * @return Error state. - */ -errorret_t entityInteractableSerialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_mut_doc *doc, - yyjson_mut_val *json -); - -/** - * Reads "enabled", "kind", "pickupId", and "message" (see - * entityInteractableSerialize()) from the given JSON object and applies - * whichever are present, leaving the rest at their current values. - * - * @param mgr The entity manager that owns the entity. - * @param entityId The entity ID. - * @param componentId The component ID. - * @param json The JSON object to read the component's fields from. - * @return Error state. - */ -errorret_t entityInteractableDeserialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_val *json -); diff --git a/src/duskrpg/entity/component/overworld/entityplayer.c b/src/duskrpg/entity/component/overworld/entityplayer.c index adf785f9..cf20e95d 100644 --- a/src/duskrpg/entity/component/overworld/entityplayer.c +++ b/src/duskrpg/entity/component/overworld/entityplayer.c @@ -181,41 +181,3 @@ void entityPlayerTryInteract( return; } } - -errorret_t entityPlayerSerialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_mut_doc *doc, - yyjson_mut_val *json -) { - entityplayer_t *player = entityPlayerGet(mgr, entityId, componentId); - - yyjson_mut_obj_add_real(doc, json, "moveSpeed", player->moveSpeed); - yyjson_mut_obj_add_real(doc, json, "jumpImpulse", player->jumpImpulse); - yyjson_mut_obj_add_real(doc, json, "turnSpeed", player->turnSpeed); - - errorOk(); -} - -errorret_t entityPlayerDeserialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_val *json -) { - entityplayer_t *player = entityPlayerGet(mgr, entityId, componentId); - - yyjson_val *v; - if((v = yyjson_obj_get(json, "moveSpeed"))) { - player->moveSpeed = (float_t)yyjson_get_num(v); - } - if((v = yyjson_obj_get(json, "jumpImpulse"))) { - player->jumpImpulse = (float_t)yyjson_get_num(v); - } - if((v = yyjson_obj_get(json, "turnSpeed"))) { - player->turnSpeed = (float_t)yyjson_get_num(v); - } - - errorOk(); -} diff --git a/src/duskrpg/entity/component/overworld/entityplayer.h b/src/duskrpg/entity/component/overworld/entityplayer.h index 6e106b63..98bbd894 100644 --- a/src/duskrpg/entity/component/overworld/entityplayer.h +++ b/src/duskrpg/entity/component/overworld/entityplayer.h @@ -8,7 +8,6 @@ #pragma once #include "entity/entitybase.h" #include "error/error.h" -#include "yyjson.h" typedef struct { float_t moveSpeed; @@ -122,40 +121,3 @@ void entityPlayerTryInteract( entitymanager_t *mgr, const entityid_t entityId ); - -/** - * Serializes the player's "moveSpeed", "jumpImpulse", and "turnSpeed" - * into the given JSON object. - * - * @param mgr The entity manager that owns the entity. - * @param entityId The entity ID. - * @param componentId The component ID. - * @param doc The mutable JSON document to allocate values from. - * @param json The JSON object to write the component's fields into. - * @return Error state. - */ -errorret_t entityPlayerSerialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_mut_doc *doc, - yyjson_mut_val *json -); - -/** - * Reads "moveSpeed", "jumpImpulse", and "turnSpeed" (see - * entityPlayerSerialize()) from the given JSON object and applies - * whichever are present, leaving the rest at their current values. - * - * @param mgr The entity manager that owns the entity. - * @param entityId The entity ID. - * @param componentId The component ID. - * @param json The JSON object to read the component's fields from. - * @return Error state. - */ -errorret_t entityPlayerDeserialize( - entitymanager_t *mgr, - const entityid_t entityId, - const componentid_t componentId, - yyjson_val *json -); diff --git a/src/duskrpg/entity/gamecomponentlist.h b/src/duskrpg/entity/gamecomponentlist.h index 64d7a2c6..e1c08aac 100644 --- a/src/duskrpg/entity/gamecomponentlist.h +++ b/src/duskrpg/entity/gamecomponentlist.h @@ -16,10 +16,7 @@ // Init function (optional) // Dispose function (optional) // Render function (optional) -// Serialize function (optional) -// Deserialize function (optional) -X(PLAYER, entityplayer_t, player, entityPlayerInit, NULL, NULL, - entityPlayerSerialize, entityPlayerDeserialize) +X(PLAYER, entityplayer_t, player, entityPlayerInit, NULL, NULL) X(INTERACTABLE, entityinteractable_t, interactable, entityInteractableInit, - NULL, NULL, entityInteractableSerialize, entityInteractableDeserialize) + NULL, NULL) diff --git a/src/duskrpg/entity/prefab/entityprefaboverworldcamera.h b/src/duskrpg/entity/prefab/entityprefaboverworldcamera.h index d79b24d0..85e3a4d7 100644 --- a/src/duskrpg/entity/prefab/entityprefaboverworldcamera.h +++ b/src/duskrpg/entity/prefab/entityprefaboverworldcamera.h @@ -12,8 +12,7 @@ /** * Applies the OVERWORLD_CAMERA prefab: a POSITION at (10, 10, 10) looking * at the world origin, and a perspective CAMERA (0.1/1000 near/far clip, - * 0.9 fov). C-coded equivalent of the hand-authored "camera" entity - * previously in assets/scenes/test.json. + * 0.9 fov). * * @param mgr The entity manager that owns the entity. * @param entityId The ID of the entity to apply the prefab to. diff --git a/src/duskrpg/entity/prefab/entityprefabplayer.h b/src/duskrpg/entity/prefab/entityprefabplayer.h index 070e0356..f72e84f1 100644 --- a/src/duskrpg/entity/prefab/entityprefabplayer.h +++ b/src/duskrpg/entity/prefab/entityprefabplayer.h @@ -13,9 +13,7 @@ * Applies the PLAYER prefab: a POSITION 5 units up, a red * SHADER_MATERIAL RENDERABLE, a PLAYER component, a DYNAMIC PHYSICS body * tagged with collideMask 0x3 (world + player layers), and a 2x2x2 cube - * TRIGGER (the interact box checked by entityPlayerTryInteract()). C-coded - * equivalent of the hand-authored "player" entity in - * assets/scenes/test.json. + * TRIGGER (the interact box checked by entityPlayerTryInteract()). * * @param mgr The entity manager that owns the entity. * @param entityId The ID of the entity to apply the prefab to. diff --git a/src/duskrpg/game/game.c b/src/duskrpg/game/game.c index baef87b6..b76b7fa9 100644 --- a/src/duskrpg/game/game.c +++ b/src/duskrpg/game/game.c @@ -8,32 +8,9 @@ #include "game/game.h" #include "scene/scene.h" #include "scene/overworldscene.h" -#include "assert/assert.h" -#include "asset/asset.h" -#include "yyjson.h" - -#define GAME_TEST_SCENE_ASSET "scenes/test.json" errorret_t gameInit(void) { - // overworldSceneCreate(); - - sceneid_t testSceneId = sceneCreate(); - - // Exercises sceneDeserialize() end-to-end: a camera looking at a - // colored cube that falls under gravity onto a static ground plane, - // described entirely as JSON rather than built with the entity API. - assetentry_t *sceneEntry = assetLock( - GAME_TEST_SCENE_ASSET, ASSET_LOADER_TYPE_JSON, NULL - ); - errorret_t ret = assetRequireLoaded(sceneEntry); - if(errorIsOk(ret)) { - ret = sceneDeserialize( - testSceneId, yyjson_doc_get_root(sceneEntry->data.json) - ); - } - assetUnlockEntry(sceneEntry); - errorChain(ret); - + sceneid_t testSceneId = overworldSceneCreate(); sceneSetActive(testSceneId); errorOk(); } diff --git a/test/entity/test_entityposition.c b/test/entity/test_entityposition.c index a8b24bc3..598b0784 100644 --- a/test/entity/test_entityposition.c +++ b/test/entity/test_entityposition.c @@ -94,50 +94,6 @@ static void test_entityPositionParentChildWorldTransform(void **state) { assert_int_equal(memoryGetAllocatedCount(), 0); } -static void test_entityPositionDeserializeLookAt(void **state) { - entitymanager_t mgr; - entityManagerInit(&mgr); - - entityid_t entity = entityManagerAdd(&mgr); - componentid_t comp = entityAddComponent( - &mgr, entity, COMPONENT_TYPE_POSITION - ); - - const char_t *json = - "{ \"x\": 0, \"y\": 5, \"z\": 10, " - "\"lookAt\": { \"x\": 0, \"y\": 0, \"z\": 0 } }"; - yyjson_doc *doc = yyjson_read(json, strlen(json), YYJSON_READ_NOFLAG); - assert_non_null(doc); - - errorret_t ret = entityPositionDeserialize( - &mgr, entity, comp, yyjson_doc_get_root(doc) - ); - yyjson_doc_free(doc); - assert_true(errorIsOk(ret)); - - // The eye point decoded back out of localTransform should match "x"/"y"/"z". - vec3 eye; - entityPositionGetLocalPosition(&mgr, entity, comp, eye); - assert_float_equal(eye[0], 0.0f, 0.0001f); - assert_float_equal(eye[1], 5.0f, 0.0001f); - assert_float_equal(eye[2], 10.0f, 0.0001f); - - // Forward (-local Z, see entityCameraGetForward's convention) should - // point from eye toward the lookAt target (the origin here). - mat4 transform; - entityPositionGetLocalTransform(&mgr, entity, comp, transform); - vec3 forward = { -transform[2][0], -transform[2][1], -transform[2][2] }; - glm_vec3_normalize(forward); - - vec3 toTarget = { -eye[0], -eye[1], -eye[2] }; - glm_vec3_normalize(toTarget); - - assert_float_equal(glm_vec3_dot(forward, toTarget), 1.0f, 0.001f); - - entityManagerDispose(&mgr); - assert_int_equal(memoryGetAllocatedCount(), 0); -} - static void test_entityPositionDisposeDeep(void **state) { entitymanager_t mgr; entityManagerInit(&mgr); @@ -167,7 +123,6 @@ int main(int argc, char **argv) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_entityPositionLocalGetSet), cmocka_unit_test(test_entityPositionParentChildWorldTransform), - cmocka_unit_test(test_entityPositionDeserializeLookAt), cmocka_unit_test(test_entityPositionDisposeDeep), }; diff --git a/test/entity/test_entitytrigger.c b/test/entity/test_entitytrigger.c index 3a0098d4..83a1a96d 100644 --- a/test/entity/test_entitytrigger.c +++ b/test/entity/test_entitytrigger.c @@ -110,56 +110,6 @@ static void test_entityTriggerDefaultsAndAccessors(void **state) { assert_int_equal(memoryGetAllocatedCount(), 0); } -static void test_entityTriggerSerializeRoundTrip(void **state) { - entitymanager_t mgr; - entityManagerInit(&mgr); - - entityid_t entity = entityManagerAdd(&mgr); - componentid_t trig = entityAddComponent( - &mgr, entity, COMPONENT_TYPE_TRIGGER - ); - - physicsshape_t sphere = { - .type = PHYSICS_SHAPE_SPHERE, - .data.sphere.radius = 3.0f - }; - entityTriggerSetShape(&mgr, entity, trig, sphere); - entityTriggerSetCollideMask(&mgr, entity, trig, 0x4); - - yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL); - yyjson_mut_val *root = yyjson_mut_obj(doc); - yyjson_mut_doc_set_root(doc, root); - assert_true( - errorIsOk(entityTriggerSerialize(&mgr, entity, trig, doc, root)) - ); - - size_t len = 0; - char_t *jsonStr = yyjson_mut_write(doc, 0, &len); - assert_non_null(jsonStr); - yyjson_mut_doc_free(doc); - - yyjson_doc *readDoc = yyjson_read(jsonStr, len, 0); - free(jsonStr); - assert_non_null(readDoc); - - entityid_t entity2 = entityManagerAdd(&mgr); - componentid_t trig2 = entityAddComponent( - &mgr, entity2, COMPONENT_TYPE_TRIGGER - ); - assert_true(errorIsOk(entityTriggerDeserialize( - &mgr, entity2, trig2, yyjson_doc_get_root(readDoc) - ))); - yyjson_doc_free(readDoc); - - physicsshape_t got = entityTriggerGetShape(&mgr, entity2, trig2); - assert_int_equal(got.type, PHYSICS_SHAPE_SPHERE); - assert_float_equal(got.data.sphere.radius, 3.0f, 0.0001f); - assert_int_equal(entityTriggerGetCollideMask(&mgr, entity2, trig2), 0x4); - - entityManagerDispose(&mgr); - assert_int_equal(memoryGetAllocatedCount(), 0); -} - static void test_entityTriggerEventSubscription(void **state) { test_resetCallbackCounts(); @@ -203,7 +153,6 @@ static void test_entityTriggerEventSubscription(void **state) { int main(int argc, char **argv) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_entityTriggerDefaultsAndAccessors), - cmocka_unit_test(test_entityTriggerSerializeRoundTrip), cmocka_unit_test(test_entityTriggerEventSubscription), }; diff --git a/test/physics/CMakeLists.txt b/test/physics/CMakeLists.txt index af8c65e5..e7603597 100644 --- a/test/physics/CMakeLists.txt +++ b/test/physics/CMakeLists.txt @@ -9,5 +9,4 @@ include(dusktest) dusktest(test_physicstest.c) dusktest(test_physicsworld.c) dusktest(test_physicsshapemesh.c) -dusktest(test_physicsshape.c) dusktest(test_triggersystem.c) diff --git a/test/physics/test_physicsshape.c b/test/physics/test_physicsshape.c deleted file mode 100644 index 091e3142..00000000 --- a/test/physics/test_physicsshape.c +++ /dev/null @@ -1,153 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "dusktest.h" -#include "util/memory.h" -#include "physics/physicsshape.h" - -static yyjson_val *test_roundTripToImmutable( - yyjson_mut_doc *doc, - yyjson_mut_val *root, - yyjson_doc **outReadDoc -) { - size_t len = 0; - char_t *jsonStr = yyjson_mut_write(doc, 0, &len); - assert_non_null(jsonStr); - yyjson_mut_doc_free(doc); - - *outReadDoc = yyjson_read(jsonStr, len, 0); - free(jsonStr); - assert_non_null(*outReadDoc); - return yyjson_doc_get_root(*outReadDoc); -} - -static void test_physicsShapeCubeRoundTrip(void **state) { - physicsshape_t shape = { - .type = PHYSICS_SHAPE_CUBE, - .data.cube.halfExtents = { 1.0f, 2.0f, 3.0f } - }; - - yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL); - yyjson_mut_val *root = yyjson_mut_obj(doc); - yyjson_mut_doc_set_root(doc, root); - assert_true(errorIsOk(physicsShapeSerialize(doc, root, &shape))); - - yyjson_doc *readDoc; - yyjson_val *root2 = test_roundTripToImmutable(doc, root, &readDoc); - - physicsshape_t got; - assert_true(errorIsOk( - physicsShapeDeserialize(yyjson_obj_get(root2, "shape"), &got) - )); - yyjson_doc_free(readDoc); - - assert_int_equal(got.type, PHYSICS_SHAPE_CUBE); - assert_float_equal(got.data.cube.halfExtents[0], 1.0f, 0.0001f); - assert_float_equal(got.data.cube.halfExtents[1], 2.0f, 0.0001f); - assert_float_equal(got.data.cube.halfExtents[2], 3.0f, 0.0001f); -} - -static void test_physicsShapeSphereRoundTrip(void **state) { - physicsshape_t shape = { - .type = PHYSICS_SHAPE_SPHERE, - .data.sphere.radius = 4.5f - }; - - yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL); - yyjson_mut_val *root = yyjson_mut_obj(doc); - yyjson_mut_doc_set_root(doc, root); - assert_true(errorIsOk(physicsShapeSerialize(doc, root, &shape))); - - yyjson_doc *readDoc; - yyjson_val *root2 = test_roundTripToImmutable(doc, root, &readDoc); - - physicsshape_t got; - assert_true(errorIsOk( - physicsShapeDeserialize(yyjson_obj_get(root2, "shape"), &got) - )); - yyjson_doc_free(readDoc); - - assert_int_equal(got.type, PHYSICS_SHAPE_SPHERE); - assert_float_equal(got.data.sphere.radius, 4.5f, 0.0001f); -} - -static void test_physicsShapeCapsuleRoundTrip(void **state) { - physicsshape_t shape = { - .type = PHYSICS_SHAPE_CAPSULE, - .data.capsule = { .radius = 0.5f, .halfHeight = 1.5f } - }; - - yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL); - yyjson_mut_val *root = yyjson_mut_obj(doc); - yyjson_mut_doc_set_root(doc, root); - assert_true(errorIsOk(physicsShapeSerialize(doc, root, &shape))); - - yyjson_doc *readDoc; - yyjson_val *root2 = test_roundTripToImmutable(doc, root, &readDoc); - - physicsshape_t got; - assert_true(errorIsOk( - physicsShapeDeserialize(yyjson_obj_get(root2, "shape"), &got) - )); - yyjson_doc_free(readDoc); - - assert_int_equal(got.type, PHYSICS_SHAPE_CAPSULE); - assert_float_equal(got.data.capsule.radius, 0.5f, 0.0001f); - assert_float_equal(got.data.capsule.halfHeight, 1.5f, 0.0001f); -} - -static void test_physicsShapePlaneRoundTrip(void **state) { - physicsshape_t shape = { - .type = PHYSICS_SHAPE_PLANE, - .data.plane = { .normal = { 0.0f, 1.0f, 0.0f }, .distance = 2.0f } - }; - - yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL); - yyjson_mut_val *root = yyjson_mut_obj(doc); - yyjson_mut_doc_set_root(doc, root); - assert_true(errorIsOk(physicsShapeSerialize(doc, root, &shape))); - - yyjson_doc *readDoc; - yyjson_val *root2 = test_roundTripToImmutable(doc, root, &readDoc); - - physicsshape_t got; - assert_true(errorIsOk( - physicsShapeDeserialize(yyjson_obj_get(root2, "shape"), &got) - )); - yyjson_doc_free(readDoc); - - assert_int_equal(got.type, PHYSICS_SHAPE_PLANE); - assert_float_equal(got.data.plane.normal[1], 1.0f, 0.0001f); - assert_float_equal(got.data.plane.distance, 2.0f, 0.0001f); -} - -static void test_physicsShapeDeserializeUnknownTypeFails(void **state) { - const char_t *json = "{\"shape\":{\"type\":\"NOT_A_SHAPE\"}}"; - yyjson_doc *readDoc = yyjson_read(json, strlen(json), YYJSON_READ_NOFLAG); - assert_non_null(readDoc); - - physicsshape_t got; - errorret_t ret = physicsShapeDeserialize( - yyjson_obj_get(yyjson_doc_get_root(readDoc), "shape"), &got - ); - assert_true(errorIsNotOk(ret)); - errorCatch(ret); - - yyjson_doc_free(readDoc); -} - -int main(int argc, char **argv) { - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_physicsShapeCubeRoundTrip), - cmocka_unit_test(test_physicsShapeSphereRoundTrip), - cmocka_unit_test(test_physicsShapeCapsuleRoundTrip), - cmocka_unit_test(test_physicsShapePlaneRoundTrip), - cmocka_unit_test(test_physicsShapeDeserializeUnknownTypeFails), - }; - - return cmocka_run_group_tests(tests, NULL, NULL); -} diff --git a/test/scene/test_scene.c b/test/scene/test_scene.c index 7ce8e2f6..f920979e 100644 --- a/test/scene/test_scene.c +++ b/test/scene/test_scene.c @@ -11,7 +11,6 @@ #include "scene/scene.h" #include "entity/entitymanager.h" #include "entity/component/display/entityposition.h" -#include "yyjson.h" static void countingUpdateCallback( entitymanager_t *mgr, @@ -139,82 +138,6 @@ static void test_sceneUpdateOnlyRunsFixedUpdateOnNonDynamicFrames(void **state) assert_int_equal(memoryGetAllocatedCount(), 0); } -static void test_sceneSerializeDeserializeParentLink(void **state) { - sceneInit(); - - sceneid_t sceneA = sceneCreate(); - entitymanager_t *mgrA = sceneGetEntities(sceneA); - - entityid_t parent = entityManagerAdd(mgrA); - componentid_t parentPos = entityAddComponent( - mgrA, parent, COMPONENT_TYPE_POSITION - ); - entitySetName(mgrA, parent, "root"); - entityPositionSetLocalPosition(mgrA, parent, parentPos, (vec3){ 10, 0, 0 }); - - entityid_t child = entityManagerAdd(mgrA); - componentid_t childPos = entityAddComponent( - mgrA, child, COMPONENT_TYPE_POSITION - ); - entityPositionSetLocalPosition(mgrA, child, childPos, (vec3){ 1, 0, 0 }); - entityPositionSetParent(mgrA, child, childPos, parent, parentPos); - - vec3 childWorldBefore; - entityPositionGetWorldPosition(mgrA, child, childPos, childWorldBefore); - assert_float_equal(childWorldBefore[0], 11.0f, 0.0001f); - - yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL); - yyjson_mut_val *root = yyjson_mut_obj(doc); - yyjson_mut_doc_set_root(doc, root); - errorret_t ret = sceneSerialize(sceneA, doc, root); - assert_true(errorIsOk(ret)); - - size_t len = 0; - char_t *jsonStr = yyjson_mut_write(doc, 0, &len); - assert_non_null(jsonStr); - yyjson_mut_doc_free(doc); - - yyjson_doc *readDoc = yyjson_read(jsonStr, len, 0); - free(jsonStr); - assert_non_null(readDoc); - - sceneid_t sceneB = sceneCreate(); - entitymanager_t *mgrB = sceneGetEntities(sceneB); - ret = sceneDeserialize(sceneB, yyjson_doc_get_root(readDoc)); - yyjson_doc_free(readDoc); - assert_true(errorIsOk(ret)); - - entityid_t reloadedParent = entityFindByName(mgrB, "root"); - assert_true(reloadedParent != ENTITY_ID_INVALID); - - entityid_t reloadedChild = ENTITY_ID_INVALID; - for(entityid_t i = 0; i < ENTITY_COUNT_MAX; i++) { - if(!(mgrB->entities[i].state & ENTITY_STATE_ACTIVE)) continue; - if(i == reloadedParent) continue; - reloadedChild = i; - break; - } - assert_true(reloadedChild != ENTITY_ID_INVALID); - - componentid_t reloadedChildPos = entityGetComponent( - mgrB, reloadedChild, COMPONENT_TYPE_POSITION - ); - assert_true(reloadedChildPos != COMPONENT_ID_INVALID); - assert_int_equal( - entityPositionGetParent(mgrB, reloadedChild, reloadedChildPos), - reloadedParent - ); - - vec3 childWorldAfter; - entityPositionGetWorldPosition( - mgrB, reloadedChild, reloadedChildPos, childWorldAfter - ); - assert_float_equal(childWorldAfter[0], childWorldBefore[0], 0.0001f); - - sceneDispose(); - assert_int_equal(memoryGetAllocatedCount(), 0); -} - static void test_sceneDestroyClearsActive(void **state) { sceneInit(); @@ -235,7 +158,6 @@ int main(int argc, char **argv) { cmocka_unit_test(test_sceneEntitiesAreIsolatedPerScene), cmocka_unit_test(test_sceneFixedUpdateOnlyTicksActiveScene), cmocka_unit_test(test_sceneUpdateOnlyRunsFixedUpdateOnNonDynamicFrames), - cmocka_unit_test(test_sceneSerializeDeserializeParentLink), cmocka_unit_test(test_sceneDestroyClearsActive), }; diff --git a/types/entity/component/component.d.ts b/types/entity/component/component.d.ts new file mode 100644 index 00000000..92ee779e --- /dev/null +++ b/types/entity/component/component.d.ts @@ -0,0 +1,36 @@ +// Copyright (c) 2026 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +/** + * A component attached to an Entity. Generic across every component + * type for now -- there are no typed per-type properties yet (e.g. no + * `.position` on a POSITION component), just the operations every + * component supports regardless of type. Only ever obtained via + * Entity.add()/Entity.getComponent(), never constructed directly. + */ +declare class Component { + /** The componenttype_t this component was added as, e.g. POSITION. */ + readonly type: number; + + /** The id of the Entity this component belongs to. */ + readonly entityId: number; + + /** Removes this component from its entity. */ + dispose(): void; +} + +/** + * Component type constants, usable with Entity.add()/getComponent(). + * The numeric values are opaque/build-specific; treat the names as the + * stable API. + */ +declare const POSITION: number; +declare const CAMERA: number; +declare const RENDERABLE: number; +declare const PHYSICS: number; +declare const TRIGGER: number; +declare const ANIMATION: number; +declare const PLAYER: number; +declare const INTERACTABLE: number; diff --git a/types/entity/entity.d.ts b/types/entity/entity.d.ts new file mode 100644 index 00000000..284d42b5 --- /dev/null +++ b/types/entity/entity.d.ts @@ -0,0 +1,36 @@ +// Copyright (c) 2026 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +/// + +/** + * A game entity. `new Entity()` adds it to the currently active scene's + * entity manager (see Scene.getActive()) -- throws if no scene is + * active. + */ +declare class Entity { + constructor(); + + /** The engine-assigned numeric entity ID. */ + readonly id: number; + + /** Optional name; empty string if unset. */ + name: string; + + /** + * Adds a component of the given type (e.g. POSITION) to this entity + * and returns its wrapper. + */ + add(type: number): Component; + + /** + * Gets this entity's component of the given type, or undefined if it + * doesn't have one. + */ + getComponent(type: number): Component | undefined; + + /** Removes this entity and all its position-component descendants. */ + dispose(): void; +} diff --git a/types/index.d.ts b/types/index.d.ts new file mode 100644 index 00000000..e84bf671 --- /dev/null +++ b/types/index.d.ts @@ -0,0 +1,8 @@ +// Copyright (c) 2026 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +/// +/// +/// diff --git a/types/scene/scene.d.ts b/types/scene/scene.d.ts new file mode 100644 index 00000000..4eb14198 --- /dev/null +++ b/types/scene/scene.d.ts @@ -0,0 +1,27 @@ +// Copyright (c) 2026 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +/** + * A scene: an isolated pool of entities/components. `new Scene()` + * creates an empty scene -- it is not made active automatically, call + * `.setActive()` for that. `Entity`/`Component` always operate on + * whichever scene is currently active, not on a specific Scene + * instance. + */ +declare class Scene { + constructor(); + + /** The engine-assigned numeric scene ID. */ + readonly id: number; + + /** Makes this the active scene (see Scene.getActive()). */ + setActive(): void; + + /** Destroys this scene and all its entities/components. */ + dispose(): void; + + /** Gets the currently active scene, or undefined if none is active. */ + static getActive(): Scene | undefined; +}