Script stuff

This commit is contained in:
2026-07-31 09:40:53 -05:00
parent d49194fc4d
commit 07f98c119a
45 changed files with 2094 additions and 201 deletions
+1
View File
@@ -74,6 +74,7 @@ errorret_t engineUpdate(void) {
consoleUpdate();
errorChain(gameUpdate());
errorChain(scriptManagerCallGlobal("update"));
errorChain(sceneUpdate());
errorChain(assetUpdate());
errorChain(uiUpdate());
@@ -80,6 +80,32 @@ void entityRenderableSetColor(
r->data.material.material.unlit.color = color;
}
void entityRenderableSetMesh(
entitymanager_t *mgr,
const entityid_t entityId,
const componentid_t componentId,
const uint8_t slot,
mesh_t *mesh
) {
assertNotNull(mesh, "Mesh cannot be null");
assertTrue(slot < ENTITY_RENDERABLE_MESHES_MAX, "Mesh slot out of bounds");
entityrenderable_t *r = componentGetData(
mgr, entityId, componentId, COMPONENT_TYPE_RENDERABLE
);
assertTrue(
r->type == ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL,
"Renderable must be ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL to set a mesh"
);
r->data.material.meshes[slot] = mesh;
r->data.material.meshOffsets[slot] = 0;
r->data.material.meshCounts[slot] = -1;
if(slot >= r->data.material.meshCount) {
r->data.material.meshCount = slot + 1;
}
}
void entityRenderableSetDraw(
entitymanager_t *mgr,
const entityid_t entityId,
@@ -142,6 +142,26 @@ void entityRenderableSetColor(
const color_t color
);
/**
* Sets one of the material renderable's meshes, drawn in full (no offset/
* count override). Only meaningful when the renderable is (or defaults
* to) ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL -- asserts otherwise.
*
* @param mgr The entity manager that owns the entity.
* @param entityId The entity to configure.
* @param componentId The renderable component.
* @param slot Index into the material's meshes array (0 to
* ENTITY_RENDERABLE_MESHES_MAX - 1).
* @param mesh The mesh to draw in that slot.
*/
void entityRenderableSetMesh(
entitymanager_t *mgr,
const entityid_t entityId,
const componentid_t componentId,
const uint8_t slot,
mesh_t *mesh
);
/**
* Sets the draw callback, switching the type to
* ENTITY_RENDERABLE_TYPE_CUSTOM.
+3
View File
@@ -10,3 +10,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
add_subdirectory(entity)
add_subdirectory(scene)
add_subdirectory(require)
add_subdirectory(display)
add_subdirectory(time)
@@ -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
modulemesh.c
)
@@ -0,0 +1,27 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "modulemesh.h"
#include "script/module/modulebase.h"
#include "display/mesh/cube.h"
#include "display/mesh/plane.h"
#include "display/mesh/sphere.h"
#include "display/mesh/capsule.h"
#include "display/mesh/quad.h"
#include "display/mesh/triprism.h"
void moduleMeshInit(void) {
moduleBaseSetWrappedPointer("MESH_CUBE", &CUBE_MESH_SIMPLE);
moduleBaseSetWrappedPointer("MESH_PLANE", &PLANE_MESH_SIMPLE);
moduleBaseSetWrappedPointer("MESH_SPHERE", &SPHERE_MESH_SIMPLE);
moduleBaseSetWrappedPointer("MESH_CAPSULE", &CAPSULE_MESH_SIMPLE);
moduleBaseSetWrappedPointer("MESH_TRIPRISM", &TRIPRISM_MESH_SIMPLE);
moduleBaseSetWrappedPointer("MESH_QUAD", &QUAD_MESH_SIMPLE);
}
void moduleMeshDispose(void) {
}
@@ -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 the built-in primitive meshes (MESH_CUBE, MESH_PLANE,
* MESH_SPHERE, MESH_CAPSULE, MESH_TRIPRISM, MESH_QUAD) as global,
* engine-owned pointer values usable with Renderable.setMesh().
*/
void moduleMeshInit(void);
/**
* Disposes the mesh module's script resources.
*/
void moduleMeshDispose(void);
@@ -6,4 +6,8 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
modulecomponent.c
modulecomponentlist.c
)
add_subdirectory(display)
add_subdirectory(physics)
@@ -0,0 +1,10 @@
# 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
modulepositioncomponent.c
modulerenderablecomponent.c
)
@@ -0,0 +1,150 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "modulepositioncomponent.h"
#include "script/module/modulebase.h"
#include "entity/component/display/entityposition.h"
#include "entity/component.h"
#include "util/string.h"
scriptproto_t MODULE_POSITION_PROTO;
moduleBaseFunction(modulePositionGetType) {
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPositionGet);
return jerry_number(h->type);
}
moduleBaseFunction(modulePositionGetEntityId) {
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPositionGet);
return jerry_number(h->entityId);
}
moduleBaseFunction(modulePositionDisposeMethod) {
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPositionGet);
componentDispose(h->mgr, h->entityId, h->componentId);
return jerry_undefined();
}
moduleBaseFunction(modulePositionToString) {
modulecomponenthandle_t *h = moduleEntityPositionGet(callInfo);
if(!h) return jerry_string_sz("Position(?)");
char_t buf[32];
stringFormat(buf, sizeof(buf), "Position(entityId=%d)", h->entityId);
return jerry_string_sz(buf);
}
moduleBaseFunction(modulePositionSetLocalPosition) {
moduleBaseRequireArgs(3);
moduleBaseRequireNumber(0); moduleBaseRequireNumber(1);
moduleBaseRequireNumber(2);
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPositionGet);
entityPositionSetLocalPosition(h->mgr, h->entityId, h->componentId, (vec3){
moduleBaseArgFloat(0), moduleBaseArgFloat(1), moduleBaseArgFloat(2)
});
return jerry_undefined();
}
moduleBaseFunction(modulePositionGetLocalPosition) {
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPositionGet);
vec3 pos;
entityPositionGetLocalPosition(h->mgr, h->entityId, h->componentId, pos);
return moduleBaseVec3ToObject(pos);
}
moduleBaseFunction(modulePositionSetLocalScale) {
moduleBaseRequireArgs(3);
moduleBaseRequireNumber(0); moduleBaseRequireNumber(1);
moduleBaseRequireNumber(2);
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPositionGet);
entityPositionSetLocalScale(h->mgr, h->entityId, h->componentId, (vec3){
moduleBaseArgFloat(0), moduleBaseArgFloat(1), moduleBaseArgFloat(2)
});
return jerry_undefined();
}
moduleBaseFunction(modulePositionGetLocalScale) {
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPositionGet);
vec3 scale;
entityPositionGetLocalScale(h->mgr, h->entityId, h->componentId, scale);
return moduleBaseVec3ToObject(scale);
}
moduleBaseFunction(modulePositionLookAt) {
moduleBaseRequireArgs(9);
for(jerry_length_t i = 0; i < 9; i++) moduleBaseRequireNumber(i);
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPositionGet);
entityPositionLookAt(
h->mgr, h->entityId, h->componentId,
(vec3){
moduleBaseArgFloat(0), moduleBaseArgFloat(1), moduleBaseArgFloat(2)
},
(vec3){
moduleBaseArgFloat(3), moduleBaseArgFloat(4), moduleBaseArgFloat(5)
},
(vec3){
moduleBaseArgFloat(6), moduleBaseArgFloat(7), moduleBaseArgFloat(8)
}
);
return jerry_undefined();
}
void moduleEntityPositionInit(void) {
scriptProtoInit(
&MODULE_POSITION_PROTO, "Position", sizeof(modulecomponenthandle_t), NULL
);
scriptProtoDefineToString(&MODULE_POSITION_PROTO, modulePositionToString);
scriptProtoDefineProp(
&MODULE_POSITION_PROTO, "type", modulePositionGetType, NULL
);
scriptProtoDefineProp(
&MODULE_POSITION_PROTO, "entityId", modulePositionGetEntityId, NULL
);
scriptProtoDefineFunc(
&MODULE_POSITION_PROTO, "dispose", modulePositionDisposeMethod
);
scriptProtoDefineFunc(
&MODULE_POSITION_PROTO, "setLocalPosition", modulePositionSetLocalPosition
);
scriptProtoDefineFunc(
&MODULE_POSITION_PROTO, "getLocalPosition", modulePositionGetLocalPosition
);
scriptProtoDefineFunc(
&MODULE_POSITION_PROTO, "setLocalScale", modulePositionSetLocalScale
);
scriptProtoDefineFunc(
&MODULE_POSITION_PROTO, "getLocalScale", modulePositionGetLocalScale
);
scriptProtoDefineFunc(
&MODULE_POSITION_PROTO, "lookAt", modulePositionLookAt
);
}
void moduleEntityPositionDispose(void) {
}
jerry_value_t moduleEntityPositionCreate(
const modulecomponenthandle_t *handle
) {
return scriptProtoCreateValue(&MODULE_POSITION_PROTO, handle);
}
modulecomponenthandle_t *moduleEntityPositionGet(
const jerry_call_info_t *callInfo
) {
return (modulecomponenthandle_t *)scriptProtoGetValue(
&MODULE_POSITION_PROTO, callInfo->this_value
);
}
@@ -0,0 +1,46 @@
/**
* 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 "script/module/entity/component/modulecomponent.h"
#include <jerryscript.h>
extern scriptproto_t MODULE_POSITION_PROTO;
/**
* Registers the Position class: a typed wrapper over COMPONENT_TYPE_
* POSITION. Has no constructor -- instances are only ever created via
* moduleEntityPositionCreate(), e.g. from Entity.add()/getComponent().
*/
void moduleEntityPositionInit(void);
/**
* Disposes the Position class's script resources.
*/
void moduleEntityPositionDispose(void);
/**
* Wraps a component handle as a new JS Position instance.
*
* @param handle The handle to copy into the new instance.
* @return The new JS object.
*/
jerry_value_t moduleEntityPositionCreate(
const modulecomponenthandle_t *handle
);
/**
* Internal. Gets the native handle wrapped by a Position 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 Position.
*/
modulecomponenthandle_t *moduleEntityPositionGet(
const jerry_call_info_t *callInfo
);
@@ -0,0 +1,123 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "modulerenderablecomponent.h"
#include "script/module/modulebase.h"
#include "entity/component/display/entityrenderable.h"
#include "entity/component.h"
#include "display/color.h"
#include "util/string.h"
scriptproto_t MODULE_RENDERABLE_PROTO;
moduleBaseFunction(moduleRenderableGetType) {
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityRenderableGet);
return jerry_number(h->type);
}
moduleBaseFunction(moduleRenderableGetEntityId) {
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityRenderableGet);
return jerry_number(h->entityId);
}
moduleBaseFunction(moduleRenderableDisposeMethod) {
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityRenderableGet);
componentDispose(h->mgr, h->entityId, h->componentId);
return jerry_undefined();
}
moduleBaseFunction(moduleRenderableToString) {
modulecomponenthandle_t *h = moduleEntityRenderableGet(callInfo);
if(!h) return jerry_string_sz("Renderable(?)");
char_t buf[32];
stringFormat(buf, sizeof(buf), "Renderable(entityId=%d)", h->entityId);
return jerry_string_sz(buf);
}
moduleBaseFunction(moduleRenderableSetColor) {
moduleBaseRequireArgs(4);
moduleBaseRequireNumber(0); moduleBaseRequireNumber(1);
moduleBaseRequireNumber(2); moduleBaseRequireNumber(3);
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityRenderableGet);
entityRenderableSetColor(h->mgr, h->entityId, h->componentId, color(
(uint8_t)moduleBaseArgInt(0), (uint8_t)moduleBaseArgInt(1),
(uint8_t)moduleBaseArgInt(2), (uint8_t)moduleBaseArgInt(3)
));
return jerry_undefined();
}
moduleBaseFunction(moduleRenderableSetMesh) {
moduleBaseRequireArgs(2); moduleBaseRequireNumber(0);
moduleBaseRequireObject(1);
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityRenderableGet);
mesh_t *mesh = (mesh_t *)moduleBaseUnwrapPointer(args[1]);
if(!mesh) return moduleBaseThrow("Renderable.setMesh: invalid mesh");
entityRenderableSetMesh(
h->mgr, h->entityId, h->componentId, (uint8_t)moduleBaseArgInt(0), mesh
);
return jerry_undefined();
}
moduleBaseFunction(moduleRenderableSetPriority) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityRenderableGet);
entityRenderableSetPriority(
h->mgr, h->entityId, h->componentId, (int8_t)moduleBaseArgInt(0)
);
return jerry_undefined();
}
void moduleEntityRenderableInit(void) {
scriptProtoInit(
&MODULE_RENDERABLE_PROTO,
"Renderable", sizeof(modulecomponenthandle_t), NULL
);
scriptProtoDefineToString(&MODULE_RENDERABLE_PROTO, moduleRenderableToString);
scriptProtoDefineProp(
&MODULE_RENDERABLE_PROTO, "type", moduleRenderableGetType, NULL
);
scriptProtoDefineProp(
&MODULE_RENDERABLE_PROTO, "entityId", moduleRenderableGetEntityId, NULL
);
scriptProtoDefineFunc(
&MODULE_RENDERABLE_PROTO, "dispose", moduleRenderableDisposeMethod
);
scriptProtoDefineFunc(
&MODULE_RENDERABLE_PROTO, "setColor", moduleRenderableSetColor
);
scriptProtoDefineFunc(
&MODULE_RENDERABLE_PROTO, "setMesh", moduleRenderableSetMesh
);
scriptProtoDefineFunc(
&MODULE_RENDERABLE_PROTO, "setPriority", moduleRenderableSetPriority
);
}
void moduleEntityRenderableDispose(void) {
}
jerry_value_t moduleEntityRenderableCreate(
const modulecomponenthandle_t *handle
) {
return scriptProtoCreateValue(&MODULE_RENDERABLE_PROTO, handle);
}
modulecomponenthandle_t *moduleEntityRenderableGet(
const jerry_call_info_t *callInfo
) {
return (modulecomponenthandle_t *)scriptProtoGetValue(
&MODULE_RENDERABLE_PROTO, callInfo->this_value
);
}
@@ -0,0 +1,46 @@
/**
* 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 "script/module/entity/component/modulecomponent.h"
#include <jerryscript.h>
extern scriptproto_t MODULE_RENDERABLE_PROTO;
/**
* Registers the Renderable class: a typed wrapper over COMPONENT_TYPE_
* RENDERABLE. Has no constructor -- instances are only ever created via
* moduleEntityRenderableCreate(), e.g. from Entity.add()/getComponent().
*/
void moduleEntityRenderableInit(void);
/**
* Disposes the Renderable class's script resources.
*/
void moduleEntityRenderableDispose(void);
/**
* Wraps a component handle as a new JS Renderable instance.
*
* @param handle The handle to copy into the new instance.
* @return The new JS object.
*/
jerry_value_t moduleEntityRenderableCreate(
const modulecomponenthandle_t *handle
);
/**
* Internal. Gets the native handle wrapped by a Renderable 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 Renderable.
*/
modulecomponenthandle_t *moduleEntityRenderableGet(
const jerry_call_info_t *callInfo
);
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "modulecomponentlist.h"
#include "script/module/entity/component/display/modulepositioncomponent.h"
#include "script/module/entity/component/physics/modulephysicscomponent.h"
#include "script/module/entity/component/display/modulerenderablecomponent.h"
void moduleComponentListInit(void) {
moduleEntityPositionInit();
moduleEntityPhysicsInit();
moduleEntityRenderableInit();
}
void moduleComponentListDispose(void) {
moduleEntityRenderableDispose();
moduleEntityPhysicsDispose();
moduleEntityPositionDispose();
}
jerry_value_t moduleComponentListCreate(
const modulecomponenthandle_t *handle
) {
switch(handle->type) {
case COMPONENT_TYPE_POSITION:
return moduleEntityPositionCreate(handle);
case COMPONENT_TYPE_PHYSICS:
return moduleEntityPhysicsCreate(handle);
case COMPONENT_TYPE_RENDERABLE:
return moduleEntityRenderableCreate(handle);
default:
return moduleComponentCreate(handle);
}
}
@@ -0,0 +1,34 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/module/entity/component/modulecomponent.h"
#include <jerryscript.h>
/**
* Registers every typed per-component-type JS wrapper module (Position,
* Physics, Renderable, ...). Called once by moduleListInit().
*/
void moduleComponentListInit(void);
/**
* Disposes every typed component wrapper module. Called once by
* moduleListDispose().
*/
void moduleComponentListDispose(void);
/**
* Wraps a component handle using the most specific JS class available
* for its type (e.g. Position for COMPONENT_TYPE_POSITION), falling back
* to the generic Component wrapper for types with no typed wrapper yet.
*
* @param handle The handle to wrap.
* @return The new JS object.
*/
jerry_value_t moduleComponentListCreate(
const modulecomponenthandle_t *handle
);
@@ -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
modulephysicscomponent.c
)
@@ -0,0 +1,211 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "modulephysicscomponent.h"
#include "script/module/modulebase.h"
#include "entity/component/physics/entityphysics.h"
#include "entity/component.h"
#include "util/string.h"
#include "util/memory.h"
scriptproto_t MODULE_PHYSICS_PROTO;
moduleBaseFunction(modulePhysicsGetType) {
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPhysicsGet);
return jerry_number(h->type);
}
moduleBaseFunction(modulePhysicsGetEntityId) {
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPhysicsGet);
return jerry_number(h->entityId);
}
moduleBaseFunction(modulePhysicsDisposeMethod) {
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPhysicsGet);
componentDispose(h->mgr, h->entityId, h->componentId);
return jerry_undefined();
}
moduleBaseFunction(modulePhysicsToString) {
modulecomponenthandle_t *h = moduleEntityPhysicsGet(callInfo);
if(!h) return jerry_string_sz("Physics(?)");
char_t buf[32];
stringFormat(buf, sizeof(buf), "Physics(entityId=%d)", h->entityId);
return jerry_string_sz(buf);
}
moduleBaseFunction(modulePhysicsSetBodyType) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPhysicsGet);
entityPhysicsSetBodyType(
h->mgr, h->entityId, h->componentId,
(physicsbodytype_t)moduleBaseArgInt(0)
);
return jerry_undefined();
}
moduleBaseFunction(modulePhysicsGetBodyType) {
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPhysicsGet);
return jerry_number(
entityPhysicsGetBodyType(h->mgr, h->entityId, h->componentId)
);
}
moduleBaseFunction(modulePhysicsSetShape) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPhysicsGet);
physicsshape_t shape;
memoryZero(&shape, sizeof(shape));
shape.type = (physicshapetype_t)moduleBaseArgInt(0);
switch(shape.type) {
case PHYSICS_SHAPE_CUBE:
moduleBaseRequireArgs(4);
moduleBaseRequireNumber(1); moduleBaseRequireNumber(2);
moduleBaseRequireNumber(3);
shape.data.cube.halfExtents[0] = moduleBaseArgFloat(1);
shape.data.cube.halfExtents[1] = moduleBaseArgFloat(2);
shape.data.cube.halfExtents[2] = moduleBaseArgFloat(3);
break;
case PHYSICS_SHAPE_SPHERE:
moduleBaseRequireArgs(2); moduleBaseRequireNumber(1);
shape.data.sphere.radius = moduleBaseArgFloat(1);
break;
case PHYSICS_SHAPE_CAPSULE:
moduleBaseRequireArgs(3);
moduleBaseRequireNumber(1); moduleBaseRequireNumber(2);
shape.data.capsule.radius = moduleBaseArgFloat(1);
shape.data.capsule.halfHeight = moduleBaseArgFloat(2);
break;
case PHYSICS_SHAPE_PLANE:
moduleBaseRequireArgs(5);
moduleBaseRequireNumber(1); moduleBaseRequireNumber(2);
moduleBaseRequireNumber(3); moduleBaseRequireNumber(4);
shape.data.plane.normal[0] = moduleBaseArgFloat(1);
shape.data.plane.normal[1] = moduleBaseArgFloat(2);
shape.data.plane.normal[2] = moduleBaseArgFloat(3);
shape.data.plane.distance = moduleBaseArgFloat(4);
break;
default:
return moduleBaseThrow("Physics.setShape: unsupported shape type");
}
entityPhysicsSetShape(h->mgr, h->entityId, h->componentId, shape);
return jerry_undefined();
}
moduleBaseFunction(modulePhysicsSetVelocity) {
moduleBaseRequireArgs(3);
moduleBaseRequireNumber(0); moduleBaseRequireNumber(1);
moduleBaseRequireNumber(2);
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPhysicsGet);
entityPhysicsSetVelocity(h->mgr, h->entityId, h->componentId, (vec3){
moduleBaseArgFloat(0), moduleBaseArgFloat(1), moduleBaseArgFloat(2)
});
return jerry_undefined();
}
moduleBaseFunction(modulePhysicsGetVelocity) {
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPhysicsGet);
vec3 velocity;
entityPhysicsGetVelocity(h->mgr, h->entityId, h->componentId, velocity);
return moduleBaseVec3ToObject(velocity);
}
moduleBaseFunction(modulePhysicsApplyImpulse) {
moduleBaseRequireArgs(3);
moduleBaseRequireNumber(0); moduleBaseRequireNumber(1);
moduleBaseRequireNumber(2);
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPhysicsGet);
entityPhysicsApplyImpulse(h->mgr, h->entityId, h->componentId, (vec3){
moduleBaseArgFloat(0), moduleBaseArgFloat(1), moduleBaseArgFloat(2)
});
return jerry_undefined();
}
moduleBaseFunction(modulePhysicsIsOnGround) {
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPhysicsGet);
return jerry_boolean(
entityPhysicsIsOnGround(h->mgr, h->entityId, h->componentId)
);
}
void moduleEntityPhysicsInit(void) {
scriptProtoInit(
&MODULE_PHYSICS_PROTO, "Physics", sizeof(modulecomponenthandle_t), NULL
);
scriptProtoDefineToString(&MODULE_PHYSICS_PROTO, modulePhysicsToString);
scriptProtoDefineProp(
&MODULE_PHYSICS_PROTO, "type", modulePhysicsGetType, NULL
);
scriptProtoDefineProp(
&MODULE_PHYSICS_PROTO, "entityId", modulePhysicsGetEntityId, NULL
);
scriptProtoDefineFunc(
&MODULE_PHYSICS_PROTO, "dispose", modulePhysicsDisposeMethod
);
scriptProtoDefineFunc(
&MODULE_PHYSICS_PROTO, "setBodyType", modulePhysicsSetBodyType
);
scriptProtoDefineFunc(
&MODULE_PHYSICS_PROTO, "getBodyType", modulePhysicsGetBodyType
);
scriptProtoDefineFunc(
&MODULE_PHYSICS_PROTO, "setShape", modulePhysicsSetShape
);
scriptProtoDefineFunc(
&MODULE_PHYSICS_PROTO, "setVelocity", modulePhysicsSetVelocity
);
scriptProtoDefineFunc(
&MODULE_PHYSICS_PROTO, "getVelocity", modulePhysicsGetVelocity
);
scriptProtoDefineFunc(
&MODULE_PHYSICS_PROTO, "applyImpulse", modulePhysicsApplyImpulse
);
scriptProtoDefineFunc(
&MODULE_PHYSICS_PROTO, "isOnGround", modulePhysicsIsOnGround
);
moduleBaseSetInt("PHYSICS_BODY_STATIC", PHYSICS_BODY_STATIC);
moduleBaseSetInt("PHYSICS_BODY_DYNAMIC", PHYSICS_BODY_DYNAMIC);
moduleBaseSetInt("PHYSICS_BODY_KINEMATIC", PHYSICS_BODY_KINEMATIC);
moduleBaseSetInt("PHYSICS_SHAPE_CUBE", PHYSICS_SHAPE_CUBE);
moduleBaseSetInt("PHYSICS_SHAPE_SPHERE", PHYSICS_SHAPE_SPHERE);
moduleBaseSetInt("PHYSICS_SHAPE_CAPSULE", PHYSICS_SHAPE_CAPSULE);
moduleBaseSetInt("PHYSICS_SHAPE_PLANE", PHYSICS_SHAPE_PLANE);
}
void moduleEntityPhysicsDispose(void) {
}
jerry_value_t moduleEntityPhysicsCreate(
const modulecomponenthandle_t *handle
) {
return scriptProtoCreateValue(&MODULE_PHYSICS_PROTO, handle);
}
modulecomponenthandle_t *moduleEntityPhysicsGet(
const jerry_call_info_t *callInfo
) {
return (modulecomponenthandle_t *)scriptProtoGetValue(
&MODULE_PHYSICS_PROTO, callInfo->this_value
);
}
@@ -0,0 +1,48 @@
/**
* 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 "script/module/entity/component/modulecomponent.h"
#include <jerryscript.h>
extern scriptproto_t MODULE_PHYSICS_PROTO;
/**
* Registers the Physics class: a typed wrapper over COMPONENT_TYPE_
* PHYSICS. Also registers PHYSICS_BODY_* and PHYSICS_SHAPE_* as global
* integer constants for use with setBodyType()/setShape(). Has no
* constructor -- instances are only ever created via
* moduleEntityPhysicsCreate(), e.g. from Entity.add()/getComponent().
*/
void moduleEntityPhysicsInit(void);
/**
* Disposes the Physics class's script resources.
*/
void moduleEntityPhysicsDispose(void);
/**
* Wraps a component handle as a new JS Physics instance.
*
* @param handle The handle to copy into the new instance.
* @return The new JS object.
*/
jerry_value_t moduleEntityPhysicsCreate(
const modulecomponenthandle_t *handle
);
/**
* Internal. Gets the native handle wrapped by a Physics 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 Physics.
*/
modulecomponenthandle_t *moduleEntityPhysicsGet(
const jerry_call_info_t *callInfo
);
+3 -2
View File
@@ -8,6 +8,7 @@
#include "moduleentity.h"
#include "script/module/modulebase.h"
#include "script/module/entity/component/modulecomponent.h"
#include "script/module/entity/component/modulecomponentlist.h"
#include "scene/scene.h"
#include "entity/entitymanager.h"
#include "entity/entity.h"
@@ -67,7 +68,7 @@ moduleBaseFunction(moduleEntityAddComponent) {
modulecomponenthandle_t h = {
.mgr = inst->mgr, .entityId = inst->id, .componentId = id, .type = type
};
return moduleComponentCreate(&h);
return moduleComponentListCreate(&h);
}
moduleBaseFunction(moduleEntityGetComponentMethod) {
@@ -85,7 +86,7 @@ moduleBaseFunction(moduleEntityGetComponentMethod) {
modulecomponenthandle_t h = {
.mgr = inst->mgr, .entityId = inst->id, .componentId = id, .type = type
};
return moduleComponentCreate(&h);
return moduleComponentListCreate(&h);
}
moduleBaseFunction(moduleEntityDisposeMethod) {
+27
View File
@@ -394,3 +394,30 @@ static inline float_t moduleBaseValueFloat(jerry_value_t val) {
static inline int32_t moduleBaseValueInt(jerry_value_t val) {
return (int32_t)jerry_value_as_number(val);
}
/**
* Wraps a vec3 as a plain JS {x, y, z} object.
*/
static inline jerry_value_t moduleBaseVec3ToObject(const vec3 v) {
jerry_value_t obj = jerry_object();
jerry_value_t xKey = jerry_string_sz("x");
jerry_value_t xVal = jerry_number(v[0]);
jerry_object_set(obj, xKey, xVal);
jerry_value_free(xVal);
jerry_value_free(xKey);
jerry_value_t yKey = jerry_string_sz("y");
jerry_value_t yVal = jerry_number(v[1]);
jerry_object_set(obj, yKey, yVal);
jerry_value_free(yVal);
jerry_value_free(yKey);
jerry_value_t zKey = jerry_string_sz("z");
jerry_value_t zVal = jerry_number(v[2]);
jerry_object_set(obj, zKey, zVal);
jerry_value_free(zVal);
jerry_value_free(zKey);
return obj;
}
+12
View File
@@ -7,13 +7,21 @@
#include "modulelist.h"
#include "script/module/moduleplatform.h"
#include "script/module/require/modulerequire.h"
#include "script/module/display/modulemesh.h"
#include "script/module/time/moduletime.h"
#include "script/module/entity/component/modulecomponent.h"
#include "script/module/entity/component/modulecomponentlist.h"
#include "script/module/entity/moduleentity.h"
#include "script/module/scene/modulescene.h"
void moduleListInit(void) {
modulePlatform();
moduleRequireInit();
moduleMeshInit();
moduleTimeInit();
moduleComponentInit();
moduleComponentListInit();
moduleEntityInit();
moduleSceneInit();
}
@@ -21,5 +29,9 @@ void moduleListInit(void) {
void moduleListDispose(void) {
moduleSceneDispose();
moduleEntityDispose();
moduleComponentListDispose();
moduleComponentDispose();
moduleTimeDispose();
moduleMeshDispose();
moduleRequireDispose();
}
+3 -2
View File
@@ -8,8 +8,9 @@
#pragma once
/**
* Registers every script module (Component, Entity, Scene, the platform
* globals). Called once by scriptManagerInit().
* Registers every script module (Component + its typed per-type
* wrappers, Entity, Scene, require(), Mesh, Time, the platform globals).
* Called once by scriptManagerInit().
*/
void moduleListInit(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
modulerequire.c
)
@@ -0,0 +1,271 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "modulerequire.h"
#include "script/module/modulebase.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "util/string.h"
#define MODULE_REQUIRE_WRAP_PREFIX "(function(module){\n"
#define MODULE_REQUIRE_WRAP_SUFFIX "\n})"
modulerequire_t MODULE_REQUIRE;
moduleBaseFunction(moduleRequireFn) {
moduleBaseRequireArgs(1); moduleBaseRequireString(0);
char_t requested[ASSET_FILE_NAME_MAX];
moduleBaseToString(args[0], requested, sizeof(requested));
char_t resolved[ASSET_FILE_NAME_MAX];
moduleRequireResolvePath(requested, resolved, sizeof(resolved));
jerry_value_t cachedModule;
if(moduleRequireCacheGet(resolved, &cachedModule)) {
return moduleBaseGetProp(cachedModule, "exports");
}
assetfile_t file;
errorret_t err = assetFileInit(&file, resolved, NULL, NULL);
if(errorIsNotOk(err)) return moduleBaseThrowError(err);
uint8_t *buffer = NULL;
size_t size = 0;
err = assetFileReadEntire(&file, &buffer, &size);
if(errorIsNotOk(err)) return moduleBaseThrowError(err);
err = assetFileDispose(&file);
if(errorIsNotOk(err)) {
memoryFree(buffer);
return moduleBaseThrowError(err);
}
// Wrap the source in a function so `module` is a real parameter, not a
// global - each required file's scope stays isolated from the others.
const size_t prefixLen = sizeof(MODULE_REQUIRE_WRAP_PREFIX) - 1;
const size_t suffixLen = sizeof(MODULE_REQUIRE_WRAP_SUFFIX) - 1;
const size_t wrappedLen = prefixLen + size + suffixLen;
char_t *wrapped = (char_t *)memoryAllocate(wrappedLen + 1);
memoryCopy(wrapped, MODULE_REQUIRE_WRAP_PREFIX, prefixLen);
memoryCopy(wrapped + prefixLen, buffer, size);
memoryCopy(wrapped + prefixLen + size, MODULE_REQUIRE_WRAP_SUFFIX, suffixLen);
wrapped[wrappedLen] = '\0';
memoryFree(buffer);
jerry_value_t fnValue = jerry_eval(
(const jerry_char_t *)wrapped, wrappedLen, JERRY_PARSE_NO_OPTS
);
memoryFree(wrapped);
if(jerry_value_is_exception(fnValue)) return fnValue;
// Cache before calling so a circular require() sees this module object
// (with whatever `module.exports` has been set so far) instead of
// recursing forever.
jerry_value_t moduleObj = jerry_object();
moduleRequireCacheSet(resolved, moduleObj);
char_t dir[ASSET_FILE_NAME_MAX];
moduleRequireDirname(resolved, dir, sizeof(dir));
moduleRequireDirPush(dir);
jerry_value_t callArgs[1] = { moduleObj };
jerry_value_t callResult = jerry_call(fnValue, jerry_undefined(), callArgs, 1);
jerry_value_free(fnValue);
moduleRequireDirPop();
if(jerry_value_is_exception(callResult)) {
moduleRequireCacheDelete(resolved);
jerry_value_free(moduleObj);
return callResult;
}
jerry_value_free(callResult);
return moduleBaseGetProp(moduleObj, "exports");
}
void moduleRequireInit(void) {
memoryZero(&MODULE_REQUIRE, sizeof(modulerequire_t));
moduleBaseDefineGlobalMethod("require", moduleRequireFn);
}
void moduleRequireDispose(void) {
for(uint8_t i = 0; i < MODULE_REQUIRE.cacheCount; i++) {
jerry_value_free(MODULE_REQUIRE.cache[i].moduleObj);
}
MODULE_REQUIRE.cacheCount = 0;
MODULE_REQUIRE.dirStackCount = 0;
}
void moduleRequireResolvePath(
const char_t *requested,
char_t *outResolved,
const size_t outResolvedSize
) {
assertNotNull(requested, "Requested path cannot be NULL");
assertNotNull(outResolved, "Output buffer cannot be NULL");
bool_t isRelative = (
(requested[0] == '.' && requested[1] == '/') ||
(requested[0] == '.' && requested[1] == '.' && requested[2] == '/')
);
char_t combined[MODULE_REQUIRE_PATH_MAX];
if(isRelative) {
stringFormat(
combined, sizeof(combined) - 1, "%s%s",
moduleRequireDirStackTop(), requested
);
} else {
stringCopy(combined, requested, sizeof(combined) - 1);
}
moduleRequireNormalize(combined, outResolved, outResolvedSize);
}
void moduleRequireNormalize(
const char_t *path,
char_t *outNormalized,
const size_t outSize
) {
assertNotNull(path, "Path cannot be NULL");
assertNotNull(outNormalized, "Output buffer cannot be NULL");
char_t scratch[MODULE_REQUIRE_PATH_MAX];
stringCopy(scratch, path, sizeof(scratch) - 1);
char_t *segments[MODULE_REQUIRE_SEGMENT_MAX];
uint8_t segmentCount = 0;
char_t *cursor = scratch;
bool_t more = true;
while(more) {
char_t *segment = cursor;
while(*cursor != '/' && *cursor != '\0') cursor++;
more = (*cursor == '/');
if(more) { *cursor = '\0'; cursor++; }
if(segment[0] == '\0' || stringEquals(segment, ".")) {
// Skip empty (e.g. leading/double slash) and "." segments.
} else if(stringEquals(segment, "..")) {
if(segmentCount > 0) segmentCount--;
} else {
assertTrue(
segmentCount < MODULE_REQUIRE_SEGMENT_MAX,
"require() path has too many segments"
);
segments[segmentCount++] = segment;
}
}
size_t written = 0;
outNormalized[0] = '\0';
for(uint8_t i = 0; i < segmentCount; i++) {
if(i > 0) {
assertTrue(written + 1 < outSize, "require() path too long");
outNormalized[written++] = '/';
}
size_t segLen = 0;
while(segments[i][segLen] != '\0') segLen++;
assertTrue(written + segLen < outSize, "require() path too long");
memoryCopy(outNormalized + written, segments[i], segLen);
written += segLen;
}
outNormalized[written] = '\0';
}
void moduleRequireDirname(
const char_t *path,
char_t *outDir,
const size_t outDirSize
) {
assertNotNull(path, "Path cannot be NULL");
assertNotNull(outDir, "Output buffer cannot be NULL");
char_t *lastSlash = stringFindLastChar(path, '/');
if(lastSlash == NULL) {
outDir[0] = '\0';
return;
}
size_t len = (size_t)(lastSlash - path) + 1;
assertTrue(len < outDirSize, "Directory path too long");
memoryCopy(outDir, path, len);
outDir[len] = '\0';
}
bool_t moduleRequireCacheGet(
const char_t *resolvedPath,
jerry_value_t *outModuleObj
) {
assertNotNull(resolvedPath, "Resolved path cannot be NULL");
assertNotNull(outModuleObj, "Output module object cannot be NULL");
for(uint8_t i = 0; i < MODULE_REQUIRE.cacheCount; i++) {
if(stringEquals(MODULE_REQUIRE.cache[i].path, resolvedPath)) {
*outModuleObj = MODULE_REQUIRE.cache[i].moduleObj;
return true;
}
}
return false;
}
void moduleRequireCacheSet(
const char_t *resolvedPath,
const jerry_value_t moduleObj
) {
assertNotNull(resolvedPath, "Resolved path cannot be NULL");
assertTrue(
MODULE_REQUIRE.cacheCount < MODULE_REQUIRE_CACHE_MAX,
"require() module cache is full"
);
modulerequirecacheentry_t *entry =
&MODULE_REQUIRE.cache[MODULE_REQUIRE.cacheCount++];
stringCopy(entry->path, resolvedPath, ASSET_FILE_NAME_MAX - 1);
entry->moduleObj = moduleObj;
}
void moduleRequireCacheDelete(const char_t *resolvedPath) {
assertNotNull(resolvedPath, "Resolved path cannot be NULL");
for(uint8_t i = 0; i < MODULE_REQUIRE.cacheCount; i++) {
if(!stringEquals(MODULE_REQUIRE.cache[i].path, resolvedPath)) continue;
MODULE_REQUIRE.cacheCount--;
MODULE_REQUIRE.cache[i] = MODULE_REQUIRE.cache[MODULE_REQUIRE.cacheCount];
return;
}
}
void moduleRequireDirPush(const char_t *dir) {
assertNotNull(dir, "Directory cannot be NULL");
assertTrue(
MODULE_REQUIRE.dirStackCount < MODULE_REQUIRE_STACK_MAX,
"require() nesting too deep"
);
stringCopy(
MODULE_REQUIRE.dirStack[MODULE_REQUIRE.dirStackCount++],
dir, ASSET_FILE_NAME_MAX - 1
);
}
void moduleRequireDirPop(void) {
assertTrue(
MODULE_REQUIRE.dirStackCount > 0, "require() dir stack underflow"
);
MODULE_REQUIRE.dirStackCount--;
}
const char_t * moduleRequireDirStackTop(void) {
if(MODULE_REQUIRE.dirStackCount == 0) return "";
return MODULE_REQUIRE.dirStack[MODULE_REQUIRE.dirStackCount - 1];
}
@@ -0,0 +1,150 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#include "asset/assetfile.h"
#include <jerryscript.h>
/** Max number of distinct files require() will keep resolved/cached. */
#define MODULE_REQUIRE_CACHE_MAX 32
/** Max require() nesting depth (a file requiring a file requiring...). */
#define MODULE_REQUIRE_STACK_MAX 16
/** Max path segments handled while resolving a single require() call. */
#define MODULE_REQUIRE_SEGMENT_MAX 16
/** Scratch buffer size used while joining a base dir with a request. */
#define MODULE_REQUIRE_PATH_MAX (ASSET_FILE_NAME_MAX * 2)
typedef struct {
char_t path[ASSET_FILE_NAME_MAX];
jerry_value_t moduleObj;
} modulerequirecacheentry_t;
typedef struct {
modulerequirecacheentry_t cache[MODULE_REQUIRE_CACHE_MAX];
uint8_t cacheCount;
/** Directories of files currently mid-require(), innermost last. */
char_t dirStack[MODULE_REQUIRE_STACK_MAX][ASSET_FILE_NAME_MAX];
uint8_t dirStackCount;
} modulerequire_t;
extern modulerequire_t MODULE_REQUIRE;
/**
* Registers the global require() function.
*/
void moduleRequireInit(void);
/**
* Frees every cached module object. Called once by moduleListDispose().
*/
void moduleRequireDispose(void);
/**
* Resolves a require() argument to an asset-root-relative path. Paths
* starting with "./" or "../" are resolved against the directory of the
* file currently being required (or the asset root if require() is not
* currently nested); any other path is treated as already asset-root-
* relative. "." and ".." segments are collapsed either way.
*
* @param requested The raw string passed to require().
* @param outResolved Output buffer for the resolved path.
* @param outResolvedSize Capacity of outResolved, including the null
* terminator.
*/
void moduleRequireResolvePath(
const char_t *requested,
char_t *outResolved,
const size_t outResolvedSize
);
/**
* Collapses "." and ".." segments out of a slash-separated path.
*
* @param path The path to normalize.
* @param outNormalized Output buffer for the normalized path.
* @param outSize Capacity of outNormalized, including the null
* terminator.
*/
void moduleRequireNormalize(
const char_t *path,
char_t *outNormalized,
const size_t outSize
);
/**
* Extracts the directory portion (including trailing slash) of a
* resolved path, or an empty string if the path has no directory.
*
* @param path The resolved path.
* @param outDir Output buffer for the directory.
* @param outDirSize Capacity of outDir, including the null terminator.
*/
void moduleRequireDirname(
const char_t *path,
char_t *outDir,
const size_t outDirSize
);
/**
* Looks up a previously require()'d module by its resolved path.
*
* @param resolvedPath The resolved path to look up.
* @param outModuleObj Receives the cached module object if found.
* @return true if a cache entry was found.
*/
bool_t moduleRequireCacheGet(
const char_t *resolvedPath,
jerry_value_t *outModuleObj
);
/**
* Registers a module object under a resolved path. Takes ownership of
* the passed value - it is only freed by moduleRequireDispose() or
* moduleRequireCacheDelete().
*
* @param resolvedPath The resolved path to cache under.
* @param moduleObj The module object to cache.
*/
void moduleRequireCacheSet(
const char_t *resolvedPath,
const jerry_value_t moduleObj
);
/**
* Evicts and frees a cache entry. Used to roll back a failed load so a
* later require() of the same path can retry instead of being stuck
* with a broken cache entry.
*
* @param resolvedPath The resolved path to evict.
*/
void moduleRequireCacheDelete(const char_t *resolvedPath);
/**
* Pushes a directory onto the require() nesting stack, becoming the new
* base for resolving relative requires until popped.
*
* @param dir The directory (including trailing slash, or empty) to push.
*/
void moduleRequireDirPush(const char_t *dir);
/**
* Pops the require() nesting stack.
*/
void moduleRequireDirPop(void);
/**
* Gets the current top of the require() nesting stack.
*
* @return The current base directory, or an empty string if the stack is
* empty (i.e. require() is not currently nested).
*/
const char_t * moduleRequireDirStackTop(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
moduletime.c
)
+29
View File
@@ -0,0 +1,29 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "moduletime.h"
#include "script/module/modulebase.h"
#include "time/time.h"
moduleBaseFunction(moduleTimeGetDelta) {
return jerry_number(TIME.delta);
}
moduleBaseFunction(moduleTimeGetTime) {
return jerry_number(TIME.time);
}
void moduleTimeInit(void) {
jerry_value_t timeObj = jerry_object();
moduleBaseDefineProperty(timeObj, "delta", moduleTimeGetDelta, NULL);
moduleBaseDefineProperty(timeObj, "time", moduleTimeGetTime, NULL);
moduleBaseSetValue("Time", timeObj);
jerry_value_free(timeObj);
}
void moduleTimeDispose(void) {
}
+20
View File
@@ -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 the global `Time` object, exposing `Time.delta` and
* `Time.time` as live getters over the engine's TIME state (see
* time/time.h). Values always reflect the most recent timeUpdate().
*/
void moduleTimeInit(void);
/**
* Disposes the time module's script resources.
*/
void moduleTimeDispose(void);
+2 -4
View File
@@ -6,12 +6,10 @@
*/
#include "game/game.h"
#include "scene/scene.h"
#include "scene/overworldscene.h"
#include "script/scriptmanager.h"
errorret_t gameInit(void) {
sceneid_t testSceneId = overworldSceneCreate();
sceneSetActive(testSceneId);
errorChain(scriptManagerExecFile("scripts/overworldscene.js", NULL));
errorOk();
}
-5
View File
@@ -3,9 +3,4 @@
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
overworldscene.c
)
add_subdirectory(prefab)
-138
View File
@@ -1,138 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "overworldscene.h"
#include "scene/scene.h"
#include "entity/entitymanager.h"
#include "entity/component/display/entityposition.h"
#include "entity/component/display/entitycamera.h"
#include "entity/component/display/entityrenderable.h"
#include "entity/component/physics/entityphysics.h"
#include "display/mesh/plane.h"
#include "display/mesh/capsule.h"
#include "display/color.h"
#include "time/time.h"
// Radius must stay well outside the floor's footprint (a 20x20 plane has a
// corner-to-center distance of 10*sqrt(2) =~ 14.1) -- orbiting inside that
// puts parts of the floor's own geometry near/behind the camera's view
// direction, which the PSP's legacy GU pipeline can't clip properly and
// drops the whole triangle instead of clipping it.
#define OVERWORLD_SCENE_CAMERA_ORBIT_RADIUS 18.0f
#define OVERWORLD_SCENE_CAMERA_ORBIT_HEIGHT 10.0f
#define OVERWORLD_SCENE_CAMERA_ORBIT_SPEED 0.5f
static overworldcameraorbit_t OVERWORLD_SCENE_CAMERA_ORBIT;
sceneid_t overworldSceneCreate(void) {
sceneid_t sceneId = sceneCreate();
entitymanager_t *mgr = sceneGetEntities(sceneId);
// Camera, orbiting the origin (see overworldSceneCameraOrbitUpdate).
entityid_t camEntity = entityManagerAdd(mgr);
componentid_t camPosition = entityAddComponent(
mgr, camEntity, COMPONENT_TYPE_POSITION
);
entityAddComponent(mgr, camEntity, COMPONENT_TYPE_CAMERA);
OVERWORLD_SCENE_CAMERA_ORBIT = (overworldcameraorbit_t){
.angle = 0.0f,
.radius = OVERWORLD_SCENE_CAMERA_ORBIT_RADIUS,
.height = OVERWORLD_SCENE_CAMERA_ORBIT_HEIGHT,
.speed = OVERWORLD_SCENE_CAMERA_ORBIT_SPEED
};
entityUpdateAdd(
mgr, camEntity, overworldSceneCameraOrbitUpdate, camPosition,
&OVERWORLD_SCENE_CAMERA_ORBIT
);
// Position it correctly for the very first rendered frame, rather than
// waiting for the first fixed tick to run.
overworldSceneCameraOrbitUpdate(
mgr, camEntity, camPosition, &OVERWORLD_SCENE_CAMERA_ORBIT
);
// Static ground plane. Physics ignores the entity's position component --
// the shape's own normal/distance fully define the plane in world space.
entityid_t planeEntity = entityManagerAdd(mgr);
componentid_t planePosition = entityAddComponent(
mgr, planeEntity, COMPONENT_TYPE_POSITION
);
entityPositionSetLocalPosition(
mgr, planeEntity, planePosition, (vec3){ -10.0f, 0.0f, -10.0f }
);
entityPositionSetLocalScale(
mgr, planeEntity, planePosition, (vec3){ 20.0f, 1.0f, 20.0f }
);
componentid_t planePhysics = entityAddComponent(
mgr, planeEntity, COMPONENT_TYPE_PHYSICS
);
entityPhysicsSetBodyType(
mgr, planeEntity, planePhysics, PHYSICS_BODY_STATIC
);
entityPhysicsSetShape(mgr, planeEntity, planePhysics, (physicsshape_t){
.type = PHYSICS_SHAPE_PLANE,
.data.plane = { .normal = { 0.0f, 1.0f, 0.0f }, .distance = 0.0f }
});
componentid_t planeRenderable = entityAddComponent(
mgr, planeEntity, COMPONENT_TYPE_RENDERABLE
);
entityrenderable_t *planeR = componentGetData(
mgr, planeEntity, planeRenderable, COMPONENT_TYPE_RENDERABLE
);
planeR->data.material.meshes[0] = &PLANE_MESH_SIMPLE;
entityRenderableSetColor(mgr, planeEntity, planeRenderable, COLOR_GRAY);
// Player: dynamic capsule body, moved relative to the camera by
// COMPONENT_TYPE_PLAYER's own update callback (see entityplayer.c).
entityid_t playerEntity = entityManagerAdd(mgr);
componentid_t playerPosition = entityAddComponent(
mgr, playerEntity, COMPONENT_TYPE_POSITION
);
entityPositionSetLocalPosition(
mgr, playerEntity, playerPosition, (vec3){ 0.0f, 2.0f, 0.0f }
);
componentid_t playerPhysics = entityAddComponent(
mgr, playerEntity, COMPONENT_TYPE_PHYSICS
);
entityPhysicsSetShape(mgr, playerEntity, playerPhysics, (physicsshape_t){
.type = PHYSICS_SHAPE_CAPSULE,
.data.capsule = { .radius = 0.5f, .halfHeight = 0.5f }
});
componentid_t playerRenderable = entityAddComponent(
mgr, playerEntity, COMPONENT_TYPE_RENDERABLE
);
entityrenderable_t *playerR = componentGetData(
mgr, playerEntity, playerRenderable, COMPONENT_TYPE_RENDERABLE
);
playerR->data.material.meshes[0] = &CAPSULE_MESH_SIMPLE;
entityRenderableSetColor(mgr, playerEntity, playerRenderable, COLOR_BLUE);
entityAddComponent(mgr, playerEntity, COMPONENT_TYPE_PLAYER);
return sceneId;
}
void overworldSceneCameraOrbitUpdate(
entitymanager_t *mgr,
const entityid_t entityId,
const componentid_t componentId,
void *user
) {
overworldcameraorbit_t *orbit = (overworldcameraorbit_t *)user;
orbit->angle += TIME.delta * orbit->speed;
vec3 eye = {
cosf(orbit->angle) * orbit->radius,
orbit->height,
sinf(orbit->angle) * orbit->radius
};
entityPositionLookAt(
mgr, entityId, componentId,
eye,
(vec3){ 0.0f, 0.0f, 0.0f },
(vec3){ 0.0f, 1.0f, 0.0f }
);
}
-45
View File
@@ -1,45 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "scene/scenebase.h"
#include "entity/entitybase.h"
typedef struct {
float_t angle;
float_t radius;
float_t height;
float_t speed;
} overworldcameraorbit_t;
/**
* Creates the overworld scene: a static ground plane, a player entity
* (dynamic capsule body, moved relative to the camera by
* COMPONENT_TYPE_PLAYER's own update callback), and a camera orbiting the
* origin (see overworldSceneCameraOrbitUpdate). Does not make the scene
* active -- call sceneSetActive with the returned ID.
*
* @return The ID of the newly created scene.
*/
sceneid_t overworldSceneCreate(void);
/**
* Per-tick update for the test camera: orbits it around the world origin
* at a fixed radius/height/speed, always looking back at the origin.
* Registered automatically by overworldSceneCreate.
*
* @param mgr The entity manager that owns the entity.
* @param entityId The camera entity ID.
* @param componentId The camera's position component ID.
* @param user Pointer to this camera's overworldcameraorbit_t state.
*/
void overworldSceneCameraOrbitUpdate(
entitymanager_t *mgr,
const entityid_t entityId,
const componentid_t componentId,
void *user
);