This commit is contained in:
2026-04-28 08:04:01 -05:00
parent 19f2a2c616
commit a41b0e916b
57 changed files with 5023 additions and 3503 deletions
+3 -12
View File
@@ -32,18 +32,9 @@ if(NOT yyjson_FOUND)
endif()
endif()
if(NOT Lua_FOUND)
find_package(Lua REQUIRED)
if(Lua_FOUND AND NOT TARGET Lua::Lua)
add_library(Lua::Lua INTERFACE IMPORTED)
set_target_properties(
Lua::Lua
PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${LUA_INCLUDE_DIR}"
INTERFACE_LINK_LIBRARIES "${LUA_LIBRARIES}"
)
endif()
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC Lua::Lua)
if(NOT jerryscript_FOUND)
find_package(jerryscript REQUIRED)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC jerryscript)
endif()
# Includes
@@ -1,82 +1,95 @@
/**
* Copyright (c) 2026 Dominic Masters
*
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "assetscriptloader.h"
#include "assert/assert.h"
#include <stdlib.h>
#include <zip.h>
errorret_t assetScriptLoader(assetfile_t *file) {
assertNotNull(file, "Asset file cannot be NULL");
assertNull(file->zipFile, "Asset file zip handle must be NULL");
assertNull(file->zipFile, "Asset file zip handle must be NULL before open");
assertNotNull(file->output, "Asset file output cannot be NULL");
assetscript_t *script = (assetscript_t *)file->output;
// Open the asset for buffering
errorChain(assetFileOpen(file));
// Request loading
if(lua_load(
script->ctx->luaState,
assetScriptReader,
file,
file->filename,
NULL
) != LUA_OK) {
const char_t *strErr = lua_tostring(script->ctx->luaState, -1);
lua_pop(script->ctx->luaState, 1);
errorThrow("Failed to load Lua script: %s", strErr);
// Accumulate full source into a dynamically grown buffer.
size_t srcLen = 0;
size_t capacity = ASSET_SCRIPT_CHUNK_SIZE;
char_t *src = (char_t *)malloc(capacity + 1);
if(!src) {
assetFileClose(file);
errorThrow("Out of memory reading script: %s", file->filename);
}
// Now loaded, exec
if(lua_pcall(script->ctx->luaState, 0, LUA_MULTRET, 0) != LUA_OK) {
const char_t *strErr = lua_tostring(script->ctx->luaState, -1);
lua_pop(script->ctx->luaState, 1);
errorThrow("Failed to execute Lua script: %s", strErr);
while(1) {
if(srcLen + ASSET_SCRIPT_CHUNK_SIZE > capacity) {
capacity = srcLen + ASSET_SCRIPT_CHUNK_SIZE;
char_t *tmp = (char_t *)realloc(src, capacity + 1);
if(!tmp) {
free(src);
assetFileClose(file);
errorThrow("Out of memory reading script: %s", file->filename);
}
src = tmp;
}
zip_int64_t n = zip_fread(
file->zipFile, src + srcLen, ASSET_SCRIPT_CHUNK_SIZE
);
if(n <= 0) break;
srcLen += (size_t)n;
}
src[srcLen] = '\0';
errorret_t closeRet = assetFileClose(file);
jerry_value_t result = jerry_eval(
(const jerry_char_t *)src,
srcLen,
JERRY_PARSE_NO_OPTS
);
free(src);
if(jerry_value_is_exception(result)) {
jerry_value_t errVal = jerry_exception_value(result, false);
jerry_value_t errStr = jerry_value_to_string(errVal);
char_t buf[256];
jerry_size_t len = jerry_string_to_buffer(
errStr, JERRY_ENCODING_UTF8, (jerry_char_t *)buf, sizeof(buf) - 1
);
buf[len] = '\0';
jerry_value_free(errStr);
jerry_value_free(errVal);
jerry_value_free(result);
errorThrow("Script error in '%s': %s", file->filename, buf);
}
// Close the file
return assetFileClose(file);
if(script->resultOut != NULL) {
*(script->resultOut) = result;
} else {
jerry_value_free(result);
}
return closeRet;
}
errorret_t assetScriptLoad(const char_t *path, scriptcontext_t *ctx) {
errorret_t assetScriptLoad(
const char_t *path,
scriptcontext_t *ctx,
jerry_value_t *resultOut
) {
assertNotNull(path, "Script path cannot be NULL");
assertNotNull(ctx, "Script context cannot be NULL");
assetscript_t script;
script.ctx = ctx;
return assetLoad(
path,
assetScriptLoader,
NULL,
&script
);
assetscript_t scriptData;
scriptData.ctx = ctx;
scriptData.resultOut = resultOut;
return assetLoad(path, assetScriptLoader, NULL, &scriptData);
}
const char_t * assetScriptReader(lua_State* L, void* data, size_t* size) {
assetfile_t *file = (assetfile_t*)data;
assertNotNull(file, "Script asset file cannot be NULL");
assertNotNull(file->zipFile, "Script asset zip handle cannot be NULL");
assertNotNull(file->output, "Script asset output cannot be NULL");
assetscript_t *script = (assetscript_t *)file->output;
assertNotNull(script, "Script asset output cannot be NULL");
zip_int64_t read = zip_fread(
file->zipFile,
script->buffer,
sizeof(script->buffer)
);
if(read < 0) {
*size = 0;
return NULL;
}
*size = (size_t)read;
return script->buffer;
}
@@ -1,6 +1,6 @@
/**
* Copyright (c) 2026 Dominic Masters
*
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
@@ -9,7 +9,7 @@
#include "asset/asset.h"
#include "script/scriptcontext.h"
#define ASSET_SCRIPT_BUFFER_SIZE 1024
#define ASSET_SCRIPT_CHUNK_SIZE 1024
typedef struct {
void *nothing;
@@ -17,12 +17,13 @@ typedef struct {
typedef struct {
scriptcontext_t *ctx;
char_t buffer[ASSET_SCRIPT_BUFFER_SIZE];
jerry_value_t *resultOut;
} assetscript_t;
/**
* Handler for script assets.
*
* Handler for script assets. Reads the full source, evaluates it with
* JerryScript, and optionally stores the result.
*
* @param file Asset file to load the script from.
* @return Any error that occurs during loading.
*/
@@ -30,19 +31,16 @@ errorret_t assetScriptLoader(assetfile_t *file);
/**
* Loads a script from the specified path.
*
* @param path Path to the script asset.
* @param ctx Script context to load the script into.
*
* @param path Path to the script asset.
* @param ctx Script context to load the script into.
* @param resultOut Optional out-parameter for the script return value.
* Caller must call jerry_value_free() if non-NULL.
* Pass NULL to discard the return value.
* @return Any error that occurs during loading.
*/
errorret_t assetScriptLoad(const char_t *path, scriptcontext_t *ctx);
/**
* Reader function for Lua to read script data from the asset.
*
* @param L Lua state.
* @param data Pointer to the scriptcontext_t structure.
* @param size Pointer to store the size of the read data.
* @return Pointer to the read data buffer.
*/
const char_t * assetScriptReader(lua_State* L, void* data, size_t* size);
errorret_t assetScriptLoad(
const char_t *path,
scriptcontext_t *ctx,
jerry_value_t *resultOut
);
+1 -1
View File
@@ -50,7 +50,7 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
/* Run the init script. */
consolePrint("Engine initialized");
errorChain(scriptContextExecFile(&SCRIPT_MANAGER.mainContext, "init.lua"));
errorChain(scriptContextExecFile(&SCRIPT_MANAGER.mainContext, "init.js", NULL));
errorOk();
}
+54 -79
View File
@@ -1,6 +1,6 @@
/**
* Copyright (c) 2026 Dominic Masters
*
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
@@ -10,16 +10,6 @@
#include "util/memory.h"
#include "console/console.h"
static int eventLuaTraceback(lua_State *L) {
const char *msg = lua_tostring(L, 1);
if(msg) {
luaL_traceback(L, L, msg, 1);
} else {
lua_pushliteral(L, "(no error message)");
}
return 1;
}
void eventInit(
event_t *event,
eventlistener_t *array,
@@ -57,19 +47,16 @@ eventsub_t eventSubscribeUser(
"Script event listener context cannot be NULL"
);
assertTrue(
user.script.luaFunctionRef != LUA_NOREF,
user.script.funcValue != 0,
"Script event listener function reference is invalid"
);
} else {
assertUnreachable("Unknown event listener type");
}
// Gen a new ID
eventsub_t id = event->nextId++;
// Did we wrap?
assertTrue(event->nextId != 0, "Event subscription ID overflow");
// Append listener
eventlistener_t *listener = &event->listenerArray[event->listenerCount++];
memoryZero(listener, sizeof(eventlistener_t));
listener->user = user;
@@ -94,49 +81,29 @@ eventsub_t eventSubscribe(
eventsub_t eventSubscribeScriptContext(
event_t *event,
scriptcontext_t *context,
const int functionIndex
jerry_value_t funcValue
) {
assertNotNull(context, "Script context cannot be NULL");
assertTrue(
lua_isfunction(context->luaState, functionIndex),
"Expected function at given index"
);
assertTrue(funcValue != 0, "Script function value is invalid");
// Create a reference to the function
lua_pushvalue(context->luaState, functionIndex);
int funcRef = luaL_ref(context->luaState, LUA_REGISTRYINDEX);
eventscript_t scriptUser = {
.context = context,
.luaFunctionRef = funcRef
};
// Note to the context that it is now a part of this event
bool_t alreadySubbed = false;
uint8_t i;
i = 0;
uint8_t i = 0;
do {
if(context->subscribedEvents[i] != event) {
i++;
continue;
if(context->subscribedEvents[i] == event) {
alreadySubbed = true;
break;
}
if(context->subscribedEvents[i] == NULL) break;
alreadySubbed = true;
break;
i++;
} while(i < SCRIPT_CONTEXT_MAX_EVENT_SUBSCRIPTIONS);
if(!alreadySubbed) {
i = 0;
do {
if(context->subscribedEvents[i] != NULL) {
i++;
continue;
if(context->subscribedEvents[i] == NULL) {
context->subscribedEvents[i] = event;
break;
}
context->subscribedEvents[i] = event;
break;
i++;
} while(i < SCRIPT_CONTEXT_MAX_EVENT_SUBSCRIPTIONS);
assertTrue(
@@ -148,7 +115,12 @@ eventsub_t eventSubscribeScriptContext(
return eventSubscribeUser(
event,
EVENT_TYPE_SCRIPT,
(eventuserdata_t){ .script = scriptUser }
(eventuserdata_t){
.script = {
.context = context,
.funcValue = funcValue
}
}
);
}
@@ -165,19 +137,13 @@ void eventUnsubscribe(event_t *event, const eventsub_t id) {
continue;
}
// Release Lua registry reference before the slot is overwritten
if(event->listenerArray[index].type == EVENT_TYPE_SCRIPT) {
scriptcontext_t *ctx = event->listenerArray[index].user.script.context;
if(ctx != NULL && ctx->luaState != NULL) {
luaL_unref(
ctx->luaState,
LUA_REGISTRYINDEX,
event->listenerArray[index].user.script.luaFunctionRef
);
jerry_value_t funcVal = event->listenerArray[index].user.script.funcValue;
if(funcVal != 0) {
jerry_value_free(funcVal);
}
}
// Swap with last and shrink
event->listenerArray[index] = event->listenerArray[--event->listenerCount];
return;
} while(index < event->listenerCount);
@@ -188,7 +154,7 @@ void eventUnsubscribeScriptContext(event_t *event, const scriptcontext_t *ctx) {
assertNotNull(ctx, "Script context cannot be NULL");
if(event->listenerCount == 0) return;
uint16_t i = 0;
do {
eventlistener_t *listener = &event->listenerArray[i];
@@ -199,9 +165,6 @@ void eventUnsubscribeScriptContext(event_t *event, const scriptcontext_t *ctx) {
i++;
continue;
}
// This listener belongs to the context and will need to go away. This will
// in turn decrement the listener count so we don't increment i here.
eventUnsubscribe(event, listener->id);
} while(i < event->listenerCount);
}
@@ -214,43 +177,55 @@ void eventInvoke(
assertNotNull(event, "Event cannot be NULL");
if(event->listenerCount == 0) return;
event->isInvoking = true;
uint16_t i = 0;
eventdata_t data ={
eventdata_t data = {
.event = event,
.eventParams = eventParams,
};
uint16_t i = 0;
do {
eventlistener_t *listener = &event->listenerArray[i];
if(listener->type == EVENT_TYPE_C) {
listener->user.c.callback(&data, listener->user.c);
} else if(listener->type == EVENT_TYPE_SCRIPT) {
lua_State *L = listener->user.script.context->luaState;
assertNotNull(L, "Lua state in event listener cannot be NULL");
jerry_value_t funcVal = listener->user.script.funcValue;
assertNotNull((void *)(uintptr_t)funcVal, "Script function value is NULL");
lua_pushcfunction(L, eventLuaTraceback);
int handlerIdx = lua_gettop(L);
lua_rawgeti(L, LUA_REGISTRYINDEX, listener->user.script.luaFunctionRef);
jerry_value_t callArgs[1];
jerry_length_t argCount = 0;
int numArgs = 0;
if(eventParams != NULL) {
lua_pushlightuserdata(L, (void *)eventParams);
numArgs = 1;
callArgs[0] = jerry_object();
jerry_object_set_native_ptr(
callArgs[0], &JS_PTR_NATIVE_INFO, (void *)eventParams
);
argCount = 1;
}
printf("Invoking Lua event listener with %d argument(s)\n", numArgs);
jerry_value_t result = jerry_call(
funcVal, jerry_undefined(), callArgs, argCount
);
if(lua_pcall(L, numArgs, 0, handlerIdx) != LUA_OK) {
const char_t *strErr = lua_tostring(L, -1);
consolePrint("Error invoking Lua event listener:\n%s\n", strErr);
lua_pop(L, 1);
if(argCount > 0) jerry_value_free(callArgs[0]);
if(jerry_value_is_exception(result)) {
jerry_value_t errStr = jerry_value_to_string(
jerry_exception_value(result, false)
);
char_t buf[256];
jerry_size_t len = jerry_string_to_buffer(
errStr, JERRY_ENCODING_UTF8, (jerry_char_t *)buf, sizeof(buf) - 1
);
buf[len] = '\0';
jerry_value_free(errStr);
consolePrint("Error invoking script event listener:\n%s\n", buf);
}
lua_pop(L, 1);
jerry_value_free(result);
} else {
assertUnreachable("Unknown event listener type");
}
@@ -258,4 +233,4 @@ void eventInvoke(
} while(i < event->listenerCount);
event->isInvoking = false;
}
}
+1 -1
View File
@@ -84,7 +84,7 @@ eventsub_t eventSubscribe(
eventsub_t eventSubscribeScriptContext(
event_t *event,
scriptcontext_t *context,
const int functionIndex
jerry_value_t funcValue
);
/**
+3 -3
View File
@@ -1,6 +1,6 @@
/**
* Copyright (c) 2026 Dominic Masters
*
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
@@ -16,7 +16,7 @@ typedef enum {
typedef struct {
scriptcontext_t *context;
int luaFunctionRef;
jerry_value_t funcValue;
} eventscript_t;
typedef struct eventc_s {
@@ -27,4 +27,4 @@ typedef struct eventc_s {
typedef union eventuserdata_u {
eventscript_t script;
eventc_t c;
} eventuserdata_t;
} eventuserdata_t;
+15 -31
View File
@@ -24,7 +24,7 @@ scene_t SCENE;
errorret_t sceneInit(void) {
memoryZero(&SCENE, sizeof(scene_t));
SCENE.scriptRef = LUA_NOREF;
SCENE.scriptRef = SCENE_SCRIPT_REF_NONE;
errorOk();
}
@@ -40,25 +40,22 @@ errorret_t sceneUpdate(void) {
}
if(SCENE.sceneActive) {
errorChain(moduleSceneCall(SCRIPT_MANAGER.mainContext.luaState, "update"));
errorChain(moduleSceneCall("update"));
}
errorOk();
}
errorret_t sceneRender(void) {
// Get Cameras
entityid_t camEnts[ENTITY_COUNT_MAX];
componentid_t camComps[ENTITY_COUNT_MAX];
entityid_t camCount = componentGetEntitiesWithComponent(
COMPONENT_TYPE_CAMERA, camEnts, camComps
);
// Prep Matricies
mat4 view, proj, model;
if(camCount > 0) {
// Get meshes
entityid_t meshEnts[ENTITY_COUNT_MAX];
componentid_t meshComps[ENTITY_COUNT_MAX];
entityid_t meshCount = componentGetEntitiesWithComponent(
@@ -68,7 +65,6 @@ errorret_t sceneRender(void) {
if(meshCount > 0) {
errorChain(shaderBind(&SHADER_UNLIT));
// For each camera.
for(entityid_t camIndex = 0; camIndex < camCount; camIndex++) {
entityid_t camEnt = camEnts[camIndex];
componentid_t camComp = camComps[camIndex];
@@ -81,15 +77,11 @@ errorret_t sceneRender(void) {
entityCameraGetProjection(camEnt, camComp, proj);
entityPositionGetTransform(camEnt, camPos, view);
// For each mesh.
for(entityid_t meshIndex = 0; meshIndex < meshCount; meshIndex++) {
entityid_t meshEnt = meshEnts[meshIndex];
componentid_t meshComp = meshComps[meshIndex];
mesh_t *mesh = entityMeshGetMesh(meshEnt, meshComp);
if(mesh == NULL) {
continue;
}
if(mesh == NULL) continue;
componentid_t meshPos = entityGetComponent(
meshEnt, COMPONENT_TYPE_POSITION
@@ -129,7 +121,6 @@ errorret_t sceneRender(void) {
}
}
// Here is where UI will go
glm_ortho(
0.0f, SCREEN.width,
SCREEN.height, 0.0f,
@@ -163,15 +154,11 @@ errorret_t sceneSetImmediate(const char_t *scene) {
}
if(SCENE.sceneActive) {
errorChain(moduleSceneCall(
SCRIPT_MANAGER.mainContext.luaState, "dispose"
));
errorChain(moduleSceneCall("dispose"));
SCENE.sceneActive = false;
}
// Wipe Scene back to a clean table before loading the next file so custom
// methods from the previous scene (e.g. Scene.doThing) cannot bleed through.
moduleSceneReset(SCRIPT_MANAGER.mainContext.luaState);
moduleSceneReset();
stringCopy(
SCENE.sceneCurrent,
@@ -180,22 +167,19 @@ errorret_t sceneSetImmediate(const char_t *scene) {
);
if(scene != NULL) {
lua_State *L = SCRIPT_MANAGER.mainContext.luaState;
jerry_value_t sceneObj = SCENE_SCRIPT_REF_NONE;
errorChain(scriptContextExecFile(
&SCRIPT_MANAGER.mainContext, scene, &sceneObj
));
int32_t stackBase = lua_gettop(L);
errorChain(scriptContextExecFile(&SCRIPT_MANAGER.mainContext, scene));
int32_t nReturns = lua_gettop(L) - stackBase;
if(nReturns < 1 || !lua_istable(L, stackBase + 1)) {
lua_settop(L, stackBase);
errorThrow("Scene '%s' must return a table", scene);
if(!jerry_value_is_object(sceneObj)) {
if(sceneObj != SCENE_SCRIPT_REF_NONE) jerry_value_free(sceneObj);
errorThrow("Scene '%s' must return an object", scene);
}
lua_settop(L, stackBase + 1);
SCENE.scriptRef = luaL_ref(L, LUA_REGISTRYINDEX);
SCENE.scriptRef = sceneObj;
errorChain(moduleSceneCall(SCRIPT_MANAGER.mainContext.luaState, "init"));
errorChain(moduleSceneCall("init"));
SCENE.sceneActive = true;
}
@@ -211,6 +195,6 @@ void sceneSet(const char_t *scene) {
}
errorret_t sceneDispose(void) {
errorChain(moduleSceneCall(SCRIPT_MANAGER.mainContext.luaState, "dispose"));
errorChain(moduleSceneCall("dispose"));
errorOk();
}
+15 -23
View File
@@ -14,63 +14,55 @@
typedef struct {
bool_t sceneActive;
int scriptRef;
jerry_value_t scriptRef;
char_t sceneCurrent[ASSET_FILE_PATH_MAX];
char_t sceneNext[ASSET_FILE_PATH_MAX];
} scene_t;
extern scene_t SCENE;
/** Sentinel value meaning no scene script is loaded. */
#define SCENE_SCRIPT_REF_NONE ((jerry_value_t)0)
/**
* Initializes the scene manager
*
* Initializes the scene manager.
*
* @return Any error state that happened.
*/
errorret_t sceneInit(void);
/**
* Ticks the scene manager, may or may not call the scene's update method
* depending on time state.
*
* Ticks the scene manager; may call the scene's update method.
*
* @return Any error state that happened.
*/
errorret_t sceneUpdate(void);
/**
* Renders the scene, happens regardless of time.
*
* Renders the scene.
*
* @return Any error state that happened.
*/
errorret_t sceneRender(void);
/**
* Internal method to immediately switch scenes, this will also delete some of
* the lua state, which can cause problems if called during a lua callback, so
* this is managed by the scene manager.
*
* Immediately switches scenes, disposing the current one first.
*
* @param scene Scene to switch to (asset file path).
* @return Any error state that happened.
*/
errorret_t sceneSetImmediate(const char_t *scene);
/**
* Requests the scene manager to change scenes when it is next able to. This
* will not guarantee that the scene is valid or without errors.
*
* Requests a scene change on the next safe opportunity.
*
* @param scene Which scene to set.
*/
void sceneSet(const char_t *scene);
/**
* Disposes of the current scene.
*
* @return Any error state that happened.
*/
errorret_t sceneDispose(void);
/**
* Lua callback when Scene.set is invoked.
*
* @param L Lua state, expects the first argument to be the scene name (string).
* @return 0, does not return any values to Lua.
*/
int sceneSetLua(lua_State *L);
+147 -89
View File
@@ -10,112 +10,170 @@
#include "display/color.h"
#include "time/time.h"
static int moduleColorIndex(lua_State *L) {
const color_t *color = (const color_t *)luaL_checkudata(L, 1, "color_mt");
assertNotNull(color, "Color struct cannot be NULL.");
const char_t *key = luaL_checkstring(L, 2);
// ---------------------------------------------------------------------------
// Native info / prototype
// ---------------------------------------------------------------------------
if(stringCompare(key, "r") == 0) { lua_pushnumber(L, color->r); return 1; }
if(stringCompare(key, "g") == 0) { lua_pushnumber(L, color->g); return 1; }
if(stringCompare(key, "b") == 0) { lua_pushnumber(L, color->b); return 1; }
if(stringCompare(key, "a") == 0) { lua_pushnumber(L, color->a); return 1; }
lua_getmetatable(L, 1);
lua_getfield(L, -1, key);
return 1;
static void freeColorNative(void *ptr, jerry_object_native_info_t *info) {
(void)info;
free(ptr);
}
static const jerry_object_native_info_t COLOR_NATIVE_INFO = {
.free_cb = freeColorNative,
.number_of_references = 0,
.offset_of_references = 0
};
static jerry_value_t s_colorProto = 0;
static int moduleColorNewIndex(lua_State *L) {
color_t *color = (color_t *)luaL_checkudata(L, 1, "color_mt");
assertNotNull(color, "Color struct cannot be NULL.");
const char_t *key = luaL_checkstring(L, 2);
// ---------------------------------------------------------------------------
// Property getters / setters
// ---------------------------------------------------------------------------
if(stringCompare(key, "r") == 0) {
color->r = (colorchannel8_t)luaL_checknumber(L, 3); return 0;
} else if(stringCompare(key, "g") == 0) {
color->g = (colorchannel8_t)luaL_checknumber(L, 3); return 0;
} else if(stringCompare(key, "b") == 0) {
color->b = (colorchannel8_t)luaL_checknumber(L, 3); return 0;
} else if(stringCompare(key, "a") == 0) {
color->a = (colorchannel8_t)luaL_checknumber(L, 3); return 0;
}
luaL_error(L, "color: unknown property '%s'", key);
return 0;
}
static int moduleColorToString(lua_State *L) {
const color_t *color = (const color_t *)luaL_checkudata(L, 1, "color_mt");
assertNotNull(color, "Color struct cannot be NULL.");
lua_pushfstring(L, "color(r=%d, g=%d, b=%d, a=%d)",
color->r, color->g, color->b, color->a
JS_FUNC(moduleColorGetR) {
color_t *color = (color_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &COLOR_NATIVE_INFO
);
return 1;
return color ? jerry_number(color->r) : jerry_undefined();
}
static int moduleColorCreate(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL.");
if(
!lua_isnumber(L, 1) || !lua_isnumber(L, 2) ||
!lua_isnumber(L, 3) || !lua_isnumber(L, 4)
) {
return luaL_error(L, "color(r, g, b, a) requires four number arguments.");
JS_FUNC(moduleColorSetR) {
color_t *color = (color_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &COLOR_NATIVE_INFO
);
if(color && args_count > 0) {
color->r = (colorchannel8_t)jerry_value_as_number(args_p[0]);
}
color_t *color = (color_t *)lua_newuserdata(L, sizeof(color_t));
luaL_getmetatable(L, "color_mt");
lua_setmetatable(L, -2);
color->r = (colorchannel8_t)lua_tonumber(L, 1);
color->g = (colorchannel8_t)lua_tonumber(L, 2);
color->b = (colorchannel8_t)lua_tonumber(L, 3);
color->a = (colorchannel8_t)lua_tonumber(L, 4);
return 1;
return jerry_undefined();
}
static int moduleColorRainbow(lua_State *L) {
JS_FUNC(moduleColorGetG) {
color_t *color = (color_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &COLOR_NATIVE_INFO
);
return color ? jerry_number(color->g) : jerry_undefined();
}
JS_FUNC(moduleColorSetG) {
color_t *color = (color_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &COLOR_NATIVE_INFO
);
if(color && args_count > 0) {
color->g = (colorchannel8_t)jerry_value_as_number(args_p[0]);
}
return jerry_undefined();
}
JS_FUNC(moduleColorGetB) {
color_t *color = (color_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &COLOR_NATIVE_INFO
);
return color ? jerry_number(color->b) : jerry_undefined();
}
JS_FUNC(moduleColorSetB) {
color_t *color = (color_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &COLOR_NATIVE_INFO
);
if(color && args_count > 0) {
color->b = (colorchannel8_t)jerry_value_as_number(args_p[0]);
}
return jerry_undefined();
}
JS_FUNC(moduleColorGetA) {
color_t *color = (color_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &COLOR_NATIVE_INFO
);
return color ? jerry_number(color->a) : jerry_undefined();
}
JS_FUNC(moduleColorSetA) {
color_t *color = (color_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &COLOR_NATIVE_INFO
);
if(color && args_count > 0) {
color->a = (colorchannel8_t)jerry_value_as_number(args_p[0]);
}
return jerry_undefined();
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/**
* Creates a new JS color object wrapping a heap-allocated color_t.
*
* @param color Value to copy into the new object.
* @return Owned jerry_value_t with COLOR_NATIVE_INFO and prototype set.
*/
static inline jerry_value_t moduleColorMakeObject(color_t color) {
color_t *ptr = (color_t *)malloc(sizeof(color_t));
*ptr = color;
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &COLOR_NATIVE_INFO, ptr);
if(s_colorProto != 0) jerry_object_set_proto(obj, s_colorProto);
return obj;
}
/**
* Returns the native color_t pointer stored in a JS value, or NULL.
*
* @param val JS value to inspect.
* @return Pointer to the color_t, or NULL if not a color object.
*/
static inline color_t *moduleColorGetNative(jerry_value_t val) {
return (color_t *)jerry_object_get_native_ptr(val, &COLOR_NATIVE_INFO);
}
// ---------------------------------------------------------------------------
// Constructors
// ---------------------------------------------------------------------------
JS_FUNC(moduleColorCreate) {
JS_REQUIRE_ARGS(4);
JS_REQUIRE_NUMBER(0);
JS_REQUIRE_NUMBER(1);
JS_REQUIRE_NUMBER(2);
JS_REQUIRE_NUMBER(3);
color_t c;
c.r = (colorchannel8_t)jerry_value_as_number(args_p[0]);
c.g = (colorchannel8_t)jerry_value_as_number(args_p[1]);
c.b = (colorchannel8_t)jerry_value_as_number(args_p[2]);
c.a = (colorchannel8_t)jerry_value_as_number(args_p[3]);
return moduleColorMakeObject(c);
}
JS_FUNC(moduleColorRainbow) {
float_t t = TIME.time * 4.0f;
if(lua_gettop(L) >= 1) {
if(!lua_isnumber(L, 1)) {
return luaL_error(L, "Rainbow time offset must be a number.");
}
t += (float_t)lua_tonumber(L, 1);
if(args_count >= 1 && jerry_value_is_number(args_p[0])) {
t += (float_t)jerry_value_as_number(args_p[0]);
}
if(lua_gettop(L) >= 2) {
if(!lua_isnumber(L, 2)) {
return luaL_error(L, "Rainbow speed multiplier must be a number.");
}
t *= (float_t)lua_tonumber(L, 2);
if(args_count >= 2 && jerry_value_is_number(args_p[1])) {
t *= (float_t)jerry_value_as_number(args_p[1]);
}
color_t *color = (color_t *)lua_newuserdata(L, sizeof(color_t));
color->r = (colorchannel8_t)((sinf(t) + 1.0f) * 0.5f * 255.0f);
color->g = (colorchannel8_t)((sinf(t + 2.0f) + 1.0f) * 0.5f * 255.0f);
color->b = (colorchannel8_t)((sinf(t + 4.0f) + 1.0f) * 0.5f * 255.0f);
color->a = 255;
luaL_getmetatable(L, "color_mt");
lua_setmetatable(L, -2);
return 1;
color_t c;
c.r = (colorchannel8_t)((sinf(t) + 1.0f) * 0.5f * 255.0f);
c.g = (colorchannel8_t)((sinf(t + 2.0f) + 1.0f) * 0.5f * 255.0f);
c.b = (colorchannel8_t)((sinf(t + 4.0f) + 1.0f) * 0.5f * 255.0f);
c.a = 255;
return moduleColorMakeObject(c);
}
static void moduleColor(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL.");
// ---------------------------------------------------------------------------
// Module init
// ---------------------------------------------------------------------------
if(luaL_newmetatable(L, "color_mt")) {
lua_pushcfunction(L, moduleColorIndex);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, moduleColorNewIndex);
lua_setfield(L, -2, "__newindex");
lua_pushcfunction(L, moduleColorToString);
lua_setfield(L, -2, "__tostring");
}
lua_pop(L, 1);
static void moduleColor(void) {
s_colorProto = jerry_object();
lua_register(L, "color", moduleColorCreate);
lua_register(L, "colorRainbow", moduleColorRainbow);
jsDefineProperty(s_colorProto, "r", moduleColorGetR, moduleColorSetR);
jsDefineProperty(s_colorProto, "g", moduleColorGetG, moduleColorSetG);
jsDefineProperty(s_colorProto, "b", moduleColorGetB, moduleColorSetB);
jsDefineProperty(s_colorProto, "a", moduleColorGetA, moduleColorSetA);
luaL_dostring(L, COLOR_SCRIPT);
jsRegister("color", moduleColorCreate);
jsRegister("colorRainbow", moduleColorRainbow);
jsEvalStr(COLOR_SCRIPT);
}
+38 -22
View File
@@ -8,35 +8,51 @@
#pragma once
#include "script/module/modulebase.h"
#include "display/screen/screen.h"
#include "display/color.h"
static int moduleScreenGetWidth(lua_State *L) {
assertNotNull(L, "Lua state is null");
lua_pushnumber(L, SCREEN.width);
return 1;
JS_FUNC(moduleScreenGetWidth) {
return jerry_number(SCREEN.width);
}
static int moduleScreenGetHeight(lua_State *L) {
assertNotNull(L, "Lua state is null");
lua_pushnumber(L, SCREEN.height);
return 1;
JS_FUNC(moduleScreenGetHeight) {
return jerry_number(SCREEN.height);
}
static int moduleScreenSetBackground(lua_State *L) {
assertNotNull(L, "Lua state is null");
if(!lua_isuserdata(L, 1)) {
luaL_error(L, "Screen background color must be a color struct");
return 0;
JS_FUNC(moduleScreenSetBackground) {
if(args_count < 1 || !jerry_value_is_object(args_p[0])) {
return JS_THROW("Screen background color must be a color object");
}
color_t *color = (color_t*)luaL_checkudata(L, 1, "color_mt");
SCREEN.background = *color;
return 0;
jerry_value_t keyR = jerry_string_sz("r");
jerry_value_t keyG = jerry_string_sz("g");
jerry_value_t keyB = jerry_string_sz("b");
jerry_value_t keyA = jerry_string_sz("a");
jerry_value_t valR = jerry_object_get(args_p[0], keyR);
jerry_value_t valG = jerry_object_get(args_p[0], keyG);
jerry_value_t valB = jerry_object_get(args_p[0], keyB);
jerry_value_t valA = jerry_object_get(args_p[0], keyA);
jerry_value_free(keyR);
jerry_value_free(keyG);
jerry_value_free(keyB);
jerry_value_free(keyA);
SCREEN.background.r = (colorchannel8_t)jerry_value_as_number(valR);
SCREEN.background.g = (colorchannel8_t)jerry_value_as_number(valG);
SCREEN.background.b = (colorchannel8_t)jerry_value_as_number(valB);
SCREEN.background.a = (colorchannel8_t)jerry_value_as_number(valA);
jerry_value_free(valR);
jerry_value_free(valG);
jerry_value_free(valB);
jerry_value_free(valA);
return jerry_undefined();
}
static void moduleScreen(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL.");
lua_register(L, "screenGetWidth", moduleScreenGetWidth);
lua_register(L, "screenGetHeight", moduleScreenGetHeight);
lua_register(L, "screenSetBackground", moduleScreenSetBackground);
static void moduleScreen(void) {
jsRegister("screenGetWidth", moduleScreenGetWidth);
jsRegister("screenGetHeight", moduleScreenGetHeight);
jsRegister("screenSetBackground", moduleScreenSetBackground);
}
+58 -51
View File
@@ -11,80 +11,87 @@
#include "display/shader/shaderunlit.h"
#include "script/module/math/modulemat4.h"
static int moduleShaderBind(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
shader_t *shader = (shader_t *)lua_touserdata(l, 1);
assertNotNull(shader, "Shader pointer cannot be NULL.");
// ---------------------------------------------------------------------------
// Functions
// ---------------------------------------------------------------------------
JS_FUNC(moduleShaderBind) {
JS_REQUIRE_ARGS(1);
shader_t *shader = (shader_t *)jsUnwrapPointer(args_p[0]);
if(!shader) return JS_THROW("shaderBind: expected a shader object");
errorret_t ret = shaderBind(shader);
if(ret.code != ERROR_OK) {
luaL_error(l, "Failed to bind shader: %s", ret.state->message);
errorCatch(errorPrint(ret));
return 0;
return JS_THROW("shaderBind: failed to bind shader");
}
return 0;
return jerry_undefined();
}
static int moduleShaderSetMatrix(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
shader_t *shader = (shader_t *)lua_touserdata(l, 1);
assertNotNull(shader, "Shader pointer cannot be NULL.");
const char_t *uniformName = luaL_checkstring(l, 2);
mat4 *mat = (mat4 *)luaL_checkudata(l, 3, "mat4_mt");
assertNotNull(mat, "Matrix pointer cannot be NULL.");
JS_FUNC(moduleShaderSetMatrix) {
JS_REQUIRE_ARGS(3);
shader_t *shader = (shader_t *)jsUnwrapPointer(args_p[0]);
if(!shader) return JS_THROW("shaderSetMatrix: expected a shader object");
errorret_t ret = shaderSetMatrix(shader, uniformName, *mat);
JS_REQUIRE_STRING(1);
char_t uniformName[128];
jsToString(args_p[1], uniformName, sizeof(uniformName));
mat4 mat;
if(!moduleMat4Check(args_p[2], mat)) {
return JS_THROW("shaderSetMatrix: third argument must be a mat4");
}
errorret_t ret = shaderSetMatrix(shader, uniformName, mat);
if(ret.code != ERROR_OK) {
luaL_error(l, "Failed to set shader matrix: %s", ret.state->message);
errorCatch(errorPrint(ret));
return 0;
return JS_THROW("shaderSetMatrix: failed to set shader matrix");
}
return 0;
return jerry_undefined();
}
static int moduleShaderSetTexture(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
shader_t *shader = (shader_t *)lua_touserdata(l, 1);
assertNotNull(shader, "Shader pointer cannot be NULL.");
const char_t *uniformName = luaL_checkstring(l, 2);
assertStrLenMin(uniformName, 1, "Uniform name cannot be empty.");
JS_FUNC(moduleShaderSetTexture) {
JS_REQUIRE_ARGS(2);
shader_t *shader = (shader_t *)jsUnwrapPointer(args_p[0]);
if(!shader) return JS_THROW("shaderSetTexture: expected a shader object");
texture_t *texture;
if(lua_isnil(l, 3)) {
texture = NULL;
} else if(lua_isuserdata(l, 3)) {
texture = (texture_t *)lua_touserdata(l, 3);
assertNotNull(texture, "Texture pointer cannot be NULL.");
} else {
luaL_error(l, "Third argument must be a texture_mt userdata or nil.");
return 0;
JS_REQUIRE_STRING(1);
char_t uniformName[128];
jsToString(args_p[1], uniformName, sizeof(uniformName));
texture_t *texture = NULL;
if(
args_count >= 3 &&
!jerry_value_is_null(args_p[2]) &&
!jerry_value_is_undefined(args_p[2])
) {
texture = (texture_t *)jsUnwrapPointer(args_p[2]);
if(!texture) return JS_THROW("shaderSetTexture: third argument must be a texture object or null");
}
errorret_t ret = shaderSetTexture(shader, uniformName, texture);
if(ret.code != ERROR_OK) {
luaL_error(l, "Failed to set shader texture: %s", ret.state->message);
errorCatch(errorPrint(ret));
return 0;
return JS_THROW("shaderSetTexture: failed to set shader texture");
}
return 0;
return jerry_undefined();
}
static void moduleShader(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL.");
// ---------------------------------------------------------------------------
// Module init
// ---------------------------------------------------------------------------
lua_pushlightuserdata(L, &SHADER_UNLIT);
lua_setglobal(L, "SHADER_UNLIT");
static void moduleShader(void) {
jerry_value_t shaderUnlitVal = jsWrapPointer(&SHADER_UNLIT);
jsSetValue("SHADER_UNLIT", shaderUnlitVal);
jerry_value_free(shaderUnlitVal);
lua_pushstring(L, SHADER_UNLIT_PROJECTION);
lua_setglobal(L, "SHADER_UNLIT_PROJECTION");
lua_pushstring(L, SHADER_UNLIT_VIEW);
lua_setglobal(L, "SHADER_UNLIT_VIEW");
lua_pushstring(L, SHADER_UNLIT_MODEL);
lua_setglobal(L, "SHADER_UNLIT_MODEL");
lua_pushstring(L, SHADER_UNLIT_TEXTURE);
lua_setglobal(L, "SHADER_UNLIT_TEXTURE");
jsSetString("SHADER_UNLIT_PROJECTION", SHADER_UNLIT_PROJECTION);
jsSetString("SHADER_UNLIT_VIEW", SHADER_UNLIT_VIEW);
jsSetString("SHADER_UNLIT_MODEL", SHADER_UNLIT_MODEL);
jsSetString("SHADER_UNLIT_TEXTURE", SHADER_UNLIT_TEXTURE);
lua_register(L, "shaderBind", moduleShaderBind);
lua_register(L, "shaderSetMatrix", moduleShaderSetMatrix);
lua_register(L, "shaderSetTexture", moduleShaderSetTexture);
jsRegister("shaderBind", moduleShaderBind);
jsRegister("shaderSetMatrix", moduleShaderSetMatrix);
jsRegister("shaderSetTexture", moduleShaderSetTexture);
}
@@ -11,61 +11,98 @@
#include "script/module/math/modulevec2.h"
#include "script/module/math/modulevec4.h"
static int moduleSpriteBatchFlush(lua_State *L) {
assertNotNull(L, "Lua state is null");
// ---------------------------------------------------------------------------
// Functions
// ---------------------------------------------------------------------------
JS_FUNC(moduleSpriteBatchFlush) {
spriteBatchFlush();
return 0;
return jerry_undefined();
}
static int moduleSpriteBatchClear(lua_State *L) {
assertNotNull(L, "Lua state is null");
JS_FUNC(moduleSpriteBatchClear) {
spriteBatchClear();
return 0;
return jerry_undefined();
}
static int moduleSpriteBatchPush(lua_State *L) {
assertNotNull(L, "Lua state is null");
JS_FUNC(moduleSpriteBatchPush) {
JS_REQUIRE_ARGS(2);
vec2 min, max;
moduleVec2Check(L, 1, min);
moduleVec2Check(L, 2, max);
color_t *color = NULL;
if(lua_gettop(L) < 3 || lua_isnil(L, 3)) {
// allow NULL (white)
} else if(lua_isuserdata(L, 3)) {
color = (color_t *)luaL_checkudata(L, 3, "color_mt");
} else {
return luaL_error(L, "Sprite color must be a color struct or nil");
vec2 minPos, maxPos;
if(!moduleVec2Check(args_p[0], minPos)) {
return JS_THROW("spriteBatchPush: first argument must be a vec2");
}
if(!moduleVec2Check(args_p[1], maxPos)) {
return JS_THROW("spriteBatchPush: second argument must be a vec2");
}
#if MESH_ENABLE_COLOR
color_t col = COLOR_WHITE;
if(
args_count >= 3 &&
jerry_value_is_object(args_p[2]) &&
!jerry_value_is_null(args_p[2]) &&
!jerry_value_is_undefined(args_p[2])
) {
jerry_value_t kr = jerry_string_sz("r");
jerry_value_t vr = jerry_object_get(args_p[2], kr);
col.r = (colorchannel8_t)jerry_value_as_number(vr);
jerry_value_free(vr);
jerry_value_free(kr);
jerry_value_t kg = jerry_string_sz("g");
jerry_value_t vg = jerry_object_get(args_p[2], kg);
col.g = (colorchannel8_t)jerry_value_as_number(vg);
jerry_value_free(vg);
jerry_value_free(kg);
jerry_value_t kb = jerry_string_sz("b");
jerry_value_t vb = jerry_object_get(args_p[2], kb);
col.b = (colorchannel8_t)jerry_value_as_number(vb);
jerry_value_free(vb);
jerry_value_free(kb);
jerry_value_t ka = jerry_string_sz("a");
jerry_value_t va = jerry_object_get(args_p[2], ka);
col.a = (colorchannel8_t)jerry_value_as_number(va);
jerry_value_free(va);
jerry_value_free(ka);
}
#endif
float_t u0 = 0.0f, v0 = 0.0f, u1 = 1.0f, v1 = 1.0f;
if(lua_gettop(L) >= 4 && !lua_isnil(L, 4)) {
vec4 uv; moduleVec4Check(L, 4, uv);
if(
args_count >= 4 &&
!jerry_value_is_null(args_p[3]) &&
!jerry_value_is_undefined(args_p[3])
) {
vec4 uv;
if(!moduleVec4Check(args_p[3], uv)) {
return JS_THROW("spriteBatchPush: fourth argument must be a vec4 or null");
}
u0 = uv[0]; v0 = uv[1]; u1 = uv[2]; v1 = uv[3];
}
errorret_t ret = spriteBatchPush(
min[0], min[1], max[0], max[1],
#if MESH_ENABLE_COLOR
color == NULL ? COLOR_WHITE : *color,
#endif
minPos[0], minPos[1], maxPos[0], maxPos[1],
#if MESH_ENABLE_COLOR
col,
#endif
u0, v0, u1, v1
);
if(ret.code != ERROR_OK) {
int err = luaL_error(L,
"Failed to push sprite to batch: %s",
ret.state->message
);
errorCatch(errorPrint(ret));
return err;
return JS_THROW("spriteBatchPush: failed to push sprite to batch");
}
return 0;
return jerry_undefined();
}
static void moduleSpriteBatch(lua_State *L) {
lua_register(L, "spriteBatchFlush", moduleSpriteBatchFlush);
lua_register(L, "spriteBatchClear", moduleSpriteBatchClear);
lua_register(L, "spriteBatchPush", moduleSpriteBatchPush);
// ---------------------------------------------------------------------------
// Module init
// ---------------------------------------------------------------------------
static void moduleSpriteBatch(void) {
jsRegister("spriteBatchFlush", moduleSpriteBatchFlush);
jsRegister("spriteBatchClear", moduleSpriteBatchClear);
jsRegister("spriteBatchPush", moduleSpriteBatchPush);
}
+60 -30
View File
@@ -11,62 +11,92 @@
#include "display/spritebatch/spritebatch.h"
#include "script/module/math/modulevec2.h"
static int moduleTextDraw(lua_State *L) {
assertNotNull(L, "Lua state is null");
// ---------------------------------------------------------------------------
// Functions
// ---------------------------------------------------------------------------
vec2 pos; moduleVec2Check(L, 1, pos);
JS_FUNC(moduleTextDraw) {
JS_REQUIRE_ARGS(2);
if(!lua_isstring(L, 2)) {
return luaL_error(L, "Text to draw must be a string");
vec2 pos;
if(!moduleVec2Check(args_p[0], pos)) {
return JS_THROW("textDraw: first argument must be a vec2 position");
}
const char_t *text = (const char_t *)lua_tostring(L, 2);
color_t *color = NULL;
if(lua_gettop(L) < 3 || lua_isnil(L, 3)) {
// allow NULL (white)
} else if(lua_isuserdata(L, 3)) {
color = (color_t *)luaL_checkudata(L, 3, "color_mt");
} else {
return luaL_error(L, "Text color must be a color struct or nil");
JS_REQUIRE_STRING(1);
char_t text[1024];
jsToString(args_p[1], text, sizeof(text));
color_t color = COLOR_WHITE;
if(
args_count >= 3 &&
jerry_value_is_object(args_p[2]) &&
!jerry_value_is_null(args_p[2]) &&
!jerry_value_is_undefined(args_p[2])
) {
jerry_value_t kr = jerry_string_sz("r");
jerry_value_t vr = jerry_object_get(args_p[2], kr);
color.r = (colorchannel8_t)jerry_value_as_number(vr);
jerry_value_free(vr);
jerry_value_free(kr);
jerry_value_t kg = jerry_string_sz("g");
jerry_value_t vg = jerry_object_get(args_p[2], kg);
color.g = (colorchannel8_t)jerry_value_as_number(vg);
jerry_value_free(vg);
jerry_value_free(kg);
jerry_value_t kb = jerry_string_sz("b");
jerry_value_t vb = jerry_object_get(args_p[2], kb);
color.b = (colorchannel8_t)jerry_value_as_number(vb);
jerry_value_free(vb);
jerry_value_free(kb);
jerry_value_t ka = jerry_string_sz("a");
jerry_value_t va = jerry_object_get(args_p[2], ka);
color.a = (colorchannel8_t)jerry_value_as_number(va);
jerry_value_free(va);
jerry_value_free(ka);
}
errorret_t ret = textDraw(
pos[0], pos[1], text,
color == NULL ? COLOR_WHITE : *color,
pos[0], pos[1], text, color,
&DEFAULT_FONT_TILESET,
&DEFAULT_FONT_TEXTURE
);
if(ret.code != ERROR_OK) {
errorCatch(errorPrint(ret));
luaL_error(L, "Failed to draw text");
return JS_THROW("textDraw: failed to draw text");
}
ret = spriteBatchFlush();
if(ret.code != ERROR_OK) {
errorCatch(errorPrint(ret));
luaL_error(L, "Failed to flush sprite batch after drawing text");
return JS_THROW("textDraw: failed to flush sprite batch");
}
return 0;
return jerry_undefined();
}
static int moduleTextMeasure(lua_State *L) {
assertNotNull(L, "Lua state is null");
JS_FUNC(moduleTextMeasure) {
JS_REQUIRE_ARGS(1);
JS_REQUIRE_STRING(0);
if(!lua_isstring(L, 1)) {
return luaL_error(L, "Text to measure must be a string");
}
const char_t *text = (const char_t *)lua_tostring(L, 1);
char_t text[1024];
jsToString(args_p[0], text, sizeof(text));
int32_t w = 0, h = 0;
textMeasure(text, &DEFAULT_FONT_TILESET, &w, &h);
vec2 size = { (float_t)w, (float_t)h };
moduleVec2Push(L, size);
return 1;
return moduleVec2Push(size);
}
static void moduleText(lua_State *L) {
assertNotNull(L, "Lua state is null");
lua_register(L, "textDraw", moduleTextDraw);
lua_register(L, "textMeasure", moduleTextMeasure);
// ---------------------------------------------------------------------------
// Module init
// ---------------------------------------------------------------------------
static void moduleText(void) {
jsRegister("textDraw", moduleTextDraw);
jsRegister("textMeasure", moduleTextMeasure);
}
+67 -61
View File
@@ -10,89 +10,95 @@
#include "display/texture/texture.h"
#include "asset/loader/display/assettextureloader.h"
static int moduleTextureIndex(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
// ---------------------------------------------------------------------------
// Native info / prototype
// ---------------------------------------------------------------------------
texture_t *tex = (texture_t *)luaL_checkudata(l, 1, "texture_mt");
assertNotNull(tex, "Texture pointer cannot be NULL.");
const char *key = luaL_checkstring(l, 2);
assertNotNull(key, "Key cannot be NULL.");
static void freeTextureNative(void *ptr, jerry_object_native_info_t *info) {
(void)info;
textureDispose((texture_t *)ptr);
free(ptr);
}
static const jerry_object_native_info_t TEXTURE_NATIVE_INFO = {
.free_cb = freeTextureNative,
.number_of_references = 0,
.offset_of_references = 0
};
static jerry_value_t s_textureProto = 0;
if(stringCompare(key, "width") == 0) {
lua_pushnumber(l, tex->width); return 1;
} else if(stringCompare(key, "height") == 0) {
lua_pushnumber(l, tex->height); return 1;
}
// ---------------------------------------------------------------------------
// Helper
// ---------------------------------------------------------------------------
lua_pushnil(l);
return 1;
/**
* Returns the native texture_t pointer stored in a JS value, or NULL.
*
* @param val JS value to inspect.
* @return Pointer to the texture_t, or NULL if not a texture object.
*/
static inline texture_t *moduleTextureGetNative(jerry_value_t val) {
return (texture_t *)jerry_object_get_native_ptr(val, &TEXTURE_NATIVE_INFO);
}
static int moduleTextureToString(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
texture_t *tex = (texture_t *)luaL_checkudata(l, 1, "texture_mt");
assertNotNull(tex, "Texture pointer cannot be NULL.");
char buffer[64];
snprintf(buffer, sizeof(buffer), "Texture(%dx%d)", tex->width, tex->height);
lua_pushstring(l, buffer);
return 1;
// ---------------------------------------------------------------------------
// Property getters (read-only)
// ---------------------------------------------------------------------------
JS_FUNC(moduleTextureGetWidth) {
texture_t *tex = (texture_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &TEXTURE_NATIVE_INFO
);
return tex ? jerry_number(tex->width) : jerry_undefined();
}
static int moduleTextureGC(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
texture_t *tex = (texture_t *)luaL_checkudata(l, 1, "texture_mt");
assertNotNull(tex, "Texture pointer cannot be NULL.");
textureDispose(tex);
return 0;
JS_FUNC(moduleTextureGetHeight) {
texture_t *tex = (texture_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &TEXTURE_NATIVE_INFO
);
return tex ? jerry_number(tex->height) : jerry_undefined();
}
static int moduleTextureLoad(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
// ---------------------------------------------------------------------------
// Constructor
// ---------------------------------------------------------------------------
if(!lua_isstring(l, 1)) {
luaL_error(l, "First argument must be a string filename.");
return 0;
}
if(!lua_isnumber(l, 2)) {
luaL_error(l, "Second argument must be a number format.");
return 0;
}
JS_FUNC(moduleTextureLoad) {
JS_REQUIRE_ARGS(2);
JS_REQUIRE_STRING(0);
JS_REQUIRE_NUMBER(1);
const char_t *filename = lua_tostring(l, 1);
assertNotNull(filename, "Filename cannot be NULL.");
assertStrLenMin(filename, 1, "Filename cannot be empty.");
char_t filename[256];
jsToString(args_p[0], filename, sizeof(filename));
texture_t *tex = (texture_t *)lua_newuserdata(l, sizeof(texture_t));
textureformat_t format = (textureformat_t)(int32_t)jerry_value_as_number(args_p[1]);
texture_t *tex = (texture_t *)malloc(sizeof(texture_t));
memoryZero(tex, sizeof(texture_t));
textureformat_t format = (textureformat_t)lua_tonumber(l, 2);
errorret_t ret = assetTextureLoad(filename, tex, format);
if(ret.code != ERROR_OK) {
errorCatch(errorPrint(ret));
luaL_error(l, "Failed to load texture asset: %s", filename);
return 0;
free(tex);
return JS_THROW("textureLoad: failed to load texture");
}
luaL_getmetatable(l, "texture_mt");
lua_setmetatable(l, -2);
return 1;
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &TEXTURE_NATIVE_INFO, tex);
if(s_textureProto != 0) jerry_object_set_proto(obj, s_textureProto);
return obj;
}
static void moduleTexture(lua_State *L) {
assertNotNull(L, "Lua state cannot be null");
// ---------------------------------------------------------------------------
// Module init
// ---------------------------------------------------------------------------
if(luaL_newmetatable(L, "texture_mt")) {
lua_pushcfunction(L, moduleTextureIndex);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, moduleTextureToString);
lua_setfield(L, -2, "__tostring");
lua_pushcfunction(L, moduleTextureGC);
lua_setfield(L, -2, "__gc");
}
lua_pop(L, 1);
static void moduleTexture(void) {
s_textureProto = jerry_object();
lua_pushnumber(L, TEXTURE_FORMAT_RGBA);
lua_setglobal(L, "TEXTURE_FORMAT_RGBA");
jsDefineProperty(s_textureProto, "width", moduleTextureGetWidth, NULL);
jsDefineProperty(s_textureProto, "height", moduleTextureGetHeight, NULL);
lua_register(L, "textureLoad", moduleTextureLoad);
jsSetInt("TEXTURE_FORMAT_RGBA", (int32_t)TEXTURE_FORMAT_RGBA);
jsRegister("textureLoad", moduleTextureLoad);
}
+107 -72
View File
@@ -11,98 +11,133 @@
#include "asset/loader/display/assettilesetloader.h"
#include "script/module/math/modulevec4.h"
static int moduleTilesetIndex(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
const char_t *key = luaL_checkstring(l, 2);
assertNotNull(key, "Key cannot be NULL.");
tileset_t *ts = (tileset_t *)luaL_checkudata(l, 1, "tileset_mt");
assertNotNull(ts, "Tileset pointer cannot be NULL.");
// ---------------------------------------------------------------------------
// Native info / prototype
// ---------------------------------------------------------------------------
if(stringCompare(key, "tileWidth") == 0) {
lua_pushnumber(l, ts->tileWidth); return 1;
} else if(stringCompare(key, "tileHeight") == 0) {
lua_pushnumber(l, ts->tileHeight); return 1;
} else if(stringCompare(key, "tileCount") == 0) {
lua_pushnumber(l, ts->tileCount); return 1;
} else if(stringCompare(key, "columns") == 0) {
lua_pushnumber(l, ts->columns); return 1;
} else if(stringCompare(key, "rows") == 0) {
lua_pushnumber(l, ts->rows); return 1;
}
lua_pushnil(l);
return 1;
static void freeTilesetNative(void *ptr, jerry_object_native_info_t *info) {
(void)info;
free(ptr);
}
static const jerry_object_native_info_t TILESET_NATIVE_INFO = {
.free_cb = freeTilesetNative,
.number_of_references = 0,
.offset_of_references = 0
};
static jerry_value_t s_tilesetProto = 0;
static int moduleTilesetToString(lua_State *l) {
tileset_t *ts = (tileset_t *)luaL_checkudata(l, 1, "tileset_mt");
assertNotNull(ts, "Tileset pointer cannot be NULL.");
lua_pushfstring(l, "Tileset: %dx%d tile, %d columns, %d rows",
ts->tileWidth, ts->tileHeight, ts->columns, ts->rows
// ---------------------------------------------------------------------------
// Property getters (read-only)
// ---------------------------------------------------------------------------
JS_FUNC(moduleTilesetGetTileWidth) {
tileset_t *ts = (tileset_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &TILESET_NATIVE_INFO
);
return 1;
return ts ? jerry_number(ts->tileWidth) : jerry_undefined();
}
static int moduleTilesetTileGetUV(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
tileset_t *ts = (tileset_t *)luaL_checkudata(l, 1, "tileset_mt");
assertNotNull(ts, "Tileset pointer cannot be NULL.");
uint16_t tileIndex = (uint16_t)luaL_checknumber(l, 2);
vec4 uv; tilesetTileGetUV(ts, tileIndex, uv);
moduleVec4Push(l, uv);
return 1;
JS_FUNC(moduleTilesetGetTileHeight) {
tileset_t *ts = (tileset_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &TILESET_NATIVE_INFO
);
return ts ? jerry_number(ts->tileHeight) : jerry_undefined();
}
static int moduleTilesetPositionGetUV(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
tileset_t *ts = (tileset_t *)luaL_checkudata(l, 1, "tileset_mt");
assertNotNull(ts, "Tileset pointer cannot be NULL.");
uint16_t column = (uint16_t)luaL_checknumber(l, 2);
uint16_t row = (uint16_t)luaL_checknumber(l, 3);
vec4 uv; tilesetPositionGetUV(ts, column, row, uv);
moduleVec4Push(l, uv);
return 1;
JS_FUNC(moduleTilesetGetTileCount) {
tileset_t *ts = (tileset_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &TILESET_NATIVE_INFO
);
return ts ? jerry_number(ts->tileCount) : jerry_undefined();
}
static int moduleTilesetLoad(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
JS_FUNC(moduleTilesetGetColumns) {
tileset_t *ts = (tileset_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &TILESET_NATIVE_INFO
);
return ts ? jerry_number(ts->columns) : jerry_undefined();
}
if(!lua_isstring(l, 1)) {
luaL_error(l, "First argument must be a string filename.");
return 0;
}
JS_FUNC(moduleTilesetGetRows) {
tileset_t *ts = (tileset_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &TILESET_NATIVE_INFO
);
return ts ? jerry_number(ts->rows) : jerry_undefined();
}
const char_t *filename = lua_tostring(l, 1);
assertNotNull(filename, "Filename cannot be NULL.");
assertStrLenMin(filename, 1, "Filename cannot be empty.");
// ---------------------------------------------------------------------------
// Functions
// ---------------------------------------------------------------------------
tileset_t *tileset = (tileset_t *)lua_newuserdata(l, sizeof(tileset_t));
memoryZero(tileset, sizeof(tileset_t));
JS_FUNC(modulesTilesetTileGetUV) {
JS_REQUIRE_ARGS(2);
tileset_t *ts = (tileset_t *)jerry_object_get_native_ptr(
args_p[0], &TILESET_NATIVE_INFO
);
if(!ts) return JS_THROW("tilesetTileGetUV: expected a tileset object");
errorret_t ret = assetTilesetLoad(filename, tileset);
JS_REQUIRE_NUMBER(1);
uint16_t tileIndex = (uint16_t)jerry_value_as_number(args_p[1]);
vec4 uv;
tilesetTileGetUV(ts, tileIndex, uv);
return moduleVec4Push(uv);
}
JS_FUNC(moduleTilesetPositionGetUV) {
JS_REQUIRE_ARGS(3);
tileset_t *ts = (tileset_t *)jerry_object_get_native_ptr(
args_p[0], &TILESET_NATIVE_INFO
);
if(!ts) return JS_THROW("tilesetPositionGetUV: expected a tileset object");
JS_REQUIRE_NUMBER(1);
JS_REQUIRE_NUMBER(2);
uint16_t col = (uint16_t)jerry_value_as_number(args_p[1]);
uint16_t row = (uint16_t)jerry_value_as_number(args_p[2]);
vec4 uv;
tilesetPositionGetUV(ts, col, row, uv);
return moduleVec4Push(uv);
}
JS_FUNC(moduleTilesetLoad) {
JS_REQUIRE_ARGS(1);
JS_REQUIRE_STRING(0);
char_t filename[256];
jsToString(args_p[0], filename, sizeof(filename));
tileset_t *ts = (tileset_t *)malloc(sizeof(tileset_t));
memoryZero(ts, sizeof(tileset_t));
errorret_t ret = assetTilesetLoad(filename, ts);
if(ret.code != ERROR_OK) {
errorCatch(errorPrint(ret));
luaL_error(l, "Failed to load tileset asset: %s", filename);
return 0;
free(ts);
return JS_THROW("tilesetLoad: failed to load tileset");
}
luaL_getmetatable(l, "tileset_mt");
lua_setmetatable(l, -2);
return 1;
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &TILESET_NATIVE_INFO, ts);
if(s_tilesetProto != 0) jerry_object_set_proto(obj, s_tilesetProto);
return obj;
}
static void moduleTileset(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
// ---------------------------------------------------------------------------
// Module init
// ---------------------------------------------------------------------------
if(luaL_newmetatable(L, "tileset_mt")) {
lua_pushcfunction(L, moduleTilesetIndex);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, moduleTilesetToString);
lua_setfield(L, -2, "__tostring");
}
lua_pop(L, 1);
static void moduleTileset(void) {
s_tilesetProto = jerry_object();
lua_register(L, "tilesetLoad", moduleTilesetLoad);
lua_register(L, "tilesetTileGetUV", moduleTilesetTileGetUV);
lua_register(L, "tilesetPositionGetUV", moduleTilesetPositionGetUV);
jsDefineProperty(s_tilesetProto, "tileWidth", moduleTilesetGetTileWidth, NULL);
jsDefineProperty(s_tilesetProto, "tileHeight", moduleTilesetGetTileHeight, NULL);
jsDefineProperty(s_tilesetProto, "tileCount", moduleTilesetGetTileCount, NULL);
jsDefineProperty(s_tilesetProto, "columns", moduleTilesetGetColumns, NULL);
jsDefineProperty(s_tilesetProto, "rows", moduleTilesetGetRows, NULL);
jsRegister("tilesetLoad", moduleTilesetLoad);
jsRegister("tilesetTileGetUV", modulesTilesetTileGetUV);
jsRegister("tilesetPositionGetUV", moduleTilesetPositionGetUV);
}
@@ -9,102 +9,67 @@
#include "script/module/modulebase.h"
#include "entity/entity.h"
#include "entity/component/display/entitycamera.h"
#include "moduleentityposition.h"
typedef struct {
entityid_t entityId;
componentid_t compId;
} entitycam_handle_t;
// ---- camera prototype ----
/**
* __index metamethod for camera component userdata.
*
* @param L Lua state. Arg 1: entitycam userdata. Arg 2: key string.
* @return 1.
*/
static int moduleEntityCameraIndex(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
static jerry_value_t s_camProto = 0;
entitycam_handle_t *h = luaL_checkudata(L, 1, "entitycam_mt");
assertNotNull(h, "invalid entitycam userdata");
// -- zNear getter/setter --
const char *k = luaL_checkstring(L, 2);
assertStrLenMin(k, 1, "property key cannot be empty");
if(stringCompare(k, "zNear") == 0) {
lua_pushnumber(L, entityCameraGetZNear(h->entityId, h->compId)); return 1;
} else if(stringCompare(k, "zFar") == 0) {
lua_pushnumber(L, entityCameraGetZFar(h->entityId, h->compId)); return 1;
}
lua_getmetatable(L, 1);
lua_getfield(L, -1, k);
return 1;
JS_FUNC(moduleEntityCameraGetZNear) {
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
return jerry_number((double)entityCameraGetZNear(eid, cid));
}
/**
* __newindex metamethod for camera component userdata.
*
* @param L Lua state. Arg 1: entitycam userdata. Arg 2: key string. Arg 3: number.
* @return 0.
*/
static int moduleEntityCameraNewIndex(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
entitycam_handle_t *h = luaL_checkudata(L, 1, "entitycam_mt");
assertNotNull(h, "invalid entitycam userdata");
const char *k = luaL_checkstring(L, 2);
assertStrLenMin(k, 1, "property key cannot be empty");
if(stringCompare(k, "zNear") == 0) {
entityCameraSetZNear(h->entityId, h->compId, (float_t)luaL_checknumber(L, 3));
return 0;
} else if(stringCompare(k, "zFar") == 0) {
entityCameraSetZFar(h->entityId, h->compId, (float_t)luaL_checknumber(L, 3));
return 0;
}
luaL_error(L, "entitycam: unknown property '%s'", k);
return 0;
JS_FUNC(moduleEntityCameraSetZNear) {
JS_REQUIRE_ARGS(1); JS_REQUIRE_NUMBER(0);
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
entityCameraSetZNear(eid, cid, (float_t)jerry_value_as_number(args_p[0]));
return jerry_undefined();
}
// -- zFar getter/setter --
JS_FUNC(moduleEntityCameraGetZFar) {
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
return jerry_number((double)entityCameraGetZFar(eid, cid));
}
JS_FUNC(moduleEntityCameraSetZFar) {
JS_REQUIRE_ARGS(1); JS_REQUIRE_NUMBER(0);
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
entityCameraSetZFar(eid, cid, (float_t)jerry_value_as_number(args_p[0]));
return jerry_undefined();
}
// -- add function --
/**
* Adds a camera component to an entity and returns its userdata handle.
*
* @param L Lua state. Arg 1: entity id (number).
* @return 1 (entitycam userdata).
* Adds a camera component to an entity and returns a handle object.
* Arg 0: entity id (number).
*/
static int moduleEntityCameraAdd(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
entityid_t id = (entityid_t)luaL_checknumber(L, 1);
JS_FUNC(moduleEntityCameraAdd) {
JS_REQUIRE_ARGS(1); JS_REQUIRE_NUMBER(0);
entityid_t id = (entityid_t)jerry_value_as_number(args_p[0]);
componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_CAMERA);
entitycam_handle_t *h = lua_newuserdata(L, sizeof(entitycam_handle_t));
assertNotNull(h, "failed to allocate entitycam userdata");
h->entityId = id;
h->compId = comp;
luaL_getmetatable(L, "entitycam_mt");
lua_setmetatable(L, -2);
return 1;
jerry_value_t handle = makeEntityHandle(id, comp);
if(s_camProto != 0) jerry_object_set_proto(handle, s_camProto);
return handle;
}
/**
* Registers the camera component metatable and entityCameraAdd global.
*
* @param L Lua state.
* Registers the camera component prototype and entityCameraAdd global.
*/
static void moduleEntityCamera(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
static void moduleEntityCamera(void) {
s_camProto = jerry_object();
luaL_newmetatable(L, "entitycam_mt");
lua_pushcfunction(L, moduleEntityCameraIndex);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, moduleEntityCameraNewIndex);
lua_setfield(L, -2, "__newindex");
lua_pop(L, 1);
jsDefineProperty(s_camProto, "zNear", moduleEntityCameraGetZNear, moduleEntityCameraSetZNear);
jsDefineProperty(s_camProto, "zFar", moduleEntityCameraGetZFar, moduleEntityCameraSetZFar);
lua_register(L, "entityCameraAdd", moduleEntityCameraAdd);
jsRegister("entityCameraAdd", moduleEntityCameraAdd);
}
@@ -9,73 +9,82 @@
#include "script/module/modulebase.h"
#include "entity/entity.h"
#include "entity/component/display/entitymaterial.h"
#include "display/color.h"
#include "moduleentityposition.h"
typedef struct {
entityid_t entityId;
componentid_t compId;
} entitymat_handle_t;
// ---- material prototype ----
static jerry_value_t s_matProto = 0;
// -- methods --
/**
* __index metamethod for material component userdata.
* Delegates all lookups to the metatable (methods only, no plain properties).
*
* @param L Lua state. Arg 1: entitymat userdata. Arg 2: key string.
* @return 1.
* Sets the material's unlit color from a color object with r, g, b, a
* properties (each a number 0-255).
* Args: colorObj (object).
*/
static int moduleEntityMaterialIndex(lua_State *L) {
lua_getmetatable(L, 1);
lua_getfield(L, -1, luaL_checkstring(L, 2));
return 1;
}
/**
* __newindex metamethod for material component userdata.
* Writes color (expects a color_mt userdata). Errors on unknown keys.
*
* @param L Lua state. Arg 1: entitymat userdata. Arg 2: key string. Arg 3: value.
* @return 0.
*/
static int moduleEntityMaterialNewIndex(lua_State *L) {
entitymat_handle_t *h = luaL_checkudata(L, 1, "entitymat_mt");
const char *k = luaL_checkstring(L, 2);
if(stringCompare(k, "color") == 0) {
const color_t *col = (const color_t *)luaL_checkudata(L, 3, "color_mt");
entityMaterialSetColor(h->entityId, h->compId, *col);
return 0;
JS_FUNC(moduleEntityMaterialSetColor) {
JS_REQUIRE_ARGS(1);
if(!jerry_value_is_object(args_p[0])) {
return JS_THROW("expected color object");
}
luaL_error(L, "entitymat: unknown property '%s'", k);
return 0;
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
jerry_value_t key;
jerry_value_t v;
color_t col;
key = jerry_string_sz("r");
v = jerry_object_get(args_p[0], key);
jerry_value_free(key);
col.r = (colorchannel8_t)jerry_value_as_number(v);
jerry_value_free(v);
key = jerry_string_sz("g");
v = jerry_object_get(args_p[0], key);
jerry_value_free(key);
col.g = (colorchannel8_t)jerry_value_as_number(v);
jerry_value_free(v);
key = jerry_string_sz("b");
v = jerry_object_get(args_p[0], key);
jerry_value_free(key);
col.b = (colorchannel8_t)jerry_value_as_number(v);
jerry_value_free(v);
key = jerry_string_sz("a");
v = jerry_object_get(args_p[0], key);
jerry_value_free(key);
col.a = (colorchannel8_t)jerry_value_as_number(v);
jerry_value_free(v);
entityMaterialSetColor(eid, cid, col);
return jerry_undefined();
}
// -- add function --
/**
* Adds a material component to an entity and returns its userdata handle.
*
* @param L Lua state. Arg 1: entity id (number).
* @return 1 (entitymat userdata).
* Adds a material component to an entity and returns a handle object.
* Arg 0: entity id (number).
*/
static int moduleEntityMaterialAdd(lua_State *L) {
entityid_t id = (entityid_t)luaL_checknumber(L, 1);
JS_FUNC(moduleEntityMaterialAdd) {
JS_REQUIRE_ARGS(1); JS_REQUIRE_NUMBER(0);
entityid_t id = (entityid_t)jerry_value_as_number(args_p[0]);
componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_MATERIAL);
entitymat_handle_t *h = lua_newuserdata(L, sizeof(entitymat_handle_t));
h->entityId = id;
h->compId = comp;
luaL_getmetatable(L, "entitymat_mt");
lua_setmetatable(L, -2);
return 1;
jerry_value_t handle = makeEntityHandle(id, comp);
if(s_matProto != 0) jerry_object_set_proto(handle, s_matProto);
return handle;
}
/**
* Registers the material component metatable and entityMaterialAdd global.
*
* @param L Lua state.
* Registers the material component prototype and entityMaterialAdd global.
*/
static void moduleEntityMaterial(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
static void moduleEntityMaterial(void) {
s_matProto = jerry_object();
luaL_newmetatable(L, "entitymat_mt");
lua_pushcfunction(L, moduleEntityMaterialIndex); lua_setfield(L, -2, "__index");
lua_pushcfunction(L, moduleEntityMaterialNewIndex); lua_setfield(L, -2, "__newindex");
lua_pop(L, 1);
jsDefineMethod(s_matProto, "setColor", moduleEntityMaterialSetColor);
lua_register(L, "entityMaterialAdd", moduleEntityMaterialAdd);
jsRegister("entityMaterialAdd", moduleEntityMaterialAdd);
}
@@ -9,85 +9,65 @@
#include "script/module/modulebase.h"
#include "entity/entity.h"
#include "entity/component/display/entitymesh.h"
#include "moduleentityposition.h"
typedef struct {
entityid_t entityId;
componentid_t compId;
} entitymesh_handle_t;
// ---- mesh prototype ----
/**
* __index metamethod for mesh component userdata.
* Delegates all lookups to the metatable (methods only, no plain properties).
*
* @param L Lua state. Arg 1: entitymesh userdata. Arg 2: key string.
* @return 1.
*/
static int moduleEntityMeshIndex(lua_State *L) {
lua_getmetatable(L, 1);
lua_getfield(L, -1, luaL_checkstring(L, 2));
return 1;
}
static jerry_value_t s_meshProto = 0;
// -- methods --
/**
* Generates an XZ-aligned plane mesh owned by this component.
*
* @param L Lua state. Arg 1: entitymesh userdata. Arg 2: width. Arg 3: depth.
* @return 0.
* Args: width (number), depth (number).
*/
static int moduleEntityMeshGeneratePlane(lua_State *L) {
entitymesh_handle_t *h = luaL_checkudata(L, 1, "entitymesh_mt");
float_t w = (float_t)luaL_checknumber(L, 2);
float_t d = (float_t)luaL_checknumber(L, 3);
errorret_t err = entityMeshGeneratePlane(h->entityId, h->compId, w, d);
if(err.code != ERROR_OK) luaL_error(L, "entityMesh: generatePlane failed");
return 0;
JS_FUNC(moduleEntityMeshGeneratePlane) {
JS_REQUIRE_ARGS(2);
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
float_t w = (float_t)jerry_value_as_number(args_p[0]);
float_t d = (float_t)jerry_value_as_number(args_p[1]);
entityMeshGeneratePlane(eid, cid, w, d);
return jerry_undefined();
}
/**
* Generates a Y-axis capsule mesh owned by this component.
*
* @param L Lua state. Arg 1: entitymesh userdata. Arg 2: radius. Arg 3: halfHeight.
* @return 0.
* Args: radius (number), halfHeight (number).
*/
static int moduleEntityMeshGenerateCapsule(lua_State *L) {
entitymesh_handle_t *h = luaL_checkudata(L, 1, "entitymesh_mt");
float_t r = (float_t)luaL_checknumber(L, 2);
float_t hh = (float_t)luaL_checknumber(L, 3);
errorret_t err = entityMeshGenerateCapsule(h->entityId, h->compId, r, hh);
if(err.code != ERROR_OK) luaL_error(L, "entityMesh: generateCapsule failed");
return 0;
JS_FUNC(moduleEntityMeshGenerateCapsule) {
JS_REQUIRE_ARGS(2);
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
float_t r = (float_t)jerry_value_as_number(args_p[0]);
float_t hh = (float_t)jerry_value_as_number(args_p[1]);
entityMeshGenerateCapsule(eid, cid, r, hh);
return jerry_undefined();
}
// -- add function --
/**
* Adds a mesh component to an entity and returns its userdata handle.
*
* @param L Lua state. Arg 1: entity id (number).
* @return 1 (entitymesh userdata).
* Adds a mesh component to an entity and returns a handle object.
* Arg 0: entity id (number).
*/
static int moduleEntityMeshAdd(lua_State *L) {
entityid_t id = (entityid_t)luaL_checknumber(L, 1);
JS_FUNC(moduleEntityMeshAdd) {
JS_REQUIRE_ARGS(1); JS_REQUIRE_NUMBER(0);
entityid_t id = (entityid_t)jerry_value_as_number(args_p[0]);
componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_MESH);
entitymesh_handle_t *h = lua_newuserdata(L, sizeof(entitymesh_handle_t));
h->entityId = id;
h->compId = comp;
luaL_getmetatable(L, "entitymesh_mt");
lua_setmetatable(L, -2);
return 1;
jerry_value_t handle = makeEntityHandle(id, comp);
if(s_meshProto != 0) jerry_object_set_proto(handle, s_meshProto);
return handle;
}
/**
* Registers the mesh component metatable and entityMeshAdd global.
*
* @param L Lua state.
* Registers the mesh component prototype and entityMeshAdd global.
*/
static void moduleEntityMesh(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
static void moduleEntityMesh(void) {
s_meshProto = jerry_object();
luaL_newmetatable(L, "entitymesh_mt");
lua_pushcfunction(L, moduleEntityMeshIndex); lua_setfield(L, -2, "__index");
lua_pushcfunction(L, moduleEntityMeshGeneratePlane); lua_setfield(L, -2, "generatePlane");
lua_pushcfunction(L, moduleEntityMeshGenerateCapsule); lua_setfield(L, -2, "generateCapsule");
lua_pop(L, 1);
jsDefineMethod(s_meshProto, "generatePlane", moduleEntityMeshGeneratePlane);
jsDefineMethod(s_meshProto, "generateCapsule", moduleEntityMeshGenerateCapsule);
lua_register(L, "entityMeshAdd", moduleEntityMeshAdd);
jsRegister("entityMeshAdd", moduleEntityMeshAdd);
}
@@ -9,210 +9,225 @@
#include "script/module/modulebase.h"
#include "entity/entity.h"
#include "entity/component/physics/entityphysics.h"
#include "moduleentityposition.h"
typedef struct {
entityid_t entityId;
componentid_t compId;
} entityphysics_handle_t;
// ---- physics prototype ----
/**
* __index metamethod for physics component userdata.
* Reads velX, velY, velZ, onGround (bool), bodyType; falls through to the
* metatable for method lookups.
*
* @param L Lua state. Arg 1: entityphysics userdata. Arg 2: key string.
* @return 1.
*/
static int moduleEntityPhysicsIndex(lua_State *L) {
entityphysics_handle_t *h = luaL_checkudata(L, 1, "entityphysics_mt");
const char *k = luaL_checkstring(L, 2);
static jerry_value_t s_physProto = 0;
// -- velocity getters/setters --
JS_FUNC(moduleEntityPhysicsGetVelX) {
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
vec3 v;
if(stringCompare(k, "velX") == 0) {
entityPhysicsGetVelocity(h->entityId, h->compId, v);
lua_pushnumber(L, v[0]); return 1;
} else if(stringCompare(k, "velY") == 0) {
entityPhysicsGetVelocity(h->entityId, h->compId, v);
lua_pushnumber(L, v[1]); return 1;
} else if(stringCompare(k, "velZ") == 0) {
entityPhysicsGetVelocity(h->entityId, h->compId, v);
lua_pushnumber(L, v[2]); return 1;
} else if(stringCompare(k, "onGround") == 0) {
lua_pushboolean(L, entityPhysicsIsOnGround(h->entityId, h->compId));
return 1;
} else if(stringCompare(k, "bodyType") == 0) {
lua_pushinteger(L, (lua_Integer)entityPhysicsGetBodyType(h->entityId, h->compId));
return 1;
}
lua_getmetatable(L, 1);
lua_getfield(L, -1, k);
return 1;
entityPhysicsGetVelocity(eid, cid, v);
return jerry_number(v[0]);
}
/**
* __newindex metamethod for physics component userdata.
* Writes velX, velY, velZ, bodyType. onGround is read-only. Errors on unknown keys.
*
* @param L Lua state. Arg 1: entityphysics userdata. Arg 2: key string. Arg 3: value.
* @return 0.
*/
static int moduleEntityPhysicsNewIndex(lua_State *L) {
entityphysics_handle_t *h = luaL_checkudata(L, 1, "entityphysics_mt");
const char *k = luaL_checkstring(L, 2);
JS_FUNC(moduleEntityPhysicsSetVelX) {
JS_REQUIRE_ARGS(1); JS_REQUIRE_NUMBER(0);
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
vec3 v;
if(stringCompare(k, "velX") == 0) {
entityPhysicsGetVelocity(h->entityId, h->compId, v);
v[0] = (float_t)luaL_checknumber(L, 3);
entityPhysicsSetVelocity(h->entityId, h->compId, v); return 0;
} else if(stringCompare(k, "velY") == 0) {
entityPhysicsGetVelocity(h->entityId, h->compId, v);
v[1] = (float_t)luaL_checknumber(L, 3);
entityPhysicsSetVelocity(h->entityId, h->compId, v); return 0;
} else if(stringCompare(k, "velZ") == 0) {
entityPhysicsGetVelocity(h->entityId, h->compId, v);
v[2] = (float_t)luaL_checknumber(L, 3);
entityPhysicsSetVelocity(h->entityId, h->compId, v); return 0;
} else if(stringCompare(k, "bodyType") == 0) {
entityPhysicsSetBodyType(
h->entityId, h->compId,
(physicsbodytype_t)luaL_checkinteger(L, 3)
);
return 0;
} else if(stringCompare(k, "onGround") == 0) {
luaL_error(L, "entityphysics: onGround is read-only");
return 0;
}
luaL_error(L, "entityphysics: unknown property '%s'", k);
return 0;
entityPhysicsGetVelocity(eid, cid, v);
v[0] = (float_t)jerry_value_as_number(args_p[0]);
entityPhysicsSetVelocity(eid, cid, v);
return jerry_undefined();
}
JS_FUNC(moduleEntityPhysicsGetVelY) {
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
vec3 v;
entityPhysicsGetVelocity(eid, cid, v);
return jerry_number(v[1]);
}
JS_FUNC(moduleEntityPhysicsSetVelY) {
JS_REQUIRE_ARGS(1); JS_REQUIRE_NUMBER(0);
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
vec3 v;
entityPhysicsGetVelocity(eid, cid, v);
v[1] = (float_t)jerry_value_as_number(args_p[0]);
entityPhysicsSetVelocity(eid, cid, v);
return jerry_undefined();
}
JS_FUNC(moduleEntityPhysicsGetVelZ) {
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
vec3 v;
entityPhysicsGetVelocity(eid, cid, v);
return jerry_number(v[2]);
}
JS_FUNC(moduleEntityPhysicsSetVelZ) {
JS_REQUIRE_ARGS(1); JS_REQUIRE_NUMBER(0);
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
vec3 v;
entityPhysicsGetVelocity(eid, cid, v);
v[2] = (float_t)jerry_value_as_number(args_p[0]);
entityPhysicsSetVelocity(eid, cid, v);
return jerry_undefined();
}
// -- onGround getter (read-only) --
JS_FUNC(moduleEntityPhysicsGetOnGround) {
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
return jerry_boolean(entityPhysicsIsOnGround(eid, cid));
}
// -- bodyType getter/setter --
JS_FUNC(moduleEntityPhysicsGetBodyType) {
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
return jerry_number((double)entityPhysicsGetBodyType(eid, cid));
}
JS_FUNC(moduleEntityPhysicsSetBodyType) {
JS_REQUIRE_ARGS(1); JS_REQUIRE_NUMBER(0);
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
entityPhysicsSetBodyType(
eid, cid,
(physicsbodytype_t)(int32_t)jerry_value_as_number(args_p[0])
);
return jerry_undefined();
}
// -- methods --
/**
* Applies an immediate velocity impulse to the physics body.
*
* @param L Lua state. Arg 1: entityphysics userdata. Args 2-4: impulse x, y, z.
* @return 0.
* Args: x, y, z (numbers).
*/
static int moduleEntityPhysicsApplyImpulse(lua_State *L) {
entityphysics_handle_t *h = luaL_checkudata(L, 1, "entityphysics_mt");
JS_FUNC(moduleEntityPhysicsApplyImpulse) {
JS_REQUIRE_ARGS(3);
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
vec3 impulse = {
(float_t)luaL_checknumber(L, 2),
(float_t)luaL_checknumber(L, 3),
(float_t)luaL_checknumber(L, 4)
(float_t)jerry_value_as_number(args_p[0]),
(float_t)jerry_value_as_number(args_p[1]),
(float_t)jerry_value_as_number(args_p[2])
};
entityPhysicsApplyImpulse(h->entityId, h->compId, impulse);
return 0;
entityPhysicsApplyImpulse(eid, cid, impulse);
return jerry_undefined();
}
/**
* Sets the physics shape to an axis-aligned box.
*
* @param L Lua state. Arg 1: entityphysics userdata. Args 2-4: half-extents x, y, z.
* @return 0.
* Args: halfX, halfY, halfZ (numbers).
*/
static int moduleEntityPhysicsSetShapeCube(lua_State *L) {
entityphysics_handle_t *h = luaL_checkudata(L, 1, "entityphysics_mt");
JS_FUNC(moduleEntityPhysicsSetShapeCube) {
JS_REQUIRE_ARGS(3);
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
physicsshape_t shape;
shape.type = PHYSICS_SHAPE_CUBE;
shape.data.cube.halfExtents[0] = (float_t)luaL_checknumber(L, 2);
shape.data.cube.halfExtents[1] = (float_t)luaL_checknumber(L, 3);
shape.data.cube.halfExtents[2] = (float_t)luaL_checknumber(L, 4);
entityPhysicsSetShape(h->entityId, h->compId, shape);
return 0;
shape.data.cube.halfExtents[0] = (float_t)jerry_value_as_number(args_p[0]);
shape.data.cube.halfExtents[1] = (float_t)jerry_value_as_number(args_p[1]);
shape.data.cube.halfExtents[2] = (float_t)jerry_value_as_number(args_p[2]);
entityPhysicsSetShape(eid, cid, shape);
return jerry_undefined();
}
/**
* Sets the physics shape to a sphere.
*
* @param L Lua state. Arg 1: entityphysics userdata. Arg 2: radius.
* @return 0.
* Args: radius (number).
*/
static int moduleEntityPhysicsSetShapeSphere(lua_State *L) {
entityphysics_handle_t *h = luaL_checkudata(L, 1, "entityphysics_mt");
JS_FUNC(moduleEntityPhysicsSetShapeSphere) {
JS_REQUIRE_ARGS(1);
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
physicsshape_t shape;
shape.type = PHYSICS_SHAPE_SPHERE;
shape.data.sphere.radius = (float_t)luaL_checknumber(L, 2);
entityPhysicsSetShape(h->entityId, h->compId, shape);
return 0;
shape.data.sphere.radius = (float_t)jerry_value_as_number(args_p[0]);
entityPhysicsSetShape(eid, cid, shape);
return jerry_undefined();
}
/**
* Sets the physics shape to a Y-axis capsule.
*
* @param L Lua state. Arg 1: entityphysics userdata. Arg 2: radius. Arg 3: halfHeight.
* @return 0.
* Args: radius (number), halfHeight (number).
*/
static int moduleEntityPhysicsSetShapeCapsule(lua_State *L) {
entityphysics_handle_t *h = luaL_checkudata(L, 1, "entityphysics_mt");
JS_FUNC(moduleEntityPhysicsSetShapeCapsule) {
JS_REQUIRE_ARGS(2);
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
physicsshape_t shape;
shape.type = PHYSICS_SHAPE_CAPSULE;
shape.data.capsule.radius = (float_t)luaL_checknumber(L, 2);
shape.data.capsule.halfHeight = (float_t)luaL_checknumber(L, 3);
entityPhysicsSetShape(h->entityId, h->compId, shape);
return 0;
shape.data.capsule.radius = (float_t)jerry_value_as_number(args_p[0]);
shape.data.capsule.halfHeight = (float_t)jerry_value_as_number(args_p[1]);
entityPhysicsSetShape(eid, cid, shape);
return jerry_undefined();
}
/**
* Sets the physics shape to an infinite plane.
*
* @param L Lua state. Arg 1: entityphysics userdata. Args 2-4: normal x, y, z.
* Arg 5: distance from origin.
* @return 0.
* Args: normalX, normalY, normalZ, distance (numbers).
*/
static int moduleEntityPhysicsSetShapePlane(lua_State *L) {
entityphysics_handle_t *h = luaL_checkudata(L, 1, "entityphysics_mt");
JS_FUNC(moduleEntityPhysicsSetShapePlane) {
JS_REQUIRE_ARGS(4);
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
physicsshape_t shape;
shape.type = PHYSICS_SHAPE_PLANE;
shape.data.plane.normal[0] = (float_t)luaL_checknumber(L, 2);
shape.data.plane.normal[1] = (float_t)luaL_checknumber(L, 3);
shape.data.plane.normal[2] = (float_t)luaL_checknumber(L, 4);
shape.data.plane.distance = (float_t)luaL_checknumber(L, 5);
entityPhysicsSetShape(h->entityId, h->compId, shape);
return 0;
shape.data.plane.normal[0] = (float_t)jerry_value_as_number(args_p[0]);
shape.data.plane.normal[1] = (float_t)jerry_value_as_number(args_p[1]);
shape.data.plane.normal[2] = (float_t)jerry_value_as_number(args_p[2]);
shape.data.plane.distance = (float_t)jerry_value_as_number(args_p[3]);
entityPhysicsSetShape(eid, cid, shape);
return jerry_undefined();
}
// -- add function --
/**
* Adds a physics component to an entity and returns its userdata handle.
*
* @param L Lua state. Arg 1: entity id (number).
* @return 1 (entityphysics userdata).
* Adds a physics component to an entity and returns a handle object.
* Arg 0: entity id (number).
*/
static int moduleEntityPhysicsAdd(lua_State *L) {
entityid_t id = (entityid_t)luaL_checknumber(L, 1);
JS_FUNC(moduleEntityPhysicsAdd) {
JS_REQUIRE_ARGS(1); JS_REQUIRE_NUMBER(0);
entityid_t id = (entityid_t)jerry_value_as_number(args_p[0]);
componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_PHYSICS);
entityphysics_handle_t *h = lua_newuserdata(L, sizeof(entityphysics_handle_t));
h->entityId = id;
h->compId = comp;
luaL_getmetatable(L, "entityphysics_mt");
lua_setmetatable(L, -2);
return 1;
jerry_value_t handle = makeEntityHandle(id, comp);
if(s_physProto != 0) jerry_object_set_proto(handle, s_physProto);
return handle;
}
/**
* Registers the physics component metatable, entityPhysicsAdd global, and
* PHYSICS_BODY_* / PHYSICS_SHAPE_* integer constants.
*
* @param L Lua state.
* Registers the physics component prototype, entityPhysicsAdd global,
* and PHYSICS_BODY_* / PHYSICS_SHAPE_* integer constants.
*/
static void moduleEntityPhysics(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
static void moduleEntityPhysics(void) {
s_physProto = jerry_object();
luaL_newmetatable(L, "entityphysics_mt");
lua_pushcfunction(L, moduleEntityPhysicsIndex); lua_setfield(L, -2, "__index");
lua_pushcfunction(L, moduleEntityPhysicsNewIndex); lua_setfield(L, -2, "__newindex");
lua_pushcfunction(L, moduleEntityPhysicsApplyImpulse); lua_setfield(L, -2, "applyImpulse");
lua_pushcfunction(L, moduleEntityPhysicsSetShapeCube); lua_setfield(L, -2, "setShapeCube");
lua_pushcfunction(L, moduleEntityPhysicsSetShapeSphere); lua_setfield(L, -2, "setShapeSphere");
lua_pushcfunction(L, moduleEntityPhysicsSetShapeCapsule); lua_setfield(L, -2, "setShapeCapsule");
lua_pushcfunction(L, moduleEntityPhysicsSetShapePlane); lua_setfield(L, -2, "setShapePlane");
lua_pop(L, 1);
jsDefineProperty(s_physProto, "velX", moduleEntityPhysicsGetVelX, moduleEntityPhysicsSetVelX);
jsDefineProperty(s_physProto, "velY", moduleEntityPhysicsGetVelY, moduleEntityPhysicsSetVelY);
jsDefineProperty(s_physProto, "velZ", moduleEntityPhysicsGetVelZ, moduleEntityPhysicsSetVelZ);
jsDefineProperty(s_physProto, "onGround", moduleEntityPhysicsGetOnGround, NULL);
jsDefineProperty(s_physProto, "bodyType", moduleEntityPhysicsGetBodyType, moduleEntityPhysicsSetBodyType);
lua_register(L, "entityPhysicsAdd", moduleEntityPhysicsAdd);
jsDefineMethod(s_physProto, "applyImpulse", moduleEntityPhysicsApplyImpulse);
jsDefineMethod(s_physProto, "setShapeCube", moduleEntityPhysicsSetShapeCube);
jsDefineMethod(s_physProto, "setShapeSphere", moduleEntityPhysicsSetShapeSphere);
jsDefineMethod(s_physProto, "setShapeCapsule", moduleEntityPhysicsSetShapeCapsule);
jsDefineMethod(s_physProto, "setShapePlane", moduleEntityPhysicsSetShapePlane);
lua_pushinteger(L, PHYSICS_BODY_STATIC); lua_setglobal(L, "PHYSICS_BODY_STATIC");
lua_pushinteger(L, PHYSICS_BODY_DYNAMIC); lua_setglobal(L, "PHYSICS_BODY_DYNAMIC");
lua_pushinteger(L, PHYSICS_BODY_KINEMATIC); lua_setglobal(L, "PHYSICS_BODY_KINEMATIC");
jsRegister("entityPhysicsAdd", moduleEntityPhysicsAdd);
lua_pushinteger(L, PHYSICS_SHAPE_CUBE); lua_setglobal(L, "PHYSICS_SHAPE_CUBE");
lua_pushinteger(L, PHYSICS_SHAPE_SPHERE); lua_setglobal(L, "PHYSICS_SHAPE_SPHERE");
lua_pushinteger(L, PHYSICS_SHAPE_CAPSULE); lua_setglobal(L, "PHYSICS_SHAPE_CAPSULE");
lua_pushinteger(L, PHYSICS_SHAPE_PLANE); lua_setglobal(L, "PHYSICS_SHAPE_PLANE");
jsSetInt("PHYSICS_BODY_STATIC", PHYSICS_BODY_STATIC);
jsSetInt("PHYSICS_BODY_DYNAMIC", PHYSICS_BODY_DYNAMIC);
jsSetInt("PHYSICS_BODY_KINEMATIC", PHYSICS_BODY_KINEMATIC);
jsSetInt("PHYSICS_SHAPE_CUBE", PHYSICS_SHAPE_CUBE);
jsSetInt("PHYSICS_SHAPE_SPHERE", PHYSICS_SHAPE_SPHERE);
jsSetInt("PHYSICS_SHAPE_CAPSULE", PHYSICS_SHAPE_CAPSULE);
jsSetInt("PHYSICS_SHAPE_PLANE", PHYSICS_SHAPE_PLANE);
}
@@ -10,166 +10,304 @@
#include "entity/entity.h"
#include "entity/component/display/entityposition.h"
typedef struct {
entityid_t entityId;
componentid_t compId;
} entitypos_handle_t;
// ---- shared entity handle helpers (included once via this file) ----
#ifndef ENTITY_HANDLE_HELPERS
#define ENTITY_HANDLE_HELPERS
/**
* __index metamethod for position component userdata.
* Reads x, y, z, rotX, rotY, rotZ, scaleX, scaleY, scaleZ; falls through to
* the metatable for method lookups.
*
* @param L Lua state. Arg 1: entitypos userdata. Arg 2: key string.
* @return 1.
* Create a plain JS object carrying _eid and _cid number properties.
* The caller is responsible for freeing the returned value.
*/
static int moduleEntityPositionIndex(lua_State *L) {
entitypos_handle_t *h = luaL_checkudata(L, 1, "entitypos_mt");
const char *k = luaL_checkstring(L, 2);
vec3 v;
if(stringCompare(k, "x") == 0) {
entityPositionGetPosition(h->entityId, h->compId, v);
lua_pushnumber(L, v[0]); return 1;
} else if(stringCompare(k, "y") == 0) {
entityPositionGetPosition(h->entityId, h->compId, v);
lua_pushnumber(L, v[1]); return 1;
} else if(stringCompare(k, "z") == 0) {
entityPositionGetPosition(h->entityId, h->compId, v);
lua_pushnumber(L, v[2]); return 1;
} else if(stringCompare(k, "rotX") == 0) {
entityPositionGetRotation(h->entityId, h->compId, v);
lua_pushnumber(L, v[0]); return 1;
} else if(stringCompare(k, "rotY") == 0) {
entityPositionGetRotation(h->entityId, h->compId, v);
lua_pushnumber(L, v[1]); return 1;
} else if(stringCompare(k, "rotZ") == 0) {
entityPositionGetRotation(h->entityId, h->compId, v);
lua_pushnumber(L, v[2]); return 1;
} else if(stringCompare(k, "scaleX") == 0) {
entityPositionGetScale(h->entityId, h->compId, v);
lua_pushnumber(L, v[0]); return 1;
} else if(stringCompare(k, "scaleY") == 0) {
entityPositionGetScale(h->entityId, h->compId, v);
lua_pushnumber(L, v[1]); return 1;
} else if(stringCompare(k, "scaleZ") == 0) {
entityPositionGetScale(h->entityId, h->compId, v);
lua_pushnumber(L, v[2]); return 1;
}
lua_getmetatable(L, 1);
lua_getfield(L, -1, k);
return 1;
static inline jerry_value_t makeEntityHandle(
entityid_t eid,
componentid_t cid
) {
jerry_value_t obj = jerry_object();
jerry_value_t key;
jerry_value_t val;
key = jerry_string_sz("_eid");
val = jerry_number((double)eid);
jerry_object_set(obj, key, val);
jerry_value_free(val);
jerry_value_free(key);
key = jerry_string_sz("_cid");
val = jerry_number((double)cid);
jerry_object_set(obj, key, val);
jerry_value_free(val);
jerry_value_free(key);
return obj;
}
/**
* __newindex metamethod for position component userdata.
* Writes x, y, z, rotX, rotY, rotZ, scaleX, scaleY, scaleZ and rebuilds the
* transform. Errors on unknown keys.
*
* @param L Lua state. Arg 1: entitypos userdata. Arg 2: key string. Arg 3: number.
* @return 0.
* Extract the entity ID stored in a handle object's _eid property.
*/
static int moduleEntityPositionNewIndex(lua_State *L) {
entitypos_handle_t *h = luaL_checkudata(L, 1, "entitypos_mt");
const char *k = luaL_checkstring(L, 2);
vec3 v;
if(stringCompare(k, "x") == 0) {
entityPositionGetPosition(h->entityId, h->compId, v);
v[0] = (float_t)luaL_checknumber(L, 3);
entityPositionSetPosition(h->entityId, h->compId, v); return 0;
} else if(stringCompare(k, "y") == 0) {
entityPositionGetPosition(h->entityId, h->compId, v);
v[1] = (float_t)luaL_checknumber(L, 3);
entityPositionSetPosition(h->entityId, h->compId, v); return 0;
} else if(stringCompare(k, "z") == 0) {
entityPositionGetPosition(h->entityId, h->compId, v);
v[2] = (float_t)luaL_checknumber(L, 3);
entityPositionSetPosition(h->entityId, h->compId, v); return 0;
} else if(stringCompare(k, "rotX") == 0) {
entityPositionGetRotation(h->entityId, h->compId, v);
v[0] = (float_t)luaL_checknumber(L, 3);
entityPositionSetRotation(h->entityId, h->compId, v); return 0;
} else if(stringCompare(k, "rotY") == 0) {
entityPositionGetRotation(h->entityId, h->compId, v);
v[1] = (float_t)luaL_checknumber(L, 3);
entityPositionSetRotation(h->entityId, h->compId, v); return 0;
} else if(stringCompare(k, "rotZ") == 0) {
entityPositionGetRotation(h->entityId, h->compId, v);
v[2] = (float_t)luaL_checknumber(L, 3);
entityPositionSetRotation(h->entityId, h->compId, v); return 0;
} else if(stringCompare(k, "scaleX") == 0) {
entityPositionGetScale(h->entityId, h->compId, v);
v[0] = (float_t)luaL_checknumber(L, 3);
entityPositionSetScale(h->entityId, h->compId, v); return 0;
} else if(stringCompare(k, "scaleY") == 0) {
entityPositionGetScale(h->entityId, h->compId, v);
v[1] = (float_t)luaL_checknumber(L, 3);
entityPositionSetScale(h->entityId, h->compId, v); return 0;
} else if(stringCompare(k, "scaleZ") == 0) {
entityPositionGetScale(h->entityId, h->compId, v);
v[2] = (float_t)luaL_checknumber(L, 3);
entityPositionSetScale(h->entityId, h->compId, v); return 0;
}
luaL_error(L, "entitypos: unknown property '%s'", k);
return 0;
static inline entityid_t getEntityId(jerry_value_t handle) {
jerry_value_t key = jerry_string_sz("_eid");
jerry_value_t val = jerry_object_get(handle, key);
jerry_value_free(key);
entityid_t id = (entityid_t)jerry_value_as_number(val);
jerry_value_free(val);
return id;
}
/**
* Extract the component ID stored in a handle object's _cid property.
*/
static inline componentid_t getCompId(jerry_value_t handle) {
jerry_value_t key = jerry_string_sz("_cid");
jerry_value_t val = jerry_object_get(handle, key);
jerry_value_free(key);
componentid_t id = (componentid_t)jerry_value_as_number(val);
jerry_value_free(val);
return id;
}
#endif /* ENTITY_HANDLE_HELPERS */
// ---- position prototype ----
static jerry_value_t s_posProto = 0;
// -- position getters/setters --
JS_FUNC(moduleEntityPositionGetX) {
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
vec3 v;
entityPositionGetPosition(eid, cid, v);
return jerry_number(v[0]);
}
JS_FUNC(moduleEntityPositionSetX) {
JS_REQUIRE_ARGS(1); JS_REQUIRE_NUMBER(0);
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
vec3 v;
entityPositionGetPosition(eid, cid, v);
v[0] = (float_t)jerry_value_as_number(args_p[0]);
entityPositionSetPosition(eid, cid, v);
return jerry_undefined();
}
JS_FUNC(moduleEntityPositionGetY) {
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
vec3 v;
entityPositionGetPosition(eid, cid, v);
return jerry_number(v[1]);
}
JS_FUNC(moduleEntityPositionSetY) {
JS_REQUIRE_ARGS(1); JS_REQUIRE_NUMBER(0);
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
vec3 v;
entityPositionGetPosition(eid, cid, v);
v[1] = (float_t)jerry_value_as_number(args_p[0]);
entityPositionSetPosition(eid, cid, v);
return jerry_undefined();
}
JS_FUNC(moduleEntityPositionGetZ) {
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
vec3 v;
entityPositionGetPosition(eid, cid, v);
return jerry_number(v[2]);
}
JS_FUNC(moduleEntityPositionSetZ) {
JS_REQUIRE_ARGS(1); JS_REQUIRE_NUMBER(0);
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
vec3 v;
entityPositionGetPosition(eid, cid, v);
v[2] = (float_t)jerry_value_as_number(args_p[0]);
entityPositionSetPosition(eid, cid, v);
return jerry_undefined();
}
// -- rotation getters/setters --
JS_FUNC(moduleEntityPositionGetRotX) {
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
vec3 v;
entityPositionGetRotation(eid, cid, v);
return jerry_number(v[0]);
}
JS_FUNC(moduleEntityPositionSetRotX) {
JS_REQUIRE_ARGS(1); JS_REQUIRE_NUMBER(0);
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
vec3 v;
entityPositionGetRotation(eid, cid, v);
v[0] = (float_t)jerry_value_as_number(args_p[0]);
entityPositionSetRotation(eid, cid, v);
return jerry_undefined();
}
JS_FUNC(moduleEntityPositionGetRotY) {
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
vec3 v;
entityPositionGetRotation(eid, cid, v);
return jerry_number(v[1]);
}
JS_FUNC(moduleEntityPositionSetRotY) {
JS_REQUIRE_ARGS(1); JS_REQUIRE_NUMBER(0);
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
vec3 v;
entityPositionGetRotation(eid, cid, v);
v[1] = (float_t)jerry_value_as_number(args_p[0]);
entityPositionSetRotation(eid, cid, v);
return jerry_undefined();
}
JS_FUNC(moduleEntityPositionGetRotZ) {
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
vec3 v;
entityPositionGetRotation(eid, cid, v);
return jerry_number(v[2]);
}
JS_FUNC(moduleEntityPositionSetRotZ) {
JS_REQUIRE_ARGS(1); JS_REQUIRE_NUMBER(0);
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
vec3 v;
entityPositionGetRotation(eid, cid, v);
v[2] = (float_t)jerry_value_as_number(args_p[0]);
entityPositionSetRotation(eid, cid, v);
return jerry_undefined();
}
// -- scale getters/setters --
JS_FUNC(moduleEntityPositionGetScaleX) {
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
vec3 v;
entityPositionGetScale(eid, cid, v);
return jerry_number(v[0]);
}
JS_FUNC(moduleEntityPositionSetScaleX) {
JS_REQUIRE_ARGS(1); JS_REQUIRE_NUMBER(0);
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
vec3 v;
entityPositionGetScale(eid, cid, v);
v[0] = (float_t)jerry_value_as_number(args_p[0]);
entityPositionSetScale(eid, cid, v);
return jerry_undefined();
}
JS_FUNC(moduleEntityPositionGetScaleY) {
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
vec3 v;
entityPositionGetScale(eid, cid, v);
return jerry_number(v[1]);
}
JS_FUNC(moduleEntityPositionSetScaleY) {
JS_REQUIRE_ARGS(1); JS_REQUIRE_NUMBER(0);
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
vec3 v;
entityPositionGetScale(eid, cid, v);
v[1] = (float_t)jerry_value_as_number(args_p[0]);
entityPositionSetScale(eid, cid, v);
return jerry_undefined();
}
JS_FUNC(moduleEntityPositionGetScaleZ) {
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
vec3 v;
entityPositionGetScale(eid, cid, v);
return jerry_number(v[2]);
}
JS_FUNC(moduleEntityPositionSetScaleZ) {
JS_REQUIRE_ARGS(1); JS_REQUIRE_NUMBER(0);
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
vec3 v;
entityPositionGetScale(eid, cid, v);
v[2] = (float_t)jerry_value_as_number(args_p[0]);
entityPositionSetScale(eid, cid, v);
return jerry_undefined();
}
// -- lookAt method --
/**
* Rotates the entity to face a world-space target point.
* An optional up vector may be provided (args 5-7); defaults to (0,1,0).
*
* @param L Lua state. Arg 1: entitypos userdata. Args 2-4: target x, y, z.
* Args 5-7 (optional): up x, y, z.
* @return 0.
* Args: target x, y, z [, up x, y, z]. Up defaults to (0,1,0).
*/
static int moduleEntityPositionLookAt(lua_State *L) {
entitypos_handle_t *h = luaL_checkudata(L, 1, "entitypos_mt");
JS_FUNC(moduleEntityPositionLookAt) {
JS_REQUIRE_ARGS(3);
entityid_t eid = getEntityId(call_info_p->this_value);
componentid_t cid = getCompId(call_info_p->this_value);
vec3 target = {
(float_t)luaL_checknumber(L, 2),
(float_t)luaL_checknumber(L, 3),
(float_t)luaL_checknumber(L, 4)
(float_t)jerry_value_as_number(args_p[0]),
(float_t)jerry_value_as_number(args_p[1]),
(float_t)jerry_value_as_number(args_p[2])
};
vec3 up = { 0.0f, 1.0f, 0.0f };
if(lua_gettop(L) >= 7) {
up[0] = (float_t)luaL_checknumber(L, 5);
up[1] = (float_t)luaL_checknumber(L, 6);
up[2] = (float_t)luaL_checknumber(L, 7);
if(args_count >= 6) {
up[0] = (float_t)jerry_value_as_number(args_p[3]);
up[1] = (float_t)jerry_value_as_number(args_p[4]);
up[2] = (float_t)jerry_value_as_number(args_p[5]);
}
vec3 eye;
entityPositionGetPosition(h->entityId, h->compId, eye);
entityPositionLookAt(h->entityId, h->compId, target, up, eye);
return 0;
entityPositionGetPosition(eid, cid, eye);
entityPositionLookAt(eid, cid, target, up, eye);
return jerry_undefined();
}
// -- add function --
/**
* Adds a position component to an entity and returns its userdata handle.
*
* @param L Lua state. Arg 1: entity id (number).
* @return 1 (entitypos userdata).
* Adds a position component to an entity and returns a handle object.
* Arg 0: entity id (number).
*/
static int moduleEntityPositionAdd(lua_State *L) {
entityid_t id = (entityid_t)luaL_checknumber(L, 1);
JS_FUNC(moduleEntityPositionAdd) {
JS_REQUIRE_ARGS(1); JS_REQUIRE_NUMBER(0);
entityid_t id = (entityid_t)jerry_value_as_number(args_p[0]);
componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_POSITION);
entitypos_handle_t *h = lua_newuserdata(L, sizeof(entitypos_handle_t));
h->entityId = id;
h->compId = comp;
luaL_getmetatable(L, "entitypos_mt");
lua_setmetatable(L, -2);
return 1;
jerry_value_t handle = makeEntityHandle(id, comp);
if(s_posProto != 0) jerry_object_set_proto(handle, s_posProto);
return handle;
}
/**
* Registers the position component metatable and entityPositionAdd global.
*
* @param L Lua state.
* Registers the position component prototype and entityPositionAdd global.
*/
static void moduleEntityPosition(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
static void moduleEntityPosition(void) {
s_posProto = jerry_object();
luaL_newmetatable(L, "entitypos_mt");
lua_pushcfunction(L, moduleEntityPositionIndex); lua_setfield(L, -2, "__index");
lua_pushcfunction(L, moduleEntityPositionNewIndex); lua_setfield(L, -2, "__newindex");
lua_pushcfunction(L, moduleEntityPositionLookAt); lua_setfield(L, -2, "lookAt");
lua_pop(L, 1);
jsDefineProperty(s_posProto, "x", moduleEntityPositionGetX, moduleEntityPositionSetX);
jsDefineProperty(s_posProto, "y", moduleEntityPositionGetY, moduleEntityPositionSetY);
jsDefineProperty(s_posProto, "z", moduleEntityPositionGetZ, moduleEntityPositionSetZ);
jsDefineProperty(s_posProto, "rotX", moduleEntityPositionGetRotX, moduleEntityPositionSetRotX);
jsDefineProperty(s_posProto, "rotY", moduleEntityPositionGetRotY, moduleEntityPositionSetRotY);
jsDefineProperty(s_posProto, "rotZ", moduleEntityPositionGetRotZ, moduleEntityPositionSetRotZ);
jsDefineProperty(s_posProto, "scaleX", moduleEntityPositionGetScaleX, moduleEntityPositionSetScaleX);
jsDefineProperty(s_posProto, "scaleY", moduleEntityPositionGetScaleY, moduleEntityPositionSetScaleY);
jsDefineProperty(s_posProto, "scaleZ", moduleEntityPositionGetScaleZ, moduleEntityPositionSetScaleZ);
lua_register(L, "entityPositionAdd", moduleEntityPositionAdd);
jsDefineMethod(s_posProto, "lookAt", moduleEntityPositionLookAt);
jsRegister("entityPositionAdd", moduleEntityPositionAdd);
}
+52 -56
View File
@@ -15,87 +15,83 @@
#include "component/moduleentitymaterial.h"
#include "component/moduleentityphysics.h"
// ---- component type constants script ----
#define X(enumName, type, field, init, dispose) \
"COMPONENT_TYPE_" #enumName " = \"" #field "\"\n"
"var COMPONENT_TYPE_" #enumName " = \"" #field "\";\n"
static const char_t *COMPONENT_TYPE_SCRIPT =
#include "entity/componentlist.h"
;
#undef X
// ---- Entity base class script ----
static const char_t *ENTITY_SCRIPT =
"Entity = {}\n"
"Entity.__index = Entity\n"
"var Entity = {};\n"
"Entity.POSITION = COMPONENT_TYPE_POSITION;\n"
"Entity.CAMERA = COMPONENT_TYPE_CAMERA;\n"
"Entity.MESH = COMPONENT_TYPE_MESH;\n"
"Entity.MATERIAL = COMPONENT_TYPE_MATERIAL;\n"
"Entity.PHYSICS = COMPONENT_TYPE_PHYSICS;\n"
"\n"
"Entity.POSITION = COMPONENT_TYPE_POSITION\n"
"Entity.CAMERA = COMPONENT_TYPE_CAMERA\n"
"Entity.MESH = COMPONENT_TYPE_MESH\n"
"Entity.MATERIAL = COMPONENT_TYPE_MATERIAL\n"
"Entity.PHYSICS = COMPONENT_TYPE_PHYSICS\n"
"var _addFns = {};\n"
"_addFns[COMPONENT_TYPE_POSITION] = entityPositionAdd;\n"
"_addFns[COMPONENT_TYPE_CAMERA] = entityCameraAdd;\n"
"_addFns[COMPONENT_TYPE_MESH] = entityMeshAdd;\n"
"_addFns[COMPONENT_TYPE_MATERIAL] = entityMaterialAdd;\n"
"_addFns[COMPONENT_TYPE_PHYSICS] = entityPhysicsAdd;\n"
"\n"
"local _addFns = {\n"
" [COMPONENT_TYPE_POSITION] = entityPositionAdd,\n"
" [COMPONENT_TYPE_CAMERA] = entityCameraAdd,\n"
" [COMPONENT_TYPE_MESH] = entityMeshAdd,\n"
" [COMPONENT_TYPE_MATERIAL] = entityMaterialAdd,\n"
" [COMPONENT_TYPE_PHYSICS] = entityPhysicsAdd,\n"
"}\n"
"Entity.create = function() {\n"
" var self = Object.create(Entity);\n"
" self.id = entityAdd();\n"
" return self;\n"
"};\n"
"\n"
"function Entity.new()\n"
" return setmetatable({ id = entityAdd() }, Entity)\n"
"end\n"
"Entity.add = function(componentType) {\n"
" var fn = _addFns[componentType];\n"
" if(!fn) throw new Error('unknown component type: ' + String(componentType));\n"
" this[componentType] = fn(this.id);\n"
" return this[componentType];\n"
"};\n"
"\n"
"function Entity:add(componentType)\n"
" local fn = _addFns[componentType]\n"
" if not fn then error('unknown component type: ' .. tostring(componentType)) end\n"
" self[componentType] = fn(self.id)\n"
" return self[componentType]\n"
"end\n"
"\n"
"function Entity:dispose()\n"
" entityRemove(self.id)\n"
"end\n"
"Entity.dispose = function() {\n"
" entityRemove(this.id);\n"
"};\n"
;
// ---- module functions ----
/**
* Allocates a new entity and pushes its id onto the Lua stack.
*
* @param L Lua state.
* @return 1 (entity id number).
* Allocates a new entity and returns its id as a JS number.
*/
static int moduleEntityAdd(lua_State *L) {
lua_pushnumber(L, (lua_Number)entityManagerAdd());
return 1;
JS_FUNC(moduleEntityAdd) {
return jerry_number((double)entityManagerAdd());
}
/**
* Disposes the entity with the given id.
*
* @param L Lua state. Arg 1: entity id (number).
* @return 0.
* Arg 0: entity id (number).
*/
static int moduleEntityRemove(lua_State *L) {
entityDispose((entityid_t)luaL_checknumber(L, 1));
return 0;
JS_FUNC(moduleEntityRemove) {
JS_REQUIRE_ARGS(1); JS_REQUIRE_NUMBER(0);
entityDispose((entityid_t)jerry_value_as_number(args_p[0]));
return jerry_undefined();
}
/**
* Registers all entity and component modules, component type constants, and
* the Entity base class into the Lua state.
*
* @param L Lua state.
* the Entity base object into the JS realm.
*/
static void moduleEntity(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
static void moduleEntity(void) {
moduleEntityPosition();
moduleEntityCamera();
moduleEntityMesh();
moduleEntityMaterial();
moduleEntityPhysics();
moduleEntityPosition(L);
moduleEntityCamera(L);
moduleEntityMesh(L);
moduleEntityMaterial(L);
moduleEntityPhysics(L);
jsRegister("entityAdd", moduleEntityAdd);
jsRegister("entityRemove", moduleEntityRemove);
lua_register(L, "entityAdd", moduleEntityAdd);
lua_register(L, "entityRemove", moduleEntityRemove);
luaL_dostring(L, COMPONENT_TYPE_SCRIPT);
luaL_dostring(L, ENTITY_SCRIPT);
jsEvalStr(COMPONENT_TYPE_SCRIPT);
jsEvalStr(ENTITY_SCRIPT);
}
+21 -36
View File
@@ -9,50 +9,35 @@
#include "script/module/modulebase.h"
#include "event/event.h"
static int moduleEventSubscribe(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
JS_FUNC(moduleEventSubscribe) {
JS_REQUIRE_ARGS(2);
JS_REQUIRE_OBJECT(0);
JS_REQUIRE_FUNCTION(1);
scriptcontext_t *context = *(scriptcontext_t **)lua_getextraspace(L);
assertNotNull(context, "Script context cannot be NULL");
event_t *event = (event_t *)jsUnwrapPointer(args_p[0]);
if(event == NULL) return JS_THROW("eventSubscribe: Expected event object");
if(!lua_islightuserdata(L, 1)) {
luaL_error(L, "eventSubscribe: Expected event pointer as first argument");
return 0;
}
jerry_value_t funcCopy = jerry_value_copy(args_p[1]);
eventsub_t id = eventSubscribeScriptContext(event, scriptContextCurrent, funcCopy);
if(!lua_isfunction(L, 2)) {
luaL_error(L, "eventSubscribe: Expected function as second argument");
return 0;
}
event_t *event = (event_t *)lua_touserdata(L, 1);
assertNotNull(event, "Event cannot be NULL");
eventsub_t id = eventSubscribeScriptContext(event, context, 2);
lua_pushnumber(L, id);
return 1;
return jerry_number((double)id);
}
static int moduleEventUnsubscribe(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
JS_FUNC(moduleEventUnsubscribe) {
JS_REQUIRE_ARGS(2);
JS_REQUIRE_OBJECT(0);
JS_REQUIRE_NUMBER(1);
if(!lua_islightuserdata(L, 1)) {
luaL_error(L, "eventUnsubscribe: Expected event pointer as first argument");
return 0;
}
if(!lua_isnumber(L, 2)) {
luaL_error(L, "eventUnsubscribe: Expected subscription ID as second argument");
return 0;
}
event_t *event = (event_t *)jsUnwrapPointer(args_p[0]);
if(event == NULL) return JS_THROW("eventUnsubscribe: Expected event object");
event_t *event = (event_t *)lua_touserdata(L, 1);
assertNotNull(event, "Event cannot be NULL");
eventsub_t id = (eventsub_t)lua_tonumber(L, 2);
eventsub_t id = (eventsub_t)jerry_value_as_number(args_p[1]);
eventUnsubscribe(event, id);
return 0;
return jerry_undefined();
}
static void moduleEvent(lua_State *L) {
lua_register(L, "eventSubscribe", moduleEventSubscribe);
lua_register(L, "eventUnsubscribe", moduleEventUnsubscribe);
static void moduleEvent(void) {
jsRegister("eventSubscribe", moduleEventSubscribe);
jsRegister("eventUnsubscribe", moduleEventUnsubscribe);
}
+73 -135
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2025 Dominic Masters
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
@@ -9,198 +9,136 @@
#include "script/module/modulebase.h"
#include "input/input.h"
static int moduleInputIndex(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL");
JS_FUNC(moduleInputBind) {
JS_REQUIRE_ARGS(2);
JS_REQUIRE_STRING(0);
JS_REQUIRE_NUMBER(1);
const char_t *key = luaL_checkstring(l, 2);
assertStrLenMin(key, 1, "Key cannot be empty.");
if(stringCompare(key, "action") == 0) {
const inputevent_t *event = (const inputevent_t*)lua_touserdata(l, 1);
if(event == NULL) {
luaL_error(l, "Expected input event as first argument");
return 0;
}
lua_pushnumber(l, event->action);
return 1;
}
lua_pushnil(l);
return 1;
}
static int moduleInputBind(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
if(!lua_isstring(L, 1)) {
luaL_error(L, "inputBind: Expected button name as first argument");
return 0;
}
if(!lua_isnumber(L, 2)) {
luaL_error(L, "inputBind: Expected action ID as second argument");
return 0;
}
const char_t *strBtn = lua_tostring(L, 1);
const inputaction_t action = (inputaction_t)lua_tonumber(L, 2);
if(strBtn == NULL || strlen(strBtn) == 0) {
luaL_error(L, "inputBind: Button name cannot be NULL or empty");
return 0;
}
char_t strBtn[128];
jsToString(args_p[0], strBtn, sizeof(strBtn));
if(strBtn[0] == '\0') return JS_THROW("inputBind: Button name cannot be empty");
const inputaction_t action = (inputaction_t)jerry_value_as_number(args_p[1]);
if(action < INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
luaL_error(L, "inputBind: Invalid action ID %d with str %s", action, strBtn);
return 0;
return JS_THROW("inputBind: Invalid action ID");
}
inputbutton_t btn = inputButtonGetByName(strBtn);
if(btn.type == INPUT_BUTTON_TYPE_NONE) {
luaL_error(L, "inputBind: Invalid button name '%s'", strBtn);
return 0;
return JS_THROW("inputBind: Invalid button name");
}
inputBind(btn, action);
return 0;
return jerry_undefined();
}
static int moduleInputIsDown(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
if(!lua_isnumber(L, 1)) {
luaL_error(L, "inputIsDown: Expected action ID as first argument");
return 0;
}
const inputaction_t action = (inputaction_t)lua_tonumber(L, 1);
JS_FUNC(moduleInputIsDown) {
JS_REQUIRE_ARGS(1);
JS_REQUIRE_NUMBER(0);
const inputaction_t action = (inputaction_t)jerry_value_as_number(args_p[0]);
if(action < INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
luaL_error(L, "inputIsDown: Invalid action ID %d", action);
return 0;
return JS_THROW("inputIsDown: Invalid action ID");
}
lua_pushboolean(L, inputIsDown(action));
return 1;
return jerry_boolean(inputIsDown(action));
}
static int moduleInputPressed(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
if(!lua_isnumber(L, 1)) {
luaL_error(L, "inputPressed: Expected action ID as first argument");
return 0;
}
const inputaction_t action = (inputaction_t)lua_tonumber(L, 1);
JS_FUNC(moduleInputPressed) {
JS_REQUIRE_ARGS(1);
JS_REQUIRE_NUMBER(0);
const inputaction_t action = (inputaction_t)jerry_value_as_number(args_p[0]);
if(action < INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
luaL_error(L, "inputPressed: Invalid action ID %d", action);
return 0;
return JS_THROW("inputPressed: Invalid action ID");
}
lua_pushboolean(L, inputPressed(action));
return 1;
return jerry_boolean(inputPressed(action));
}
static int moduleInputReleased(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
if(!lua_isnumber(L, 1)) {
luaL_error(L, "inputReleased: Expected action ID as first argument");
return 0;
}
const inputaction_t action = (inputaction_t)lua_tonumber(L, 1);
JS_FUNC(moduleInputReleased) {
JS_REQUIRE_ARGS(1);
JS_REQUIRE_NUMBER(0);
const inputaction_t action = (inputaction_t)jerry_value_as_number(args_p[0]);
if(action < INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
luaL_error(L, "inputReleased: Invalid action ID %d", action);
return 0;
return JS_THROW("inputReleased: Invalid action ID");
}
lua_pushboolean(L, inputReleased(action));
return 1;
return jerry_boolean(inputReleased(action));
}
static int moduleInputGetValue(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
JS_FUNC(moduleInputGetValue) {
JS_REQUIRE_ARGS(1);
JS_REQUIRE_NUMBER(0);
if(!lua_isnumber(L, 1)) {
luaL_error(L, "inputGetValue: Expected action ID as first argument");
return 0;
}
const inputaction_t action = (inputaction_t)lua_tonumber(L, 1);
const inputaction_t action = (inputaction_t)jerry_value_as_number(args_p[0]);
if(action < INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
luaL_error(L, "inputGetValue: Invalid action ID %d", action);
return 0;
return JS_THROW("inputGetValue: Invalid action ID");
}
lua_pushnumber(L, inputGetCurrentValue(action));
return 1;
return jerry_number(inputGetCurrentValue(action));
}
static int moduleInputAxis(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
JS_FUNC(moduleInputAxis) {
JS_REQUIRE_ARGS(2);
JS_REQUIRE_NUMBER(0);
JS_REQUIRE_NUMBER(1);
if(!lua_isnumber(L, 1) || !lua_isnumber(L, 2)) {
luaL_error(L, "inputAxis: Expected two action IDs as arguments (neg, pos)");
return 0;
}
const inputaction_t neg = (inputaction_t)lua_tonumber(L, 1);
const inputaction_t pos = (inputaction_t)lua_tonumber(L, 2);
const inputaction_t neg = (inputaction_t)jerry_value_as_number(args_p[0]);
const inputaction_t pos = (inputaction_t)jerry_value_as_number(args_p[1]);
if(neg < INPUT_ACTION_NULL || neg >= INPUT_ACTION_COUNT) {
luaL_error(L, "inputAxis: Invalid negative action ID %d", neg);
return 0;
return JS_THROW("inputAxis: Invalid negative action ID");
}
if(pos < INPUT_ACTION_NULL || pos >= INPUT_ACTION_COUNT) {
luaL_error(L, "inputAxis: Invalid positive action ID %d", pos);
return 0;
return JS_THROW("inputAxis: Invalid positive action ID");
}
lua_pushnumber(L, inputAxis(neg, pos));
return 1;
return jerry_number(inputAxis(neg, pos));
}
static void moduleInput(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
JS_FUNC(moduleInputGetEventAction) {
JS_REQUIRE_ARGS(1);
luaL_dostring(L, INPUT_ACTION_SCRIPT);
const inputevent_t *event = (const inputevent_t *)jsUnwrapPointer(args_p[0]);
if(event == NULL) return JS_THROW("inputGetEventAction: Expected input event object");
luaL_dostring(L,
return jerry_number(event->action);
}
static void moduleInput(void) {
jsEvalStr(INPUT_ACTION_SCRIPT);
jsEvalStr(
""
#ifdef DUSK_INPUT_KEYBOARD
"INPUT_KEYBOARD = true\n"
"var INPUT_KEYBOARD = true;\n"
#endif
#ifdef DUSK_INPUT_GAMEPAD
"INPUT_GAMEPAD = true\n"
"var INPUT_GAMEPAD = true;\n"
#endif
#ifdef DUSK_INPUT_POINTER
"INPUT_POINTER = true\n"
"var INPUT_POINTER = true;\n"
#endif
#ifdef DUSK_INPUT_TOUCH
"INPUT_TOUCH = true\n"
"var INPUT_TOUCH = true;\n"
#endif
);
if(luaL_newmetatable(L, "input_mt")) {
lua_pushcfunction(L, moduleInputIndex);
lua_setfield(L, -2, "__index");
}
lua_pop(L, 1);
jerry_value_t evPressed = jsWrapPointer(&INPUT.eventPressed);
jsSetValue("INPUT_EVENT_PRESSED", evPressed);
jerry_value_free(evPressed);
lua_pushlightuserdata(L, &INPUT.eventPressed);
lua_setglobal(L, "INPUT_EVENT_PRESSED");
jerry_value_t evReleased = jsWrapPointer(&INPUT.eventReleased);
jsSetValue("INPUT_EVENT_RELEASED", evReleased);
jerry_value_free(evReleased);
lua_pushlightuserdata(L, &INPUT.eventReleased);
lua_setglobal(L, "INPUT_EVENT_RELEASED");
lua_register(L, "inputBind", moduleInputBind);
lua_register(L, "inputIsDown", moduleInputIsDown);
lua_register(L, "inputPressed", moduleInputPressed);
lua_register(L, "inputReleased", moduleInputReleased);
lua_register(L, "inputGetValue", moduleInputGetValue);
lua_register(L, "inputAxis", moduleInputAxis);
jsRegister("inputBind", moduleInputBind);
jsRegister("inputIsDown", moduleInputIsDown);
jsRegister("inputPressed", moduleInputPressed);
jsRegister("inputReleased", moduleInputReleased);
jsRegister("inputGetValue", moduleInputGetValue);
jsRegister("inputAxis", moduleInputAxis);
jsRegister("inputGetEventAction", moduleInputGetEventAction);
}
+39 -46
View File
@@ -9,61 +9,57 @@
#include "script/module/modulebase.h"
#include "locale/localemanager.h"
static int moduleLocaleGetText(lua_State *L) {
if(!lua_isstring(L, 1)) {
luaL_error(L, "Expected message ID as first argument");
return 0;
}
#define MODULE_LOCALE_MAX_ARGS 16
const char_t *id = lua_tostring(L, 1);
if(id == NULL || strlen(id) == 0) {
luaL_error(L, "Message ID cannot be NULL or empty");
return 0;
}
JS_FUNC(moduleLocaleGetText) {
JS_REQUIRE_ARGS(1);
JS_REQUIRE_STRING(0);
char_t id[256];
jsToString(args_p[0], id, sizeof(id));
if(id[0] == '\0') return JS_THROW("localeGetText: Message ID cannot be empty");
int32_t plural = 0;
int top = lua_gettop(L);
int argStart = 2;
jerry_length_t argStart = 1;
if(top >= 2 && !lua_isnil(L, 2)) {
if(!lua_isnumber(L, 2)) {
luaL_error(L, "Expected plural as second argument");
return 0;
if(args_count >= 2 && !jerry_value_is_undefined(args_p[1])) {
if(!jerry_value_is_number(args_p[1])) {
return JS_THROW("localeGetText: Expected plural as second argument");
}
plural = (int32_t)lua_tonumber(L, 2);
if(plural < 0) {
luaL_error(L, "Plural cannot be negative");
return 0;
}
argStart = 3;
plural = (int32_t)jerry_value_as_number(args_p[1]);
if(plural < 0) return JS_THROW("localeGetText: Plural cannot be negative");
argStart = 2;
}
size_t argCount = (top >= argStart) ? (size_t)(top - argStart + 1) : 0;
#define MODULE_LOCALE_MAX_ARGS 16
assetlocalearg_t argsStack[MODULE_LOCALE_MAX_ARGS];
size_t argCount = (args_count > argStart) ? (size_t)(args_count - argStart) : 0;
if(argCount > MODULE_LOCALE_MAX_ARGS) {
luaL_error(L, "Too many args (max %d)", MODULE_LOCALE_MAX_ARGS);
return 0;
return JS_THROW("localeGetText: Too many format arguments");
}
assetlocalearg_t argsStack[MODULE_LOCALE_MAX_ARGS];
char_t strBufs[MODULE_LOCALE_MAX_ARGS][128];
for(size_t i = 0; i < argCount; ++i) {
int luaIndex = argStart + (int)i;
if(lua_isinteger(L, luaIndex)) {
argsStack[i].type = ASSET_LOCALE_ARG_INT;
argsStack[i].intValue = (int32_t)lua_tonumber(L, luaIndex);
} else if(lua_isnumber(L, luaIndex)) {
argsStack[i].type = ASSET_LOCALE_ARG_FLOAT;
argsStack[i].floatValue = lua_tonumber(L, luaIndex);
} else if(lua_isstring(L, luaIndex)) {
jerry_value_t arg = args_p[argStart + i];
if(jerry_value_is_number(arg)) {
double num = jerry_value_as_number(arg);
if(num == (double)(int32_t)num) {
argsStack[i].type = ASSET_LOCALE_ARG_INT;
argsStack[i].intValue = (int32_t)num;
} else {
argsStack[i].type = ASSET_LOCALE_ARG_FLOAT;
argsStack[i].floatValue = (float_t)num;
}
} else if(jerry_value_is_string(arg)) {
jsToString(arg, strBufs[i], sizeof(strBufs[i]));
argsStack[i].type = ASSET_LOCALE_ARG_STRING;
argsStack[i].stringValue = lua_tostring(L, luaIndex);
argsStack[i].stringValue = strBufs[i];
} else {
luaL_error(L, "Unsupported localization argument type");
return 0;
return JS_THROW("localeGetText: Unsupported argument type");
}
}
#undef MODULE_LOCALE_MAX_ARGS
char_t buffer[1024];
errorret_t err = localeManagerGetTextArgs(
@@ -71,15 +67,12 @@ static int moduleLocaleGetText(lua_State *L) {
);
if(err.code != ERROR_OK) {
errorCatch(errorPrint(err));
luaL_error(L, "Failed to get localized text for ID '%s'", id);
return 0;
return JS_THROW("localeGetText: Failed to get localized text");
}
lua_pushstring(L, buffer);
return 1;
return jerry_string_sz(buffer);
}
static void moduleLocale(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
lua_register(L, "localeGetText", moduleLocaleGetText);
static void moduleLocale(void) {
jsRegister("localeGetText", moduleLocaleGetText);
}
+265 -276
View File
@@ -7,293 +7,282 @@
#pragma once
#include "script/module/modulebase.h"
#include "assert/assert.h"
#include "cglm/cglm.h"
#include "modulevec3.h"
#include "modulevec4.h"
/**
* Pushes a new mat4 userdata onto the Lua stack with the mat4_mt metatable.
*
* @param L Lua state.
* @param m Source matrix to copy.
*/
static void moduleMat4Push(lua_State *L, mat4 m) {
assertNotNull(L, "Lua state cannot be NULL");
// Native info for heap-allocated mat4 (float[4][4])
static void freeMat4Native(void *ptr, jerry_object_native_info_t *info) {
(void)info;
free(ptr);
}
static const jerry_object_native_info_t MAT4_NATIVE_INFO = {
.free_cb = freeMat4Native,
.number_of_references = 0,
.offset_of_references = 0
};
static jerry_value_t s_mat4Proto = 0;
mat4 *u = (mat4 *)lua_newuserdata(L, sizeof(mat4));
glm_mat4_copy(m, *u);
luaL_getmetatable(L, "mat4_mt");
lua_setmetatable(L, -2);
// ---------------------------------------------------------------------------
// Methods
// ---------------------------------------------------------------------------
JS_FUNC(moduleMatMul) {
JS_REQUIRE_ARGS(1);
float_t (*a)[4] = (float_t (*)[4])jerry_object_get_native_ptr(
call_info_p->this_value, &MAT4_NATIVE_INFO
);
if(!a) return JS_THROW("mat4.mul: invalid this");
float_t (*b)[4] = (float_t (*)[4])jerry_object_get_native_ptr(
args_p[0], &MAT4_NATIVE_INFO
);
if(!b) return JS_THROW("mat4.mul: argument must be a mat4");
float_t (*r)[4] = (float_t (*)[4])malloc(sizeof(mat4));
glm_mat4_mul(a, b, r);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &MAT4_NATIVE_INFO, r);
jerry_object_set_proto(obj, s_mat4Proto);
return obj;
}
/**
* Reads a mat4 userdata from the given stack index into out.
*
* @param L Lua state.
* @param idx Stack index of the mat4 userdata.
* @param out Destination matrix.
*/
static void moduleMat4Check(lua_State *L, int idx, mat4 out) {
assertNotNull(L, "Lua state cannot be NULL");
mat4 *m = (mat4 *)luaL_checkudata(L, idx, "mat4_mt");
glm_mat4_copy(*m, out);
JS_FUNC(moduleMatTranspose) {
float_t (*m)[4] = (float_t (*)[4])jerry_object_get_native_ptr(
call_info_p->this_value, &MAT4_NATIVE_INFO
);
if(!m) return JS_THROW("mat4.transpose: invalid this");
float_t (*r)[4] = (float_t (*)[4])malloc(sizeof(mat4));
glm_mat4_transpose_to(m, r);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &MAT4_NATIVE_INFO, r);
jerry_object_set_proto(obj, s_mat4Proto);
return obj;
}
/**
* __index metamethod for mat4 userdata.
* Delegates all lookups to the metatable (methods only).
*
* @param L Lua state. Arg 1: mat4 userdata. Arg 2: key string.
* @return 1.
*/
static int moduleMat4Index(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
lua_getmetatable(L, 1);
lua_getfield(L, -1, luaL_checkstring(L, 2));
lua_remove(L, -2);
return 1;
JS_FUNC(moduleMatInverse) {
float_t (*m)[4] = (float_t (*)[4])jerry_object_get_native_ptr(
call_info_p->this_value, &MAT4_NATIVE_INFO
);
if(!m) return JS_THROW("mat4.inverse: invalid this");
float_t (*r)[4] = (float_t (*)[4])malloc(sizeof(mat4));
glm_mat4_inv(m, r);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &MAT4_NATIVE_INFO, r);
jerry_object_set_proto(obj, s_mat4Proto);
return obj;
}
/**
* __mul metamethod: returns a * b as a new mat4.
*
* @param L Lua state. Arg 1: mat4. Arg 2: mat4.
* @return 1 (new mat4).
*/
static int moduleMat4OpMul(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
mat4 *a = (mat4 *)luaL_checkudata(L, 1, "mat4_mt");
assertNotNull(a, "invalid mat4 userdata");
mat4 *b = (mat4 *)luaL_checkudata(L, 2, "mat4_mt");
assertNotNull(b, "invalid mat4 userdata");
mat4 r;
glm_mat4_mul(*a, *b, r);
moduleMat4Push(L, r);
return 1;
JS_FUNC(moduleMatDeterminant) {
float_t (*m)[4] = (float_t (*)[4])jerry_object_get_native_ptr(
call_info_p->this_value, &MAT4_NATIVE_INFO
);
if(!m) return JS_THROW("mat4.determinant: invalid this");
return jerry_number(glm_mat4_det(m));
}
/**
* __tostring metamethod: returns a placeholder string.
*
* @param L Lua state. Arg 1: mat4.
* @return 1 (string).
*/
static int moduleMat4OpToString(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
lua_pushstring(L, "mat4(...)");
return 1;
}
/**
* Returns the transpose of a mat4 as a new mat4.
*
* @param L Lua state. Arg 1: mat4.
* @return 1 (new mat4).
*/
static int moduleMat4Transpose(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
mat4 *m = (mat4 *)luaL_checkudata(L, 1, "mat4_mt");
assertNotNull(m, "invalid mat4 userdata");
mat4 r;
glm_mat4_transpose_to(*m, r);
moduleMat4Push(L, r);
return 1;
}
/**
* Returns the inverse of a mat4 as a new mat4.
*
* @param L Lua state. Arg 1: mat4.
* @return 1 (new mat4).
*/
static int moduleMat4Inverse(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
mat4 *m = (mat4 *)luaL_checkudata(L, 1, "mat4_mt");
assertNotNull(m, "invalid mat4 userdata");
mat4 r;
glm_mat4_inv(*m, r);
moduleMat4Push(L, r);
return 1;
}
/**
* Multiplies a mat4 by a vec3, with an optional w component (default 1.0).
*
* @param L Lua state. Arg 1: mat4. Arg 2: vec3. Arg 3 (optional): number w.
* @return 1 (new vec3).
*/
static int moduleMat4MulVec3(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
mat4 *m = (mat4 *)luaL_checkudata(L, 1, "mat4_mt");
assertNotNull(m, "invalid mat4 userdata");
vec3 v;
moduleVec3Check(L, 2, v);
float_t w = lua_gettop(L) >= 3 ? (float_t)luaL_checknumber(L, 3) : 1.0f;
vec3 r;
glm_mat4_mulv3(*m, v, w, r);
moduleVec3Push(L, r);
return 1;
}
/**
* Multiplies a mat4 by a vec4.
*
* @param L Lua state. Arg 1: mat4. Arg 2: vec4.
* @return 1 (new vec4).
*/
static int moduleMat4MulVec4(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
mat4 *m = (mat4 *)luaL_checkudata(L, 1, "mat4_mt");
assertNotNull(m, "invalid mat4 userdata");
vec4 v;
moduleVec4Check(L, 2, v);
vec4 r;
glm_mat4_mulv(*m, v, r);
moduleVec4Push(L, r);
return 1;
}
/**
* Returns a copy of a mat4 translated by a vec3.
*
* @param L Lua state. Arg 1: mat4. Arg 2: vec3.
* @return 1 (new mat4).
*/
static int moduleMat4Translate(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
mat4 *m = (mat4 *)luaL_checkudata(L, 1, "mat4_mt");
assertNotNull(m, "invalid mat4 userdata");
vec3 v;
moduleVec3Check(L, 2, v);
mat4 r;
glm_mat4_copy(*m, r);
glm_translate(r, v);
moduleMat4Push(L, r);
return 1;
}
/**
* Returns a copy of a mat4 scaled by a vec3.
*
* @param L Lua state. Arg 1: mat4. Arg 2: vec3.
* @return 1 (new mat4).
*/
static int moduleMat4Scale(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
mat4 *m = (mat4 *)luaL_checkudata(L, 1, "mat4_mt");
assertNotNull(m, "invalid mat4 userdata");
vec3 v;
moduleVec3Check(L, 2, v);
mat4 r;
glm_mat4_copy(*m, r);
glm_scale(r, v);
moduleMat4Push(L, r);
return 1;
}
/**
* Returns a new identity mat4.
*
* @param L Lua state.
* @return 1 (new mat4).
*/
static int moduleMat4Identity(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
mat4 r;
glm_mat4_identity(r);
moduleMat4Push(L, r);
return 1;
}
/**
* Returns the determinant of a mat4.
*
* @param L Lua state. Arg 1: mat4.
* @return 1 (number).
*/
static int moduleMat4Determinant(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
mat4 *m = (mat4 *)luaL_checkudata(L, 1, "mat4_mt");
assertNotNull(m, "invalid mat4 userdata");
lua_pushnumber(L, (lua_Number)glm_mat4_det(*m));
return 1;
}
/**
* Constructor: creates a new identity mat4.
*
* @param L Lua state.
* @return 1 (new mat4).
*/
static int moduleMat4Create(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
mat4 m;
glm_mat4_identity(m);
moduleMat4Push(L, m);
return 1;
}
/**
* Registers the mat4 metatable and mat4 constructor global.
*
* @param L Lua state.
*/
static void moduleMat4(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
if(!luaL_newmetatable(L, "mat4_mt")) {
lua_pop(L, 1);
return;
JS_FUNC(moduleMatMulVec3) {
JS_REQUIRE_ARGS(1);
float_t (*m)[4] = (float_t (*)[4])jerry_object_get_native_ptr(
call_info_p->this_value, &MAT4_NATIVE_INFO
);
if(!m) return JS_THROW("mat4.mulVec3: invalid this");
vec3 vin;
if(!moduleVec3Check(args_p[0], vin)) {
return JS_THROW("mat4.mulVec3: first argument must be a vec3");
}
lua_pushcfunction(L, moduleMat4Index);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, moduleMat4OpMul);
lua_setfield(L, -2, "__mul");
lua_pushcfunction(L, moduleMat4OpToString);
lua_setfield(L, -2, "__tostring");
lua_pushcfunction(L, moduleMat4Transpose);
lua_setfield(L, -2, "transpose");
lua_pushcfunction(L, moduleMat4Inverse);
lua_setfield(L, -2, "inverse");
lua_pushcfunction(L, moduleMat4MulVec3);
lua_setfield(L, -2, "mulVec3");
lua_pushcfunction(L, moduleMat4MulVec4);
lua_setfield(L, -2, "mulVec4");
lua_pushcfunction(L, moduleMat4Translate);
lua_setfield(L, -2, "translate");
lua_pushcfunction(L, moduleMat4Scale);
lua_setfield(L, -2, "scale");
lua_pushcfunction(L, moduleMat4Identity);
lua_setfield(L, -2, "identity");
lua_pushcfunction(L, moduleMat4Determinant);
lua_setfield(L, -2, "determinant");
lua_pop(L, 1);
lua_register(L, "mat4", moduleMat4Create);
float_t w = (args_count >= 2 && jerry_value_is_number(args_p[1]))
? (float_t)jerry_value_as_number(args_p[1])
: 1.0f;
vec3 vout;
glm_mat4_mulv3(m, vin, w, vout);
return moduleVec3Push(vout);
}
JS_FUNC(moduleMatMulVec4) {
JS_REQUIRE_ARGS(1);
float_t (*m)[4] = (float_t (*)[4])jerry_object_get_native_ptr(
call_info_p->this_value, &MAT4_NATIVE_INFO
);
if(!m) return JS_THROW("mat4.mulVec4: invalid this");
vec4 vin;
if(!moduleVec4Check(args_p[0], vin)) {
return JS_THROW("mat4.mulVec4: first argument must be a vec4");
}
vec4 vout;
glm_mat4_mulv(m, vin, vout);
return moduleVec4Push(vout);
}
JS_FUNC(moduleMatTranslate) {
JS_REQUIRE_ARGS(1);
float_t (*m)[4] = (float_t (*)[4])jerry_object_get_native_ptr(
call_info_p->this_value, &MAT4_NATIVE_INFO
);
if(!m) return JS_THROW("mat4.translate: invalid this");
vec3 tv;
if(!moduleVec3Check(args_p[0], tv)) {
return JS_THROW("mat4.translate: argument must be a vec3");
}
float_t (*r)[4] = (float_t (*)[4])malloc(sizeof(mat4));
glm_mat4_copy(m, r);
glm_translate(r, tv);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &MAT4_NATIVE_INFO, r);
jerry_object_set_proto(obj, s_mat4Proto);
return obj;
}
JS_FUNC(moduleMatScale) {
JS_REQUIRE_ARGS(1);
float_t (*m)[4] = (float_t (*)[4])jerry_object_get_native_ptr(
call_info_p->this_value, &MAT4_NATIVE_INFO
);
if(!m) return JS_THROW("mat4.scale: invalid this");
vec3 sv;
if(!moduleVec3Check(args_p[0], sv)) {
return JS_THROW("mat4.scale: argument must be a vec3");
}
float_t (*r)[4] = (float_t (*)[4])malloc(sizeof(mat4));
glm_mat4_copy(m, r);
glm_scale(r, sv);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &MAT4_NATIVE_INFO, r);
jerry_object_set_proto(obj, s_mat4Proto);
return obj;
}
JS_FUNC(moduleMatIdentityMethod) {
float_t (*r)[4] = (float_t (*)[4])malloc(sizeof(mat4));
glm_mat4_identity(r);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &MAT4_NATIVE_INFO, r);
jerry_object_set_proto(obj, s_mat4Proto);
return obj;
}
// ---------------------------------------------------------------------------
// Global constructor / factory functions
// ---------------------------------------------------------------------------
/**
* mat4Identity() - returns a new identity matrix.
*/
JS_FUNC(moduleMatIdentity) {
float_t (*r)[4] = (float_t (*)[4])malloc(sizeof(mat4));
glm_mat4_identity(r);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &MAT4_NATIVE_INFO, r);
jerry_object_set_proto(obj, s_mat4Proto);
return obj;
}
/**
* mat4Perspective(fov, aspect, near, far) - returns a perspective projection
* matrix.
*/
JS_FUNC(moduleMatPerspective) {
JS_REQUIRE_ARGS(4);
JS_REQUIRE_NUMBER(0);
JS_REQUIRE_NUMBER(1);
JS_REQUIRE_NUMBER(2);
JS_REQUIRE_NUMBER(3);
float_t fov = (float_t)jerry_value_as_number(args_p[0]);
float_t aspect = (float_t)jerry_value_as_number(args_p[1]);
float_t znear = (float_t)jerry_value_as_number(args_p[2]);
float_t zfar = (float_t)jerry_value_as_number(args_p[3]);
float_t (*r)[4] = (float_t (*)[4])malloc(sizeof(mat4));
glm_perspective(fov, aspect, znear, zfar, r);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &MAT4_NATIVE_INFO, r);
jerry_object_set_proto(obj, s_mat4Proto);
return obj;
}
/**
* mat4LookAt(eye, center, up) - returns a view matrix.
* eye, center, up are vec3 objects.
*/
JS_FUNC(moduleMatLookAt) {
JS_REQUIRE_ARGS(3);
vec3 eye, center, up;
if(!moduleVec3Check(args_p[0], eye)) {
return JS_THROW("mat4LookAt: first argument (eye) must be a vec3");
}
if(!moduleVec3Check(args_p[1], center)) {
return JS_THROW("mat4LookAt: second argument (center) must be a vec3");
}
if(!moduleVec3Check(args_p[2], up)) {
return JS_THROW("mat4LookAt: third argument (up) must be a vec3");
}
float_t (*r)[4] = (float_t (*)[4])malloc(sizeof(mat4));
glm_lookat(eye, center, up, r);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &MAT4_NATIVE_INFO, r);
jerry_object_set_proto(obj, s_mat4Proto);
return obj;
}
// ---------------------------------------------------------------------------
// Helper: push a cglm mat4 as a new JS object
// ---------------------------------------------------------------------------
/**
* Wraps a copy of a cglm mat4 as a new JerryScript object.
*
* @param m Source mat4 (float[4][4]) to copy.
* @return Owned jerry_value_t with native ptr set.
*/
static inline jerry_value_t moduleMat4Push(float (*m)[4]) {
float_t (*copy)[4] = (float_t (*)[4])malloc(sizeof(mat4));
glm_mat4_copy(m, copy);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &MAT4_NATIVE_INFO, copy);
jerry_object_set_proto(obj, s_mat4Proto);
return obj;
}
// ---------------------------------------------------------------------------
// Helper: extract mat4 from a JS object
// ---------------------------------------------------------------------------
/**
* Reads a JerryScript mat4 object into an existing mat4 (float[4][4]).
*
* @param val JS value to read from.
* @param out Destination mat4.
* @return true if val carries a valid mat4 native ptr.
*/
static inline bool_t moduleMat4Check(jerry_value_t val, float (*out)[4]) {
float_t (*m)[4] = (float_t (*)[4])jerry_object_get_native_ptr(
val, &MAT4_NATIVE_INFO
);
if(!m) return false;
glm_mat4_copy(m, out);
return true;
}
// ---------------------------------------------------------------------------
// Module init
// ---------------------------------------------------------------------------
/**
* Creates the mat4 prototype with all methods, then registers the global
* factory functions mat4Identity(), mat4Perspective(), and mat4LookAt().
*/
static void moduleMat4(void) {
s_mat4Proto = jerry_object();
jsDefineMethod(s_mat4Proto, "mul", moduleMatMul);
jsDefineMethod(s_mat4Proto, "transpose", moduleMatTranspose);
jsDefineMethod(s_mat4Proto, "inverse", moduleMatInverse);
jsDefineMethod(s_mat4Proto, "determinant", moduleMatDeterminant);
jsDefineMethod(s_mat4Proto, "mulVec3", moduleMatMulVec3);
jsDefineMethod(s_mat4Proto, "mulVec4", moduleMatMulVec4);
jsDefineMethod(s_mat4Proto, "translate", moduleMatTranslate);
jsDefineMethod(s_mat4Proto, "scale", moduleMatScale);
jsDefineMethod(s_mat4Proto, "identity", moduleMatIdentityMethod);
jsRegister("mat4Identity", moduleMatIdentity);
jsRegister("mat4Perspective", moduleMatPerspective);
jsRegister("mat4LookAt", moduleMatLookAt);
}
+5 -9
View File
@@ -14,14 +14,10 @@
/**
* Registers all math modules: vec2, vec3, vec4, mat4.
*
* @param L Lua state.
*/
static void moduleMath(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
moduleVec2(L);
moduleVec3(L);
moduleVec4(L);
moduleMat4(L);
static void moduleMath(void) {
moduleVec2();
moduleVec3();
moduleVec4();
moduleMat4();
}
+217 -391
View File
@@ -7,441 +7,267 @@
#pragma once
#include "script/module/modulebase.h"
#include "assert/assert.h"
#include "cglm/cglm.h"
/**
* Pushes a new vec2 userdata onto the Lua stack with the vec2_mt metatable.
*
* @param L Lua state.
* @param v Source vector to copy.
*/
static void moduleVec2Push(lua_State *L, vec2 v) {
assertNotNull(L, "Lua state cannot be NULL");
// Native info for heap-allocated vec2 (float[2])
static void freeVec2Native(void *ptr, jerry_object_native_info_t *info) {
(void)info;
free(ptr);
}
static const jerry_object_native_info_t VEC2_NATIVE_INFO = {
.free_cb = freeVec2Native,
.number_of_references = 0,
.offset_of_references = 0
};
static jerry_value_t s_vec2Proto = 0;
vec2 *u = (vec2 *)lua_newuserdata(L, sizeof(vec2));
glm_vec2_copy(v, *u);
luaL_getmetatable(L, "vec2_mt");
lua_setmetatable(L, -2);
// ---------------------------------------------------------------------------
// Property getters / setters
// ---------------------------------------------------------------------------
JS_FUNC(moduleVec2GetX) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC2_NATIVE_INFO
);
return v ? jerry_number(v[0]) : jerry_undefined();
}
JS_FUNC(moduleVec2SetX) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC2_NATIVE_INFO
);
if(v && args_count > 0) v[0] = (float_t)jerry_value_as_number(args_p[0]);
return jerry_undefined();
}
/**
* Reads a vec2 userdata from the given stack index into out.
*
* @param L Lua state.
* @param idx Stack index of the vec2 userdata.
* @param out Destination vector.
*/
static void moduleVec2Check(lua_State *L, int idx, vec2 out) {
assertNotNull(L, "Lua state cannot be NULL");
vec2 *v = (vec2 *)luaL_checkudata(L, idx, "vec2_mt");
glm_vec2_copy(*v, out);
JS_FUNC(moduleVec2GetY) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC2_NATIVE_INFO
);
return v ? jerry_number(v[1]) : jerry_undefined();
}
JS_FUNC(moduleVec2SetY) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC2_NATIVE_INFO
);
if(v && args_count > 0) v[1] = (float_t)jerry_value_as_number(args_p[0]);
return jerry_undefined();
}
/**
* __index metamethod for vec2 userdata.
* Supports integer indices 1-2 and string keys x, y; falls through to the
* metatable for method lookups.
*
* @param L Lua state. Arg 1: vec2 userdata. Arg 2: integer or string key.
* @return 1.
*/
static int moduleVec2Index(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
// ---------------------------------------------------------------------------
// Methods
// ---------------------------------------------------------------------------
vec2 *v = (vec2 *)luaL_checkudata(L, 1, "vec2_mt");
assertNotNull(v, "invalid vec2 userdata");
const char *key = luaL_checkstring(L, 2);
assertStrLenMin(key, 1, "property key cannot be empty");
if(lua_isnumber(L, 2)) {
lua_Integer i = lua_tointeger(L, 2);
if(i >= 1 && i <= 2) {
lua_pushnumber(L, (lua_Number)(*v)[i - 1]);
return 1;
}
return 0;
}
if(stringCompare(key, "x") == 0) {
lua_pushnumber(L, (lua_Number)(*v)[0]);
return 1;
}
if(stringCompare(key, "y") == 0) {
lua_pushnumber(L, (lua_Number)(*v)[1]);
return 1;
}
lua_getmetatable(L, 1);
lua_getfield(L, -1, key);
lua_remove(L, -2);
return 1;
JS_FUNC(moduleVec2Dot) {
JS_REQUIRE_ARGS(1);
float_t *a = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC2_NATIVE_INFO
);
if(!a) return JS_THROW("vec2.dot: invalid this");
float_t *b = (float_t *)jerry_object_get_native_ptr(args_p[0], &VEC2_NATIVE_INFO);
if(!b) return JS_THROW("vec2.dot: argument must be a vec2");
return jerry_number(glm_vec2_dot(a, b));
}
/**
* __newindex metamethod for vec2 userdata.
* Writes x, y. Errors on unknown keys.
*
* @param L Lua state. Arg 1: vec2 userdata. Arg 2: key string. Arg 3: number.
* @return 0.
*/
static int moduleVec2NewIndex(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec2 *v = (vec2 *)luaL_checkudata(L, 1, "vec2_mt");
assertNotNull(v, "invalid vec2 userdata");
const char *key = luaL_checkstring(L, 2);
assertStrLenMin(key, 1, "property key cannot be empty");
if(stringCompare(key, "x") == 0) {
(*v)[0] = (float_t)luaL_checknumber(L, 3);
return 0;
}
if(stringCompare(key, "y") == 0) {
(*v)[1] = (float_t)luaL_checknumber(L, 3);
return 0;
}
luaL_error(L, "vec2: unknown property '%s'", key);
return 0;
JS_FUNC(moduleVec2Length) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC2_NATIVE_INFO
);
if(!v) return JS_THROW("vec2.length: invalid this");
return jerry_number(glm_vec2_norm(v));
}
/**
* __add metamethod: returns a + b as a new vec2.
*
* @param L Lua state. Arg 1: vec2. Arg 2: vec2.
* @return 1 (new vec2).
*/
static int moduleVec2OpAdd(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec2 *a = (vec2 *)luaL_checkudata(L, 1, "vec2_mt");
assertNotNull(a, "invalid vec2 userdata");
vec2 *b = (vec2 *)luaL_checkudata(L, 2, "vec2_mt");
assertNotNull(b, "invalid vec2 userdata");
vec2 r;
glm_vec2_add(*a, *b, r);
moduleVec2Push(L, r);
return 1;
JS_FUNC(moduleVec2LengthSq) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC2_NATIVE_INFO
);
if(!v) return JS_THROW("vec2.lengthSq: invalid this");
return jerry_number(glm_vec2_norm2(v));
}
/**
* __sub metamethod: returns a - b as a new vec2.
*
* @param L Lua state. Arg 1: vec2. Arg 2: vec2.
* @return 1 (new vec2).
*/
static int moduleVec2OpSub(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec2 *a = (vec2 *)luaL_checkudata(L, 1, "vec2_mt");
assertNotNull(a, "invalid vec2 userdata");
vec2 *b = (vec2 *)luaL_checkudata(L, 2, "vec2_mt");
assertNotNull(b, "invalid vec2 userdata");
vec2 r;
glm_vec2_sub(*a, *b, r);
moduleVec2Push(L, r);
return 1;
JS_FUNC(moduleVec2Normalize) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC2_NATIVE_INFO
);
if(!v) return JS_THROW("vec2.normalize: invalid this");
float_t *r = (float_t *)malloc(sizeof(vec2));
glm_vec2_normalize_to(v, r);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &VEC2_NATIVE_INFO, r);
jerry_object_set_proto(obj, s_vec2Proto);
return obj;
}
/**
* __mul metamethod: returns v * scalar or scalar * v as a new vec2.
*
* @param L Lua state. Arg 1: vec2 or number. Arg 2: number or vec2.
* @return 1 (new vec2).
*/
static int moduleVec2OpMul(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec2 r;
if(lua_isnumber(L, 1)) {
float_t s = (float_t)lua_tonumber(L, 1);
vec2 *v = (vec2 *)luaL_checkudata(L, 2, "vec2_mt");
assertNotNull(v, "invalid vec2 userdata");
glm_vec2_scale(*v, s, r);
} else {
vec2 *v = (vec2 *)luaL_checkudata(L, 1, "vec2_mt");
assertNotNull(v, "invalid vec2 userdata");
float_t s = (float_t)luaL_checknumber(L, 2);
glm_vec2_scale(*v, s, r);
}
moduleVec2Push(L, r);
return 1;
JS_FUNC(moduleVec2Negate) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC2_NATIVE_INFO
);
if(!v) return JS_THROW("vec2.negate: invalid this");
float_t *r = (float_t *)malloc(sizeof(vec2));
glm_vec2_negate_to(v, r);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &VEC2_NATIVE_INFO, r);
jerry_object_set_proto(obj, s_vec2Proto);
return obj;
}
/**
* __div metamethod: returns v / scalar as a new vec2.
*
* @param L Lua state. Arg 1: vec2. Arg 2: number.
* @return 1 (new vec2).
*/
static int moduleVec2OpDiv(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec2 *v = (vec2 *)luaL_checkudata(L, 1, "vec2_mt");
assertNotNull(v, "invalid vec2 userdata");
float_t s = (float_t)luaL_checknumber(L, 2);
vec2 r;
glm_vec2_divs(*v, s, r);
moduleVec2Push(L, r);
return 1;
JS_FUNC(moduleVec2Add) {
JS_REQUIRE_ARGS(1);
float_t *a = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC2_NATIVE_INFO
);
if(!a) return JS_THROW("vec2.add: invalid this");
float_t *b = (float_t *)jerry_object_get_native_ptr(args_p[0], &VEC2_NATIVE_INFO);
if(!b) return JS_THROW("vec2.add: argument must be a vec2");
float_t *r = (float_t *)malloc(sizeof(vec2));
glm_vec2_add(a, b, r);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &VEC2_NATIVE_INFO, r);
jerry_object_set_proto(obj, s_vec2Proto);
return obj;
}
/**
* __unm metamethod: returns -v as a new vec2.
*
* @param L Lua state. Arg 1: vec2.
* @return 1 (new vec2).
*/
static int moduleVec2OpUnm(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec2 *v = (vec2 *)luaL_checkudata(L, 1, "vec2_mt");
assertNotNull(v, "invalid vec2 userdata");
vec2 r;
glm_vec2_negate_to(*v, r);
moduleVec2Push(L, r);
return 1;
JS_FUNC(moduleVec2Sub) {
JS_REQUIRE_ARGS(1);
float_t *a = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC2_NATIVE_INFO
);
if(!a) return JS_THROW("vec2.sub: invalid this");
float_t *b = (float_t *)jerry_object_get_native_ptr(args_p[0], &VEC2_NATIVE_INFO);
if(!b) return JS_THROW("vec2.sub: argument must be a vec2");
float_t *r = (float_t *)malloc(sizeof(vec2));
glm_vec2_sub(a, b, r);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &VEC2_NATIVE_INFO, r);
jerry_object_set_proto(obj, s_vec2Proto);
return obj;
}
/**
* __eq metamethod: component-wise equality test.
*
* @param L Lua state. Arg 1: vec2. Arg 2: vec2.
* @return 1 (boolean).
*/
static int moduleVec2OpEq(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec2 *a = (vec2 *)luaL_checkudata(L, 1, "vec2_mt");
assertNotNull(a, "invalid vec2 userdata");
vec2 *b = (vec2 *)luaL_checkudata(L, 2, "vec2_mt");
assertNotNull(b, "invalid vec2 userdata");
lua_pushboolean(L, (*a)[0] == (*b)[0] && (*a)[1] == (*b)[1]);
return 1;
JS_FUNC(moduleVec2Scale) {
JS_REQUIRE_ARGS(1);
JS_REQUIRE_NUMBER(0);
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC2_NATIVE_INFO
);
if(!v) return JS_THROW("vec2.scale: invalid this");
float_t s = (float_t)jerry_value_as_number(args_p[0]);
float_t *r = (float_t *)malloc(sizeof(vec2));
glm_vec2_scale(v, s, r);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &VEC2_NATIVE_INFO, r);
jerry_object_set_proto(obj, s_vec2Proto);
return obj;
}
/**
* __tostring metamethod: returns a human-readable representation.
*
* @param L Lua state. Arg 1: vec2.
* @return 1 (string).
*/
static int moduleVec2OpToString(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec2 *v = (vec2 *)luaL_checkudata(L, 1, "vec2_mt");
assertNotNull(v, "invalid vec2 userdata");
char buf[64];
snprintf(buf, sizeof(buf), "vec2(%.3f, %.3f)", (*v)[0], (*v)[1]);
lua_pushstring(L, buf);
return 1;
JS_FUNC(moduleVec2Lerp) {
JS_REQUIRE_ARGS(2);
JS_REQUIRE_NUMBER(1);
float_t *a = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC2_NATIVE_INFO
);
if(!a) return JS_THROW("vec2.lerp: invalid this");
float_t *b = (float_t *)jerry_object_get_native_ptr(args_p[0], &VEC2_NATIVE_INFO);
if(!b) return JS_THROW("vec2.lerp: first argument must be a vec2");
float_t t = (float_t)jerry_value_as_number(args_p[1]);
float_t *r = (float_t *)malloc(sizeof(vec2));
glm_vec2_lerp(a, b, t, r);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &VEC2_NATIVE_INFO, r);
jerry_object_set_proto(obj, s_vec2Proto);
return obj;
}
/**
* Returns the dot product of two vec2 values.
*
* @param L Lua state. Arg 1: vec2. Arg 2: vec2.
* @return 1 (number).
*/
static int moduleVec2Dot(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec2 *a = (vec2 *)luaL_checkudata(L, 1, "vec2_mt");
assertNotNull(a, "invalid vec2 userdata");
vec2 *b = (vec2 *)luaL_checkudata(L, 2, "vec2_mt");
assertNotNull(b, "invalid vec2 userdata");
lua_pushnumber(L, (lua_Number)glm_vec2_dot(*a, *b));
return 1;
JS_FUNC(moduleVec2Distance) {
JS_REQUIRE_ARGS(1);
float_t *a = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC2_NATIVE_INFO
);
if(!a) return JS_THROW("vec2.distance: invalid this");
float_t *b = (float_t *)jerry_object_get_native_ptr(args_p[0], &VEC2_NATIVE_INFO);
if(!b) return JS_THROW("vec2.distance: argument must be a vec2");
return jerry_number(glm_vec2_distance(a, b));
}
/**
* Returns the length (magnitude) of a vec2.
*
* @param L Lua state. Arg 1: vec2.
* @return 1 (number).
*/
static int moduleVec2Length(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
// ---------------------------------------------------------------------------
// Constructor
// ---------------------------------------------------------------------------
vec2 *v = (vec2 *)luaL_checkudata(L, 1, "vec2_mt");
assertNotNull(v, "invalid vec2 userdata");
lua_pushnumber(L, (lua_Number)glm_vec2_norm(*v));
return 1;
JS_FUNC(moduleVec2Create) {
JS_REQUIRE_ARGS(2);
JS_REQUIRE_NUMBER(0);
JS_REQUIRE_NUMBER(1);
float_t *v = (float_t *)malloc(sizeof(vec2));
v[0] = (float_t)jerry_value_as_number(args_p[0]);
v[1] = (float_t)jerry_value_as_number(args_p[1]);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &VEC2_NATIVE_INFO, v);
jerry_object_set_proto(obj, s_vec2Proto);
return obj;
}
/**
* Returns the squared length of a vec2.
*
* @param L Lua state. Arg 1: vec2.
* @return 1 (number).
*/
static int moduleVec2LengthSq(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec2 *v = (vec2 *)luaL_checkudata(L, 1, "vec2_mt");
assertNotNull(v, "invalid vec2 userdata");
lua_pushnumber(L, (lua_Number)glm_vec2_norm2(*v));
return 1;
}
// ---------------------------------------------------------------------------
// Helper: push a cglm vec2 as a new JS object
// ---------------------------------------------------------------------------
/**
* Returns a normalized copy of a vec2.
* Wraps a copy of a cglm vec2 as a new JerryScript object.
*
* @param L Lua state. Arg 1: vec2.
* @return 1 (new vec2).
* @param v Source float[2] to copy.
* @return Owned jerry_value_t with native ptr set.
*/
static int moduleVec2Normalize(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec2 *v = (vec2 *)luaL_checkudata(L, 1, "vec2_mt");
assertNotNull(v, "invalid vec2 userdata");
vec2 r;
glm_vec2_normalize_to(*v, r);
moduleVec2Push(L, r);
return 1;
static inline jerry_value_t moduleVec2Push(const float_t *v) {
float_t *copy = (float_t *)malloc(sizeof(vec2));
copy[0] = v[0];
copy[1] = v[1];
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &VEC2_NATIVE_INFO, copy);
jerry_object_set_proto(obj, s_vec2Proto);
return obj;
}
// ---------------------------------------------------------------------------
// Helper: extract vec2 from a JS object
// ---------------------------------------------------------------------------
/**
* Linearly interpolates between two vec2 values.
* Reads a JerryScript vec2 object into an existing float[2].
*
* @param L Lua state. Arg 1: vec2 a. Arg 2: vec2 b. Arg 3: number t.
* @return 1 (new vec2).
* @param val JS value to read from.
* @param out Destination float[2].
* @return true if val carries a valid vec2 native ptr.
*/
static int moduleVec2Lerp(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec2 *a = (vec2 *)luaL_checkudata(L, 1, "vec2_mt");
assertNotNull(a, "invalid vec2 userdata");
vec2 *b = (vec2 *)luaL_checkudata(L, 2, "vec2_mt");
assertNotNull(b, "invalid vec2 userdata");
float_t t = (float_t)luaL_checknumber(L, 3);
vec2 r;
glm_vec2_lerp(*a, *b, t, r);
moduleVec2Push(L, r);
return 1;
static inline bool_t moduleVec2Check(jerry_value_t val, float_t *out) {
float_t *v = (float_t *)jerry_object_get_native_ptr(val, &VEC2_NATIVE_INFO);
if(!v) return false;
out[0] = v[0];
out[1] = v[1];
return true;
}
// ---------------------------------------------------------------------------
// Module init
// ---------------------------------------------------------------------------
/**
* Returns the distance between two vec2 points.
*
* @param L Lua state. Arg 1: vec2 a. Arg 2: vec2 b.
* @return 1 (number).
* Creates the vec2 prototype with x/y getter-setter properties and all
* methods, then registers the global vec2() constructor.
*/
static int moduleVec2Distance(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
static void moduleVec2(void) {
s_vec2Proto = jerry_object();
vec2 *a = (vec2 *)luaL_checkudata(L, 1, "vec2_mt");
assertNotNull(a, "invalid vec2 userdata");
jsDefineProperty(s_vec2Proto, "x", moduleVec2GetX, moduleVec2SetX);
jsDefineProperty(s_vec2Proto, "y", moduleVec2GetY, moduleVec2SetY);
vec2 *b = (vec2 *)luaL_checkudata(L, 2, "vec2_mt");
assertNotNull(b, "invalid vec2 userdata");
jsDefineMethod(s_vec2Proto, "dot", moduleVec2Dot);
jsDefineMethod(s_vec2Proto, "length", moduleVec2Length);
jsDefineMethod(s_vec2Proto, "lengthSq", moduleVec2LengthSq);
jsDefineMethod(s_vec2Proto, "normalize", moduleVec2Normalize);
jsDefineMethod(s_vec2Proto, "negate", moduleVec2Negate);
jsDefineMethod(s_vec2Proto, "add", moduleVec2Add);
jsDefineMethod(s_vec2Proto, "sub", moduleVec2Sub);
jsDefineMethod(s_vec2Proto, "scale", moduleVec2Scale);
jsDefineMethod(s_vec2Proto, "lerp", moduleVec2Lerp);
jsDefineMethod(s_vec2Proto, "distance", moduleVec2Distance);
lua_pushnumber(L, (lua_Number)glm_vec2_distance(*a, *b));
return 1;
}
/**
* Returns a negated copy of a vec2.
*
* @param L Lua state. Arg 1: vec2.
* @return 1 (new vec2).
*/
static int moduleVec2Negate(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec2 *v = (vec2 *)luaL_checkudata(L, 1, "vec2_mt");
assertNotNull(v, "invalid vec2 userdata");
vec2 r;
glm_vec2_negate_to(*v, r);
moduleVec2Push(L, r);
return 1;
}
/**
* Constructor: creates a vec2 from two optional numbers (defaults to 0).
*
* @param L Lua state. Arg 1 (optional): x. Arg 2 (optional): y.
* @return 1 (new vec2).
*/
static int moduleVec2Create(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec2 v = {0, 0};
int top = lua_gettop(L);
if(top >= 1) v[0] = (float_t)luaL_checknumber(L, 1);
if(top >= 2) v[1] = (float_t)luaL_checknumber(L, 2);
moduleVec2Push(L, v);
return 1;
}
/**
* Registers the vec2 metatable and vec2 constructor global.
*
* @param L Lua state.
*/
static void moduleVec2(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
if(!luaL_newmetatable(L, "vec2_mt")) {
lua_pop(L, 1);
return;
}
lua_pushcfunction(L, moduleVec2Index);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, moduleVec2NewIndex);
lua_setfield(L, -2, "__newindex");
lua_pushcfunction(L, moduleVec2OpAdd);
lua_setfield(L, -2, "__add");
lua_pushcfunction(L, moduleVec2OpSub);
lua_setfield(L, -2, "__sub");
lua_pushcfunction(L, moduleVec2OpMul);
lua_setfield(L, -2, "__mul");
lua_pushcfunction(L, moduleVec2OpDiv);
lua_setfield(L, -2, "__div");
lua_pushcfunction(L, moduleVec2OpUnm);
lua_setfield(L, -2, "__unm");
lua_pushcfunction(L, moduleVec2OpEq);
lua_setfield(L, -2, "__eq");
lua_pushcfunction(L, moduleVec2OpToString);
lua_setfield(L, -2, "__tostring");
lua_pushcfunction(L, moduleVec2Dot);
lua_setfield(L, -2, "dot");
lua_pushcfunction(L, moduleVec2Length);
lua_setfield(L, -2, "length");
lua_pushcfunction(L, moduleVec2LengthSq);
lua_setfield(L, -2, "lengthSq");
lua_pushcfunction(L, moduleVec2Normalize);
lua_setfield(L, -2, "normalize");
lua_pushcfunction(L, moduleVec2Lerp);
lua_setfield(L, -2, "lerp");
lua_pushcfunction(L, moduleVec2Distance);
lua_setfield(L, -2, "distance");
lua_pushcfunction(L, moduleVec2Negate);
lua_setfield(L, -2, "negate");
lua_pop(L, 1);
lua_register(L, "vec2", moduleVec2Create);
jsRegister("vec2", moduleVec2Create);
}
+250 -422
View File
@@ -7,475 +7,303 @@
#pragma once
#include "script/module/modulebase.h"
#include "assert/assert.h"
#include "cglm/cglm.h"
/**
* Pushes a new vec3 userdata onto the Lua stack with the vec3_mt metatable.
*
* @param L Lua state.
* @param v Source vector to copy.
*/
static void moduleVec3Push(lua_State *L, vec3 v) {
assertNotNull(L, "Lua state cannot be NULL");
// Native info for heap-allocated vec3 (float[3])
static void freeVec3Native(void *ptr, jerry_object_native_info_t *info) {
(void)info;
free(ptr);
}
static const jerry_object_native_info_t VEC3_NATIVE_INFO = {
.free_cb = freeVec3Native,
.number_of_references = 0,
.offset_of_references = 0
};
static jerry_value_t s_vec3Proto = 0;
vec3 *u = (vec3 *)lua_newuserdata(L, sizeof(vec3));
glm_vec3_copy(v, *u);
luaL_getmetatable(L, "vec3_mt");
lua_setmetatable(L, -2);
// ---------------------------------------------------------------------------
// Property getters / setters
// ---------------------------------------------------------------------------
JS_FUNC(moduleVec3GetX) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC3_NATIVE_INFO
);
return v ? jerry_number(v[0]) : jerry_undefined();
}
JS_FUNC(moduleVec3SetX) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC3_NATIVE_INFO
);
if(v && args_count > 0) v[0] = (float_t)jerry_value_as_number(args_p[0]);
return jerry_undefined();
}
/**
* Reads a vec3 userdata from the given stack index into out.
*
* @param L Lua state.
* @param idx Stack index of the vec3 userdata.
* @param out Destination vector.
*/
static void moduleVec3Check(lua_State *L, int idx, vec3 out) {
assertNotNull(L, "Lua state cannot be NULL");
vec3 *v = (vec3 *)luaL_checkudata(L, idx, "vec3_mt");
glm_vec3_copy(*v, out);
JS_FUNC(moduleVec3GetY) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC3_NATIVE_INFO
);
return v ? jerry_number(v[1]) : jerry_undefined();
}
JS_FUNC(moduleVec3SetY) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC3_NATIVE_INFO
);
if(v && args_count > 0) v[1] = (float_t)jerry_value_as_number(args_p[0]);
return jerry_undefined();
}
/**
* __index metamethod for vec3 userdata.
* Supports integer indices 1-3 and string keys x, y, z; falls through to the
* metatable for method lookups.
*
* @param L Lua state. Arg 1: vec3 userdata. Arg 2: integer or string key.
* @return 1.
*/
static int moduleVec3Index(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec3 *v = (vec3 *)luaL_checkudata(L, 1, "vec3_mt");
assertNotNull(v, "invalid vec3 userdata");
const char *key = luaL_checkstring(L, 2);
assertStrLenMin(key, 1, "property key cannot be empty");
if(lua_isnumber(L, 2)) {
lua_Integer i = lua_tointeger(L, 2);
if(i >= 1 && i <= 3) {
lua_pushnumber(L, (lua_Number)(*v)[i - 1]);
return 1;
}
return 0;
}
if(stringCompare(key, "x") == 0) {
lua_pushnumber(L, (lua_Number)(*v)[0]);
return 1;
}
if(stringCompare(key, "y") == 0) {
lua_pushnumber(L, (lua_Number)(*v)[1]);
return 1;
}
if(stringCompare(key, "z") == 0) {
lua_pushnumber(L, (lua_Number)(*v)[2]);
return 1;
}
lua_getmetatable(L, 1);
lua_getfield(L, -1, key);
lua_remove(L, -2);
return 1;
JS_FUNC(moduleVec3GetZ) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC3_NATIVE_INFO
);
return v ? jerry_number(v[2]) : jerry_undefined();
}
JS_FUNC(moduleVec3SetZ) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC3_NATIVE_INFO
);
if(v && args_count > 0) v[2] = (float_t)jerry_value_as_number(args_p[0]);
return jerry_undefined();
}
/**
* __newindex metamethod for vec3 userdata.
* Writes x, y, z. Errors on unknown keys.
*
* @param L Lua state. Arg 1: vec3 userdata. Arg 2: key string. Arg 3: number.
* @return 0.
*/
static int moduleVec3NewIndex(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
// ---------------------------------------------------------------------------
// Methods
// ---------------------------------------------------------------------------
vec3 *v = (vec3 *)luaL_checkudata(L, 1, "vec3_mt");
assertNotNull(v, "invalid vec3 userdata");
const char *key = luaL_checkstring(L, 2);
assertStrLenMin(key, 1, "property key cannot be empty");
if(stringCompare(key, "x") == 0) {
(*v)[0] = (float_t)luaL_checknumber(L, 3);
return 0;
}
if(stringCompare(key, "y") == 0) {
(*v)[1] = (float_t)luaL_checknumber(L, 3);
return 0;
}
if(stringCompare(key, "z") == 0) {
(*v)[2] = (float_t)luaL_checknumber(L, 3);
return 0;
}
luaL_error(L, "vec3: unknown property '%s'", key);
return 0;
JS_FUNC(moduleVec3Dot) {
JS_REQUIRE_ARGS(1);
float_t *a = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC3_NATIVE_INFO
);
if(!a) return JS_THROW("vec3.dot: invalid this");
float_t *b = (float_t *)jerry_object_get_native_ptr(args_p[0], &VEC3_NATIVE_INFO);
if(!b) return JS_THROW("vec3.dot: argument must be a vec3");
return jerry_number(glm_vec3_dot(a, b));
}
/**
* __add metamethod: returns a + b as a new vec3.
*
* @param L Lua state. Arg 1: vec3. Arg 2: vec3.
* @return 1 (new vec3).
*/
static int moduleVec3OpAdd(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec3 *a = (vec3 *)luaL_checkudata(L, 1, "vec3_mt");
assertNotNull(a, "invalid vec3 userdata");
vec3 *b = (vec3 *)luaL_checkudata(L, 2, "vec3_mt");
assertNotNull(b, "invalid vec3 userdata");
vec3 r;
glm_vec3_add(*a, *b, r);
moduleVec3Push(L, r);
return 1;
JS_FUNC(moduleVec3Cross) {
JS_REQUIRE_ARGS(1);
float_t *a = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC3_NATIVE_INFO
);
if(!a) return JS_THROW("vec3.cross: invalid this");
float_t *b = (float_t *)jerry_object_get_native_ptr(args_p[0], &VEC3_NATIVE_INFO);
if(!b) return JS_THROW("vec3.cross: argument must be a vec3");
float_t *r = (float_t *)malloc(sizeof(vec3));
glm_vec3_cross(a, b, r);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &VEC3_NATIVE_INFO, r);
jerry_object_set_proto(obj, s_vec3Proto);
return obj;
}
/**
* __sub metamethod: returns a - b as a new vec3.
*
* @param L Lua state. Arg 1: vec3. Arg 2: vec3.
* @return 1 (new vec3).
*/
static int moduleVec3OpSub(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec3 *a = (vec3 *)luaL_checkudata(L, 1, "vec3_mt");
assertNotNull(a, "invalid vec3 userdata");
vec3 *b = (vec3 *)luaL_checkudata(L, 2, "vec3_mt");
assertNotNull(b, "invalid vec3 userdata");
vec3 r;
glm_vec3_sub(*a, *b, r);
moduleVec3Push(L, r);
return 1;
JS_FUNC(moduleVec3Length) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC3_NATIVE_INFO
);
if(!v) return JS_THROW("vec3.length: invalid this");
return jerry_number(glm_vec3_norm(v));
}
/**
* __mul metamethod: returns v * scalar or scalar * v as a new vec3.
*
* @param L Lua state. Arg 1: vec3 or number. Arg 2: number or vec3.
* @return 1 (new vec3).
*/
static int moduleVec3OpMul(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec3 r;
if(lua_isnumber(L, 1)) {
float_t s = (float_t)lua_tonumber(L, 1);
vec3 *v = (vec3 *)luaL_checkudata(L, 2, "vec3_mt");
assertNotNull(v, "invalid vec3 userdata");
glm_vec3_scale(*v, s, r);
} else {
vec3 *v = (vec3 *)luaL_checkudata(L, 1, "vec3_mt");
assertNotNull(v, "invalid vec3 userdata");
float_t s = (float_t)luaL_checknumber(L, 2);
glm_vec3_scale(*v, s, r);
}
moduleVec3Push(L, r);
return 1;
JS_FUNC(moduleVec3LengthSq) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC3_NATIVE_INFO
);
if(!v) return JS_THROW("vec3.lengthSq: invalid this");
return jerry_number(glm_vec3_norm2(v));
}
/**
* __div metamethod: returns v / scalar as a new vec3.
*
* @param L Lua state. Arg 1: vec3. Arg 2: number.
* @return 1 (new vec3).
*/
static int moduleVec3OpDiv(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec3 *v = (vec3 *)luaL_checkudata(L, 1, "vec3_mt");
assertNotNull(v, "invalid vec3 userdata");
float_t s = (float_t)luaL_checknumber(L, 2);
vec3 r;
glm_vec3_divs(*v, s, r);
moduleVec3Push(L, r);
return 1;
JS_FUNC(moduleVec3Normalize) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC3_NATIVE_INFO
);
if(!v) return JS_THROW("vec3.normalize: invalid this");
float_t *r = (float_t *)malloc(sizeof(vec3));
glm_vec3_normalize_to(v, r);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &VEC3_NATIVE_INFO, r);
jerry_object_set_proto(obj, s_vec3Proto);
return obj;
}
/**
* __unm metamethod: returns -v as a new vec3.
*
* @param L Lua state. Arg 1: vec3.
* @return 1 (new vec3).
*/
static int moduleVec3OpUnm(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec3 *v = (vec3 *)luaL_checkudata(L, 1, "vec3_mt");
assertNotNull(v, "invalid vec3 userdata");
vec3 r;
glm_vec3_negate_to(*v, r);
moduleVec3Push(L, r);
return 1;
JS_FUNC(moduleVec3Negate) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC3_NATIVE_INFO
);
if(!v) return JS_THROW("vec3.negate: invalid this");
float_t *r = (float_t *)malloc(sizeof(vec3));
glm_vec3_negate_to(v, r);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &VEC3_NATIVE_INFO, r);
jerry_object_set_proto(obj, s_vec3Proto);
return obj;
}
/**
* __eq metamethod: component-wise equality test.
*
* @param L Lua state. Arg 1: vec3. Arg 2: vec3.
* @return 1 (boolean).
*/
static int moduleVec3OpEq(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec3 *a = (vec3 *)luaL_checkudata(L, 1, "vec3_mt");
assertNotNull(a, "invalid vec3 userdata");
vec3 *b = (vec3 *)luaL_checkudata(L, 2, "vec3_mt");
assertNotNull(b, "invalid vec3 userdata");
lua_pushboolean(L, (*a)[0] == (*b)[0] && (*a)[1] == (*b)[1] && (*a)[2] == (*b)[2]);
return 1;
JS_FUNC(moduleVec3Add) {
JS_REQUIRE_ARGS(1);
float_t *a = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC3_NATIVE_INFO
);
if(!a) return JS_THROW("vec3.add: invalid this");
float_t *b = (float_t *)jerry_object_get_native_ptr(args_p[0], &VEC3_NATIVE_INFO);
if(!b) return JS_THROW("vec3.add: argument must be a vec3");
float_t *r = (float_t *)malloc(sizeof(vec3));
glm_vec3_add(a, b, r);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &VEC3_NATIVE_INFO, r);
jerry_object_set_proto(obj, s_vec3Proto);
return obj;
}
/**
* __tostring metamethod: returns a human-readable representation.
*
* @param L Lua state. Arg 1: vec3.
* @return 1 (string).
*/
static int moduleVec3OpToString(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec3 *v = (vec3 *)luaL_checkudata(L, 1, "vec3_mt");
assertNotNull(v, "invalid vec3 userdata");
char buf[80];
snprintf(buf, sizeof(buf), "vec3(%.3f, %.3f, %.3f)", (*v)[0], (*v)[1], (*v)[2]);
lua_pushstring(L, buf);
return 1;
JS_FUNC(moduleVec3Sub) {
JS_REQUIRE_ARGS(1);
float_t *a = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC3_NATIVE_INFO
);
if(!a) return JS_THROW("vec3.sub: invalid this");
float_t *b = (float_t *)jerry_object_get_native_ptr(args_p[0], &VEC3_NATIVE_INFO);
if(!b) return JS_THROW("vec3.sub: argument must be a vec3");
float_t *r = (float_t *)malloc(sizeof(vec3));
glm_vec3_sub(a, b, r);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &VEC3_NATIVE_INFO, r);
jerry_object_set_proto(obj, s_vec3Proto);
return obj;
}
/**
* Returns the dot product of two vec3 values.
*
* @param L Lua state. Arg 1: vec3. Arg 2: vec3.
* @return 1 (number).
*/
static int moduleVec3Dot(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec3 *a = (vec3 *)luaL_checkudata(L, 1, "vec3_mt");
assertNotNull(a, "invalid vec3 userdata");
vec3 *b = (vec3 *)luaL_checkudata(L, 2, "vec3_mt");
assertNotNull(b, "invalid vec3 userdata");
lua_pushnumber(L, (lua_Number)glm_vec3_dot(*a, *b));
return 1;
JS_FUNC(moduleVec3Scale) {
JS_REQUIRE_ARGS(1);
JS_REQUIRE_NUMBER(0);
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC3_NATIVE_INFO
);
if(!v) return JS_THROW("vec3.scale: invalid this");
float_t s = (float_t)jerry_value_as_number(args_p[0]);
float_t *r = (float_t *)malloc(sizeof(vec3));
glm_vec3_scale(v, s, r);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &VEC3_NATIVE_INFO, r);
jerry_object_set_proto(obj, s_vec3Proto);
return obj;
}
/**
* Returns the cross product of two vec3 values as a new vec3.
*
* @param L Lua state. Arg 1: vec3. Arg 2: vec3.
* @return 1 (new vec3).
*/
static int moduleVec3Cross(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec3 *a = (vec3 *)luaL_checkudata(L, 1, "vec3_mt");
assertNotNull(a, "invalid vec3 userdata");
vec3 *b = (vec3 *)luaL_checkudata(L, 2, "vec3_mt");
assertNotNull(b, "invalid vec3 userdata");
vec3 r;
glm_vec3_cross(*a, *b, r);
moduleVec3Push(L, r);
return 1;
JS_FUNC(moduleVec3Lerp) {
JS_REQUIRE_ARGS(2);
JS_REQUIRE_NUMBER(1);
float_t *a = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC3_NATIVE_INFO
);
if(!a) return JS_THROW("vec3.lerp: invalid this");
float_t *b = (float_t *)jerry_object_get_native_ptr(args_p[0], &VEC3_NATIVE_INFO);
if(!b) return JS_THROW("vec3.lerp: first argument must be a vec3");
float_t t = (float_t)jerry_value_as_number(args_p[1]);
float_t *r = (float_t *)malloc(sizeof(vec3));
glm_vec3_lerp(a, b, t, r);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &VEC3_NATIVE_INFO, r);
jerry_object_set_proto(obj, s_vec3Proto);
return obj;
}
/**
* Returns the length (magnitude) of a vec3.
*
* @param L Lua state. Arg 1: vec3.
* @return 1 (number).
*/
static int moduleVec3Length(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec3 *v = (vec3 *)luaL_checkudata(L, 1, "vec3_mt");
assertNotNull(v, "invalid vec3 userdata");
lua_pushnumber(L, (lua_Number)glm_vec3_norm(*v));
return 1;
JS_FUNC(moduleVec3Distance) {
JS_REQUIRE_ARGS(1);
float_t *a = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC3_NATIVE_INFO
);
if(!a) return JS_THROW("vec3.distance: invalid this");
float_t *b = (float_t *)jerry_object_get_native_ptr(args_p[0], &VEC3_NATIVE_INFO);
if(!b) return JS_THROW("vec3.distance: argument must be a vec3");
return jerry_number(glm_vec3_distance(a, b));
}
/**
* Returns the squared length of a vec3.
*
* @param L Lua state. Arg 1: vec3.
* @return 1 (number).
*/
static int moduleVec3LengthSq(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
// ---------------------------------------------------------------------------
// Constructor
// ---------------------------------------------------------------------------
vec3 *v = (vec3 *)luaL_checkudata(L, 1, "vec3_mt");
assertNotNull(v, "invalid vec3 userdata");
lua_pushnumber(L, (lua_Number)glm_vec3_norm2(*v));
return 1;
JS_FUNC(moduleVec3Create) {
JS_REQUIRE_ARGS(3);
JS_REQUIRE_NUMBER(0);
JS_REQUIRE_NUMBER(1);
JS_REQUIRE_NUMBER(2);
float_t *v = (float_t *)malloc(sizeof(vec3));
v[0] = (float_t)jerry_value_as_number(args_p[0]);
v[1] = (float_t)jerry_value_as_number(args_p[1]);
v[2] = (float_t)jerry_value_as_number(args_p[2]);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &VEC3_NATIVE_INFO, v);
jerry_object_set_proto(obj, s_vec3Proto);
return obj;
}
/**
* Returns a normalized copy of a vec3.
*
* @param L Lua state. Arg 1: vec3.
* @return 1 (new vec3).
*/
static int moduleVec3Normalize(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec3 *v = (vec3 *)luaL_checkudata(L, 1, "vec3_mt");
assertNotNull(v, "invalid vec3 userdata");
vec3 r;
glm_vec3_normalize_to(*v, r);
moduleVec3Push(L, r);
return 1;
}
// ---------------------------------------------------------------------------
// Helper: push a cglm vec3 as a new JS object
// ---------------------------------------------------------------------------
/**
* Linearly interpolates between two vec3 values.
* Wraps a copy of a cglm vec3 as a new JerryScript object.
*
* @param L Lua state. Arg 1: vec3 a. Arg 2: vec3 b. Arg 3: number t.
* @return 1 (new vec3).
* @param v Source float[3] to copy.
* @return Owned jerry_value_t with native ptr set.
*/
static int moduleVec3Lerp(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec3 *a = (vec3 *)luaL_checkudata(L, 1, "vec3_mt");
assertNotNull(a, "invalid vec3 userdata");
vec3 *b = (vec3 *)luaL_checkudata(L, 2, "vec3_mt");
assertNotNull(b, "invalid vec3 userdata");
float_t t = (float_t)luaL_checknumber(L, 3);
vec3 r;
glm_vec3_lerp(*a, *b, t, r);
moduleVec3Push(L, r);
return 1;
static inline jerry_value_t moduleVec3Push(const float_t *v) {
float_t *copy = (float_t *)malloc(sizeof(vec3));
copy[0] = v[0];
copy[1] = v[1];
copy[2] = v[2];
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &VEC3_NATIVE_INFO, copy);
jerry_object_set_proto(obj, s_vec3Proto);
return obj;
}
// ---------------------------------------------------------------------------
// Helper: extract vec3 from a JS object
// ---------------------------------------------------------------------------
/**
* Returns the distance between two vec3 points.
* Reads a JerryScript vec3 object into an existing float[3].
*
* @param L Lua state. Arg 1: vec3 a. Arg 2: vec3 b.
* @return 1 (number).
* @param val JS value to read from.
* @param out Destination float[3].
* @return true if val carries a valid vec3 native ptr.
*/
static int moduleVec3Distance(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec3 *a = (vec3 *)luaL_checkudata(L, 1, "vec3_mt");
assertNotNull(a, "invalid vec3 userdata");
vec3 *b = (vec3 *)luaL_checkudata(L, 2, "vec3_mt");
assertNotNull(b, "invalid vec3 userdata");
lua_pushnumber(L, (lua_Number)glm_vec3_distance(*a, *b));
return 1;
static inline bool_t moduleVec3Check(jerry_value_t val, float_t *out) {
float_t *v = (float_t *)jerry_object_get_native_ptr(val, &VEC3_NATIVE_INFO);
if(!v) return false;
out[0] = v[0];
out[1] = v[1];
out[2] = v[2];
return true;
}
// ---------------------------------------------------------------------------
// Module init
// ---------------------------------------------------------------------------
/**
* Returns a negated copy of a vec3.
*
* @param L Lua state. Arg 1: vec3.
* @return 1 (new vec3).
* Creates the vec3 prototype with x/y/z getter-setter properties and all
* methods, then registers the global vec3() constructor.
*/
static int moduleVec3Negate(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
static void moduleVec3(void) {
s_vec3Proto = jerry_object();
vec3 *v = (vec3 *)luaL_checkudata(L, 1, "vec3_mt");
assertNotNull(v, "invalid vec3 userdata");
jsDefineProperty(s_vec3Proto, "x", moduleVec3GetX, moduleVec3SetX);
jsDefineProperty(s_vec3Proto, "y", moduleVec3GetY, moduleVec3SetY);
jsDefineProperty(s_vec3Proto, "z", moduleVec3GetZ, moduleVec3SetZ);
vec3 r;
glm_vec3_negate_to(*v, r);
moduleVec3Push(L, r);
return 1;
}
/**
* Constructor: creates a vec3 from three optional numbers (defaults to 0).
*
* @param L Lua state. Arg 1 (optional): x. Arg 2 (optional): y. Arg 3 (optional): z.
* @return 1 (new vec3).
*/
static int moduleVec3Create(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec3 v = {0, 0, 0};
int top = lua_gettop(L);
if(top >= 1) v[0] = (float_t)luaL_checknumber(L, 1);
if(top >= 2) v[1] = (float_t)luaL_checknumber(L, 2);
if(top >= 3) v[2] = (float_t)luaL_checknumber(L, 3);
moduleVec3Push(L, v);
return 1;
}
/**
* Registers the vec3 metatable and vec3 constructor global.
*
* @param L Lua state.
*/
static void moduleVec3(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
if(!luaL_newmetatable(L, "vec3_mt")) {
lua_pop(L, 1);
return;
}
lua_pushcfunction(L, moduleVec3Index);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, moduleVec3NewIndex);
lua_setfield(L, -2, "__newindex");
lua_pushcfunction(L, moduleVec3OpAdd);
lua_setfield(L, -2, "__add");
lua_pushcfunction(L, moduleVec3OpSub);
lua_setfield(L, -2, "__sub");
lua_pushcfunction(L, moduleVec3OpMul);
lua_setfield(L, -2, "__mul");
lua_pushcfunction(L, moduleVec3OpDiv);
lua_setfield(L, -2, "__div");
lua_pushcfunction(L, moduleVec3OpUnm);
lua_setfield(L, -2, "__unm");
lua_pushcfunction(L, moduleVec3OpEq);
lua_setfield(L, -2, "__eq");
lua_pushcfunction(L, moduleVec3OpToString);
lua_setfield(L, -2, "__tostring");
lua_pushcfunction(L, moduleVec3Dot);
lua_setfield(L, -2, "dot");
lua_pushcfunction(L, moduleVec3Cross);
lua_setfield(L, -2, "cross");
lua_pushcfunction(L, moduleVec3Length);
lua_setfield(L, -2, "length");
lua_pushcfunction(L, moduleVec3LengthSq);
lua_setfield(L, -2, "lengthSq");
lua_pushcfunction(L, moduleVec3Normalize);
lua_setfield(L, -2, "normalize");
lua_pushcfunction(L, moduleVec3Lerp);
lua_setfield(L, -2, "lerp");
lua_pushcfunction(L, moduleVec3Distance);
lua_setfield(L, -2, "distance");
lua_pushcfunction(L, moduleVec3Negate);
lua_setfield(L, -2, "negate");
lua_pop(L, 1);
lua_register(L, "vec3", moduleVec3Create);
jsDefineMethod(s_vec3Proto, "dot", moduleVec3Dot);
jsDefineMethod(s_vec3Proto, "cross", moduleVec3Cross);
jsDefineMethod(s_vec3Proto, "length", moduleVec3Length);
jsDefineMethod(s_vec3Proto, "lengthSq", moduleVec3LengthSq);
jsDefineMethod(s_vec3Proto, "normalize", moduleVec3Normalize);
jsDefineMethod(s_vec3Proto, "negate", moduleVec3Negate);
jsDefineMethod(s_vec3Proto, "add", moduleVec3Add);
jsDefineMethod(s_vec3Proto, "sub", moduleVec3Sub);
jsDefineMethod(s_vec3Proto, "scale", moduleVec3Scale);
jsDefineMethod(s_vec3Proto, "lerp", moduleVec3Lerp);
jsDefineMethod(s_vec3Proto, "distance", moduleVec3Distance);
jsRegister("vec3", moduleVec3Create);
}
+333 -420
View File
@@ -7,445 +7,358 @@
#pragma once
#include "script/module/modulebase.h"
#include "assert/assert.h"
#include "cglm/cglm.h"
/**
* Pushes a new vec4 userdata onto the Lua stack with the vec4_mt metatable.
*
* @param L Lua state.
* @param v Source vector to copy.
*/
static void moduleVec4Push(lua_State *L, vec4 v) {
assertNotNull(L, "Lua state cannot be NULL");
vec4 *u = (vec4 *)lua_newuserdata(L, sizeof(vec4));
glm_vec4_copy(v, *u);
luaL_getmetatable(L, "vec4_mt");
lua_setmetatable(L, -2);
// Native info for heap-allocated vec4 (float[4])
static void freeVec4Native(void *ptr, jerry_object_native_info_t *info) {
(void)info;
free(ptr);
}
static const jerry_object_native_info_t VEC4_NATIVE_INFO = {
.free_cb = freeVec4Native,
.number_of_references = 0,
.offset_of_references = 0
};
static jerry_value_t s_vec4Proto = 0;
/**
* Reads a vec4 userdata from the given stack index into out.
*
* @param L Lua state.
* @param idx Stack index of the vec4 userdata.
* @param out Destination vector.
*/
static void moduleVec4Check(lua_State *L, int idx, vec4 out) {
assertNotNull(L, "Lua state cannot be NULL");
// ---------------------------------------------------------------------------
// Property getters / setters (x/u0, y/v0, z/u1, w/v1)
// ---------------------------------------------------------------------------
vec4 *v = (vec4 *)luaL_checkudata(L, idx, "vec4_mt");
glm_vec4_copy(*v, out);
}
/**
* __index metamethod for vec4 userdata.
* Supports integer indices 1-4 and string keys x/u0, y/v0, z/u1, w/v1;
* falls through to the metatable for method lookups.
*
* @param L Lua state. Arg 1: vec4 userdata. Arg 2: integer or string key.
* @return 1.
*/
static int moduleVec4Index(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec4 *v = (vec4 *)luaL_checkudata(L, 1, "vec4_mt");
assertNotNull(v, "invalid vec4 userdata");
const char *key = luaL_checkstring(L, 2);
assertStrLenMin(key, 1, "property key cannot be empty");
if(lua_isnumber(L, 2)) {
lua_Integer i = lua_tointeger(L, 2);
if(i >= 1 && i <= 4) {
lua_pushnumber(L, (lua_Number)(*v)[i - 1]);
return 1;
}
return 0;
}
if(stringEquals(key, "x") || stringEquals(key, "u0")) {
lua_pushnumber(L, (lua_Number)(*v)[0]);
return 1;
}
if(stringEquals(key, "y") || stringEquals(key, "v0")) {
lua_pushnumber(L, (lua_Number)(*v)[1]);
return 1;
}
if(stringEquals(key, "z") || stringEquals(key, "u1")) {
lua_pushnumber(L, (lua_Number)(*v)[2]);
return 1;
}
if(stringEquals(key, "w") || stringEquals(key, "v1")) {
lua_pushnumber(L, (lua_Number)(*v)[3]);
return 1;
}
lua_getmetatable(L, 1);
lua_getfield(L, -1, key);
lua_remove(L, -2);
return 1;
}
/**
* __newindex metamethod for vec4 userdata.
* Writes x/u0, y/v0, z/u1, w/v1. Errors on unknown keys.
*
* @param L Lua state. Arg 1: vec4 userdata. Arg 2: key string. Arg 3: number.
* @return 0.
*/
static int moduleVec4NewIndex(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec4 *v = (vec4 *)luaL_checkudata(L, 1, "vec4_mt");
assertNotNull(v, "invalid vec4 userdata");
const char *key = luaL_checkstring(L, 2);
assertStrLenMin(key, 1, "property key cannot be empty");
if(stringEquals(key, "x") || stringEquals(key, "u0")) {
(*v)[0] = (float_t)luaL_checknumber(L, 3);
return 0;
}
if(stringEquals(key, "y") || stringEquals(key, "v0")) {
(*v)[1] = (float_t)luaL_checknumber(L, 3);
return 0;
}
if(stringEquals(key, "z") || stringEquals(key, "u1")) {
(*v)[2] = (float_t)luaL_checknumber(L, 3);
return 0;
}
if(stringEquals(key, "w") || stringEquals(key, "v1")) {
(*v)[3] = (float_t)luaL_checknumber(L, 3);
return 0;
}
luaL_error(L, "vec4: unknown property '%s'", key);
return 0;
}
/**
* __add metamethod: returns a + b as a new vec4.
*
* @param L Lua state. Arg 1: vec4. Arg 2: vec4.
* @return 1 (new vec4).
*/
static int moduleVec4OpAdd(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec4 *a = (vec4 *)luaL_checkudata(L, 1, "vec4_mt");
assertNotNull(a, "invalid vec4 userdata");
vec4 *b = (vec4 *)luaL_checkudata(L, 2, "vec4_mt");
assertNotNull(b, "invalid vec4 userdata");
vec4 r;
glm_vec4_add(*a, *b, r);
moduleVec4Push(L, r);
return 1;
}
/**
* __sub metamethod: returns a - b as a new vec4.
*
* @param L Lua state. Arg 1: vec4. Arg 2: vec4.
* @return 1 (new vec4).
*/
static int moduleVec4OpSub(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec4 *a = (vec4 *)luaL_checkudata(L, 1, "vec4_mt");
assertNotNull(a, "invalid vec4 userdata");
vec4 *b = (vec4 *)luaL_checkudata(L, 2, "vec4_mt");
assertNotNull(b, "invalid vec4 userdata");
vec4 r;
glm_vec4_sub(*a, *b, r);
moduleVec4Push(L, r);
return 1;
}
/**
* __mul metamethod: returns v * scalar or scalar * v as a new vec4.
*
* @param L Lua state. Arg 1: vec4 or number. Arg 2: number or vec4.
* @return 1 (new vec4).
*/
static int moduleVec4OpMul(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec4 r;
if(lua_isnumber(L, 1)) {
float_t s = (float_t)lua_tonumber(L, 1);
vec4 *v = (vec4 *)luaL_checkudata(L, 2, "vec4_mt");
assertNotNull(v, "invalid vec4 userdata");
glm_vec4_scale(*v, s, r);
} else {
vec4 *v = (vec4 *)luaL_checkudata(L, 1, "vec4_mt");
assertNotNull(v, "invalid vec4 userdata");
float_t s = (float_t)luaL_checknumber(L, 2);
glm_vec4_scale(*v, s, r);
}
moduleVec4Push(L, r);
return 1;
}
/**
* __div metamethod: returns v / scalar as a new vec4.
*
* @param L Lua state. Arg 1: vec4. Arg 2: number.
* @return 1 (new vec4).
*/
static int moduleVec4OpDiv(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec4 *v = (vec4 *)luaL_checkudata(L, 1, "vec4_mt");
assertNotNull(v, "invalid vec4 userdata");
float_t s = (float_t)luaL_checknumber(L, 2);
vec4 r;
glm_vec4_divs(*v, s, r);
moduleVec4Push(L, r);
return 1;
}
/**
* __unm metamethod: returns -v as a new vec4.
*
* @param L Lua state. Arg 1: vec4.
* @return 1 (new vec4).
*/
static int moduleVec4OpUnm(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec4 *v = (vec4 *)luaL_checkudata(L, 1, "vec4_mt");
assertNotNull(v, "invalid vec4 userdata");
vec4 r;
glm_vec4_negate_to(*v, r);
moduleVec4Push(L, r);
return 1;
}
/**
* __eq metamethod: component-wise equality test.
*
* @param L Lua state. Arg 1: vec4. Arg 2: vec4.
* @return 1 (boolean).
*/
static int moduleVec4OpEq(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec4 *a = (vec4 *)luaL_checkudata(L, 1, "vec4_mt");
assertNotNull(a, "invalid vec4 userdata");
vec4 *b = (vec4 *)luaL_checkudata(L, 2, "vec4_mt");
assertNotNull(b, "invalid vec4 userdata");
lua_pushboolean(L,
(*a)[0] == (*b)[0] && (*a)[1] == (*b)[1] &&
(*a)[2] == (*b)[2] && (*a)[3] == (*b)[3]
JS_FUNC(moduleVec4GetX) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC4_NATIVE_INFO
);
return 1;
return v ? jerry_number(v[0]) : jerry_undefined();
}
JS_FUNC(moduleVec4SetX) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC4_NATIVE_INFO
);
if(v && args_count > 0) v[0] = (float_t)jerry_value_as_number(args_p[0]);
return jerry_undefined();
}
JS_FUNC(moduleVec4GetY) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC4_NATIVE_INFO
);
return v ? jerry_number(v[1]) : jerry_undefined();
}
JS_FUNC(moduleVec4SetY) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC4_NATIVE_INFO
);
if(v && args_count > 0) v[1] = (float_t)jerry_value_as_number(args_p[0]);
return jerry_undefined();
}
JS_FUNC(moduleVec4GetZ) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC4_NATIVE_INFO
);
return v ? jerry_number(v[2]) : jerry_undefined();
}
JS_FUNC(moduleVec4SetZ) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC4_NATIVE_INFO
);
if(v && args_count > 0) v[2] = (float_t)jerry_value_as_number(args_p[0]);
return jerry_undefined();
}
JS_FUNC(moduleVec4GetW) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC4_NATIVE_INFO
);
return v ? jerry_number(v[3]) : jerry_undefined();
}
JS_FUNC(moduleVec4SetW) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC4_NATIVE_INFO
);
if(v && args_count > 0) v[3] = (float_t)jerry_value_as_number(args_p[0]);
return jerry_undefined();
}
// u0/v0/u1/v1 aliases share the same backing floats
JS_FUNC(moduleVec4GetU0) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC4_NATIVE_INFO
);
return v ? jerry_number(v[0]) : jerry_undefined();
}
JS_FUNC(moduleVec4SetU0) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC4_NATIVE_INFO
);
if(v && args_count > 0) v[0] = (float_t)jerry_value_as_number(args_p[0]);
return jerry_undefined();
}
JS_FUNC(moduleVec4GetV0) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC4_NATIVE_INFO
);
return v ? jerry_number(v[1]) : jerry_undefined();
}
JS_FUNC(moduleVec4SetV0) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC4_NATIVE_INFO
);
if(v && args_count > 0) v[1] = (float_t)jerry_value_as_number(args_p[0]);
return jerry_undefined();
}
JS_FUNC(moduleVec4GetU1) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC4_NATIVE_INFO
);
return v ? jerry_number(v[2]) : jerry_undefined();
}
JS_FUNC(moduleVec4SetU1) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC4_NATIVE_INFO
);
if(v && args_count > 0) v[2] = (float_t)jerry_value_as_number(args_p[0]);
return jerry_undefined();
}
JS_FUNC(moduleVec4GetV1) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC4_NATIVE_INFO
);
return v ? jerry_number(v[3]) : jerry_undefined();
}
JS_FUNC(moduleVec4SetV1) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC4_NATIVE_INFO
);
if(v && args_count > 0) v[3] = (float_t)jerry_value_as_number(args_p[0]);
return jerry_undefined();
}
// ---------------------------------------------------------------------------
// Methods
// ---------------------------------------------------------------------------
JS_FUNC(moduleVec4Dot) {
JS_REQUIRE_ARGS(1);
float_t *a = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC4_NATIVE_INFO
);
if(!a) return JS_THROW("vec4.dot: invalid this");
float_t *b = (float_t *)jerry_object_get_native_ptr(args_p[0], &VEC4_NATIVE_INFO);
if(!b) return JS_THROW("vec4.dot: argument must be a vec4");
return jerry_number(glm_vec4_dot(a, b));
}
JS_FUNC(moduleVec4Length) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC4_NATIVE_INFO
);
if(!v) return JS_THROW("vec4.length: invalid this");
return jerry_number(glm_vec4_norm(v));
}
JS_FUNC(moduleVec4LengthSq) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC4_NATIVE_INFO
);
if(!v) return JS_THROW("vec4.lengthSq: invalid this");
return jerry_number(glm_vec4_norm2(v));
}
JS_FUNC(moduleVec4Normalize) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC4_NATIVE_INFO
);
if(!v) return JS_THROW("vec4.normalize: invalid this");
float_t *r = (float_t *)malloc(sizeof(vec4));
glm_vec4_normalize_to(v, r);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &VEC4_NATIVE_INFO, r);
jerry_object_set_proto(obj, s_vec4Proto);
return obj;
}
JS_FUNC(moduleVec4Negate) {
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC4_NATIVE_INFO
);
if(!v) return JS_THROW("vec4.negate: invalid this");
float_t *r = (float_t *)malloc(sizeof(vec4));
glm_vec4_negate_to(v, r);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &VEC4_NATIVE_INFO, r);
jerry_object_set_proto(obj, s_vec4Proto);
return obj;
}
JS_FUNC(moduleVec4Add) {
JS_REQUIRE_ARGS(1);
float_t *a = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC4_NATIVE_INFO
);
if(!a) return JS_THROW("vec4.add: invalid this");
float_t *b = (float_t *)jerry_object_get_native_ptr(args_p[0], &VEC4_NATIVE_INFO);
if(!b) return JS_THROW("vec4.add: argument must be a vec4");
float_t *r = (float_t *)malloc(sizeof(vec4));
glm_vec4_add(a, b, r);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &VEC4_NATIVE_INFO, r);
jerry_object_set_proto(obj, s_vec4Proto);
return obj;
}
JS_FUNC(moduleVec4Sub) {
JS_REQUIRE_ARGS(1);
float_t *a = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC4_NATIVE_INFO
);
if(!a) return JS_THROW("vec4.sub: invalid this");
float_t *b = (float_t *)jerry_object_get_native_ptr(args_p[0], &VEC4_NATIVE_INFO);
if(!b) return JS_THROW("vec4.sub: argument must be a vec4");
float_t *r = (float_t *)malloc(sizeof(vec4));
glm_vec4_sub(a, b, r);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &VEC4_NATIVE_INFO, r);
jerry_object_set_proto(obj, s_vec4Proto);
return obj;
}
JS_FUNC(moduleVec4Scale) {
JS_REQUIRE_ARGS(1);
JS_REQUIRE_NUMBER(0);
float_t *v = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC4_NATIVE_INFO
);
if(!v) return JS_THROW("vec4.scale: invalid this");
float_t s = (float_t)jerry_value_as_number(args_p[0]);
float_t *r = (float_t *)malloc(sizeof(vec4));
glm_vec4_scale(v, s, r);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &VEC4_NATIVE_INFO, r);
jerry_object_set_proto(obj, s_vec4Proto);
return obj;
}
JS_FUNC(moduleVec4Lerp) {
JS_REQUIRE_ARGS(2);
JS_REQUIRE_NUMBER(1);
float_t *a = (float_t *)jerry_object_get_native_ptr(
call_info_p->this_value, &VEC4_NATIVE_INFO
);
if(!a) return JS_THROW("vec4.lerp: invalid this");
float_t *b = (float_t *)jerry_object_get_native_ptr(args_p[0], &VEC4_NATIVE_INFO);
if(!b) return JS_THROW("vec4.lerp: first argument must be a vec4");
float_t t = (float_t)jerry_value_as_number(args_p[1]);
float_t *r = (float_t *)malloc(sizeof(vec4));
glm_vec4_lerp(a, b, t, r);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &VEC4_NATIVE_INFO, r);
jerry_object_set_proto(obj, s_vec4Proto);
return obj;
}
// ---------------------------------------------------------------------------
// Constructor
// ---------------------------------------------------------------------------
JS_FUNC(moduleVec4Create) {
JS_REQUIRE_ARGS(4);
JS_REQUIRE_NUMBER(0);
JS_REQUIRE_NUMBER(1);
JS_REQUIRE_NUMBER(2);
JS_REQUIRE_NUMBER(3);
float_t *v = (float_t *)malloc(sizeof(vec4));
v[0] = (float_t)jerry_value_as_number(args_p[0]);
v[1] = (float_t)jerry_value_as_number(args_p[1]);
v[2] = (float_t)jerry_value_as_number(args_p[2]);
v[3] = (float_t)jerry_value_as_number(args_p[3]);
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &VEC4_NATIVE_INFO, v);
jerry_object_set_proto(obj, s_vec4Proto);
return obj;
}
// ---------------------------------------------------------------------------
// Helper: push a cglm vec4 as a new JS object
// ---------------------------------------------------------------------------
/**
* __tostring metamethod: returns a human-readable representation.
* Wraps a copy of a cglm vec4 as a new JerryScript object.
*
* @param L Lua state. Arg 1: vec4.
* @return 1 (string).
* @param v Source float[4] to copy.
* @return Owned jerry_value_t with native ptr set.
*/
static int moduleVec4OpToString(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec4 *v = (vec4 *)luaL_checkudata(L, 1, "vec4_mt");
assertNotNull(v, "invalid vec4 userdata");
char buf[96];
snprintf(buf, sizeof(buf), "vec4(%.3f, %.3f, %.3f, %.3f)", (*v)[0], (*v)[1], (*v)[2], (*v)[3]);
lua_pushstring(L, buf);
return 1;
static inline jerry_value_t moduleVec4Push(const float_t *v) {
float_t *copy = (float_t *)malloc(sizeof(vec4));
copy[0] = v[0];
copy[1] = v[1];
copy[2] = v[2];
copy[3] = v[3];
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &VEC4_NATIVE_INFO, copy);
jerry_object_set_proto(obj, s_vec4Proto);
return obj;
}
// ---------------------------------------------------------------------------
// Helper: extract vec4 from a JS object
// ---------------------------------------------------------------------------
/**
* Returns the dot product of two vec4 values.
* Reads a JerryScript vec4 object into an existing float[4].
*
* @param L Lua state. Arg 1: vec4. Arg 2: vec4.
* @return 1 (number).
* @param val JS value to read from.
* @param out Destination float[4].
* @return true if val carries a valid vec4 native ptr.
*/
static int moduleVec4Dot(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec4 *a = (vec4 *)luaL_checkudata(L, 1, "vec4_mt");
assertNotNull(a, "invalid vec4 userdata");
vec4 *b = (vec4 *)luaL_checkudata(L, 2, "vec4_mt");
assertNotNull(b, "invalid vec4 userdata");
lua_pushnumber(L, (lua_Number)glm_vec4_dot(*a, *b));
return 1;
static inline bool_t moduleVec4Check(jerry_value_t val, float_t *out) {
float_t *v = (float_t *)jerry_object_get_native_ptr(val, &VEC4_NATIVE_INFO);
if(!v) return false;
out[0] = v[0];
out[1] = v[1];
out[2] = v[2];
out[3] = v[3];
return true;
}
// ---------------------------------------------------------------------------
// Module init
// ---------------------------------------------------------------------------
/**
* Returns the length (magnitude) of a vec4.
*
* @param L Lua state. Arg 1: vec4.
* @return 1 (number).
* Creates the vec4 prototype with x/y/z/w (and u0/v0/u1/v1 alias)
* getter-setter properties and all methods, then registers the global
* vec4() constructor.
*/
static int moduleVec4Length(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
static void moduleVec4(void) {
s_vec4Proto = jerry_object();
vec4 *v = (vec4 *)luaL_checkudata(L, 1, "vec4_mt");
assertNotNull(v, "invalid vec4 userdata");
// Primary component names
jsDefineProperty(s_vec4Proto, "x", moduleVec4GetX, moduleVec4SetX);
jsDefineProperty(s_vec4Proto, "y", moduleVec4GetY, moduleVec4SetY);
jsDefineProperty(s_vec4Proto, "z", moduleVec4GetZ, moduleVec4SetZ);
jsDefineProperty(s_vec4Proto, "w", moduleVec4GetW, moduleVec4SetW);
lua_pushnumber(L, (lua_Number)glm_vec4_norm(*v));
return 1;
}
/**
* Returns the squared length of a vec4.
*
* @param L Lua state. Arg 1: vec4.
* @return 1 (number).
*/
static int moduleVec4LengthSq(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec4 *v = (vec4 *)luaL_checkudata(L, 1, "vec4_mt");
assertNotNull(v, "invalid vec4 userdata");
lua_pushnumber(L, (lua_Number)glm_vec4_norm2(*v));
return 1;
}
/**
* Returns a normalized copy of a vec4.
*
* @param L Lua state. Arg 1: vec4.
* @return 1 (new vec4).
*/
static int moduleVec4Normalize(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec4 *v = (vec4 *)luaL_checkudata(L, 1, "vec4_mt");
assertNotNull(v, "invalid vec4 userdata");
vec4 r;
glm_vec4_normalize_to(*v, r);
moduleVec4Push(L, r);
return 1;
}
/**
* Linearly interpolates between two vec4 values.
*
* @param L Lua state. Arg 1: vec4 a. Arg 2: vec4 b. Arg 3: number t.
* @return 1 (new vec4).
*/
static int moduleVec4Lerp(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec4 *a = (vec4 *)luaL_checkudata(L, 1, "vec4_mt");
assertNotNull(a, "invalid vec4 userdata");
vec4 *b = (vec4 *)luaL_checkudata(L, 2, "vec4_mt");
assertNotNull(b, "invalid vec4 userdata");
float_t t = (float_t)luaL_checknumber(L, 3);
vec4 r;
glm_vec4_lerp(*a, *b, t, r);
moduleVec4Push(L, r);
return 1;
}
/**
* Returns a negated copy of a vec4.
*
* @param L Lua state. Arg 1: vec4.
* @return 1 (new vec4).
*/
static int moduleVec4Negate(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec4 *v = (vec4 *)luaL_checkudata(L, 1, "vec4_mt");
assertNotNull(v, "invalid vec4 userdata");
vec4 r;
glm_vec4_negate_to(*v, r);
moduleVec4Push(L, r);
return 1;
}
/**
* Constructor: creates a vec4 from four optional numbers (defaults to 0).
*
* @param L Lua state. Args 1-4 (optional): x, y, z, w.
* @return 1 (new vec4).
*/
static int moduleVec4Create(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
vec4 v = {0, 0, 0, 0};
int top = lua_gettop(L);
if(top >= 1) v[0] = (float_t)luaL_checknumber(L, 1);
if(top >= 2) v[1] = (float_t)luaL_checknumber(L, 2);
if(top >= 3) v[2] = (float_t)luaL_checknumber(L, 3);
if(top >= 4) v[3] = (float_t)luaL_checknumber(L, 4);
moduleVec4Push(L, v);
return 1;
}
/**
* Registers the vec4 metatable and vec4 constructor global.
*
* @param L Lua state.
*/
static void moduleVec4(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
if(!luaL_newmetatable(L, "vec4_mt")) {
lua_pop(L, 1);
return;
}
lua_pushcfunction(L, moduleVec4Index);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, moduleVec4NewIndex);
lua_setfield(L, -2, "__newindex");
lua_pushcfunction(L, moduleVec4OpAdd);
lua_setfield(L, -2, "__add");
lua_pushcfunction(L, moduleVec4OpSub);
lua_setfield(L, -2, "__sub");
lua_pushcfunction(L, moduleVec4OpMul);
lua_setfield(L, -2, "__mul");
lua_pushcfunction(L, moduleVec4OpDiv);
lua_setfield(L, -2, "__div");
lua_pushcfunction(L, moduleVec4OpUnm);
lua_setfield(L, -2, "__unm");
lua_pushcfunction(L, moduleVec4OpEq);
lua_setfield(L, -2, "__eq");
lua_pushcfunction(L, moduleVec4OpToString);
lua_setfield(L, -2, "__tostring");
lua_pushcfunction(L, moduleVec4Dot);
lua_setfield(L, -2, "dot");
lua_pushcfunction(L, moduleVec4Length);
lua_setfield(L, -2, "length");
lua_pushcfunction(L, moduleVec4LengthSq);
lua_setfield(L, -2, "lengthSq");
lua_pushcfunction(L, moduleVec4Normalize);
lua_setfield(L, -2, "normalize");
lua_pushcfunction(L, moduleVec4Lerp);
lua_setfield(L, -2, "lerp");
lua_pushcfunction(L, moduleVec4Negate);
lua_setfield(L, -2, "negate");
lua_pop(L, 1);
lua_register(L, "vec4", moduleVec4Create);
// UV alias names (same backing components)
jsDefineProperty(s_vec4Proto, "u0", moduleVec4GetU0, moduleVec4SetU0);
jsDefineProperty(s_vec4Proto, "v0", moduleVec4GetV0, moduleVec4SetV0);
jsDefineProperty(s_vec4Proto, "u1", moduleVec4GetU1, moduleVec4SetU1);
jsDefineProperty(s_vec4Proto, "v1", moduleVec4GetV1, moduleVec4SetV1);
jsDefineMethod(s_vec4Proto, "dot", moduleVec4Dot);
jsDefineMethod(s_vec4Proto, "length", moduleVec4Length);
jsDefineMethod(s_vec4Proto, "lengthSq", moduleVec4LengthSq);
jsDefineMethod(s_vec4Proto, "normalize", moduleVec4Normalize);
jsDefineMethod(s_vec4Proto, "negate", moduleVec4Negate);
jsDefineMethod(s_vec4Proto, "add", moduleVec4Add);
jsDefineMethod(s_vec4Proto, "sub", moduleVec4Sub);
jsDefineMethod(s_vec4Proto, "scale", moduleVec4Scale);
jsDefineMethod(s_vec4Proto, "lerp", moduleVec4Lerp);
jsRegister("vec4", moduleVec4Create);
}
+20 -22
View File
@@ -1,6 +1,6 @@
/**
* Copyright (c) 2026 Dominic Masters
*
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
@@ -24,24 +24,22 @@
#include "script/module/display/moduletileset.h"
#include "script/module/scene/modulescene.h"
void moduleRegister(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
moduleScript(L);
moduleEntity(L);
moduleInput(L);
modulePlatform(L);
moduleLocale(L);
moduleTime(L);
moduleEvent(L);
moduleColor(L);
moduleSpriteBatch(L);
moduleMath(L);
moduleShader(L);
moduleUi(L);
moduleText(L);
moduleScreen(L);
moduleTexture(L);
moduleTileset(L);
moduleScene(L);
}
static void moduleRegister(void) {
moduleScript();
moduleEntity();
moduleInput();
modulePlatform();
moduleLocale();
moduleTime();
moduleEvent();
moduleColor();
moduleSpriteBatch();
moduleMath();
moduleShader();
moduleUi();
moduleText();
moduleScreen();
moduleTexture();
moduleTileset();
moduleScene();
}
+255 -2
View File
@@ -1,6 +1,6 @@
/**
* Copyright (c) 2026 Dominic Masters
*
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
@@ -9,4 +9,257 @@
#include "script/scriptcontext.h"
#include "assert/assert.h"
#include "util/string.h"
#include "util/memory.h"
#include "util/memory.h"
#include <stdlib.h>
/**
* Standard JerryScript external function signature.
* Usage: JS_FUNC(myFunction) { ... }
*/
#define JS_FUNC(name) \
static jerry_value_t name( \
const jerry_call_info_t *call_info_p, \
const jerry_value_t args_p[], \
const jerry_length_t args_count)
/**
* Return a type-error exception from a module function.
* Usage: return JS_THROW("message");
*/
#define JS_THROW(msg) jerry_throw_sz(JERRY_ERROR_TYPE, (msg))
/**
* Assert minimum argument count; return type error if not met.
*/
#define JS_REQUIRE_ARGS(n) do { \
if((jerry_length_t)(args_count) < (jerry_length_t)(n)) { \
return JS_THROW("Not enough arguments"); \
} \
} while(0)
/**
* Assert an argument is a number; return type error if not.
*/
#define JS_REQUIRE_NUMBER(i) do { \
if(!jerry_value_is_number(args_p[(i)])) { \
return JS_THROW("Expected number argument"); \
} \
} while(0)
/**
* Assert an argument is a string; return type error if not.
*/
#define JS_REQUIRE_STRING(i) do { \
if(!jerry_value_is_string(args_p[(i)])) { \
return JS_THROW("Expected string argument"); \
} \
} while(0)
/**
* Assert an argument is a function; return type error if not.
*/
#define JS_REQUIRE_FUNCTION(i) do { \
if(!jerry_value_is_function(args_p[(i)])) { \
return JS_THROW("Expected function argument"); \
} \
} while(0)
/**
* Assert an argument is an object; return type error if not.
*/
#define JS_REQUIRE_OBJECT(i) do { \
if(!jerry_value_is_object(args_p[(i)])) { \
return JS_THROW("Expected object argument"); \
} \
} while(0)
/* JS_PTR_NATIVE_INFO is declared in scriptcontext.h and defined in
scriptcontext.c so all TUs share a single address for native-ptr lookups. */
/**
* Register a global JS function.
*
* @param name Function name as seen in scripts.
* @param fn C handler function.
*/
static inline void jsRegister(
const char_t *name,
jerry_external_handler_t fn
) {
jerry_value_t global = jerry_current_realm();
jerry_value_t key = jerry_string_sz(name);
jerry_value_t func = jerry_function_external(fn);
jerry_object_set(global, key, func);
jerry_value_free(func);
jerry_value_free(key);
jerry_value_free(global);
}
/**
* Set a global numeric constant.
*/
static inline void jsSetNumber(const char_t *name, double value) {
jerry_value_t global = jerry_current_realm();
jerry_value_t key = jerry_string_sz(name);
jerry_value_t val = jerry_number(value);
jerry_object_set(global, key, val);
jerry_value_free(val);
jerry_value_free(key);
jerry_value_free(global);
}
/**
* Set a global integer constant.
*/
static inline void jsSetInt(const char_t *name, int32_t value) {
jsSetNumber(name, (double)value);
}
/**
* Set a global string constant.
*/
static inline void jsSetString(const char_t *name, const char_t *value) {
jerry_value_t global = jerry_current_realm();
jerry_value_t key = jerry_string_sz(name);
jerry_value_t val = jerry_string_sz(value);
jerry_object_set(global, key, val);
jerry_value_free(val);
jerry_value_free(key);
jerry_value_free(global);
}
/**
* Set a global JS value. Caller retains ownership of the value and must free
* it independently.
*/
static inline void jsSetValue(const char_t *name, jerry_value_t value) {
jerry_value_t global = jerry_current_realm();
jerry_value_t key = jerry_string_sz(name);
jerry_object_set(global, key, value);
jerry_value_free(key);
jerry_value_free(global);
}
/**
* Wrap an engine-owned C pointer as a JS object (no GC free callback).
* Used for global singletons like INPUT_EVENT_PRESSED, SHADER_UNLIT, etc.
*/
static inline jerry_value_t jsWrapPointer(void *ptr) {
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &JS_PTR_NATIVE_INFO, ptr);
return obj;
}
/**
* Unwrap a C pointer from a JS object created by jsWrapPointer.
* Returns NULL if the object does not carry a matching native pointer.
*/
static inline void *jsUnwrapPointer(jerry_value_t val) {
if(!jerry_value_is_object(val)) return NULL;
return jerry_object_get_native_ptr(val, &JS_PTR_NATIVE_INFO);
}
/**
* Evaluate a JS source string in the global scope.
* Errors are silently discarded; use scriptContextExec for error-propagating
* execution.
*/
static inline void jsEvalStr(const char_t *script) {
jerry_value_t result = jerry_eval(
(const jerry_char_t *)script,
strlen(script),
JERRY_PARSE_NO_OPTS
);
jerry_value_free(result);
}
/**
* Copy a JerryScript string value into a C buffer (null-terminated).
*
* @param val Jerry string value.
* @param buf Output buffer.
* @param buflen Buffer capacity including the null terminator.
*/
static inline void jsToString(
jerry_value_t val,
char_t *buf,
jerry_size_t buflen
) {
jerry_size_t len = jerry_string_to_buffer(
val, JERRY_ENCODING_UTF8, (jerry_char_t *)buf, buflen - 1
);
buf[len] = '\0';
}
/**
* Define a named property on a JS object with getter and optional setter.
* Both getter and setter are external C functions.
*
* @param obj Target object (e.g. a prototype).
* @param name Property name.
* @param getter C getter handler.
* @param setter C setter handler, or NULL for read-only property.
*/
static inline void jsDefineProperty(
jerry_value_t obj,
const char_t *name,
jerry_external_handler_t getter,
jerry_external_handler_t setter
) {
jerry_property_descriptor_t desc;
memset(&desc, 0, sizeof(desc));
desc.flags = (uint16_t)(
JERRY_PROP_IS_GET_DEFINED |
JERRY_PROP_IS_ENUMERABLE_DEFINED | JERRY_PROP_IS_ENUMERABLE |
JERRY_PROP_IS_CONFIGURABLE_DEFINED | JERRY_PROP_IS_CONFIGURABLE
);
desc.getter = jerry_function_external(getter);
if(setter != NULL) {
desc.flags |= JERRY_PROP_IS_SET_DEFINED;
desc.setter = jerry_function_external(setter);
}
jerry_value_t key = jerry_string_sz(name);
jerry_value_t result = jerry_object_define_own_prop(obj, key, &desc);
jerry_value_free(result);
jerry_value_free(key);
jerry_value_free(desc.getter);
if(setter != NULL) jerry_value_free(desc.setter);
}
/**
* Set a named method (C function) on a JS object.
*
* @param obj Target object (e.g. a prototype).
* @param name Method name.
* @param fn C handler function.
*/
static inline void jsDefineMethod(
jerry_value_t obj,
const char_t *name,
jerry_external_handler_t fn
) {
jerry_value_t key = jerry_string_sz(name);
jerry_value_t func = jerry_function_external(fn);
jerry_object_set(obj, key, func);
jerry_value_free(func);
jerry_value_free(key);
}
/**
* Format an error message from a JerryScript exception value.
* Caller must ensure buf is large enough.
*/
static inline void jsExceptionMessage(
jerry_value_t exception,
char_t *buf,
size_t buflen
) {
jerry_value_t errVal = jerry_exception_value(exception, false);
jerry_value_t errStr = jerry_value_to_string(errVal);
jerry_size_t len = jerry_string_to_buffer(
errStr, JERRY_ENCODING_UTF8, (jerry_char_t *)buf, (jerry_size_t)(buflen - 1)
);
buf[len] = '\0';
jerry_value_free(errStr);
jerry_value_free(errVal);
}
+6 -9
View File
@@ -1,27 +1,24 @@
/**
* Copyright (c) 2025 Dominic Masters
*
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptcontext.h"
#include "assert/assert.h"
#include "script/module/modulebase.h"
#include "script/module/moduleplatformplatform.h"
#ifndef DUSK_TARGET_SYSTEM
#error "DUSK_TARGET_SYSTEM must be defined"
#endif
#define MODULE_PLATFORM_VALUE "PLATFORM = '" DUSK_TARGET_SYSTEM "'\n"
#define MODULE_PLATFORM_VALUE "var PLATFORM = '" DUSK_TARGET_SYSTEM "';\n"
static void modulePlatform(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
luaL_dostring(L, MODULE_PLATFORM_VALUE);
static void modulePlatform(void) {
jsEvalStr(MODULE_PLATFORM_VALUE);
#ifdef modulePlatformPlatform
modulePlatformPlatform(L);
modulePlatformPlatform();
#endif
}
+45 -95
View File
@@ -1,6 +1,6 @@
/**
* Copyright (c) 2026 Dominic Masters
*
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
@@ -9,118 +9,68 @@
#include "script/module/modulebase.h"
#include "scene/scene.h"
/**
* __index metamethod for the Scene table. Handles dynamic read-only properties.
*
* @param L Lua state. Arg 1: table, Arg 2: key.
* @return 1.
*/
static int moduleSceneIndex(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
JS_FUNC(moduleSceneSet) {
JS_REQUIRE_ARGS(1);
JS_REQUIRE_STRING(0);
const char_t *key = lua_tostring(L, 2);
assertNotNull(key, "Scene property key cannot be NULL");
char_t name[ASSET_FILE_PATH_MAX];
jsToString(args_p[0], name, sizeof(name));
if(name[0] == '\0') return JS_THROW("Scene.set: Scene name cannot be empty");
// if(stringEquals(key, "current")) {
// if(SCENE.sceneActive) {
// lua_pushstring(L, SCENE.sceneCurrent);
// } else {
// lua_pushnil(L);
// }
// return 1;
// }
lua_pushnil(L);
return 1;
sceneSet(name);
return jerry_undefined();
}
/**
* Attached Scene.set method to invoke internal C method.
*
* @param L Lua state.
* @return Number of return values on the Lua stack.
*/
static int moduleSceneSet(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
JS_FUNC(moduleSceneGetCurrent) {
if(SCENE.sceneCurrent[0] == '\0') return jerry_undefined();
return jerry_string_sz(SCENE.sceneCurrent);
}
if(!lua_isstring(L, 1)) {
luaL_error(L, "Scene.set requires a string argument");
return 0;
static void moduleSceneReset(void) {
if(SCENE.scriptRef != SCENE_SCRIPT_REF_NONE) {
jerry_value_free(SCENE.scriptRef);
SCENE.scriptRef = SCENE_SCRIPT_REF_NONE;
}
sceneSet(lua_tostring(L, 1));
return 0;
jerry_value_t obj = jerry_object();
jsDefineMethod(obj, "set", moduleSceneSet);
jsDefineProperty(obj, "current", moduleSceneGetCurrent, NULL);
jsSetValue("Scene", obj);
jerry_value_free(obj);
}
/**
* Resets the scene back to a clean slate, this is called before loading a new
* scene to ensure that no old state bleeds through.
*/
static void moduleSceneReset(lua_State *L) {
if(SCENE.scriptRef != LUA_NOREF) {
luaL_unref(L, LUA_REGISTRYINDEX, SCENE.scriptRef);
SCENE.scriptRef = LUA_NOREF;
}
lua_newtable(L);
// Scene.set
lua_pushcfunction(L, moduleSceneSet);
lua_setfield(L, -2, "set");
// Metatable for dynamic read-only properties (e.g. Scene.current)
lua_newtable(L);
lua_pushcfunction(L, moduleSceneIndex);
lua_setfield(L, -2, "__index");
lua_setmetatable(L, -2);
lua_setglobal(L, "Scene");
}
/**
* Invokes a method on the current scene.
*
* @param method Which method to call.
* @return Any error state that happened.
*/
static errorret_t moduleSceneCall(lua_State *L, const char_t *method) {
assertNotNull(L, "Lua state cannot be NULL");
static errorret_t moduleSceneCall(const char_t *method) {
assertStrLenMin(method, 1, "Method name cannot be empty");
assertTrue(
SCENE.scriptRef != LUA_NOREF && SCENE.scriptRef != LUA_REFNIL,
"No active scene script to call method on"
);
// Get the scene table
lua_rawgeti(L, LUA_REGISTRYINDEX, SCENE.scriptRef);
if(!lua_istable(L, -1)) {
lua_pop(L, 1);
errorThrow("Scene script ref %d is not a table", SCENE.scriptRef);
if(SCENE.scriptRef == SCENE_SCRIPT_REF_NONE) {
errorThrow("No active scene script to call method on");
}
// Get the method from the scene table
lua_getfield(L, -1, method);
if(!lua_isfunction(L, -1)) {
lua_pop(L, 2);
errorThrow("Scene method '%s' not found", method);// TODO: Needed?
jerry_value_t key = jerry_string_sz(method);
jerry_value_t fn = jerry_object_get(SCENE.scriptRef, key);
jerry_value_free(key);
if(!jerry_value_is_function(fn)) {
jerry_value_free(fn);
errorThrow("Scene method '%s' not found", method);
}
// Push the scene table as the first argument (self)
lua_pushvalue(L, -2);
jerry_value_t result = jerry_call(fn, SCENE.scriptRef, NULL, 0);
jerry_value_free(fn);
// Call the method with 1 argument (the scene table) and 0 return values
if(lua_pcall(L, 1, 0, 0) != LUA_OK) {
const char_t *err = lua_tostring(L, -1);
lua_pop(L, 2);// Pops the error message and the scene table
errorThrow("Scene:%s failed: %s", method, err);
if(jerry_value_is_exception(result)) {
char_t errMsg[512];
jsExceptionMessage(result, errMsg, sizeof(errMsg));
jerry_value_free(result);
errorThrow("Scene:%s failed: %s", method, errMsg);
}
lua_pop(L, 1);// Pops the scene table
jerry_value_free(result);
errorOk();
}
static void moduleScene(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
moduleSceneReset(L);
}
static void moduleScene(void) {
moduleSceneReset();
}
+40 -46
View File
@@ -9,74 +9,68 @@
#include "script/module/modulebase.h"
#include "console/console.h"
static int moduleScriptPrint(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
JS_FUNC(moduleScriptPrint) {
char_t buf[512];
char_t msg[4096];
size_t msgLen = 0;
int n = lua_gettop(L);
luaL_Buffer b;
luaL_buffinit(L, &b);
for(jerry_length_t i = 0; i < args_count; ++i) {
jerry_value_t strVal = jerry_value_to_string(args_p[i]);
jsToString(strVal, buf, sizeof(buf));
jerry_value_free(strVal);
for(int i = 1; i <= n; ++i) {
size_t len;
const char *s = luaL_tolstring(L, i, &len);
luaL_addlstring(&b, s, len);
lua_pop(L, 1);
if(i < n) luaL_addlstring(&b, "\t", 1);
size_t partLen = strlen(buf);
if(msgLen + partLen + 1 < sizeof(msg)) {
stringCopy(msg + msgLen, buf, sizeof(msg) - msgLen);
msgLen += partLen;
}
if(i + 1 < args_count && msgLen + 1 < sizeof(msg)) {
msg[msgLen++] = '\t';
msg[msgLen] = '\0';
}
}
luaL_pushresult(&b);
const char *msg = lua_tostring(L, -1);
consolePrint("%s", msg);
return 0;
return jerry_undefined();
}
static int moduleScriptInclude(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
if(!lua_isstring(L, 1)) {
luaL_error(L, "Expected string filename");
return 0;
JS_FUNC(moduleScriptInclude) {
if(args_count < 1 || !jerry_value_is_string(args_p[0])) {
return JS_THROW("Expected string filename");
}
scriptcontext_t* ctx = *(scriptcontext_t**)lua_getextraspace(L);
if(ctx == NULL) {
luaL_error(L, "Script context is NULL");
return 0;
}
char_t filename[1024];
jsToString(args_p[0], filename, sizeof(filename));
const char_t *filename = luaL_checkstring(L, 1);
if(filename == NULL || filename[0] == '\0') {
luaL_error(L, "Filename cannot be NULL");
return 0;
if(filename[0] == '\0') {
return JS_THROW("Filename cannot be empty");
}
char_t buffer[1024];
stringCopy(buffer, filename, 1024);
stringCopy(buffer, filename, sizeof(buffer));
size_t len = strlen(buffer);
if(len < 4 || stringCompare(&buffer[len - 4], ".lua") != 0) {
if(len + 4 >= 1024) {
luaL_error(L, "Filename too long to append .lua");
return 0;
if(len < 3 || stringCompare(&buffer[len - 3], ".js") != 0) {
if(len + 3 >= sizeof(buffer)) {
return JS_THROW("Filename too long to append .js");
}
stringCopy(&buffer[len], ".lua", 5);
stringCopy(&buffer[len], ".js", 4);
}
int32_t stackBase = lua_gettop(L);
errorret_t err = scriptContextExecFile(ctx, buffer);
jerry_value_t result = 0;
errorret_t err = scriptContextExecFile(scriptContextCurrent, buffer, &result);
if(err.code != ERROR_OK) {
luaL_error(L, "Failed to include script file: %s", buffer);
if(result != 0) jerry_value_free(result);
errorCatch(errorPrint(err));
return 0;
return JS_THROW("Failed to include script file");
}
return lua_gettop(L) - stackBase;
if(result == 0) return jerry_undefined();
return result;
}
static void moduleScript(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
lua_register(L, "print", moduleScriptPrint);
lua_register(L, "include", moduleScriptInclude);
static void moduleScript(void) {
jsRegister("print", moduleScriptPrint);
jsRegister("include", moduleScriptInclude);
}
+12 -27
View File
@@ -9,33 +9,18 @@
#include "script/module/modulebase.h"
#include "time/time.h"
static int moduleTimeIndex(lua_State *L) {
const char_t *key = lua_tostring(L, 2);
assertStrLenMin(key, 1, "Key cannot be empty.");
if(stringCompare(key, "delta") == 0) {
lua_pushnumber(L, TIME.delta);
return 1;
} else if(stringCompare(key, "time") == 0) {
lua_pushnumber(L, TIME.time);
return 1;
}
lua_pushnil(L);
return 1;
JS_FUNC(moduleTimeGetDelta) {
return jerry_number(TIME.delta);
}
static void moduleTime(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
if(luaL_newmetatable(L, "time_mt")) {
lua_pushcfunction(L, moduleTimeIndex);
lua_setfield(L, -2, "__index");
}
lua_pop(L, 1);
dusktime_t **ud = (dusktime_t**)lua_newuserdata(L, sizeof(dusktime_t*));
*ud = &TIME;
luaL_setmetatable(L, "time_mt");
lua_setglobal(L, "TIME");
JS_FUNC(moduleTimeGetTime) {
return jerry_number(TIME.time);
}
static void moduleTime(void) {
jerry_value_t obj = jerry_object();
jsDefineProperty(obj, "delta", moduleTimeGetDelta, NULL);
jsDefineProperty(obj, "time", moduleTimeGetTime, NULL);
jsSetValue("TIME", obj);
jerry_value_free(obj);
}
+1 -2
View File
@@ -8,6 +8,5 @@
#pragma once
#include "script/module/modulebase.h"
static void moduleUi(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
static void moduleUi(void) {
}
+42 -27
View File
@@ -1,6 +1,6 @@
/**
* Copyright (c) 2025 Dominic Masters
*
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
@@ -17,27 +17,26 @@
#include "script/scriptgame.h"
#endif
scriptcontext_t *scriptContextCurrent = NULL;
const jerry_object_native_info_t JS_PTR_NATIVE_INFO = {
.free_cb = NULL,
.number_of_references = 0,
.offset_of_references = 0
};
errorret_t scriptContextInit(scriptcontext_t *context) {
assertNotNull(context, "Script context cannot be NULL");
memoryZero(context, sizeof(scriptcontext_t));
// Create a new Lua state for this context.
context->luaState = luaL_newstate();
if(context->luaState == NULL) {
errorThrow("Failed to init Lua state");
}
luaL_openlibs(context->luaState);
jerry_init(JERRY_INIT_EMPTY);
scriptContextCurrent = context;
// Store context in Lua extraspace
*(scriptcontext_t**)lua_getextraspace(context->luaState) = context;
moduleRegister();
// Register built-in script modules.
moduleRegister(context->luaState);
// Fire any game script init function if defined.
#ifdef SCRIPT_GAME_INIT
SCRIPT_GAME_INIT(L);
SCRIPT_GAME_INIT();
#endif
errorOk();
@@ -47,33 +46,49 @@ errorret_t scriptContextExec(scriptcontext_t *context, const char_t *script) {
assertNotNull(context, "Script context cannot be NULL");
assertNotNull(script, "Script cannot be NULL");
if(luaL_dostring(context->luaState, script) != LUA_OK) {
const char_t *strErr = lua_tostring(context->luaState, -1);
lua_pop(context->luaState, 1);
errorThrow("Failed to execute Lua: %s", strErr);
jerry_value_t result = jerry_eval(
(const jerry_char_t *)script,
strlen(script),
JERRY_PARSE_NO_OPTS
);
if(jerry_value_is_exception(result)) {
jerry_value_t errVal = jerry_exception_value(result, false);
jerry_value_t errStr = jerry_value_to_string(errVal);
char_t buf[256];
jerry_size_t len = jerry_string_to_buffer(
errStr, JERRY_ENCODING_UTF8, (jerry_char_t *)buf, sizeof(buf) - 1
);
buf[len] = '\0';
jerry_value_free(errStr);
jerry_value_free(errVal);
jerry_value_free(result);
errorThrow("Failed to execute script: %s", buf);
}
jerry_value_free(result);
errorOk();
}
errorret_t scriptContextExecFile(scriptcontext_t *ctx, const char_t *fname) {
errorret_t scriptContextExecFile(
scriptcontext_t *ctx,
const char_t *fname,
jerry_value_t *resultOut
) {
assertNotNull(ctx, "Script context cannot be NULL");
assertNotNull(fname, "Filename cannot be NULL");
return assetScriptLoad(fname, ctx);
return assetScriptLoad(fname, ctx, resultOut);
}
void scriptContextDispose(scriptcontext_t *context) {
assertNotNull(context, "Script context cannot be NULL");
assertNotNull(context->luaState, "Lua state is not initialized");
for(uint8_t i = 0; i < SCRIPT_CONTEXT_MAX_EVENT_SUBSCRIPTIONS; i++) {
event_t *event = context->subscribedEvents[i];
if(event == NULL) continue;
eventUnsubscribeScriptContext(event, context);
}
if(context->luaState != NULL) {
lua_close(context->luaState);
context->luaState = NULL;
}
}
jerry_cleanup();
scriptContextCurrent = NULL;
}
+29 -15
View File
@@ -1,6 +1,6 @@
/**
* Copyright (c) 2025 Dominic Masters
*
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
@@ -8,48 +8,62 @@
#pragma once
#include "error/error.h"
#include "scriptvalue.h"
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <jerryscript.h>
typedef struct event_s event_t;
#define SCRIPT_CONTEXT_MAX_EVENT_SUBSCRIPTIONS 64
typedef struct scriptcontext_s {
lua_State *luaState;
event_t* subscribedEvents[SCRIPT_CONTEXT_MAX_EVENT_SUBSCRIPTIONS];
} scriptcontext_t;
/** Points to the currently active script context. Set by scriptContextInit. */
extern scriptcontext_t *scriptContextCurrent;
/**
* Singleton native-info tag for engine-owned C pointers wrapped in JS objects.
* A single global instance ensures jerry_object_get_native_ptr() matches across
* all compilation units (including event.c and module headers).
*/
extern const jerry_object_native_info_t JS_PTR_NATIVE_INFO;
/**
* Initialize a script context.
*
*
* @param context The script context to initialize.
* @return The error return value.
*/
errorret_t scriptContextInit(scriptcontext_t *context);
/**
* Execute a script within a script context.
*
* Execute a script string within a script context.
*
* @param context The script context to use.
* @param script The script to execute.
* @param script The JS source to execute.
* @return The error return value.
*/
errorret_t scriptContextExec(scriptcontext_t *context, const char_t *script);
/**
* Execute a script from a file within a script context.
*
* @param ctx The script context to use.
* @param fname The filename of the script to execute.
*
* @param ctx The script context to use.
* @param fname The filename of the script to execute.
* @param result Optional out-parameter for the script's return value.
* Caller must call jerry_value_free() on it when done.
* Pass NULL to discard the return value.
* @return The error return value.
*/
errorret_t scriptContextExecFile(scriptcontext_t *ctx, const char_t *fname);
errorret_t scriptContextExecFile(
scriptcontext_t *ctx,
const char_t *fname,
jerry_value_t *result
);
/**
* Dispose of a script context.
*
*
* @param context The script context to dispose of.
*/
void scriptContextDispose(scriptcontext_t *context);
void scriptContextDispose(scriptcontext_t *context);
@@ -1,13 +1,13 @@
/**
* Copyright (c) 2026 Dominic Masters
*
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptcontext.h"
#include "script/module/modulebase.h"
static void modulePlatformDolphin(lua_State *L) {
luaL_dostring(L, "DOLPHIN = true\n");
}
static void modulePlatformDolphin(void) {
jsEvalStr("var DOLPHIN = true;\n");
}
@@ -1,13 +1,13 @@
/**
* Copyright (c) 2026 Dominic Masters
*
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptcontext.h"
#include "script/module/modulebase.h"
static void modulePlatformLinux(lua_State *L) {
luaL_dostring(L, "LINUX = true\n");
}
static void modulePlatformLinux(void) {
jsEvalStr("var LINUX = true;\n");
}
@@ -1,13 +1,13 @@
/**
* Copyright (c) 2026 Dominic Masters
*
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptcontext.h"
#include "script/module/modulebase.h"
void modulePlatformPSP(lua_State *L) {
luaL_dostring(L, "PSP = true\n");
}
static void modulePlatformPSP(void) {
jsEvalStr("var PSP = true;\n");
}
+92 -133
View File
@@ -6,236 +6,195 @@
*/
#pragma once
#include "script/scriptcontext.h"
#include "script/module/modulebase.h"
#include "item/inventory.h"
#include "item/backpack.h"
#include "assert/assert.h"
static int moduleInventoryItemExists(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
if(!lua_islightuserdata(L, 1)) {
luaL_error(L, "inventoryItemExists: Expected inventory pointer as first argument");
return 0;
JS_FUNC(moduleInventoryItemExists) {
if(!jerry_value_is_object(args_p[0])) {
return JS_THROW("inventoryItemExists: Expected inventory pointer as first argument");
}
if(!lua_isnumber(L, 2)) {
luaL_error(L, "inventoryItemExists: Expected item ID as second argument");
return 0;
if(!jerry_value_is_number(args_p[1])) {
return JS_THROW("inventoryItemExists: Expected item ID as second argument");
}
inventory_t *inventory = (inventory_t *)lua_touserdata(L, 1);
itemid_t item = (itemid_t)lua_tonumber(L, 2);
inventory_t *inventory = (inventory_t *)jsUnwrapPointer(args_p[0]);
itemid_t item = (itemid_t)jerry_value_as_number(args_p[1]);
assertNotNull(inventory, "Inventory pointer cannot be NULL.");
if(item == ITEM_ID_NULL) {
luaL_error(L, "inventoryItemExists: Item ID cannot be ITEM_ID_NULL");
return 0;
return JS_THROW("inventoryItemExists: Item ID cannot be ITEM_ID_NULL");
}
bool_t hasItem = inventoryItemExists(inventory, item);
lua_pushboolean(L, hasItem);
return 1;
return jerry_boolean(hasItem);
}
static int moduleInventorySet(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
if(!lua_islightuserdata(L, 1)) {
luaL_error(L, "inventorySet: Expected inventory pointer as first argument");
return 0;
JS_FUNC(moduleInventorySet) {
if(!jerry_value_is_object(args_p[0])) {
return JS_THROW("inventorySet: Expected inventory pointer as first argument");
}
if(!lua_isnumber(L, 2)) {
luaL_error(L, "inventorySet: Expected item ID as second argument");
return 0;
if(!jerry_value_is_number(args_p[1])) {
return JS_THROW("inventorySet: Expected item ID as second argument");
}
if(!lua_isnumber(L, 3)) {
luaL_error(L, "inventorySet: Expected quantity as third argument");
return 0;
if(!jerry_value_is_number(args_p[2])) {
return JS_THROW("inventorySet: Expected quantity as third argument");
}
inventory_t *inventory = (inventory_t *)lua_touserdata(L, 1);
itemid_t item = (itemid_t)lua_tonumber(L, 2);
uint8_t quantity = (uint8_t)lua_tonumber(L, 3);
inventory_t *inventory = (inventory_t *)jsUnwrapPointer(args_p[0]);
itemid_t item = (itemid_t)jerry_value_as_number(args_p[1]);
uint8_t quantity = (uint8_t)jerry_value_as_number(args_p[2]);
assertNotNull(inventory, "Inventory pointer cannot be NULL.");
inventorySet(inventory, item, quantity);
return 0;
return jerry_undefined();
}
static int moduleInventoryAdd(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
if(!lua_islightuserdata(L, 1)) {
luaL_error(L, "inventoryAdd: Expected inventory pointer as first argument");
return 0;
JS_FUNC(moduleInventoryAdd) {
if(!jerry_value_is_object(args_p[0])) {
return JS_THROW("inventoryAdd: Expected inventory pointer as first argument");
}
if(!lua_isnumber(L, 2)) {
luaL_error(L, "inventoryAdd: Expected item ID as second argument");
return 0;
if(!jerry_value_is_number(args_p[1])) {
return JS_THROW("inventoryAdd: Expected item ID as second argument");
}
if(!lua_isnumber(L, 3)) {
luaL_error(L, "inventoryAdd: Expected quantity as third argument");
return 0;
if(!jerry_value_is_number(args_p[2])) {
return JS_THROW("inventoryAdd: Expected quantity as third argument");
}
inventory_t *inventory = (inventory_t *)lua_touserdata(L, 1);
itemid_t item = (itemid_t)lua_tonumber(L, 2);
uint8_t quantity = (uint8_t)lua_tonumber(L, 3);
inventory_t *inventory = (inventory_t *)jsUnwrapPointer(args_p[0]);
itemid_t item = (itemid_t)jerry_value_as_number(args_p[1]);
uint8_t quantity = (uint8_t)jerry_value_as_number(args_p[2]);
assertNotNull(inventory, "Inventory pointer cannot be NULL.");
inventoryAdd(inventory, item, quantity);
return 0;
return jerry_undefined();
}
static int moduleInventoryRemove(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
if(!lua_islightuserdata(L, 1)) {
luaL_error(L, "inventoryRemove: Expected inventory pointer as first argument");
return 0;
JS_FUNC(moduleInventoryRemove) {
if(!jerry_value_is_object(args_p[0])) {
return JS_THROW("inventoryRemove: Expected inventory pointer as first argument");
}
if(!lua_isnumber(L, 2)) {
luaL_error(L, "inventoryRemove: Expected item ID as second argument");
return 0;
if(!jerry_value_is_number(args_p[1])) {
return JS_THROW("inventoryRemove: Expected item ID as second argument");
}
inventory_t *inventory = (inventory_t *)lua_touserdata(L, 1);
itemid_t item = (itemid_t)lua_tonumber(L, 2);
inventory_t *inventory = (inventory_t *)jsUnwrapPointer(args_p[0]);
itemid_t item = (itemid_t)jerry_value_as_number(args_p[1]);
assertNotNull(inventory, "Inventory pointer cannot be NULL.");
if(lua_gettop(L) >= 3) {
if(!lua_isnumber(L, 3)) {
luaL_error(L, "inventoryRemove: Expected quantity as third argument");
return 0;
if(args_count >= 3) {
if(!jerry_value_is_number(args_p[2])) {
return JS_THROW("inventoryRemove: Expected quantity as third argument");
}
uint8_t amount = (uint8_t)lua_tonumber(L, 3);
uint8_t amount = (uint8_t)jerry_value_as_number(args_p[2]);
uint8_t currentQuantity = inventoryGetCount(inventory, item);
if(amount >= currentQuantity) {
inventoryRemove(inventory, item);
return 0;
return jerry_undefined();
}
inventorySet(inventory, item, currentQuantity - amount);
return 0;
return jerry_undefined();
}
inventoryRemove(inventory, item);
return 0;
return jerry_undefined();
}
static int moduleInventoryGetCount(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
if(!lua_islightuserdata(L, 1)) {
luaL_error(L, "inventoryGetCount: Expected inventory pointer as first argument");
return 0;
JS_FUNC(moduleInventoryGetCount) {
if(!jerry_value_is_object(args_p[0])) {
return JS_THROW("inventoryGetCount: Expected inventory pointer as first argument");
}
if(!lua_isnumber(L, 2)) {
luaL_error(L, "inventoryGetCount: Expected item ID as second argument");
return 0;
if(!jerry_value_is_number(args_p[1])) {
return JS_THROW("inventoryGetCount: Expected item ID as second argument");
}
inventory_t *inventory = (inventory_t *)lua_touserdata(L, 1);
itemid_t item = (itemid_t)lua_tonumber(L, 2);
inventory_t *inventory = (inventory_t *)jsUnwrapPointer(args_p[0]);
itemid_t item = (itemid_t)jerry_value_as_number(args_p[1]);
assertNotNull(inventory, "Inventory pointer cannot be NULL.");
uint8_t count = inventoryGetCount(inventory, item);
lua_pushnumber(L, count);
return 1;
return jerry_number(count);
}
static int moduleInventoryIsFull(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
if(!lua_islightuserdata(L, 1)) {
luaL_error(L, "inventoryIsFull: Expected inventory pointer as first argument");
return 0;
JS_FUNC(moduleInventoryIsFull) {
if(!jerry_value_is_object(args_p[0])) {
return JS_THROW("inventoryIsFull: Expected inventory pointer as first argument");
}
inventory_t *inventory = (inventory_t *)lua_touserdata(L, 1);
inventory_t *inventory = (inventory_t *)jsUnwrapPointer(args_p[0]);
assertNotNull(inventory, "Inventory pointer cannot be NULL.");
bool_t isFull = inventoryIsFull(inventory);
lua_pushboolean(L, isFull);
return 1;
return jerry_boolean(isFull);
}
static int moduleInventoryItemFull(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
if(!lua_islightuserdata(L, 1)) {
luaL_error(L, "inventoryItemFull: Expected inventory pointer as first argument");
return 0;
JS_FUNC(moduleInventoryItemFull) {
if(!jerry_value_is_object(args_p[0])) {
return JS_THROW("inventoryItemFull: Expected inventory pointer as first argument");
}
if(!lua_isnumber(L, 2)) {
luaL_error(L, "inventoryItemFull: Expected item ID as second argument");
return 0;
if(!jerry_value_is_number(args_p[1])) {
return JS_THROW("inventoryItemFull: Expected item ID as second argument");
}
inventory_t *inventory = (inventory_t *)lua_touserdata(L, 1);
itemid_t item = (itemid_t)lua_tonumber(L, 2);
inventory_t *inventory = (inventory_t *)jsUnwrapPointer(args_p[0]);
itemid_t item = (itemid_t)jerry_value_as_number(args_p[1]);
assertNotNull(inventory, "Inventory pointer cannot be NULL.");
bool_t isFull = inventoryItemFull(inventory, item);
lua_pushboolean(L, isFull);
return 1;
return jerry_boolean(isFull);
}
static int moduleInventorySort(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
if(!lua_islightuserdata(L, 1)) {
luaL_error(L, "inventorySort: Expected inventory pointer as first argument");
return 0;
JS_FUNC(moduleInventorySort) {
if(!jerry_value_is_object(args_p[0])) {
return JS_THROW("inventorySort: Expected inventory pointer as first argument");
}
if(!lua_isnumber(L, 2)) {
luaL_error(L, "inventorySort: Expected sort type as second argument");
return 0;
if(!jerry_value_is_number(args_p[1])) {
return JS_THROW("inventorySort: Expected sort type as second argument");
}
bool_t reverse = false;
if(lua_gettop(L) >= 3) {
if(!lua_isboolean(L, 3)) {
luaL_error(L, "inventorySort: Expected reverse flag as third argument");
return 0;
if(args_count >= 3) {
if(!jerry_value_is_boolean(args_p[2])) {
return JS_THROW("inventorySort: Expected reverse flag as third argument");
}
reverse = (bool_t)lua_toboolean(L, 3);
reverse = (bool_t)jerry_value_is_true(args_p[2]);
}
inventory_t *inventory = (inventory_t *)lua_touserdata(L, 1);
inventorysort_t sortBy = (inventorysort_t)lua_tonumber(L, 2);
inventory_t *inventory = (inventory_t *)jsUnwrapPointer(args_p[0]);
inventorysort_t sortBy = (inventorysort_t)jerry_value_as_number(args_p[1]);
assertNotNull(inventory, "Inventory pointer cannot be NULL.");
inventorySort(inventory, sortBy, reverse);
return 0;
return jerry_undefined();
}
static void moduleItem(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
static void moduleItem(void) {
jsEvalStr(ITEM_SCRIPT);
luaL_dostring(L, ITEM_SCRIPT);
jerry_value_t backpack = jsWrapPointer(&BACKPACK);
jsSetValue("BACKPACK", backpack);
jerry_value_free(backpack);
lua_pushlightuserdata(L, &BACKPACK);
lua_setglobal(L, "BACKPACK");
lua_register(L, "inventoryItemExists", moduleInventoryItemExists);
lua_register(L, "inventoryAdd", moduleInventoryAdd);
lua_register(L, "inventorySet", moduleInventorySet);
lua_register(L, "inventoryRemove", moduleInventoryRemove);
lua_register(L, "inventoryGetCount", moduleInventoryGetCount);
lua_register(L, "inventoryIsFull", moduleInventoryIsFull);
lua_register(L, "inventoryItemFull", moduleInventoryItemFull);
lua_register(L, "inventorySort", moduleInventorySort);
jsRegister("inventoryItemExists", moduleInventoryItemExists);
jsRegister("inventoryAdd", moduleInventoryAdd);
jsRegister("inventorySet", moduleInventorySet);
jsRegister("inventoryRemove", moduleInventoryRemove);
jsRegister("inventoryGetCount", moduleInventoryGetCount);
jsRegister("inventoryIsFull", moduleInventoryIsFull);
jsRegister("inventoryItemFull", moduleInventoryItemFull);
jsRegister("inventorySort", moduleInventorySort);
}
@@ -6,96 +6,58 @@
*/
#pragma once
#include "script/scriptcontext.h"
#include "script/module/modulebase.h"
#include "assert/assert.h"
#include "story/storyflag.h"
static int moduleStoryFlagGet(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
if(!lua_isnumber(L, 1)) {
luaL_error(L, "Expected flag ID.");
return 0;
}
storyflag_t flag = (storyflag_t)lua_tonumber(L, 1);
JS_FUNC(moduleStoryFlagGet) {
JS_REQUIRE_ARGS(1);
JS_REQUIRE_NUMBER(0);
storyflag_t flag = (storyflag_t)jerry_value_as_number(args_p[0]);
if(flag <= STORY_FLAG_NULL || flag >= STORY_FLAG_COUNT) {
luaL_error(L, "Invalid flag ID %d", flag);
return 0;
return JS_THROW("storyFlagGet: invalid flag ID");
}
storyflagvalue_t value = storyFlagGet(flag);
lua_pushnumber(L, value);
return 1;
return jerry_number((double)storyFlagGet(flag));
}
static int moduleStoryFlagSet(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
if(!lua_isnumber(L, 1)) {
luaL_error(L, "Expected flag ID.");
return 0;
}
if(!lua_isnumber(L, 2)) {
luaL_error(L, "Expected flag value.");
return 0;
}
storyflag_t flag = (storyflag_t)lua_tonumber(L, 1);
JS_FUNC(moduleStoryFlagSet) {
JS_REQUIRE_ARGS(2);
JS_REQUIRE_NUMBER(0);
JS_REQUIRE_NUMBER(1);
storyflag_t flag = (storyflag_t)jerry_value_as_number(args_p[0]);
if(flag <= STORY_FLAG_NULL || flag >= STORY_FLAG_COUNT) {
luaL_error(L, "Invalid flag ID %d", flag);
return 0;
return JS_THROW("storyFlagSet: invalid flag ID");
}
storyflagvalue_t value = (storyflagvalue_t)lua_tonumber(L, 2);
storyflagvalue_t value = (storyflagvalue_t)jerry_value_as_number(args_p[1]);
storyFlagSet(flag, value);
return 0;
return jerry_undefined();
}
static int moduleStoryFlagIncrement(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
if(!lua_isnumber(L, 1)) {
luaL_error(L, "Expected flag ID.");
return 0;
}
storyflag_t flag = (storyflag_t)lua_tonumber(L, 1);
JS_FUNC(moduleStoryFlagIncrement) {
JS_REQUIRE_ARGS(1);
JS_REQUIRE_NUMBER(0);
storyflag_t flag = (storyflag_t)jerry_value_as_number(args_p[0]);
if(flag <= STORY_FLAG_NULL || flag >= STORY_FLAG_COUNT) {
luaL_error(L, "Invalid flag ID %d", flag);
return 0;
return JS_THROW("storyFlagIncrement: invalid flag ID");
}
storyflagvalue_t value = storyFlagGet(flag);
storyFlagSet(flag, value + 1);
return 0;
storyFlagSet(flag, storyFlagGet(flag) + 1);
return jerry_undefined();
}
static int moduleStoryFlagDecrement(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
if(!lua_isnumber(L, 1)) {
luaL_error(L, "Expected flag ID.");
return 0;
}
storyflag_t flag = (storyflag_t)lua_tonumber(L, 1);
JS_FUNC(moduleStoryFlagDecrement) {
JS_REQUIRE_ARGS(1);
JS_REQUIRE_NUMBER(0);
storyflag_t flag = (storyflag_t)jerry_value_as_number(args_p[0]);
if(flag <= STORY_FLAG_NULL || flag >= STORY_FLAG_COUNT) {
luaL_error(L, "Invalid flag ID %d", flag);
return 0;
return JS_THROW("storyFlagDecrement: invalid flag ID");
}
storyflagvalue_t value = storyFlagGet(flag);
storyFlagSet(flag, value - 1);
return 0;
storyFlagSet(flag, storyFlagGet(flag) - 1);
return jerry_undefined();
}
static void moduleStoryFlag(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
lua_register(L, "storyFlagGet", moduleStoryFlagGet);
lua_register(L, "storyFlagSet", moduleStoryFlagSet);
lua_register(L, "storyFlagIncrement", moduleStoryFlagIncrement);
lua_register(L, "storyFlagDecrement", moduleStoryFlagDecrement);
static void moduleStoryFlag(void) {
jsRegister("storyFlagGet", moduleStoryFlagGet);
jsRegister("storyFlagSet", moduleStoryFlagSet);
jsRegister("storyFlagIncrement", moduleStoryFlagIncrement);
jsRegister("storyFlagDecrement", moduleStoryFlagDecrement);
}