Refactored and simplified lua stuff a lot.
Some checks failed
Build Dusk / run-tests (push) Failing after 1m23s
Build Dusk / build-linux (push) Failing after 1m47s
Build Dusk / build-psp (push) Failing after 1m41s

This commit is contained in:
2026-02-01 21:28:21 -06:00
parent 78e1ae885a
commit 053778a502
53 changed files with 638 additions and 804 deletions

View File

@@ -45,5 +45,5 @@ int moduleMapLoad(lua_State *L) {
void moduleMapSystem(scriptcontext_t *context) {
assertNotNull(context, "Script context cannot be NULL");
scriptContextRegFunc(context, "mapLoad", moduleMapLoad);
lua_register(context->luaState, "mapLoad", moduleMapLoad);
}

View File

@@ -128,8 +128,8 @@ int scriptFuncEntitySetZ(lua_State *L) {
void scriptFuncEntity(scriptcontext_t *context) {
assertNotNull(context, "Script context cannot be NULL");
scriptContextRegFunc(context, "entityAdd", scriptFuncEntityAdd);
scriptContextRegFunc(context, "entitySetX", scriptFuncEntitySetX);
scriptContextRegFunc(context, "entitySetY", scriptFuncEntitySetY);
scriptContextRegFunc(context, "entitySetZ", scriptFuncEntitySetZ);
lua_register(context->luaState, "entityAdd", scriptFuncEntityAdd);
lua_register(context->luaState, "entitySetX", scriptFuncEntitySetX);
lua_register(context->luaState, "entitySetY", scriptFuncEntitySetY);
lua_register(context->luaState, "entitySetZ", scriptFuncEntitySetZ);
}

View File

@@ -1,10 +1,17 @@
# Copyright (c) 2026 Dominic Masters
#
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
scriptstruct.c
)
ui.c
uitext.c
uidebug.c
uiframe.c
uitextbox.c
)
# Subdirs
add_subdirectory(element)

67
archive/ui/ui.c Normal file
View File

@@ -0,0 +1,67 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "ui.h"
#include "assert/assert.h"
#include "ui/uidebug.h"
#include "util/memory.h"
#include "display/tileset/tileset_minogram.h"
#include "display/screen.h"
// #include "ui/uitextbox.h"
ui_t UI;
errorret_t uiInit(void) {
memoryZero(&UI, sizeof(ui_t));
cameraInitOrthographic(&UI.camera);
// Initialize UI components here
uiSetFont(&TILESET_MINOGRAM);
errorOk();
}
void uiUpdate(void) {
// Update UI state here
UI.camera.orthographic.left = 0;
UI.camera.orthographic.right = SCREEN.width;
UI.camera.orthographic.top = 0;
UI.camera.orthographic.bottom = SCREEN.height;
// uiTextboxUpdate();
}
void uiRender(void) {
cameraPushMatrix(&UI.camera);
// Render UI elements here
if(UI.fontTexture.width > 0) {
uiDebugRender(UI.fontTileset, &UI.fontTexture);
}
cameraPopMatrix();
}
errorret_t uiSetFont(const tileset_t *fontTileset) {
if(UI.fontTexture.width > 0) {
textureDispose(&UI.fontTexture);
UI.fontTexture.width = -1;
}
assertNotNull(fontTileset, "Font tileset cannot be NULL.");
UI.fontTileset = fontTileset;
errorChain(assetLoad(UI.fontTileset->image, &UI.fontTexture));
errorOk();
}
void uiDispose(void) {
if(UI.fontTexture.width > 0) {
textureDispose(&UI.fontTexture);
UI.fontTexture.width = -1;
}
}

51
archive/ui/ui.h Normal file
View File

@@ -0,0 +1,51 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "asset/asset.h"
#include "display/texture.h"
#include "display/tileset/tileset.h"
#include "display/camera/camera.h"
typedef struct {
camera_t camera;
texture_t fontTexture;
const tileset_t *fontTileset;
} ui_t;
extern ui_t UI;
/**
* Initializes the UI system, loading necessary resources.
*
* @return An errorret_t indicating success or failure.
*/
errorret_t uiInit(void);
/**
* Updates the UI state, handling user interactions and animations.
*/
void uiUpdate(void);
/**
* Renders the UI elements to the screen.
*/
void uiRender(void);
/**
* Sets the font tileset for UI text rendering.
*
* @param fontTileset Pointer to the tileset to use for UI fonts.
*
* @return An errorret_t indicating success or failure.
*/
errorret_t uiSetFont(const tileset_t *fontTileset);
/**
* Cleans up and frees all UI resources.
*/
void uiDispose(void);

View File

@@ -1,21 +1,17 @@
module('spritebatch')
module('time')
-- module('time')
module('camera')
module('glm')
-- module('glm')
module('color')
camera = cameraCreate(CAMERA_PROJECTION_TYPE_PERSPECTIVE)
color = colorBlue()
function sceneDispose()
-- print('Disposing initial scene')
end
function sceneUpdate()
color.r = 255 * (math.sin(TIME.time) + 1) * 0.5
color.g = 255 * (math.sin(TIME.time + 2) + 1) * 0.5
color.b = 255 * (math.sin(TIME.time + 4) + 1) * 0.5
end
function sceneRender()
@@ -25,7 +21,7 @@ function sceneRender()
nil,
0, 0,
1, 2,
color
colorBlue()
)
spriteBatchFlush()

View File

@@ -3,4 +3,5 @@
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
add_asset(TILESET minogram.png type=PALETTIZED tileWidth=6 tileHeight=10 columns=16 rows=6)# Fixes PSP rendering
add_asset(TILESET minogram.png type=PALETTIZED tileWidth=6 tileHeight=10 columns=16 rows=6)# Fixes PSP rendering
add_asset(SCRIPT test.lua)

View File

@@ -1,6 +1,9 @@
module('ui')
module('color')
module('spritebatch')
function draw(x, y)
uiDrawRect(COLOR_WHITE, x, y, 32, 32)
function render(x, y, w, h)
spriteBatchPush(
nil,
x, y,
w, h
)
end

View File

@@ -65,5 +65,4 @@ add_subdirectory(script)
add_subdirectory(thread)
add_subdirectory(time)
add_subdirectory(ui)
add_subdirectory(ui2)
add_subdirectory(util)

View File

@@ -23,8 +23,6 @@ typedef enum {
} cameraviewtype_t;
typedef struct {
cameraprojectiontype_t projType;
cameraviewtype_t viewType;
union {
mat4 view;
@@ -63,6 +61,9 @@ typedef struct {
float_t nearClip;
float_t farClip;
cameraprojectiontype_t projType;
cameraviewtype_t viewType;
} camera_t;
/**

View File

@@ -8,7 +8,6 @@
#include "event.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "script/struct/scriptstruct.h"
void eventInit(
event_t *event,
@@ -228,10 +227,11 @@ void eventInvoke(
lua_rawgeti(L, LUA_REGISTRYINDEX, listener->user.script.luaFunctionRef);
if(eventParams != NULL && metatableName != NULL) {
scriptStructPushMetatable(
listener->user.script.context,
metatableName,
(void *)eventParams
lua_getmetatable(L, -1);
luaL_getmetatable(L, metatableName);
assertTrue(
lua_rawequal(L, -1, -2),
"Event parameter metatable does not match expected type"
);
}

View File

@@ -12,5 +12,4 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
)
# Subdirectories
add_subdirectory(module)
add_subdirectory(struct)
add_subdirectory(module)

View File

@@ -9,19 +9,20 @@
#include "assert/assert.h"
#include "display/camera/camera.h"
#include "util/memory.h"
#include "script/struct/scriptstruct.h"
#include "util/string.h"
void moduleCamera(scriptcontext_t *context) {
assertNotNull(context, "Context cannot be NULL.");
// Structure
scriptStructRegister(
context,
"camera_mt",
moduleCameraGetter,
moduleCameraSetter
);
// Create metatable for camera structure.
if(luaL_newmetatable(context->luaState, "camera_mt")) {
// Metatable methods
lua_pushcfunction(context->luaState, moduleCameraIndex);
lua_setfield(context->luaState, -2, "__index");
lua_pushcfunction(context->luaState, moduleCameraNewIndex);
lua_setfield(context->luaState, -2, "__newindex");
lua_pop(context->luaState, 1);
}
// Definitions
#define MODULE_CAMERA_SCRIPT_LEN 64
@@ -67,9 +68,9 @@ void moduleCamera(scriptcontext_t *context) {
);
// Methods
scriptContextRegFunc(context, "cameraCreate", moduleCameraCreate);
scriptContextRegFunc(context, "cameraPushMatrix", moduleCameraPushMatrix);
scriptContextRegFunc(context, "cameraPopMatrix", moduleCameraPopMatrix);
lua_register(context->luaState, "cameraCreate", moduleCameraCreate);
lua_register(context->luaState, "cameraPushMatrix", moduleCameraPushMatrix);
lua_register(context->luaState, "cameraPopMatrix", moduleCameraPopMatrix);
}
int moduleCameraCreate(lua_State *L) {
@@ -84,12 +85,13 @@ int moduleCameraCreate(lua_State *L) {
projType = (cameraprojectiontype_t)luaL_checkinteger(L, 1);
}
// Create camera.
// Create camera that Lua will own
camera_t *cam = (camera_t *)lua_newuserdata(L, sizeof(camera_t));
memoryZero(cam, sizeof(camera_t));
// Push metatable.
scriptStructPushMetatable(context, "camera_mt", cam);
// Set metatable
luaL_getmetatable(L, "camera_mt");
lua_setmetatable(L, -2);
// Init camera
switch(projType) {
@@ -114,9 +116,11 @@ int moduleCameraCreate(lua_State *L) {
int moduleCameraPushMatrix(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL.");
assertTrue(lua_gettop(L) >= 1, "cameraPushMatrix requires 1 arg.");
assertTrue(lua_isuserdata(L, 1), "cameraPushMatrix arg must be userdata.");
// Camera should be provided (pointer to camera_t).
camera_t *cam = *(camera_t **)lua_touserdata(L, 1);
camera_t *cam = (camera_t *)luaL_checkudata(L, 1, "camera_mt");
assertNotNull(cam, "Camera pointer cannot be NULL.");
cameraPushMatrix(cam);
@@ -129,64 +133,51 @@ int moduleCameraPopMatrix(lua_State *L) {
return 0;
}
void moduleCameraGetter(
const scriptcontext_t *context,
const char_t *key,
const void *structPtr,
scriptvalue_t *outValue
) {
assertNotNull(context, "Script context cannot be NULL.");
assertNotNull(key, "Key cannot be NULL.");
assertNotNull(structPtr, "Structure pointer cannot be NULL.");
assertNotNull(outValue, "Output value cannot be NULL.");
int moduleCameraIndex(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
camera_t *cam = (camera_t *)structPtr;
const char_t *key = luaL_checkstring(l, 2);
assertStrLenMin(key, 1, "Key cannot be empty.");
camera_t *cam = (camera_t *)luaL_checkudata(l, 1, "camera_mt");
assertNotNull(cam, "Camera pointer cannot be NULL.");
if(stringCompare(key, "near") == 0) {
outValue->type = SCRIPT_VALUE_TYPE_FLOAT;
outValue->value.floatValue = cam->nearClip;
return;
lua_pushnumber(l, cam->nearClip);
return 1;
} else if(stringCompare(key, "far") == 0) {
outValue->type = SCRIPT_VALUE_TYPE_FLOAT;
outValue->value.floatValue = cam->farClip;
return;
lua_pushnumber(l, cam->farClip);
return 1;
} else if(stringCompare(key, "nearClip") == 0) {
outValue->type = SCRIPT_VALUE_TYPE_FLOAT;
outValue->value.floatValue = cam->nearClip;
return;
lua_pushnumber(l, cam->nearClip);
return 1;
} else if(stringCompare(key, "farClip") == 0) {
outValue->type = SCRIPT_VALUE_TYPE_FLOAT;
outValue->value.floatValue = cam->farClip;
return;
lua_pushnumber(l, cam->farClip);
return 1;
}
// Perspective relative values
if(cam->projType == CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) {
if(stringCompare(key, "left") == 0) {
outValue->type = SCRIPT_VALUE_TYPE_FLOAT;
outValue->value.floatValue = cam->orthographic.left;
return;
lua_pushnumber(l, cam->orthographic.left);
return 1;
} else if(stringCompare(key, "right") == 0) {
outValue->type = SCRIPT_VALUE_TYPE_FLOAT;
outValue->value.floatValue = cam->orthographic.right;
return;
lua_pushnumber(l, cam->orthographic.right);
return 1;
} else if(stringCompare(key, "top") == 0) {
outValue->type = SCRIPT_VALUE_TYPE_FLOAT;
outValue->value.floatValue = cam->orthographic.top;
return;
lua_pushnumber(l, cam->orthographic.top);
return 1;
} else if(stringCompare(key, "bottom") == 0) {
outValue->type = SCRIPT_VALUE_TYPE_FLOAT;
outValue->value.floatValue = cam->orthographic.bottom;
return;
lua_pushnumber(l, cam->orthographic.bottom);
return 1;
}
} else if(
cam->projType == CAMERA_PROJECTION_TYPE_PERSPECTIVE ||
cam->projType == CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED
) {
if(stringCompare(key, "fov") == 0) {
outValue->type = SCRIPT_VALUE_TYPE_FLOAT;
outValue->value.floatValue = cam->perspective.fov;
return;
lua_pushnumber(l, cam->perspective.fov);
return 1;
}
}
@@ -194,107 +185,92 @@ void moduleCameraGetter(
if(cam->viewType == CAMERA_VIEW_TYPE_MATRIX) {
} else if(cam->viewType == CAMERA_VIEW_TYPE_LOOKAT) {
if(stringCompare(key, "position") == 0) {
scriptStructPushMetatable(
context,
"vec3_mt",
(void*)&cam->lookat.position
);
outValue->type = SCRIPT_VALUE_TYPE_USERDATA;
return;
}
// TODO: Push vec3
} else if(cam->viewType == CAMERA_VIEW_TYPE_LOOKAT_PIXEL_PERFECT) {
} else if(cam->viewType == CAMERA_VIEW_TYPE_2D) {
}
lua_pushnil(l);
return 1;
}
void moduleCameraSetter(
const scriptcontext_t *context,
const char_t *key,
void *structPtr,
const scriptvalue_t *inValue
) {
assertNotNull(context, "Script context cannot be NULL.");
assertNotNull(key, "Key cannot be NULL.");
assertNotNull(structPtr, "Structure pointer cannot be NULL.");
assertNotNull(inValue, "Input value cannot be NULL.");
int moduleCameraNewIndex(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
const char_t *key = luaL_checkstring(l, 2);
assertStrLenMin(key, 1, "Key cannot be empty.");
camera_t *cam = (camera_t *)structPtr;
camera_t *cam = (camera_t *)luaL_checkudata(l, 1, "camera_mt");
assertNotNull(cam, "Camera pointer cannot be NULL.");
if(stringCompare(key, "near") == 0) {
if(inValue->type == SCRIPT_VALUE_TYPE_FLOAT) {
cam->nearClip = inValue->value.floatValue;
} else if(inValue->type == SCRIPT_VALUE_TYPE_INT) {
cam->nearClip = (float_t)inValue->value.intValue;
if(stringCompare(key, "near") == 0 || stringCompare(key, "nearClip") == 0) {
if(!lua_isnumber(l, 3)) {
luaL_error(l, "Camera near clip must be a number.");
}
return;
} else if(stringCompare(key, "far") == 0) {
if(inValue->type == SCRIPT_VALUE_TYPE_FLOAT) {
cam->farClip = inValue->value.floatValue;
} else if(inValue->type == SCRIPT_VALUE_TYPE_INT) {
cam->farClip = (float_t)inValue->value.intValue;
cam->nearClip = (float_t)lua_tonumber(l, 3);
return 0;
}
if(stringCompare(key, "far") == 0 || stringCompare(key, "farClip") == 0) {
if(!lua_isnumber(l, 3)) {
luaL_error(l, "Camera far clip must be a number.");
}
return;
} else if(stringCompare(key, "nearClip") == 0) {
if(inValue->type == SCRIPT_VALUE_TYPE_FLOAT) {
cam->nearClip = inValue->value.floatValue;
} else if(inValue->type == SCRIPT_VALUE_TYPE_INT) {
cam->nearClip = (float_t)inValue->value.intValue;
}
return;
} else if(stringCompare(key, "farClip") == 0) {
if(inValue->type == SCRIPT_VALUE_TYPE_FLOAT) {
cam->farClip = inValue->value.floatValue;
} else if(inValue->type == SCRIPT_VALUE_TYPE_INT) {
cam->farClip = (float_t)inValue->value.intValue;
}
return;
cam->farClip = (float_t)lua_tonumber(l, 3);
return 0;
}
// Perspective relative values
if(cam->projType == CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) {
if(stringCompare(key, "left") == 0) {
if(inValue->type == SCRIPT_VALUE_TYPE_FLOAT) {
cam->orthographic.left = inValue->value.floatValue;
} else if(inValue->type == SCRIPT_VALUE_TYPE_INT) {
cam->orthographic.left = (float_t)inValue->value.intValue;
if(!lua_isnumber(l, 3)) {
luaL_error(l, "Camera orthographic left must be a number.");
}
return;
} else if(stringCompare(key, "right") == 0) {
if(inValue->type == SCRIPT_VALUE_TYPE_FLOAT) {
cam->orthographic.right = inValue->value.floatValue;
} else if(inValue->type == SCRIPT_VALUE_TYPE_INT) {
cam->orthographic.right = (float_t)inValue->value.intValue;
cam->orthographic.left = (float_t)lua_tonumber(l, 3);
return 0;
}
if(stringCompare(key, "right") == 0) {
if(!lua_isnumber(l, 3)) {
luaL_error(l, "Camera orthographic right must be a number.");
}
return;
} else if(stringCompare(key, "top") == 0) {
if(inValue->type == SCRIPT_VALUE_TYPE_FLOAT) {
cam->orthographic.top = inValue->value.floatValue;
} else if(inValue->type == SCRIPT_VALUE_TYPE_INT) {
cam->orthographic.top = (float_t)inValue->value.intValue;
cam->orthographic.right = (float_t)lua_tonumber(l, 3);
return 0;
}
if(stringCompare(key, "top") == 0) {
if(!lua_isnumber(l, 3)) {
luaL_error(l, "Camera orthographic top must be a number.");
}
return;
} else if(stringCompare(key, "bottom") == 0) {
if(inValue->type == SCRIPT_VALUE_TYPE_FLOAT) {
cam->orthographic.bottom = inValue->value.floatValue;
} else if(inValue->type == SCRIPT_VALUE_TYPE_INT) {
cam->orthographic.bottom = (float_t)inValue->value.intValue;
cam->orthographic.top = (float_t)lua_tonumber(l, 3);
return 0;
}
if(stringCompare(key, "bottom") == 0) {
if(!lua_isnumber(l, 3)) {
luaL_error(l, "Camera orthographic bottom must be a number.");
}
return;
cam->orthographic.bottom = (float_t)lua_tonumber(l, 3);
return 0;
}
} else if(
cam->projType == CAMERA_PROJECTION_TYPE_PERSPECTIVE ||
cam->projType == CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED
) {
if(stringCompare(key, "fov") == 0) {
if(inValue->type == SCRIPT_VALUE_TYPE_FLOAT) {
cam->perspective.fov = inValue->value.floatValue;
} else if(inValue->type == SCRIPT_VALUE_TYPE_INT) {
cam->perspective.fov = (float_t)inValue->value.intValue;
if(!lua_isnumber(l, 3)) {
luaL_error(l, "Camera perspective FOV must be a number.");
}
return;
cam->perspective.fov = (float_t)lua_tonumber(l, 3);
return 0;
}
}
luaL_error(
l,
"Attempted to set unknown or read-only camera field '%s'.",
key
);
return 0;
}

View File

@@ -42,29 +42,13 @@ int moduleCameraPopMatrix(lua_State *L);
/**
* Getter for camera structure fields.
*
* @param context The script context.
* @param key The field key.
* @param structPtr Pointer to the camera structure.
* @param outValue Output script value.
* @param l The Lua state.
*/
void moduleCameraGetter(
const scriptcontext_t *context,
const char_t *key,
const void *structPtr,
scriptvalue_t *outValue
);
int moduleCameraIndex(lua_State *l);
/**
* Setter for camera structure fields.
*
* @param context The script context.
* @param key The field key.
* @param structPtr Pointer to the camera structure.
* @param inValue Input script value.
*
* @param l The Lua state.
*/
void moduleCameraSetter(
const scriptcontext_t *context,
const char_t *key,
void *structPtr,
const scriptvalue_t *inValue
);
int moduleCameraNewIndex(lua_State *l);

View File

@@ -8,17 +8,25 @@
#include "modulecolor.h"
#include "display/color.h"
#include "assert/assert.h"
#include "script/struct/scriptstruct.h"
#include "util/string.h"
void moduleColor(scriptcontext_t *context) {
assertNotNull(context, "Context cannot be NULL.");
scriptStructRegister(
context, "color_mt", moduleColorGetter, moduleColorSetter
);
if(luaL_newmetatable(context->luaState, "color_mt")) {
// Metatable is new, set __index and __newindex
lua_pushstring(context->luaState, "__index");
lua_pushcfunction(context->luaState, moduleColorIndex);
lua_settable(context->luaState, -3);
scriptContextRegFunc(context, "color", moduleColorFuncColor);
lua_pushstring(context->luaState, "__newindex");
lua_pushcfunction(context->luaState, moduleColorNewIndex);
lua_settable(context->luaState, -3);
lua_pop(context->luaState, 1);
}
lua_register(context->luaState, "color", moduleColorFuncColor);
scriptContextExec(context, COLOR_SCRIPT);
}
@@ -42,92 +50,85 @@ int moduleColorFuncColor(lua_State *L) {
colorchannel8_t b = (colorchannel8_t)lua_tonumber(L, 3);
colorchannel8_t a = (colorchannel8_t)lua_tonumber(L, 4);
// Create color_t as lua memory
color_t *color = (color_t*)lua_newuserdata(L, sizeof(color_t));
// Create color_t as lua memory, and push metatable
color_t *color = (color_t *)lua_newuserdata(L, sizeof(color_t));
luaL_getmetatable(L, "color_mt");
lua_setmetatable(L, -2);
// Initial values.
color->r = r;
color->g = g;
color->b = b;
color->a = a;
// Push color struct
scriptStructPushMetatable(context, "color_mt", color);
return 1;
}
void moduleColorGetter(
const scriptcontext_t *context,
const char_t *key,
const void *structPtr,
scriptvalue_t *outValue
) {
const color_t *color = (color_t*)structPtr;
int moduleColorIndex(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL.");
const char_t *key = lua_tostring(L, 2);
assertStrLenMin(key, 1, "Key cannot be empty.");
const color_t *color = (const color_t*)luaL_checkudata(L, 1, "color_mt");
assertNotNull(color, "Color struct cannot be NULL.");
if(stringCompare(key, "r") == 0) {
outValue->type = SCRIPT_VALUE_TYPE_INT;
outValue->value.intValue = color->r;
return;
lua_pushinteger(L, color->r);
return 1;
}
if(stringCompare(key, "g") == 0) {
outValue->type = SCRIPT_VALUE_TYPE_INT;
outValue->value.intValue = color->g;
return;
lua_pushinteger(L, color->g);
return 1;
}
if(stringCompare(key, "b") == 0) {
outValue->type = SCRIPT_VALUE_TYPE_INT;
outValue->value.intValue = color->b;
return;
lua_pushinteger(L, color->b);
return 1;
}
if(stringCompare(key, "a") == 0) {
outValue->type = SCRIPT_VALUE_TYPE_INT;
outValue->value.intValue = color->a;
return;
lua_pushinteger(L, color->a);
return 1;
}
lua_pushnil(L);
return 1;
}
void moduleColorSetter(
const scriptcontext_t *context,
const char_t *key,
void *structPtr,
const scriptvalue_t *inValue
) {
color_t *color = (color_t*)structPtr;
int moduleColorNewIndex(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL.");
const char_t *key = lua_tostring(L, 2);
assertStrLenMin(key, 1, "Key cannot be empty.");
color_t *color = (color_t*)luaL_checkudata(L, 1, "color_mt");
assertNotNull(color, "Color struct cannot be NULL.");
if(stringCompare(key, "r") == 0) {
if(inValue->type == SCRIPT_VALUE_TYPE_INT) {
color->r = (colorchannel8_t)inValue->value.intValue;
} else if(inValue->type == SCRIPT_VALUE_TYPE_FLOAT) {
color->r = (colorchannel8_t)(inValue->value.floatValue);
} else {
assertUnreachable("Invalid type for color channel r");
if(!lua_isnumber(L, 3)) {
return luaL_error(L, "color channel r must be a number.");
}
return;
color->r = (colorchannel8_t)lua_tonumber(L, 3);
return 0;
} else if(stringCompare(key, "g") == 0) {
if(inValue->type == SCRIPT_VALUE_TYPE_INT) {
color->g = (colorchannel8_t)inValue->value.intValue;
} else if(inValue->type == SCRIPT_VALUE_TYPE_FLOAT) {
color->g = (colorchannel8_t)(inValue->value.floatValue);
} else {
assertUnreachable("Invalid type for color channel g");
if(!lua_isnumber(L, 3)) {
return luaL_error(L, "color channel g must be a number.");
}
return;
color->g = (colorchannel8_t)lua_tonumber(L, 3);
return 0;
} else if(stringCompare(key, "b") == 0) {
if(inValue->type == SCRIPT_VALUE_TYPE_INT) {
color->b = (colorchannel8_t)inValue->value.intValue;
} else if(inValue->type == SCRIPT_VALUE_TYPE_FLOAT) {
color->b = (colorchannel8_t)(inValue->value.floatValue);
} else {
assertUnreachable("Invalid type for color channel b");
if(!lua_isnumber(L, 3)) {
return luaL_error(L, "color channel b must be a number.");
}
return;
color->b = (colorchannel8_t)lua_tonumber(L, 3);
return 0;
} else if(stringCompare(key, "a") == 0) {
if(inValue->type == SCRIPT_VALUE_TYPE_INT) {
color->a = (colorchannel8_t)inValue->value.intValue;
} else if(inValue->type == SCRIPT_VALUE_TYPE_FLOAT) {
color->a = (colorchannel8_t)(inValue->value.floatValue);
} else {
assertUnreachable("Invalid type for color channel a");
if(!lua_isnumber(L, 3)) {
return luaL_error(L, "color channel a must be a number.");
}
return;
color->a = (colorchannel8_t)lua_tonumber(L, 3);
return 0;
}
luaL_error(L, "Invalid color channel key.");
return 0;
}

View File

@@ -24,31 +24,16 @@ void moduleColor(scriptcontext_t *context);
int moduleColorFuncColor(lua_State *L);
/**
* Getter function for the color structure.
*
* @param context The script context.
* @param key The key to get.
* @param structPtr Pointer to the color structure.
* @param outValue Output value.
* Index function for the color structure.
* @param L The Lua state.
* @return Number of return values.
*/
void moduleColorGetter(
const scriptcontext_t *context,
const char_t *key,
const void *structPtr,
scriptvalue_t *outValue
);
int moduleColorIndex(lua_State *L);
/**
* Setter function for the color structure.
* New index function for the color structure.
*
* @param context The script context.
* @param key The key to set.
* @param structPtr Pointer to the color structure.
* @param inValue Input value.
* @param L The Lua state.
* @return Number of return values.
*/
void moduleColorSetter(
const scriptcontext_t *context,
const char_t *key,
void *structPtr,
const scriptvalue_t *inValue
);
int moduleColorNewIndex(lua_State *L);

View File

@@ -7,46 +7,45 @@
#include "moduleglm.h"
#include "assert/assert.h"
#include "script/struct/scriptstruct.h"
#include "util/string.h"
#include "util/memory.h"
void moduleGLM(scriptcontext_t *context) {
assertNotNull(context, "Context cannot be NULL.");
scriptStructRegister(
context,
"vec3_mt",
moduleGLMVec3Getter,
NULL
);
// scriptStructRegister(
// context,
// "vec3_mt",
// moduleGLMVec3Getter,
// NULL
// );
}
void moduleGLMVec3Getter(
const scriptcontext_t *context,
const char_t *key,
const void *structPtr,
scriptvalue_t *outValue
) {
assertNotNull(context, "Script context cannot be NULL.");
assertNotNull(key, "Key cannot be NULL.");
assertNotNull(structPtr, "Structure pointer cannot be NULL.");
assertNotNull(outValue, "Output value cannot be NULL.");
// void moduleGLMVec3Getter(
// const scriptcontext_t *context,
// const char_t *key,
// const void *structPtr,
// scriptvalue_t *outValue
// ) {
// assertNotNull(context, "Script context cannot be NULL.");
// assertNotNull(key, "Key cannot be NULL.");
// assertNotNull(structPtr, "Structure pointer cannot be NULL.");
// assertNotNull(outValue, "Output value cannot be NULL.");
printf("Getting vec3 field %s\n", key);
// printf("Getting vec3 field %s\n", key);
vec3 *v = (vec3 *)structPtr;
if(stringCompare(key, "x") == 0) {
outValue->type = SCRIPT_VALUE_TYPE_FLOAT;
outValue->value.floatValue = (*v)[0];
return;
} else if(stringCompare(key, "y") == 0) {
outValue->type = SCRIPT_VALUE_TYPE_FLOAT;
outValue->value.floatValue = (*v)[1];
return;
} else if(stringCompare(key, "z") == 0) {
outValue->type = SCRIPT_VALUE_TYPE_FLOAT;
outValue->value.floatValue = (*v)[2];
return;
}
}
// vec3 *v = (vec3 *)structPtr;
// if(stringCompare(key, "x") == 0) {
// outValue->type = SCRIPT_VALUE_TYPE_FLOAT;
// outValue->value.floatValue = (*v)[0];
// return;
// } else if(stringCompare(key, "y") == 0) {
// outValue->type = SCRIPT_VALUE_TYPE_FLOAT;
// outValue->value.floatValue = (*v)[1];
// return;
// } else if(stringCompare(key, "z") == 0) {
// outValue->type = SCRIPT_VALUE_TYPE_FLOAT;
// outValue->value.floatValue = (*v)[2];
// return;
// }
// }

View File

@@ -10,9 +10,9 @@
#include "assert/assert.h"
void moduleSpriteBatch(scriptcontext_t *context) {
scriptContextRegFunc(context, "spriteBatchFlush", moduleSpriteBatchFlush);
scriptContextRegFunc(context, "spriteBatchClear", moduleSpriteBatchClear);
scriptContextRegFunc(context, "spriteBatchPush", moduleSpriteBatchPush);
lua_register(context->luaState, "spriteBatchFlush", moduleSpriteBatchFlush);
lua_register(context->luaState, "spriteBatchClear", moduleSpriteBatchClear);
lua_register(context->luaState, "spriteBatchPush", moduleSpriteBatchPush);
}
int moduleSpriteBatchFlush(lua_State *L) {
@@ -47,11 +47,10 @@ int moduleSpriteBatchPush(lua_State *L) {
// Color (struct) or nil for white
color_t *color = NULL;
if(lua_isuserdata(L, 6)) {
color = *(color_t**)lua_touserdata(L, 6);
assertNotNull(color, "Color struct cannot be NULL");
} else if(lua_isnil(L, 6)) {
// Extrapolate later.
if(lua_gettop(L) < 6 || lua_isnil(L, 6)) {
// Allow NULL
} else if(lua_isuserdata(L, 6)) {
color = (color_t*)luaL_checkudata(L, 6, "color_mt");
} else {
return luaL_error(L, "Sprite color must be a color struct or nil");
}

View File

@@ -41,5 +41,5 @@ int moduleEventSubscribe(lua_State *L) {
void moduleEvent(scriptcontext_t *context) {
// Reg functions
scriptContextRegFunc(context, "eventSubscribe", moduleEventSubscribe);
lua_register(context->luaState, "eventSubscribe", moduleEventSubscribe);
}

View File

@@ -9,7 +9,6 @@
#include "input/input.h"
#include "assert/assert.h"
#include "util/string.h"
#include "script/struct/scriptstruct.h"
void moduleInput(scriptcontext_t *context) {
assertNotNull(context, "Script context cannot be NULL");
@@ -37,27 +36,39 @@ void moduleInput(scriptcontext_t *context) {
);
// Script structure
scriptStructRegister(context, "input_mt", &moduleInputEventGetter, NULL);
if(luaL_newmetatable(context->luaState, "input_mt")) {
// Metatable methods
lua_pushcfunction(context->luaState, moduleInputIndex);
lua_setfield(context->luaState, -2, "__index");
}
// Events
scriptContextRegPointer(context,"INPUT_EVENT_PRESSED",&INPUT.eventPressed);
scriptContextRegPointer(context,"INPUT_EVENT_RELEASED",&INPUT.eventReleased);
lua_pushlightuserdata(context->luaState, &INPUT.eventPressed);
lua_setglobal(context->luaState, "INPUT_EVENT_PRESSED");
lua_pushlightuserdata(context->luaState, &INPUT.eventReleased);
lua_setglobal(context->luaState, "INPUT_EVENT_RELEASED");
// Bind methods
scriptContextRegFunc(context, "inputBind", moduleInputBind);
lua_register(context->luaState, "inputBind", moduleInputBind);
}
void moduleInputEventGetter(
const scriptcontext_t *context,
const char_t *key,
const void *structPtr,
scriptvalue_t *outValue
) {
int moduleInputIndex(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL");
const char_t *key = luaL_checkstring(l, 2);
assertStrLenMin(key, 1, "Key cannot be empty.");
if(stringCompare(key, "action") == 0) {
outValue->type = SCRIPT_VALUE_TYPE_INT;
outValue->value.intValue = ((const inputevent_t*)structPtr)->action;
return;
lua_pushinteger(
l,
((const inputevent_t*)lua_touserdata(l, 1))->action
);
return 1;
}
return 0;
}
int moduleInputBind(lua_State *L) {

View File

@@ -16,19 +16,11 @@
void moduleInput(scriptcontext_t *context);
/**
* Script event getter for input events.
* Getter for input structure fields.
*
* @param context The script context.
* @param key The key to get.
* @param structPtr Pointer to the input event struct.
* @param outValue Output script value.
* @param l The Lua state.
*/
void moduleInputEventGetter(
const scriptcontext_t *context,
const char_t *key,
const void *structPtr,
scriptvalue_t *outValue
);
int moduleInputIndex(lua_State *l);
/**
* Script binding for binding an input button to an action.

View File

@@ -17,19 +17,20 @@ void moduleItem(scriptcontext_t *context) {
scriptContextExec(context, ITEM_SCRIPT);
// Bind BACKPACK const pointer
scriptContextRegPointer(context, "BACKPACK", (void *)&BACKPACK);
lua_pushlightuserdata(context->luaState, &BACKPACK);
lua_setglobal(context->luaState, "BACKPACK");
// Bind Methods
scriptContextRegFunc(
context, "inventoryItemExists", moduleInventoryItemExists
lua_register(
context->luaState, "inventoryItemExists", moduleInventoryItemExists
);
scriptContextRegFunc(context, "inventoryAdd", moduleInventoryAdd);
scriptContextRegFunc(context, "inventorySet", moduleInventorySet);
scriptContextRegFunc(context, "inventoryRemove", moduleInventoryRemove);
scriptContextRegFunc(context, "inventoryGetCount", moduleInventoryGetCount);
scriptContextRegFunc(context, "inventoryIsFull", moduleInventoryIsFull);
scriptContextRegFunc(context, "inventoryItemFull", moduleInventoryItemFull);
scriptContextRegFunc(context, "inventorySort", moduleInventorySort);
lua_register(context->luaState, "inventoryAdd", moduleInventoryAdd);
lua_register(context->luaState, "inventorySet", moduleInventorySet);
lua_register(context->luaState, "inventoryRemove", moduleInventoryRemove);
lua_register(context->luaState, "inventoryGetCount", moduleInventoryGetCount);
lua_register(context->luaState, "inventoryIsFull", moduleInventoryIsFull);
lua_register(context->luaState, "inventoryItemFull", moduleInventoryItemFull);
lua_register(context->luaState, "inventorySort", moduleInventorySort);
}
int moduleInventoryItemExists(lua_State *L) {

View File

@@ -15,9 +15,9 @@ void moduleLocale(scriptcontext_t *context) {
// Execute the locale script definitions.
scriptContextExec(context, LOCALE_SCRIPT);
scriptContextRegFunc(context, "localeGet", moduleLocaleGet);
scriptContextRegFunc(context, "localeSet", moduleLocaleSet);
scriptContextRegFunc(context, "localeGetName", moduleLocaleGetName);
lua_register(context->luaState, "localeGet", moduleLocaleGet);
lua_register(context->luaState, "localeSet", moduleLocaleSet);
lua_register(context->luaState, "localeGetName", moduleLocaleGetName);
}
int moduleLocaleGet(lua_State *L) {

View File

@@ -12,7 +12,7 @@
void moduleScene(scriptcontext_t *ctx) {
assertNotNull(ctx, "Script context cannot be NULL");
scriptContextRegFunc(ctx, "sceneSet", moduleSceneSet);
lua_register(ctx->luaState, "sceneSet", moduleSceneSet);
}
int moduleSceneSet(lua_State *L) {

View File

@@ -14,9 +14,9 @@
void moduleSystem(scriptcontext_t *context) {
assertNotNull(context, "Script context cannot be NULL");
scriptContextRegFunc(context, "print", moduleSysPrint);
scriptContextRegFunc(context, "include", moduleSysInclude);
scriptContextRegFunc(context, "module", moduleSysModule);
lua_register(context->luaState, "print", moduleSysPrint);
lua_register(context->luaState, "include", moduleSysInclude);
lua_register(context->luaState, "module", moduleSysModule);
}
int moduleSysPrint(lua_State *L) {

View File

@@ -7,31 +7,30 @@
#include "moduletime.h"
#include "assert/assert.h"
#include "script/struct/scriptstruct.h"
void moduleTime(scriptcontext_t *ctx) {
assertNotNull(ctx, "Script context cannot be NULL");
// Script structure
scriptStructRegister(ctx, "time_mt", moduleTimeGetter, NULL);
// Register struct
scriptStructPush(ctx, "time_mt", "TIME", &TIME);
if(luaL_newmetatable(ctx->luaState, "time_mt")) {
// Metatable is new, set __index
lua_pushstring(ctx->luaState, "__index");
lua_pushcfunction(ctx->luaState, moduleTimeIndex);
lua_settable(ctx->luaState, -3);
}
}
void moduleTimeGetter(
const scriptcontext_t *context,
const char_t *key,
const void *structPtr,
scriptvalue_t *outValue
) {
int moduleTimeIndex(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL.");
const char_t *key = lua_tostring(L, 2);
assertStrLenMin(key, 1, "Key cannot be empty.");
if(stringCompare(key, "delta") == 0) {
outValue->type = SCRIPT_VALUE_TYPE_FLOAT;
outValue->value.floatValue = TIME.delta;
return;
lua_pushnumber(L, TIME.delta);
return 1;
} else if(stringCompare(key, "time") == 0) {
outValue->type = SCRIPT_VALUE_TYPE_FLOAT;
outValue->value.floatValue = TIME.time;
return;
lua_pushnumber(L, TIME.time);
return 1;
}
}

View File

@@ -18,16 +18,8 @@
void moduleTime(scriptcontext_t *ctx);
/**
* Getter function for the TIME struct in scripts.
*
* @param context The script context.
* @param key The key being accessed.
* @param structPtr Pointer to the struct instance.
* @param outValue Output parameter to store the retrieved value.
* Index function for the time structure.
* @param L The Lua state.
* @return Number of return values.
*/
void moduleTimeGetter(
const scriptcontext_t *context,
const char_t *key,
const void *structPtr,
scriptvalue_t *outValue
);
int moduleTimeIndex(lua_State *L);

View File

@@ -7,9 +7,30 @@
#include "moduleui.h"
#include "assert/assert.h"
#include "ui/ui.h"
void moduleUi(scriptcontext_t *ctx) {
assertNotNull(ctx, "Script context cannot be NULL");
lua_register(ctx->luaState, "uiPush", moduleUiPush);
}
int moduleUiPush(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
// Expect path string. TODO Support non scripted ui elements.
if(!lua_isstring(L, 1)) {
return luaL_error(L, "uiPush expects a string path as the first argument");
}
const char_t *path = lua_tostring(L, 1);
printf("Pushing UI from script: %s\n", path);
errorret_t err = uiPushScript(path);
if(err.code != ERROR_OK) {
errorCatch(errorPrint(err));
return luaL_error(L, "uiPush failed for path: %s", path);
}
return 0;
}

View File

@@ -13,4 +13,14 @@
*
* @param ctx The script context to register the module in.
*/
void moduleUi(scriptcontext_t *ctx);
void moduleUi(scriptcontext_t *ctx);
/**
* Lua binding for uiPush function.
*
* Expects a string path to the UI script as the first argument.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleUiPush(lua_State *L);

View File

@@ -38,31 +38,6 @@ errorret_t scriptContextInit(scriptcontext_t *context) {
errorOk();
}
void scriptContextRegFunc(
scriptcontext_t *context,
const char_t *fnName,
lua_CFunction function
) {
assertNotNull(context, "Script context cannot be NULL");
assertNotNull(fnName, "Function name cannot be NULL");
assertNotNull(function, "Function cannot be NULL");
lua_register(context->luaState, fnName, function);
}
void scriptContextRegPointer(
scriptcontext_t *context,
const char_t *name,
void *pointer
) {
assertNotNull(context, "Script context cannot be NULL");
assertNotNull(name, "Name cannot be NULL");
assertNotNull(pointer, "Pointer cannot be NULL");
lua_pushlightuserdata(context->luaState, pointer);
lua_setglobal(context->luaState, name);
}
errorret_t scriptContextCallFunc(
scriptcontext_t *context,
const char_t *fnName,

View File

@@ -29,32 +29,6 @@ typedef struct scriptcontext_s {
*/
errorret_t scriptContextInit(scriptcontext_t *context);
/**
* Register a C function within a script context.
*
* @param context The script context to use.
* @param fnName The name of the function in Lua.
* @param function The C function to register.
*/
void scriptContextRegFunc(
scriptcontext_t *context,
const char_t *fnName,
lua_CFunction function
);
/**
* Register a pointer within a script context.
*
* @param context The script context to use.
* @param name The name of the pointer in Lua.
* @param pointer The pointer to register.
*/
void scriptContextRegPointer(
scriptcontext_t *context,
const char_t *name,
void *pointer
);
/**
* Call a Lua function within a script context.
*

View File

@@ -1,191 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scriptstruct.h"
#include "assert/assert.h"
#include "util/memory.h"
void scriptStructRegister(
scriptcontext_t *context,
const char_t *metatableName,
const scriptstructgetter_t getter,
const scriptstructsetter_t setter
) {
assertNotNull(context, "Script context cannot be NULL");
assertNotNull(metatableName, "Metatable name cannot be NULL");
// Create metatable
if(!luaL_newmetatable(context->luaState, metatableName)) return;
// Create a Lua owned structure for holding the metatable context.
structmetatablecontext_t *metaContext = lua_newuserdata(
context->luaState,
sizeof(structmetatablecontext_t)
);
metaContext->context = context;
metaContext->getter = getter;
metaContext->setter = setter;
// Store in the metatable.
lua_setfield(context->luaState, -2, "__structmetatablecontext");
// Set __index and __newindex metamethods for Lua.
lua_pushcfunction(context->luaState, scriptStructIndex);
lua_setfield(context->luaState, -2, "__index");
lua_pushcfunction(context->luaState, scriptStructNewIndex);
lua_setfield(context->luaState, -2, "__newindex");
}
int scriptStructIndex(lua_State *l) {
structmetatablecontext_t *ctx = scriptStructGetMetatableContext(l);
if(!ctx->getter) {
luaL_error(l, "Attempt to read from write-only structure field");
return 0;
}
const char_t *key = lua_tostring(l, 2);
void *structPtr = *(void **)lua_touserdata(l, 1);
assertNotNull(structPtr, "Structure pointer cannot be NULL");
scriptvalue_t outValue = { .type = SCRIPT_VALUE_TYPE_NIL };
ctx->getter(ctx->context, key, structPtr, &outValue);
switch(outValue.type) {
case SCRIPT_VALUE_TYPE_INT:
lua_pushinteger(l, outValue.value.intValue);
break;
case SCRIPT_VALUE_TYPE_FLOAT:
lua_pushnumber(l, outValue.value.floatValue);
break;
case SCRIPT_VALUE_TYPE_STRING:
assertNotNull(outValue.value.strValue, "String value cannot be NULL");
lua_pushstring(l, outValue.value.strValue);
memoryFree((void *)outValue.value.strValue);
break;
case SCRIPT_VALUE_TYPE_BOOL:
lua_pushboolean(l, outValue.value.boolValue);
break;
case SCRIPT_VALUE_TYPE_NIL:
lua_pushnil(l);
break;
case SCRIPT_VALUE_TYPE_USERDATA:
break;
default:
assertUnreachable("Unsupported value type for struct field retrieval");
break;
}
return 1;
}
int scriptStructNewIndex(lua_State *l) {
structmetatablecontext_t *ctx = scriptStructGetMetatableContext(l);
if(ctx->setter == NULL) {
luaL_error(l, "Attempt to set read-only structure field");
return 0;
}
const char_t *key = lua_tostring(l, 2);
void *structPtr = *(void **)lua_touserdata(l, 1);
assertNotNull(structPtr, "Structure pointer cannot be NULL");
scriptvalue_t inValue;
int t = lua_type(l, 3);
switch(t) {
case LUA_TNUMBER:
if(lua_isinteger(l, 3)) {
inValue.type = SCRIPT_VALUE_TYPE_INT;
inValue.value.intValue = (int32_t)lua_tointeger(l, 3);
} else {
inValue.type = SCRIPT_VALUE_TYPE_FLOAT;
inValue.value.floatValue = (float)lua_tonumber(l, 3);
}
break;
case LUA_TSTRING:
inValue.type = SCRIPT_VALUE_TYPE_STRING;
inValue.value.strValue = lua_tostring(l, 3);
break;
case LUA_TBOOLEAN:
inValue.type = SCRIPT_VALUE_TYPE_BOOL;
inValue.value.boolValue = lua_toboolean(l, 3);
break;
case LUA_TNIL:
inValue.type = SCRIPT_VALUE_TYPE_NIL;
break;
default:
assertUnreachable("Unsupported value type for struct field assignment");
break;
}
ctx->setter(ctx->context, key, structPtr, &inValue);
lua_pushnil(l);
return 1;
}
structmetatablecontext_t * scriptStructGetMetatableContext(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
if(!lua_getmetatable(L, 1)) {
assertUnreachable("Expected metatable on structure");
}
lua_getfield(L, -1, "__structmetatablecontext");
structmetatablecontext_t *metaContext = (
(structmetatablecontext_t *)lua_touserdata(L, -1)
);
assertNotNull(metaContext, "Metatable context userdata cannot be NULL");
lua_pop(L, 2);
return metaContext;
}
void scriptStructPushMetatable(
const scriptcontext_t *context,
const char_t *metatableName,
void *structPtr
) {
assertNotNull(context, "Script context cannot be NULL");
assertNotNull(metatableName, "Metatable name cannot be NULL");
assertNotNull(structPtr, "Structure pointer cannot be NULL");
// Create userdata
void **ud = (void **)lua_newuserdata(context->luaState, sizeof(void *));
*ud = structPtr;
// Set metatable
luaL_getmetatable(context->luaState, metatableName);
lua_setmetatable(context->luaState, -2);
}
void scriptStructPush(
scriptcontext_t *context,
const char_t *metatableName,
const char_t *variableName,
void *structPtr
) {
assertNotNull(context, "Script context cannot be NULL");
assertNotNull(metatableName, "Metatable name cannot be NULL");
assertNotNull(variableName, "Variable name cannot be NULL");
assertNotNull(structPtr, "Structure pointer cannot be NULL");
scriptStructPushMetatable(context, metatableName, structPtr);
lua_setglobal(context->luaState, variableName);
}

View File

@@ -1,86 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "scriptstructgetter.h"
#include "scriptstructsetter.h"
typedef struct {
scriptcontext_t *context;
scriptstructgetter_t getter;
scriptstructsetter_t setter;
} structmetatablecontext_t;
/**
* Registers a script structure to allow a script to access its fields. You will
* be creating a new metatable in Lua with the given name, so Ideally use a name
* like Ideally struct_mt e.g. player_t or entity_t.
*
* @param context The script context.
* @param metatableName The name of the metatable to register
* @param getter The getter function for retrieving field values.
* @param setter The setter function for setting field values.
*/
void scriptStructRegister(
scriptcontext_t *context,
const char_t *metatableName,
const scriptstructgetter_t getter,
const scriptstructsetter_t setter
);
/**
* Callback received from Lua to index (get) a structure field.
*
* @param l The Lua state.
* @return The number of return values.
*/
int scriptStructIndex(lua_State *l);
/**
* Callback received from Lua to newindex (set) a structure field.
*
* @param l The Lua state.
* @return The number of return values.
*/
int scriptStructNewIndex(lua_State *l);
/**
* Pops the metatable context from the given Lua state. This is only valid from
* within the index or newindex methods.
*
* @param l The Lua state.
* @return The metatable context.
*/
structmetatablecontext_t *scriptStructGetMetatableContext(lua_State *l);
/**
* Pushes a structure metatable onto the Lua stack.
*
* @param context The script context.
* @param metatableName The name of the metatable to associate with.
* @param structPtr Pointer to the structure to push.
*/
void scriptStructPushMetatable(
const scriptcontext_t *context,
const char_t *metatableName,
void *structPtr
);
/**
* Pushes a structure onto the Lua stack and assigns it to a global variable.
*
* @param context The script context.
* @param metatableName The name of the metatable to associate with.
* @param variableName The name of the global variable to assign to.
* @param structPtr Pointer to the structure to push.
*/
void scriptStructPush(
scriptcontext_t *context,
const char_t *metatableName,
const char_t *variableName,
void *structPtr
);

View File

@@ -1,16 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptcontext.h"
typedef void (*scriptstructgetter_t)(
const scriptcontext_t *context,
const char_t *key,
const void *structPtr,
scriptvalue_t *outValue
);

View File

@@ -1,16 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptcontext.h"
typedef void (*scriptstructsetter_t)(
const scriptcontext_t *context,
const char_t *key,
void *structPtr,
const scriptvalue_t *inValue
);

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
@@ -7,11 +7,5 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
ui.c
uitext.c
uidebug.c
uiframe.c
uitextbox.c
)
# Subdirs
add_subdirectory(element)
uielement.c
)

View File

@@ -1,67 +1,68 @@
/**
* Copyright (c) 2025 Dominic Masters
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "ui.h"
#include "assert/assert.h"
#include "ui/uidebug.h"
#include "util/memory.h"
#include "display/tileset/tileset_minogram.h"
#include "display/screen.h"
// #include "ui/uitextbox.h"
#include "assert/assert.h"
#include "display/spritebatch.h"
ui_t UI;
errorret_t uiInit(void) {
memoryZero(&UI, sizeof(ui_t));
cameraInitOrthographic(&UI.camera);
// Initialize UI components here
uiSetFont(&TILESET_MINOGRAM);
errorOk();
}
void uiUpdate(void) {
// Update UI state here
UI.camera.orthographic.left = 0;
UI.camera.orthographic.right = SCREEN.width;
UI.camera.orthographic.top = 0;
UI.camera.orthographic.bottom = SCREEN.height;
// uiTextboxUpdate();
uint_fast8_t i = 0;
while(i < UI.stackSize) {
errorCatch(errorPrint(uiElementUpdate(&UI.stack[i])));
i++;
}
}
void uiRender(void) {
cameraPushMatrix(&UI.camera);
// Render UI elements here
if(UI.fontTexture.width > 0) {
uiDebugRender(UI.fontTileset, &UI.fontTexture);
spriteBatchClear();
uint_fast8_t i = 0;
while(i < UI.stackSize) {
errorCatch(errorPrint(uiElementRender(&UI.stack[i])));
i++;
}
// spriteBatchPush(
// NULL,
// 0, 0, 32, 32, COLOR_RED, 0, 0, 1, 1
// );
spriteBatchFlush();
cameraPopMatrix();
}
errorret_t uiSetFont(const tileset_t *fontTileset) {
if(UI.fontTexture.width > 0) {
textureDispose(&UI.fontTexture);
UI.fontTexture.width = -1;
}
assertNotNull(fontTileset, "Font tileset cannot be NULL.");
UI.fontTileset = fontTileset;
errorChain(assetLoad(UI.fontTileset->image, &UI.fontTexture));
errorret_t uiPushScript(const char_t *path) {
assertTrue(UI.stackSize < UI_STACK_SIZE, "UI stack overflow");
uielement_t *element = &UI.stack[UI.stackSize];
errorChain(uiElementInitScript(element, path));
UI.stackSize++;
// Ret Id or something?
errorOk();
}
void uiPop(void) {
assertTrue(UI.stackSize > 0, "UI stack underflow");
UI.stackSize--;
uielement_t *element = &UI.stack[UI.stackSize];
uiElementDispose(element);
}
void uiDispose(void) {
if(UI.fontTexture.width > 0) {
textureDispose(&UI.fontTexture);
UI.fontTexture.width = -1;
while(UI.stackSize > 0) {
uiPop();
}
}

View File

@@ -1,51 +1,53 @@
/**
* Copyright (c) 2025 Dominic Masters
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "asset/asset.h"
#include "display/texture.h"
#include "display/tileset/tileset.h"
#include "display/camera/camera.h"
#include "ui/uielement.h"
#define UI_STACK_SIZE 64
typedef struct {
camera_t camera;
texture_t fontTexture;
const tileset_t *fontTileset;
uielement_t stack[UI_STACK_SIZE];
uint_fast8_t stackSize;
} ui_t;
extern ui_t UI;
/**
* Initializes the UI system, loading necessary resources.
*
* @return An errorret_t indicating success or failure.
* Initializes the UI system.
*/
errorret_t uiInit(void);
/**
* Updates the UI state, handling user interactions and animations.
* Updates the UI system.
*/
void uiUpdate(void);
/**
* Renders the UI elements to the screen.
* Renders the UI system.
*/
void uiRender(void);
/**
* Sets the font tileset for UI text rendering.
* Pushes a new UI element onto the UI stack from a script file.
*
* @param fontTileset Pointer to the tileset to use for UI fonts.
*
* @return An errorret_t indicating success or failure.
* @param path Path to the UI script file.
*/
errorret_t uiSetFont(const tileset_t *fontTileset);
errorret_t uiPushScript(const char_t *path);
/**
* Cleans up and frees all UI resources.
* Pops the top UI element from the UI stack.
*/
void uiPop(void);
/**
* Disposes of the UI system.
*/
void uiDispose(void);

57
src/ui/uielement.c Normal file
View File

@@ -0,0 +1,57 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "uielement.h"
#include "display/screen.h"
errorret_t uiElementInitScript(
uielement_t *element,
const char_t *path
) {
errorChain(scriptContextInit(&element->ctx));
errorChain(scriptContextExecFile(&element->ctx, path));
errorOk();
}
errorret_t uiElementUpdate(uielement_t *element) {
if(!scriptContextHasFunc(&element->ctx, "update")) errorOk();
errorChain(scriptContextCallFunc(
&element->ctx,
"update",
NULL,
0,
NULL
));
errorOk();
}
errorret_t uiElementRender(uielement_t *element) {
if(!scriptContextHasFunc(&element->ctx, "render")) errorOk();
// scriptvalue_t args[4] = {
// { .type = SCRIPT_VALUE_TYPE_FLOAT, .value.floatValue = 0.0f },
// { .type = SCRIPT_VALUE_TYPE_FLOAT, .value.floatValue = 0.0f },
// { .type = SCRIPT_VALUE_TYPE_FLOAT, .value.floatValue = SCREEN.width },
// { .type = SCRIPT_VALUE_TYPE_FLOAT, .value.floatValue = SCREEN.height },
// };
// errorChain(scriptContextCallFunc(
// &element->ctx,
// "render",
// args,
// 4,
// NULL
// ));
errorOk();
}
void uiElementDispose(uielement_t *element) {
scriptContextDispose(&element->ctx);
}

46
src/ui/uielement.h Normal file
View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptcontext.h"
// TODO: Support both scripted and native UI elements.
typedef struct {
scriptcontext_t ctx;
} uielement_t;
/**
* Initializes a scripted UI Element.
*
* @param element The UI element to initialize.
* @param path Path to the UI script file.
*/
errorret_t uiElementInitScript(
uielement_t *element,
const char_t *path
);
/**
* Updates/ticks a UI element's logic.
*
* @param element The UI element to tick.
*/
errorret_t uiElementUpdate(uielement_t *element);
/**
* Renders a UI element.
*
* @param element The UI element to render.
*/
errorret_t uiElementRender(uielement_t *element);
/**
* Disposes of a UI element.
*
* @param element The UI element to dispose of.
*/
void uiElementDispose(uielement_t *element);