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/<name>.json"/ "scenes/<name>.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 <noreply@anthropic.com>
This commit is contained in:
@@ -188,21 +188,85 @@ simply left undefined — the core guards calls with `#ifdef`.
|
|||||||
## Adding a new entity component
|
## Adding a new entity component
|
||||||
1. Create `src/dusk/entity/component/<category>/entityMyComp.h/.c` with
|
1. Create `src/dusk/entity/component/<category>/entityMyComp.h/.c` with
|
||||||
struct `entityMyComp_t`, `entityMyCompInit()`, and optionally
|
struct `entityMyComp_t`, `entityMyCompInit()`, and optionally
|
||||||
`entityMyCompDispose()`.
|
`entityMyCompDispose()`, `entityMyCompRender()`.
|
||||||
2. Add the include to `src/dusk/entity/componentlist.h` header block.
|
2. Add the include to `src/dusk/entity/componentlist.h` header block
|
||||||
3. Add a row to `src/dusk/entity/componentlist.h`:
|
(or `src/duskrpg/entity/gamecomponentlist.h` for a game-specific
|
||||||
|
component, appended after the engine's inbuilt ones).
|
||||||
|
3. Add a row:
|
||||||
```c
|
```c
|
||||||
X(MYCOMP, entityMyComp_t, myComp, entityMyCompInit, NULL, NULL)
|
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).
|
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/<category>/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
|
## 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/<category>/moduleMyMod.h/.c`.
|
1. Create `src/dusk/script/module/<category>/moduleMyMod.h/.c`.
|
||||||
- Declare `extern scriptproto_t MODULE_MYMOD_PROTO;` in the header.
|
- 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
|
- Register props/funcs in `moduleMyModInit()` with
|
||||||
`scriptProtoDefineProp` / `scriptProtoDefineFunc` /
|
`scriptProtoDefineProp` / `scriptProtoDefineFunc` /
|
||||||
`scriptProtoDefineStaticFunc`.
|
`scriptProtoDefineStaticFunc`.
|
||||||
@@ -210,9 +274,12 @@ simply left undefined — the core guards calls with `#ifdef`.
|
|||||||
`src/dusk/script/module/modulelist.c` and call
|
`src/dusk/script/module/modulelist.c` and call
|
||||||
`moduleMyModInit()` in `moduleListInit()` (and `Dispose` in
|
`moduleMyModInit()` in `moduleListInit()` (and `Dispose` in
|
||||||
`moduleListDispose()`).
|
`moduleListDispose()`).
|
||||||
3. For component modules also register in
|
3. For a component module that adds a *typed* wrapper for a specific
|
||||||
`src/dusk/script/module/entity/component/modulecomponentlist.c`
|
component type, create
|
||||||
so `entity.add()` returns the typed wrapper.
|
`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/<category>/mymod.d.ts` and add a
|
4. Create `types/<category>/mymod.d.ts` and add a
|
||||||
`/// <reference path="..." />` line to `types/index.d.ts`.
|
`/// <reference path="..." />` line to `types/index.d.ts`.
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
#include "display/display.h"
|
#include "display/display.h"
|
||||||
#include "scene/scene.h"
|
#include "scene/scene.h"
|
||||||
#include "asset/asset.h"
|
#include "asset/asset.h"
|
||||||
|
#include "script/scriptmanager.h"
|
||||||
#include "ui/ui.h"
|
#include "ui/ui.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include "network/network.h"
|
#include "network/network.h"
|
||||||
@@ -39,6 +40,7 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
|||||||
errorChain(systemInit());
|
errorChain(systemInit());
|
||||||
errorChain(inputInit());
|
errorChain(inputInit());
|
||||||
errorChain(assetInit());
|
errorChain(assetInit());
|
||||||
|
errorChain(scriptManagerInit());
|
||||||
errorChain(saveInit());
|
errorChain(saveInit());
|
||||||
errorChain(saveSettingsLoad());
|
errorChain(saveSettingsLoad());
|
||||||
errorChain(localeManagerInit());
|
errorChain(localeManagerInit());
|
||||||
@@ -101,6 +103,7 @@ errorret_t engineDispose(void) {
|
|||||||
consoleDispose();
|
consoleDispose();
|
||||||
errorChain(displayDispose());
|
errorChain(displayDispose());
|
||||||
errorChain(saveDispose());
|
errorChain(saveDispose());
|
||||||
|
errorChain(scriptManagerDispose());
|
||||||
errorChain(assetDispose());
|
errorChain(assetDispose());
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
|
|||||||
@@ -12,15 +12,13 @@
|
|||||||
componentdefinition_t COMPONENT_DEFINITIONS[] = {
|
componentdefinition_t COMPONENT_DEFINITIONS[] = {
|
||||||
[COMPONENT_TYPE_NULL] = { 0 },
|
[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] = { \
|
[COMPONENT_TYPE_##enm] = { \
|
||||||
.enumName = #enm, \
|
.enumName = #enm, \
|
||||||
.name = #field, \
|
.name = #field, \
|
||||||
.init = iMethod, \
|
.init = iMethod, \
|
||||||
.dispose = dMethod, \
|
.dispose = dMethod, \
|
||||||
.render = rMethod, \
|
.render = rMethod \
|
||||||
.serialize = sMethod, \
|
|
||||||
.deserialize = dsMethod \
|
|
||||||
},
|
},
|
||||||
|
|
||||||
#include "componentlist.h"
|
#include "componentlist.h"
|
||||||
@@ -152,48 +150,3 @@ void componentDispose(
|
|||||||
|
|
||||||
cmp->type = COMPONENT_TYPE_NULL;
|
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();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,17 +8,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "entitybase.h"
|
#include "entitybase.h"
|
||||||
#include "error/error.h"
|
#include "error/error.h"
|
||||||
#include "yyjson.h"
|
|
||||||
|
|
||||||
#define X(enumName, type, field, init, dispose, render, serialize, \
|
#define X(enumName, type, field, init, dispose, render) \
|
||||||
deserialize) \
|
|
||||||
// do nothing
|
// do nothing
|
||||||
#include "componentlist.h"
|
#include "componentlist.h"
|
||||||
#undef X
|
#undef X
|
||||||
|
|
||||||
typedef union {
|
typedef union {
|
||||||
#define X(enumName, type, field, init, dispose, render, serialize, \
|
#define X(enumName, type, field, init, dispose, render) type field;
|
||||||
deserialize) type field;
|
|
||||||
#include "componentlist.h"
|
#include "componentlist.h"
|
||||||
#undef X
|
#undef X
|
||||||
} componentdata_t;
|
} componentdata_t;
|
||||||
@@ -50,55 +47,18 @@ typedef errorret_t (*componentcallbackerror_t)(
|
|||||||
const componentid_t componentId
|
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 {
|
typedef struct {
|
||||||
const char_t *enumName;
|
const char_t *enumName;
|
||||||
const char_t *name;
|
const char_t *name;
|
||||||
componentcallback_t init;
|
componentcallback_t init;
|
||||||
componentcallback_t dispose;
|
componentcallback_t dispose;
|
||||||
componentcallbackerror_t render;
|
componentcallbackerror_t render;
|
||||||
componentserializecallback_t serialize;
|
|
||||||
componentdeserializecallback_t deserialize;
|
|
||||||
} componentdefinition_t;
|
} componentdefinition_t;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
COMPONENT_TYPE_NULL,
|
COMPONENT_TYPE_NULL,
|
||||||
|
|
||||||
#define X(enumName, type, field, init, dispose, render, serialize, \
|
#define X(enumName, type, field, init, dispose, render) \
|
||||||
deserialize) \
|
|
||||||
COMPONENT_TYPE_##enumName,
|
COMPONENT_TYPE_##enumName,
|
||||||
#include "componentlist.h"
|
#include "componentlist.h"
|
||||||
#undef X
|
#undef X
|
||||||
@@ -195,45 +155,3 @@ void componentDispose(
|
|||||||
* @return Error state.
|
* @return Error state.
|
||||||
*/
|
*/
|
||||||
errorret_t componentRenderAll(entitymanager_t *mgr);
|
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
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
#include "entity/entity.h"
|
#include "entity/entity.h"
|
||||||
#include "entity/component/display/entityposition.h"
|
#include "entity/component/display/entityposition.h"
|
||||||
#include "display/screen/screen.h"
|
#include "display/screen/screen.h"
|
||||||
#include "util/string.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
|
|
||||||
void entityCameraInit(
|
void entityCameraInit(
|
||||||
entitymanager_t *mgr,
|
entitymanager_t *mgr,
|
||||||
@@ -140,98 +138,3 @@ void entityCameraLookAtPixelPerfect(
|
|||||||
vec3 up = { 0.0f, 0.0f, -1.0f };
|
vec3 up = { 0.0f, 0.0f, -1.0f };
|
||||||
entityPositionLookAt(mgr, ent, posComp, eye, (float_t *)point, up);
|
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();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "entity/entitybase.h"
|
#include "entity/entitybase.h"
|
||||||
#include "error/error.h"
|
#include "error/error.h"
|
||||||
#include "yyjson.h"
|
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE,
|
ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE,
|
||||||
@@ -121,44 +120,3 @@ void entityCameraLookAtPixelPerfect(
|
|||||||
const vec3 eyeOffset,
|
const vec3 eyeOffset,
|
||||||
const float_t scale
|
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
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -667,106 +667,3 @@ void entityPositionEnsureWorld(entitymanager_t *mgr, entityposition_t *pos) {
|
|||||||
|
|
||||||
pos->flags &= ~ENTITY_POSITION_FLAG_WORLD_DIRTY;
|
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();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "entity/entitybase.h"
|
#include "entity/entitybase.h"
|
||||||
#include "error/error.h"
|
#include "error/error.h"
|
||||||
#include "yyjson.h"
|
|
||||||
|
|
||||||
/** Maximum number of child position components this node can track. */
|
/** Maximum number of child position components this node can track. */
|
||||||
#define ENTITY_POSITION_CHILDREN_MAX 8
|
#define ENTITY_POSITION_CHILDREN_MAX 8
|
||||||
@@ -459,62 +458,3 @@ void entityPositionEnsureLocal(entityposition_t *pos);
|
|||||||
* @param pos The position component to update.
|
* @param pos The position component to update.
|
||||||
*/
|
*/
|
||||||
void entityPositionEnsureWorld(entitymanager_t *mgr, entityposition_t *pos);
|
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
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
#include "display/display.h"
|
#include "display/display.h"
|
||||||
#include "display/mesh/cube.h"
|
#include "display/mesh/cube.h"
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "util/string.h"
|
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
|
|
||||||
void entityRenderableInit(
|
void entityRenderableInit(
|
||||||
@@ -165,147 +164,3 @@ errorret_t entityRenderableDrawCustom(
|
|||||||
) {
|
) {
|
||||||
return custom->draw(mgr, entityId, componentId, custom->drawUser);
|
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();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
#include "display/spritebatch/spritebatch.h"
|
#include "display/spritebatch/spritebatch.h"
|
||||||
#include "display/displaystate.h"
|
#include "display/displaystate.h"
|
||||||
#include "error/error.h"
|
#include "error/error.h"
|
||||||
#include "yyjson.h"
|
|
||||||
|
|
||||||
#define ENTITY_RENDERABLE_SPRITEBATCH_SPRITES_MAX 64
|
#define ENTITY_RENDERABLE_SPRITEBATCH_SPRITES_MAX 64
|
||||||
#define ENTITY_RENDERABLE_MESHES_MAX 8
|
#define ENTITY_RENDERABLE_MESHES_MAX 8
|
||||||
@@ -213,47 +212,3 @@ errorret_t entityRenderableDrawCustom(
|
|||||||
const componentid_t componentId,
|
const componentid_t componentId,
|
||||||
const entityrenderablecustom_t *custom
|
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
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -8,8 +8,6 @@
|
|||||||
#include "entityphysics.h"
|
#include "entityphysics.h"
|
||||||
#include "entity/entitymanager.h"
|
#include "entity/entitymanager.h"
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "util/string.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
|
|
||||||
void entityPhysicsInit(
|
void entityPhysicsInit(
|
||||||
entitymanager_t *mgr,
|
entitymanager_t *mgr,
|
||||||
@@ -135,102 +133,3 @@ uint32_t entityPhysicsGetCollideMask(
|
|||||||
entityphysics_t *phys = entityPhysicsGet(mgr, entityId, componentId);
|
entityphysics_t *phys = entityPhysicsGet(mgr, entityId, componentId);
|
||||||
return phys->collideMask;
|
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();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
#include "physics/physicsshape.h"
|
#include "physics/physicsshape.h"
|
||||||
#include "physics/physicsbodytype.h"
|
#include "physics/physicsbodytype.h"
|
||||||
#include "error/error.h"
|
#include "error/error.h"
|
||||||
#include "yyjson.h"
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
physicsbodytype_t type;
|
physicsbodytype_t type;
|
||||||
@@ -210,45 +209,3 @@ uint32_t entityPhysicsGetCollideMask(
|
|||||||
const entityid_t entityId,
|
const entityid_t entityId,
|
||||||
const componentid_t componentId
|
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
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
#include "entitytrigger.h"
|
#include "entitytrigger.h"
|
||||||
#include "entity/entitymanager.h"
|
#include "entity/entitymanager.h"
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "assert/assert.h"
|
|
||||||
|
|
||||||
void entityTriggerInit(
|
void entityTriggerInit(
|
||||||
entitymanager_t *mgr,
|
entitymanager_t *mgr,
|
||||||
@@ -104,39 +103,3 @@ bool_t entityTriggerIsOccupyingEntity(
|
|||||||
}
|
}
|
||||||
return false;
|
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();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
#include "entity/entitybase.h"
|
#include "entity/entitybase.h"
|
||||||
#include "physics/physicsshape.h"
|
#include "physics/physicsshape.h"
|
||||||
#include "error/error.h"
|
#include "error/error.h"
|
||||||
#include "yyjson.h"
|
|
||||||
|
|
||||||
/** Maximum number of entities a single trigger can track at once. */
|
/** Maximum number of entities a single trigger can track at once. */
|
||||||
#define ENTITY_TRIGGER_OCCUPANTS_MAX 8
|
#define ENTITY_TRIGGER_OCCUPANTS_MAX 8
|
||||||
@@ -225,41 +224,3 @@ bool_t entityTriggerIsOccupyingEntity(
|
|||||||
const componentid_t componentId,
|
const componentid_t componentId,
|
||||||
const entityid_t otherEntityId
|
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
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -18,25 +18,14 @@
|
|||||||
// Init function (optional)
|
// Init function (optional)
|
||||||
// Dispose function (optional)
|
// Dispose function (optional)
|
||||||
// Render function (optional)
|
// Render function (optional)
|
||||||
// Serialize function (optional)
|
|
||||||
// Deserialize function (optional)
|
|
||||||
|
|
||||||
X(POSITION, entityposition_t, position, entityPositionInit, NULL, NULL,
|
X(POSITION, entityposition_t, position, entityPositionInit, NULL, NULL)
|
||||||
entityPositionSerialize, entityPositionDeserialize)
|
X(CAMERA, entitycamera_t, camera, entityCameraInit, NULL, NULL)
|
||||||
X(CAMERA, entitycamera_t, camera, entityCameraInit, NULL, NULL,
|
|
||||||
entityCameraSerialize, entityCameraDeserialize)
|
|
||||||
X(RENDERABLE, entityrenderable_t, renderable,
|
X(RENDERABLE, entityrenderable_t, renderable,
|
||||||
entityRenderableInit, entityRenderableDispose, NULL,
|
entityRenderableInit, entityRenderableDispose, NULL)
|
||||||
entityRenderableSerialize, entityRenderableDeserialize)
|
X(PHYSICS, entityphysics_t, physics, entityPhysicsInit, NULL, NULL)
|
||||||
X(PHYSICS, entityphysics_t, physics, entityPhysicsInit, NULL, NULL,
|
X(TRIGGER, entitytrigger_t, trigger, entityTriggerInit, NULL, NULL)
|
||||||
entityPhysicsSerialize, entityPhysicsDeserialize)
|
X(ANIMATION, entityanimation_t, animation, entityAnimationInit, NULL, NULL)
|
||||||
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)
|
|
||||||
|
|
||||||
// Game-specific components
|
// Game-specific components
|
||||||
#include "entity/gamecomponentlist.h"
|
#include "entity/gamecomponentlist.h"
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
#include "entitymanager.h"
|
#include "entitymanager.h"
|
||||||
#include "component/display/entityposition.h"
|
#include "component/display/entityposition.h"
|
||||||
#include "entityprefab.h"
|
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
@@ -214,93 +213,3 @@ void entityDisposeRemove(
|
|||||||
return;
|
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();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "component.h"
|
#include "component.h"
|
||||||
#include "error/error.h"
|
#include "error/error.h"
|
||||||
#include "yyjson.h"
|
|
||||||
|
|
||||||
#define ENTITY_STATE_ACTIVE (1 << 0)
|
#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
|
* 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
|
* by default (entityInit zeroes it) -- but are required to make the
|
||||||
* entity referenceable by name, e.g. as a parent in POSITION's "parent"
|
* entity referenceable by name, e.g. as a parent via
|
||||||
* JSON field (see entityPositionSetParent()).
|
* entityPositionSetParent().
|
||||||
*
|
*
|
||||||
* @param mgr The entity manager that owns the entity.
|
* @param mgr The entity manager that owns the entity.
|
||||||
* @param entityId The ID of the entity to name.
|
* @param entityId The ID of the entity to name.
|
||||||
@@ -201,61 +200,3 @@ void entityDisposeRemove(
|
|||||||
const entityid_t entityId,
|
const entityid_t entityId,
|
||||||
const entitycallback_t callback
|
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": <enumName>, ...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/<name>.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
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -6,11 +6,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "entityprefab.h"
|
#include "entityprefab.h"
|
||||||
#include "entity.h"
|
|
||||||
#include "asset/asset.h"
|
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include "yyjson.h"
|
|
||||||
|
|
||||||
// Pulls in every prefab's real header (entityprefablist.h's one-time
|
// Pulls in every prefab's real header (entityprefablist.h's one-time
|
||||||
// #include lines) exactly once -- each header's #pragma once then makes
|
// #include lines) exactly once -- each header's #pragma once then makes
|
||||||
@@ -55,18 +52,5 @@ errorret_t entityPrefabResolveAndApply(
|
|||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
char_t assetPath[ASSET_FILE_NAME_MAX];
|
errorThrow("Unknown entity prefab '%s'", name);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,12 +56,10 @@ errorret_t entityPrefabInit(
|
|||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves a prefab by name and applies it to the given entity. First
|
* Resolves a prefab by name and applies it to the given entity, by
|
||||||
* searches the C-coded ENTITY_PREFABS[] registry (see
|
* searching the C-coded ENTITY_PREFABS[] registry (see
|
||||||
* entityprefablist.h) for a matching name; if none match, falls back to
|
* entityprefablist.h) for a matching name. Throws if no prefab with that
|
||||||
* loading "prefabs/<name>.json" as a JSON asset and applying it the same
|
* name is registered.
|
||||||
* way an entity's own JSON definition is applied (see entityDeserialize()
|
|
||||||
* and its "prefab"/"extend" keys).
|
|
||||||
*
|
*
|
||||||
* @param mgr The entity manager that owns the entity.
|
* @param mgr The entity manager that owns the entity.
|
||||||
* @param entityId The ID of the entity to apply the prefab to.
|
* @param entityId The ID of the entity to apply the prefab to.
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
PUBLIC
|
PUBLIC
|
||||||
physicsworld.c
|
physicsworld.c
|
||||||
physicstest.c
|
physicstest.c
|
||||||
physicsshape.c
|
|
||||||
physicsshapemesh.c
|
physicsshapemesh.c
|
||||||
triggersystem.c
|
triggersystem.c
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "dusk.h"
|
#include "dusk.h"
|
||||||
#include "error/error.h"
|
#include "error/error.h"
|
||||||
#include "yyjson.h"
|
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
PHYSICS_SHAPE_CUBE,
|
PHYSICS_SHAPE_CUBE,
|
||||||
@@ -109,35 +108,3 @@ typedef struct physicsshape_t {
|
|||||||
physicshapetype_t type;
|
physicshapetype_t type;
|
||||||
physicsshapedata_t data;
|
physicsshapedata_t data;
|
||||||
} physicsshape_t;
|
} 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
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
#include "scene.h"
|
#include "scene.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "util/string.h"
|
|
||||||
#include "time/time.h"
|
#include "time/time.h"
|
||||||
#include "display/screen/screen.h"
|
#include "display/screen/screen.h"
|
||||||
#include "display/shader/shaderunlit.h"
|
#include "display/shader/shaderunlit.h"
|
||||||
@@ -15,7 +14,6 @@
|
|||||||
#include "entity/component/display/entityposition.h"
|
#include "entity/component/display/entityposition.h"
|
||||||
#include "entity/component/display/entityrenderable.h"
|
#include "entity/component/display/entityrenderable.h"
|
||||||
#include "physics/triggersystem.h"
|
#include "physics/triggersystem.h"
|
||||||
#include "sceneprefab.h"
|
|
||||||
#include "ui/ui.h"
|
#include "ui/ui.h"
|
||||||
#include "console/console.h"
|
#include "console/console.h"
|
||||||
|
|
||||||
@@ -194,144 +192,3 @@ errorret_t sceneDispose(void) {
|
|||||||
}
|
}
|
||||||
errorOk();
|
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();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
#include "entity/entitymanager.h"
|
#include "entity/entitymanager.h"
|
||||||
#include "physics/physicsworld.h"
|
#include "physics/physicsworld.h"
|
||||||
#include "error/error.h"
|
#include "error/error.h"
|
||||||
#include "yyjson.h"
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
bool_t used;
|
bool_t used;
|
||||||
@@ -121,73 +120,3 @@ errorret_t sceneRender(void);
|
|||||||
* @return An error if the dispose failed, or errorOk() if it succeeded.
|
* @return An error if the dispose failed, or errorOk() if it succeeded.
|
||||||
*/
|
*/
|
||||||
errorret_t sceneDispose(void);
|
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/<name>.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
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -6,11 +6,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "sceneprefab.h"
|
#include "sceneprefab.h"
|
||||||
#include "scene.h"
|
|
||||||
#include "asset/asset.h"
|
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include "yyjson.h"
|
|
||||||
|
|
||||||
// Pulls in every scene prefab's real header (sceneprefablist.h's one-time
|
// Pulls in every scene prefab's real header (sceneprefablist.h's one-time
|
||||||
// #include lines) exactly once -- each header's #pragma once then makes
|
// #include lines) exactly once -- each header's #pragma once then makes
|
||||||
@@ -53,16 +50,5 @@ errorret_t scenePrefabResolveAndApply(
|
|||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
char_t assetPath[ASSET_FILE_NAME_MAX];
|
errorThrow("Unknown scene prefab '%s'", name);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,12 +50,10 @@ errorret_t scenePrefabInit(
|
|||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves a scene prefab by name and applies it to the given scene.
|
* Resolves a scene prefab by name and applies it to the given scene, by
|
||||||
* First searches the C-coded SCENE_PREFABS[] registry (see
|
* searching the C-coded SCENE_PREFABS[] registry (see
|
||||||
* sceneprefablist.h) for a matching name; if none match, falls back to
|
* sceneprefablist.h) for a matching name. Throws if no prefab with that
|
||||||
* loading "scenes/<name>.json" as a JSON asset and applying it the same
|
* name is registered.
|
||||||
* way a scene's own JSON definition is applied (see sceneDeserialize()
|
|
||||||
* and its "prefab"/"extend" keys).
|
|
||||||
*
|
*
|
||||||
* @param sceneId The ID of the scene to apply the prefab to.
|
* @param sceneId The ID of the scene to apply the prefab to.
|
||||||
* @param name The scene prefab name to resolve.
|
* @param name The scene prefab name to resolve.
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 <jerryscript.h>
|
||||||
|
|
||||||
|
/** 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);
|
||||||
@@ -156,91 +156,3 @@ errorret_t entityInteractableTryInteract(
|
|||||||
|
|
||||||
errorOk();
|
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();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "entity/entitybase.h"
|
#include "entity/entitybase.h"
|
||||||
#include "error/error.h"
|
#include "error/error.h"
|
||||||
#include "yyjson.h"
|
|
||||||
|
|
||||||
typedef struct cutscene_s cutscene_t;
|
typedef struct cutscene_s cutscene_t;
|
||||||
|
|
||||||
@@ -231,45 +230,3 @@ errorret_t entityInteractableTryInteract(
|
|||||||
const componentid_t componentId,
|
const componentid_t componentId,
|
||||||
const entityid_t interactorEntityId
|
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
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -181,41 +181,3 @@ void entityPlayerTryInteract(
|
|||||||
return;
|
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();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "entity/entitybase.h"
|
#include "entity/entitybase.h"
|
||||||
#include "error/error.h"
|
#include "error/error.h"
|
||||||
#include "yyjson.h"
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
float_t moveSpeed;
|
float_t moveSpeed;
|
||||||
@@ -122,40 +121,3 @@ void entityPlayerTryInteract(
|
|||||||
entitymanager_t *mgr,
|
entitymanager_t *mgr,
|
||||||
const entityid_t entityId
|
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
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -16,10 +16,7 @@
|
|||||||
// Init function (optional)
|
// Init function (optional)
|
||||||
// Dispose function (optional)
|
// Dispose function (optional)
|
||||||
// Render function (optional)
|
// Render function (optional)
|
||||||
// Serialize function (optional)
|
|
||||||
// Deserialize function (optional)
|
|
||||||
|
|
||||||
X(PLAYER, entityplayer_t, player, entityPlayerInit, NULL, NULL,
|
X(PLAYER, entityplayer_t, player, entityPlayerInit, NULL, NULL)
|
||||||
entityPlayerSerialize, entityPlayerDeserialize)
|
|
||||||
X(INTERACTABLE, entityinteractable_t, interactable, entityInteractableInit,
|
X(INTERACTABLE, entityinteractable_t, interactable, entityInteractableInit,
|
||||||
NULL, NULL, entityInteractableSerialize, entityInteractableDeserialize)
|
NULL, NULL)
|
||||||
|
|||||||
@@ -12,8 +12,7 @@
|
|||||||
/**
|
/**
|
||||||
* Applies the OVERWORLD_CAMERA prefab: a POSITION at (10, 10, 10) looking
|
* 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,
|
* 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
|
* 0.9 fov).
|
||||||
* previously in assets/scenes/test.json.
|
|
||||||
*
|
*
|
||||||
* @param mgr The entity manager that owns the entity.
|
* @param mgr The entity manager that owns the entity.
|
||||||
* @param entityId The ID of the entity to apply the prefab to.
|
* @param entityId The ID of the entity to apply the prefab to.
|
||||||
|
|||||||
@@ -13,9 +13,7 @@
|
|||||||
* Applies the PLAYER prefab: a POSITION 5 units up, a red
|
* Applies the PLAYER prefab: a POSITION 5 units up, a red
|
||||||
* SHADER_MATERIAL RENDERABLE, a PLAYER component, a DYNAMIC PHYSICS body
|
* SHADER_MATERIAL RENDERABLE, a PLAYER component, a DYNAMIC PHYSICS body
|
||||||
* tagged with collideMask 0x3 (world + player layers), and a 2x2x2 cube
|
* tagged with collideMask 0x3 (world + player layers), and a 2x2x2 cube
|
||||||
* TRIGGER (the interact box checked by entityPlayerTryInteract()). C-coded
|
* TRIGGER (the interact box checked by entityPlayerTryInteract()).
|
||||||
* equivalent of the hand-authored "player" entity in
|
|
||||||
* assets/scenes/test.json.
|
|
||||||
*
|
*
|
||||||
* @param mgr The entity manager that owns the entity.
|
* @param mgr The entity manager that owns the entity.
|
||||||
* @param entityId The ID of the entity to apply the prefab to.
|
* @param entityId The ID of the entity to apply the prefab to.
|
||||||
|
|||||||
+1
-24
@@ -8,32 +8,9 @@
|
|||||||
#include "game/game.h"
|
#include "game/game.h"
|
||||||
#include "scene/scene.h"
|
#include "scene/scene.h"
|
||||||
#include "scene/overworldscene.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) {
|
errorret_t gameInit(void) {
|
||||||
// overworldSceneCreate();
|
sceneid_t testSceneId = 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);
|
|
||||||
|
|
||||||
sceneSetActive(testSceneId);
|
sceneSetActive(testSceneId);
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,50 +94,6 @@ static void test_entityPositionParentChildWorldTransform(void **state) {
|
|||||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
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) {
|
static void test_entityPositionDisposeDeep(void **state) {
|
||||||
entitymanager_t mgr;
|
entitymanager_t mgr;
|
||||||
entityManagerInit(&mgr);
|
entityManagerInit(&mgr);
|
||||||
@@ -167,7 +123,6 @@ int main(int argc, char **argv) {
|
|||||||
const struct CMUnitTest tests[] = {
|
const struct CMUnitTest tests[] = {
|
||||||
cmocka_unit_test(test_entityPositionLocalGetSet),
|
cmocka_unit_test(test_entityPositionLocalGetSet),
|
||||||
cmocka_unit_test(test_entityPositionParentChildWorldTransform),
|
cmocka_unit_test(test_entityPositionParentChildWorldTransform),
|
||||||
cmocka_unit_test(test_entityPositionDeserializeLookAt),
|
|
||||||
cmocka_unit_test(test_entityPositionDisposeDeep),
|
cmocka_unit_test(test_entityPositionDisposeDeep),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -110,56 +110,6 @@ static void test_entityTriggerDefaultsAndAccessors(void **state) {
|
|||||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
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) {
|
static void test_entityTriggerEventSubscription(void **state) {
|
||||||
test_resetCallbackCounts();
|
test_resetCallbackCounts();
|
||||||
|
|
||||||
@@ -203,7 +153,6 @@ static void test_entityTriggerEventSubscription(void **state) {
|
|||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
const struct CMUnitTest tests[] = {
|
const struct CMUnitTest tests[] = {
|
||||||
cmocka_unit_test(test_entityTriggerDefaultsAndAccessors),
|
cmocka_unit_test(test_entityTriggerDefaultsAndAccessors),
|
||||||
cmocka_unit_test(test_entityTriggerSerializeRoundTrip),
|
|
||||||
cmocka_unit_test(test_entityTriggerEventSubscription),
|
cmocka_unit_test(test_entityTriggerEventSubscription),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,5 +9,4 @@ include(dusktest)
|
|||||||
dusktest(test_physicstest.c)
|
dusktest(test_physicstest.c)
|
||||||
dusktest(test_physicsworld.c)
|
dusktest(test_physicsworld.c)
|
||||||
dusktest(test_physicsshapemesh.c)
|
dusktest(test_physicsshapemesh.c)
|
||||||
dusktest(test_physicsshape.c)
|
|
||||||
dusktest(test_triggersystem.c)
|
dusktest(test_triggersystem.c)
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,6 @@
|
|||||||
#include "scene/scene.h"
|
#include "scene/scene.h"
|
||||||
#include "entity/entitymanager.h"
|
#include "entity/entitymanager.h"
|
||||||
#include "entity/component/display/entityposition.h"
|
#include "entity/component/display/entityposition.h"
|
||||||
#include "yyjson.h"
|
|
||||||
|
|
||||||
static void countingUpdateCallback(
|
static void countingUpdateCallback(
|
||||||
entitymanager_t *mgr,
|
entitymanager_t *mgr,
|
||||||
@@ -139,82 +138,6 @@ static void test_sceneUpdateOnlyRunsFixedUpdateOnNonDynamicFrames(void **state)
|
|||||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
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) {
|
static void test_sceneDestroyClearsActive(void **state) {
|
||||||
sceneInit();
|
sceneInit();
|
||||||
|
|
||||||
@@ -235,7 +158,6 @@ int main(int argc, char **argv) {
|
|||||||
cmocka_unit_test(test_sceneEntitiesAreIsolatedPerScene),
|
cmocka_unit_test(test_sceneEntitiesAreIsolatedPerScene),
|
||||||
cmocka_unit_test(test_sceneFixedUpdateOnlyTicksActiveScene),
|
cmocka_unit_test(test_sceneFixedUpdateOnlyTicksActiveScene),
|
||||||
cmocka_unit_test(test_sceneUpdateOnlyRunsFixedUpdateOnNonDynamicFrames),
|
cmocka_unit_test(test_sceneUpdateOnlyRunsFixedUpdateOnNonDynamicFrames),
|
||||||
cmocka_unit_test(test_sceneSerializeDeserializeParentLink),
|
|
||||||
cmocka_unit_test(test_sceneDestroyClearsActive),
|
cmocka_unit_test(test_sceneDestroyClearsActive),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+36
@@ -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;
|
||||||
Vendored
+36
@@ -0,0 +1,36 @@
|
|||||||
|
// Copyright (c) 2026 Dominic Masters
|
||||||
|
//
|
||||||
|
// This software is released under the MIT License.
|
||||||
|
// https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
/// <reference path="component/component.d.ts" />
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
Vendored
+8
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2026 Dominic Masters
|
||||||
|
//
|
||||||
|
// This software is released under the MIT License.
|
||||||
|
// https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
/// <reference path="entity/entity.d.ts" />
|
||||||
|
/// <reference path="entity/component/component.d.ts" />
|
||||||
|
/// <reference path="scene/scene.d.ts" />
|
||||||
Vendored
+27
@@ -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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user