1 Commits

Author SHA1 Message Date
YourWishes 9c71df5bfd mbed first pass 2026-04-19 16:22:00 -05:00
186 changed files with 6190 additions and 5907 deletions
+15 -15
View File
@@ -53,21 +53,21 @@ jobs:
path: ./git-artifcats/Dusk
if-no-files-found: error
# build-vita:
# runs-on: ubuntu-latest
# steps:
# - name: Checkout repository
# uses: actions/checkout@v6
# - name: Set up Docker
# uses: docker/setup-docker-action@v5
# - name: Build Vita
# run: ./scripts/build-vita-docker.sh
# - name: Upload Vita binary
# uses: actions/upload-artifact@v6
# with:
# name: dusk-vita
# path: build-vita/Dusk.vpk
# if-no-files-found: error
build-vita:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker
uses: docker/setup-docker-action@v5
- name: Build Vita
run: ./scripts/build-vita-docker.sh
- name: Upload Vita binary
uses: actions/upload-artifact@v6
with:
name: dusk-vita
path: build-vita/Dusk.vpk
if-no-files-found: error
build-knulli:
runs-on: ubuntu-latest
+1 -3
View File
@@ -4,13 +4,11 @@
# https://opensource.org/licenses/MIT
# Setup
cmake_minimum_required(VERSION 3.13)
cmake_minimum_required(VERSION 3.18)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules)
# [cmake] This is allowed only when policy CMP0079 is set to NEW.
cmake_policy(SET CMP0079 NEW)
option(DUSK_BUILD_TESTS "Enable tests" OFF)
-22
View File
@@ -1,22 +0,0 @@
var OverworldEntity = include('entities/OverworldEntity.js');
function CubeEntity() {
OverworldEntity.call(this);
this.add(MESH);
this.add(MATERIAL);
}
CubeEntity.prototype = Object.create(OverworldEntity.prototype);
CubeEntity.prototype.constructor = CubeEntity;
CubeEntity.prototype.update = function() {
OverworldEntity.prototype.update.call(this);
var speed = 3.0;
var move = Input.axis2D(INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT, INPUT_ACTION_UP, INPUT_ACTION_DOWN);
this.position.position.x += move.x * speed * TIME.delta;
this.position.position.z += move.y * speed * TIME.delta;
this.material.setColor(Color.rainbow());
};
module = CubeEntity;
-21
View File
@@ -1,21 +0,0 @@
function OverworldEntity() {
Entity.call(this);
this.add(POSITION);
}
OverworldEntity.prototype = Object.create(Entity.prototype);
OverworldEntity.prototype.constructor = OverworldEntity;
OverworldEntity.prototype.update = function() {
// var speed = 3.0;
// var move = Input.axis2D(INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT, INPUT_ACTION_UP, INPUT_ACTION_DOWN);
// this.position.position.x += move.x * speed * TIME.delta;
// this.position.position.z += move.y * speed * TIME.delta;
}
OverworldEntity.prototype.dispose = function() {
// Nothing to dispose
}
module = OverworldEntity;
-77
View File
@@ -1,77 +0,0 @@
Console.visible = true;
// Default input bindings.
if (typeof PSP !== 'undefined') {
Input.bind("up", INPUT_ACTION_UP);
Input.bind("down", INPUT_ACTION_DOWN);
Input.bind("left", INPUT_ACTION_LEFT);
Input.bind("right", INPUT_ACTION_RIGHT);
Input.bind("accept", INPUT_ACTION_ACCEPT);
Input.bind("cancel", INPUT_ACTION_CANCEL);
Input.bind("select", INPUT_ACTION_RAGEQUIT);
Input.bind("lstick_up", INPUT_ACTION_UP);
Input.bind("lstick_down", INPUT_ACTION_DOWN);
Input.bind("lstick_left", INPUT_ACTION_LEFT);
Input.bind("lstick_right", INPUT_ACTION_RIGHT);
Input.bind("triangle", INPUT_ACTION_CONSOLE);
} else if (typeof DOLPHIN !== 'undefined') {
Input.bind("up", INPUT_ACTION_UP);
Input.bind("down", INPUT_ACTION_DOWN);
Input.bind("left", INPUT_ACTION_LEFT);
Input.bind("right", INPUT_ACTION_RIGHT);
Input.bind("b", INPUT_ACTION_CANCEL);
Input.bind("a", INPUT_ACTION_ACCEPT);
Input.bind("z", INPUT_ACTION_CONSOLE);
Input.bind("lstick_up", INPUT_ACTION_UP);
Input.bind("lstick_down", INPUT_ACTION_DOWN);
Input.bind("lstick_left", INPUT_ACTION_LEFT);
Input.bind("lstick_right", INPUT_ACTION_RIGHT);
} else if (typeof LINUX !== 'undefined') {
if (typeof INPUT_KEYBOARD !== 'undefined') {
Input.bind("w", INPUT_ACTION_UP);
Input.bind("s", INPUT_ACTION_DOWN);
Input.bind("a", INPUT_ACTION_LEFT);
Input.bind("d", INPUT_ACTION_RIGHT);
Input.bind("left", INPUT_ACTION_LEFT);
Input.bind("right", INPUT_ACTION_RIGHT);
Input.bind("up", INPUT_ACTION_UP);
Input.bind("down", INPUT_ACTION_DOWN);
Input.bind("enter", INPUT_ACTION_ACCEPT);
Input.bind("e", INPUT_ACTION_ACCEPT);
Input.bind("q", INPUT_ACTION_CANCEL);
Input.bind("escape", INPUT_ACTION_RAGEQUIT);
Input.bind("`", INPUT_ACTION_CONSOLE);
}
if (typeof INPUT_GAMEPAD !== 'undefined') {
Input.bind("gamepad_up", INPUT_ACTION_UP);
Input.bind("gamepad_down", INPUT_ACTION_DOWN);
Input.bind("gamepad_left", INPUT_ACTION_LEFT);
Input.bind("gamepad_right", INPUT_ACTION_RIGHT);
Input.bind("gamepad_a", INPUT_ACTION_ACCEPT);
Input.bind("gamepad_b", INPUT_ACTION_CANCEL);
Input.bind("gamepad_back", INPUT_ACTION_RAGEQUIT);
Input.bind("gamepad_lstick_up", INPUT_ACTION_UP);
Input.bind("gamepad_lstick_down", INPUT_ACTION_DOWN);
Input.bind("gamepad_lstick_left", INPUT_ACTION_LEFT);
Input.bind("gamepad_lstick_right", INPUT_ACTION_RIGHT);
}
if (typeof INPUT_POINTER !== 'undefined') {
Input.bind("mouse_x", INPUT_ACTION_POINTERX);
Input.bind("mouse_y", INPUT_ACTION_POINTERY);
}
} else {
print("Unknown platform, no default input bindings set.");
}
Scene.set('scenes/cube.js');
+76
View File
@@ -0,0 +1,76 @@
module('input')
module('platform')
module('scene')
module('locale')
-- Default Input bindings.
if PSP then
inputBind("up", INPUT_ACTION_UP)
inputBind("down", INPUT_ACTION_DOWN)
inputBind("left", INPUT_ACTION_LEFT)
inputBind("right", INPUT_ACTION_RIGHT)
inputBind("accept", INPUT_ACTION_ACCEPT)
inputBind("cancel", INPUT_ACTION_CANCEL)
inputBind("select", INPUT_ACTION_RAGEQUIT)
inputBind("lstick_up", INPUT_ACTION_UP)
inputBind("lstick_down", INPUT_ACTION_DOWN)
inputBind("lstick_left", INPUT_ACTION_LEFT)
inputBind("lstick_right", INPUT_ACTION_RIGHT)
elseif DOLPHIN then
inputBind("up", INPUT_ACTION_UP)
inputBind("down", INPUT_ACTION_DOWN)
inputBind("left", INPUT_ACTION_LEFT)
inputBind("right", INPUT_ACTION_RIGHT)
inputBind("b", INPUT_ACTION_CANCEL)
inputBind("a", INPUT_ACTION_ACCEPT)
inputBind("z", INPUT_ACTION_RAGEQUIT)
inputBind("lstick_up", INPUT_ACTION_UP)
inputBind("lstick_down", INPUT_ACTION_DOWN)
inputBind("lstick_left", INPUT_ACTION_LEFT)
inputBind("lstick_right", INPUT_ACTION_RIGHT)
elseif LINUX then
if INPUT_KEYBOARD then
inputBind("w", INPUT_ACTION_UP)
inputBind("s", INPUT_ACTION_DOWN)
inputBind("a", INPUT_ACTION_LEFT)
inputBind("d", INPUT_ACTION_RIGHT)
inputBind("left", INPUT_ACTION_LEFT)
inputBind("right", INPUT_ACTION_RIGHT)
inputBind("up", INPUT_ACTION_UP)
inputBind("down", INPUT_ACTION_DOWN)
inputBind("enter", INPUT_ACTION_ACCEPT)
inputBind("e", INPUT_ACTION_ACCEPT)
inputBind("q", INPUT_ACTION_CANCEL)
inputBind("escape", INPUT_ACTION_RAGEQUIT)
end
if INPUT_GAMEPAD then
inputBind("gamepad_up", INPUT_ACTION_UP)
inputBind("gamepad_down", INPUT_ACTION_DOWN)
inputBind("gamepad_left", INPUT_ACTION_LEFT)
inputBind("gamepad_right", INPUT_ACTION_RIGHT)
inputBind("gamepad_a", INPUT_ACTION_ACCEPT)
inputBind("gamepad_b", INPUT_ACTION_CANCEL)
inputBind("gamepad_back", INPUT_ACTION_RAGEQUIT)
inputBind("gamepad_lstick_up", INPUT_ACTION_UP)
inputBind("gamepad_lstick_down", INPUT_ACTION_DOWN)
inputBind("gamepad_lstick_left", INPUT_ACTION_LEFT)
inputBind("gamepad_lstick_right", INPUT_ACTION_RIGHT)
end
if INPUT_POINTER then
inputBind("mouse_x", INPUT_ACTION_POINTERX)
inputBind("mouse_y", INPUT_ACTION_POINTERY)
end
else
print("Unknown platform, no default input bindings set.")
end
-32
View File
@@ -1,32 +0,0 @@
var CubeEntity = include('entities/CubeEntity.js');
function CubeScene() {
this.cam = new Entity();
this.cam.add(POSITION);
this.cam.add(CAMERA);
this.cam.position.position = new Vec3(3, 3, 3);
this.cam.position.lookAt(new Vec3(0, 0, 0));
this.cube = new CubeEntity();
this.spriteEnt = new Entity();
this.spriteEnt.add(POSITION);
this.spriteEnt.position.position = new Vec3(16, 16, 0);
// this.spriteEnt.sprite.setTexture('ui/minogram.png');
}
CubeScene.prototype = Object.create(Scene.prototype);
CubeScene.prototype.constructor = CubeScene;
CubeScene.prototype.update = function() {
this.cube.update();
};
CubeScene.prototype.dispose = function() {
this.cam.dispose();
this.cube.dispose();
this.spriteEnt.dispose();
};
module = CubeScene;
+25
View File
@@ -0,0 +1,25 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
/**
* Wii/Dolphin overrides for mbedtls 4.x.
* Used as both MBEDTLS_USER_CONFIG_FILE and TF_PSA_CRYPTO_USER_CONFIG_FILE,
* so intentionally has no include guard (operations are idempotent).
*/
/* Disable the mbedtls TCP/socket layer — Wii has no Unix/Windows sockets.
* We provide our own socket implementation via networksocket.c. */
#undef MBEDTLS_NET_C
/* Disable built-in Unix/Windows entropy; use driver-provided entropy instead.
* We implement mbedtls_platform_get_entropy() in networktls.c. */
#undef MBEDTLS_PSA_BUILTIN_GET_ENTROPY
#define MBEDTLS_PSA_DRIVER_GET_ENTROPY
/* Disable the built-in ms_time; we implement mbedtls_ms_time() via OGC
* timer (gettime / ticks_to_millisecs) in networktls.c. */
#define MBEDTLS_PLATFORM_MS_TIME_ALT
-96
View File
@@ -1,96 +0,0 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Turn things off we don't need
set(JERRY_CMDLINE OFF CACHE BOOL "" FORCE)
set(JERRY_EXT ON CACHE BOOL "" FORCE)
set(JERRY_DEBUGGER OFF CACHE BOOL "" FORCE)
set(JERRY_BUILTIN_DATE OFF CACHE BOOL "" FORCE)
set(ENABLE_LTO OFF CACHE BOOL "" FORCE)
# Fetch Jerry
include(FetchContent)
FetchContent_Declare(
jerryscript
GIT_REPOSITORY https://git.wish.moe/YourWishes/jerryscript
GIT_TAG float32-fix
)
FetchContent_MakeAvailable(jerryscript)
# Mark found
set(jerryscript_FOUND ON)
# Define targets
if(TARGET jerryscript-core)
set(JERRY_CORE_TARGET jerryscript-core)
elseif(TARGET jerry-core)
set(JERRY_CORE_TARGET jerry-core)
endif()
if(TARGET jerryscript-ext)
set(JERRY_EXT_TARGET jerryscript-ext)
elseif(TARGET jerry-ext)
set(JERRY_EXT_TARGET jerry-ext)
endif()
if(TARGET jerryscript-port-default)
set(JERRY_PORT_TARGET jerryscript-port-default)
elseif(TARGET jerry-port-default)
set(JERRY_PORT_TARGET jerry-port-default)
elseif(TARGET jerryscript-port)
set(JERRY_PORT_TARGET jerryscript-port)
elseif(TARGET jerry-port)
set(JERRY_PORT_TARGET jerry-port)
endif()
if(NOT JERRY_CORE_TARGET)
message(FATAL_ERROR "JerryScript core target not found")
endif()
if(NOT JERRY_EXT_TARGET)
message(FATAL_ERROR "JerryScript ext target not found")
endif()
if(NOT JERRY_PORT_TARGET)
message(FATAL_ERROR "JerryScript port target not found")
endif()
foreach(tgt IN ITEMS
${JERRY_CORE_TARGET}
${JERRY_EXT_TARGET}
${JERRY_PORT_TARGET}
)
if(TARGET ${tgt})
set_property(TARGET ${tgt} PROPERTY INTERPROCEDURAL_OPTIMIZATION OFF)
target_compile_definitions(${JERRY_CORE_TARGET} PRIVATE
JERRY_NUMBER_TYPE_FLOAT64=0
JERRY_BUILTIN_DATE=0
)
endif()
endforeach()
# Export include dirs through the targets
target_include_directories(${JERRY_CORE_TARGET} INTERFACE
${jerryscript_SOURCE_DIR}/jerry-core/include
)
target_include_directories(${JERRY_EXT_TARGET} INTERFACE
${jerryscript_SOURCE_DIR}/jerry-ext/include
)
target_include_directories(${JERRY_PORT_TARGET} INTERFACE
${jerryscript_SOURCE_DIR}/jerry-port/default/include
)
# Suppress JerryScript-only warning
if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(${JERRY_CORE_TARGET} PRIVATE
-Wno-error
)
endif()
add_library(jerryscript::core ALIAS ${JERRY_CORE_TARGET})
add_library(jerryscript::ext ALIAS ${JERRY_EXT_TARGET})
add_library(jerryscript::port ALIAS ${JERRY_PORT_TARGET})
+201
View File
@@ -0,0 +1,201 @@
# Findmbedtls.cmake
#
# Usage:
# find_package(mbedtls REQUIRED)
#
# Optional cache variables the parent project may set before calling:
# MBEDTLS_FETCHCONTENT_VERSION e.g. "v3.6.4" or "mbedtls-4.1.0"
# MBEDTLS_FETCHCONTENT_GIT_REPOSITORY
# MBEDTLS_FETCHCONTENT_GIT_TAG
# MBEDTLS_FETCHCONTENT_BASE_DIR
# MBEDTLS_BUILD_SHARED ON/OFF
#
# Provided variables:
# mbedtls_FOUND
# MBEDTLS_FOUND
# MBEDTLS_INCLUDE_DIRS
# MBEDTLS_LIBRARIES
#
# Provided imported targets:
# MbedTLS::mbedtls
# MbedTLS::mbedx509
# MbedTLS::mbedcrypto
include_guard(GLOBAL)
include(FetchContent)
include(FindPackageHandleStandardArgs)
set(_MBEDTLS_DEFAULT_REPOSITORY "https://github.com/Mbed-TLS/mbedtls.git")
set(_MBEDTLS_DEFAULT_TAG "v4.1.0")
set(MBEDTLS_FETCHCONTENT_GIT_REPOSITORY
"${_MBEDTLS_DEFAULT_REPOSITORY}"
CACHE STRING "Git repository for fetching Mbed TLS")
if(DEFINED MBEDTLS_FETCHCONTENT_VERSION AND NOT DEFINED MBEDTLS_FETCHCONTENT_GIT_TAG)
set(MBEDTLS_FETCHCONTENT_GIT_TAG
"${MBEDTLS_FETCHCONTENT_VERSION}"
CACHE STRING "Git tag/branch/commit for fetching Mbed TLS")
endif()
set(MBEDTLS_FETCHCONTENT_GIT_TAG
"${MBEDTLS_FETCHCONTENT_GIT_TAG}"
CACHE STRING "Git tag/branch/commit for fetching Mbed TLS")
if(NOT MBEDTLS_FETCHCONTENT_GIT_TAG)
set(MBEDTLS_FETCHCONTENT_GIT_TAG "${_MBEDTLS_DEFAULT_TAG}" CACHE STRING "" FORCE)
endif()
option(MBEDTLS_BUILD_SHARED "Build Mbed TLS shared libraries" OFF)
# 1) Prefer an installed package config if available.
find_package(MbedTLS CONFIG QUIET)
if(TARGET MbedTLS::mbedtls AND TARGET MbedTLS::mbedx509 AND TARGET MbedTLS::mbedcrypto)
set(mbedtls_FOUND TRUE)
set(MBEDTLS_FOUND TRUE)
set(MBEDTLS_LIBRARIES
MbedTLS::mbedtls
MbedTLS::mbedx509
MbedTLS::mbedcrypto)
set(MBEDTLS_INCLUDE_DIRS "")
return()
endif()
# 2) If upstream exported plain targets instead of namespaced ones, alias them.
if(TARGET mbedtls AND TARGET mbedx509 AND TARGET mbedcrypto)
if(NOT TARGET MbedTLS::mbedtls)
add_library(MbedTLS::mbedtls INTERFACE IMPORTED)
target_link_libraries(MbedTLS::mbedtls INTERFACE mbedtls)
endif()
if(NOT TARGET MbedTLS::mbedx509)
add_library(MbedTLS::mbedx509 INTERFACE IMPORTED)
target_link_libraries(MbedTLS::mbedx509 INTERFACE mbedx509)
endif()
if(NOT TARGET MbedTLS::mbedcrypto)
add_library(MbedTLS::mbedcrypto INTERFACE IMPORTED)
target_link_libraries(MbedTLS::mbedcrypto INTERFACE mbedcrypto)
endif()
set(mbedtls_FOUND TRUE)
set(MBEDTLS_FOUND TRUE)
set(MBEDTLS_LIBRARIES
MbedTLS::mbedtls
MbedTLS::mbedx509
MbedTLS::mbedcrypto)
set(MBEDTLS_INCLUDE_DIRS "")
return()
endif()
# 3) Fetch and build Mbed TLS.
# Upstream options:
# - USE_STATIC_MBEDTLS_LIBRARY / USE_SHARED_MBEDTLS_LIBRARY
# - ENABLE_PROGRAMS / ENABLE_TESTING
# - MBEDTLS_AS_SUBPROJECT / DISABLE_PACKAGE_CONFIG_AND_INSTALL
# - MBEDTLS_TARGET_PREFIX
#
# These are supported by the upstream CMake build. :contentReference[oaicite:1]{index=1}
set(FETCHCONTENT_QUIET FALSE)
if(MBEDTLS_FETCHCONTENT_BASE_DIR)
set(FETCHCONTENT_BASE_DIR "${MBEDTLS_FETCHCONTENT_BASE_DIR}")
endif()
# Avoid polluting the parent build and skip extras we usually do not want.
set(ENABLE_PROGRAMS OFF CACHE BOOL "" FORCE)
set(ENABLE_TESTING OFF CACHE BOOL "" FORCE)
set(DISABLE_PACKAGE_CONFIG_AND_INSTALL ON CACHE BOOL "" FORCE)
set(MBEDTLS_AS_SUBPROJECT ON CACHE BOOL "" FORCE)
set(MBEDTLS_TARGET_PREFIX "" CACHE STRING "" FORCE)
if(MBEDTLS_BUILD_SHARED)
set(USE_SHARED_MBEDTLS_LIBRARY ON CACHE BOOL "" FORCE)
set(USE_STATIC_MBEDTLS_LIBRARY OFF CACHE BOOL "" FORCE)
else()
set(USE_SHARED_MBEDTLS_LIBRARY OFF CACHE BOOL "" FORCE)
set(USE_STATIC_MBEDTLS_LIBRARY ON CACHE BOOL "" FORCE)
endif()
FetchContent_Declare(
mbedtls_fc
GIT_REPOSITORY "${MBEDTLS_FETCHCONTENT_GIT_REPOSITORY}"
GIT_TAG "${MBEDTLS_FETCHCONTENT_GIT_TAG}"
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(mbedtls_fc)
# 4) Normalize targets across upstream versions.
#
# Mbed TLS 3.x:
# mbedtls, mbedx509, mbedcrypto
#
# Mbed TLS 4.x:
# mbedtls, mbedx509, tfpsacrypto
#
# Map everything to stable namespaced targets for the consumer. :contentReference[oaicite:2]{index=2}
set(_mbedtls_tls_target "")
set(_mbedtls_x509_target "")
set(_mbedtls_crypto_target "")
if(TARGET mbedtls)
set(_mbedtls_tls_target mbedtls)
elseif(TARGET MbedTLS::mbedtls)
set(_mbedtls_tls_target MbedTLS::mbedtls)
endif()
if(TARGET mbedx509)
set(_mbedtls_x509_target mbedx509)
elseif(TARGET MbedTLS::mbedx509)
set(_mbedtls_x509_target MbedTLS::mbedx509)
endif()
if(TARGET mbedcrypto)
set(_mbedtls_crypto_target mbedcrypto)
elseif(TARGET tfpsacrypto)
set(_mbedtls_crypto_target tfpsacrypto)
elseif(TARGET MbedTLS::mbedcrypto)
set(_mbedtls_crypto_target MbedTLS::mbedcrypto)
endif()
if(_mbedtls_tls_target AND NOT TARGET MbedTLS::mbedtls)
add_library(MbedTLS::mbedtls INTERFACE IMPORTED)
target_link_libraries(MbedTLS::mbedtls INTERFACE "${_mbedtls_tls_target}")
endif()
if(_mbedtls_x509_target AND NOT TARGET MbedTLS::mbedx509)
add_library(MbedTLS::mbedx509 INTERFACE IMPORTED)
target_link_libraries(MbedTLS::mbedx509 INTERFACE "${_mbedtls_x509_target}")
endif()
if(_mbedtls_crypto_target AND NOT TARGET MbedTLS::mbedcrypto)
add_library(MbedTLS::mbedcrypto INTERFACE IMPORTED)
target_link_libraries(MbedTLS::mbedcrypto INTERFACE "${_mbedtls_crypto_target}")
endif()
find_package_handle_standard_args(
mbedtls
REQUIRED_VARS
_mbedtls_tls_target
_mbedtls_x509_target
_mbedtls_crypto_target
)
if(mbedtls_FOUND)
set(MBEDTLS_FOUND TRUE)
set(MBEDTLS_LIBRARIES
MbedTLS::mbedtls
MbedTLS::mbedx509
MbedTLS::mbedcrypto)
# Best-effort include directory discovery for legacy consumers.
get_target_property(_mbedtls_inc "${_mbedtls_tls_target}" INTERFACE_INCLUDE_DIRECTORIES)
if(_mbedtls_inc)
set(MBEDTLS_INCLUDE_DIRS "${_mbedtls_inc}")
else()
set(MBEDTLS_INCLUDE_DIRS "")
endif()
endif()
+36 -1
View File
@@ -1,10 +1,23 @@
# mbedtls/tf-psa-crypto user config overrides for Wii/Dolphin.
# Both variables point to the same file; it is included after each library's
# default config header, so #undef/#define are applied on top of the defaults.
# Must be set before find_package(mbedtls) so FetchContent picks them up.
set(TF_PSA_CRYPTO_USER_CONFIG_FILE
"${CMAKE_SOURCE_DIR}/cmake/mbedtls_dolphin_config.h"
CACHE FILEPATH "tf-psa-crypto user config for Wii/Dolphin" FORCE)
set(MBEDTLS_USER_CONFIG_FILE
"${CMAKE_SOURCE_DIR}/cmake/mbedtls_dolphin_config.h"
CACHE FILEPATH "mbedtls user config for Wii/Dolphin" FORCE)
# Target definitions
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_DOLPHIN
DUSK_INPUT_GAMEPAD
DUSK_DISPLAY_WIDTH=640
DUSK_DISPLAY_HEIGHT=480
DUSK_THREAD_PTHREAD
MBEDTLS_PSA_DRIVER_GET_ENTROPY
MBEDTLS_PLATFORM_MS_TIME_ALT
THREAD_PTHREAD=1
)
# Custom compiler flags
@@ -22,9 +35,31 @@ set(CGLM_SHARED OFF CACHE BOOL "Build cglm shared" FORCE)
set(CGLM_STATIC ON CACHE BOOL "Build cglm static" FORCE)
find_package(cglm REQUIRED)
# Compile lua
include(FetchContent)
FetchContent_Declare(
liblua
URL https://www.lua.org/ftp/lua-5.5.0.tar.gz
)
FetchContent_MakeAvailable(liblua)
set(LUA_SRC_DIR "${liblua_SOURCE_DIR}/src")
set(LUA_C_FILES
lapi.c lauxlib.c lbaselib.c lcode.c lcorolib.c lctype.c ldblib.c ldebug.c
ldo.c ldump.c lfunc.c lgc.c linit.c liolib.c llex.c lmathlib.c lmem.c
loadlib.c lobject.c lopcodes.c loslib.c lparser.c lstate.c lstring.c
lstrlib.c ltable.c ltablib.c ltm.c lundump.c lutf8lib.c lvm.c lzio.c
)
list(TRANSFORM LUA_C_FILES PREPEND "${LUA_SRC_DIR}/")
add_library(liblua STATIC ${LUA_C_FILES})
target_include_directories(liblua PUBLIC "${LUA_SRC_DIR}")
target_compile_definitions(liblua PRIVATE LUA_USE_C89)
add_library(lua::lua ALIAS liblua)
set(Lua_FOUND TRUE CACHE BOOL "Lua found" FORCE)
# Link libraries
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
cglm
liblua
m
fat
PkgConfig::zip
+5 -2
View File
@@ -26,11 +26,14 @@ target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
# CURL::libcurl
)
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
${MBEDTLS_INCLUDE_DIR}
)
# Define platform-specific macros.
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_SDL2
DUSK_OPENGL
DUSK_CONSOLE_POSIX
# DUSK_OPENGL_LEGACY
DUSK_LINUX
DUSK_DISPLAY_SIZE_DYNAMIC
@@ -42,5 +45,5 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_INPUT_GAMEPAD
DUSK_TIME_DYNAMIC
DUSK_NETWORK_IPV6
DUSK_THREAD_PTHREAD
THREAD_PTHREAD=1
)
+4 -21
View File
@@ -1,19 +1,6 @@
# Fixes some problems building JerryScript
set(CMAKE_AR "$ENV{PSPDEV}/bin/psp-ar" CACHE FILEPATH "" FORCE)
set(CMAKE_RANLIB "$ENV{PSPDEV}/bin/psp-ranlib" CACHE FILEPATH "" FORCE)
set(CMAKE_C_COMPILER_AR "$ENV{PSPDEV}/bin/psp-ar" CACHE FILEPATH "" FORCE)
set(CMAKE_C_COMPILER_RANLIB "$ENV{PSPDEV}/bin/psp-ranlib" CACHE FILEPATH "" FORCE)
set(CMAKE_C_ARCHIVE_CREATE "$ENV{PSPDEV}/bin/psp-ar qc <TARGET> <LINK_FLAGS> <OBJECTS>")
set(CMAKE_C_ARCHIVE_APPEND "$ENV{PSPDEV}/bin/psp-ar q <TARGET> <LINK_FLAGS> <OBJECTS>")
set(CMAKE_C_ARCHIVE_FINISH "$ENV{PSPDEV}/bin/psp-ranlib <TARGET>")
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION OFF CACHE BOOL "" FORCE)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_C OFF CACHE BOOL "" FORCE)
set(JERRY_LTO OFF CACHE BOOL "" FORCE)
find_package(jerryscript REQUIRED)
find_package(SDL2 REQUIRED)
find_package(OpenGL REQUIRED)
target_link_libraries(${DUSK_BINARY_TARGET_NAME} PUBLIC
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
${SDL2_LIBRARIES}
SDL2
pthread
@@ -42,17 +29,13 @@ target_link_libraries(${DUSK_BINARY_TARGET_NAME} PUBLIC
pspnet_apctl
psphttp
pspssl
jerryscript::core
jerryscript::ext
jerryscript::port
)
target_include_directories(${DUSK_BINARY_TARGET_NAME} PRIVATE
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
${SDL2_INCLUDE_DIRS}
)
target_compile_definitions(${DUSK_BINARY_TARGET_NAME} PUBLIC
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_SDL2
DUSK_OPENGL
DUSK_PSP
@@ -61,7 +44,7 @@ target_compile_definitions(${DUSK_BINARY_TARGET_NAME} PUBLIC
DUSK_OPENGL_LEGACY
DUSK_DISPLAY_WIDTH=480
DUSK_DISPLAY_HEIGHT=272
DUSK_THREAD_PTHREAD
THREAD_PTHREAD=1
)
# Postbuild, create .pbp file for PSP.
+22
View File
@@ -20,9 +20,31 @@ set(CGLM_SHARED OFF CACHE BOOL "Build cglm shared" FORCE)
set(CGLM_STATIC ON CACHE BOOL "Build cglm static" FORCE)
find_package(cglm REQUIRED)
# Compile lua
include(FetchContent)
FetchContent_Declare(
liblua
URL https://www.lua.org/ftp/lua-5.5.0.tar.gz
)
FetchContent_MakeAvailable(liblua)
set(LUA_SRC_DIR "${liblua_SOURCE_DIR}/src")
set(LUA_C_FILES
lapi.c lauxlib.c lbaselib.c lcode.c lcorolib.c lctype.c ldblib.c ldebug.c
ldo.c ldump.c lfunc.c lgc.c linit.c liolib.c llex.c lmathlib.c lmem.c
loadlib.c lobject.c lopcodes.c loslib.c lparser.c lstate.c lstring.c
lstrlib.c ltable.c ltablib.c ltm.c lundump.c lutf8lib.c lvm.c lzio.c
)
list(TRANSFORM LUA_C_FILES PREPEND "${LUA_SRC_DIR}/")
add_library(liblua STATIC ${LUA_C_FILES})
target_include_directories(liblua PUBLIC "${LUA_SRC_DIR}")
target_compile_definitions(liblua PRIVATE LUA_USE_C89)
add_library(lua::lua ALIAS liblua)
set(Lua_FOUND TRUE CACHE BOOL "Lua found" FORCE)
# Link libraries
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
${SDL2_LIBRARIES}
liblua
cglm
SDL2
SDL2main
+1 -1
View File
@@ -1,6 +1,6 @@
FROM devkitpro/devkitppc
WORKDIR /workdir
RUN apt update && \
apt install -y python3 python3-pip python3-polib python3-pil python3-dotenv python3-pyqt5 python3-opengl && \
apt install -y python3 python3-pip python3-polib python3-pil python3-dotenv python3-pyqt5 python3-opengl python3-jsonschema python3-jinja2 python3-jsonschema && \
dkp-pacman -S --needed --noconfirm gamecube-sdl2 ppc-liblzma ppc-libzip
VOLUME ["/workdir"]
+23 -9
View File
@@ -32,13 +32,28 @@ if(NOT yyjson_FOUND)
endif()
endif()
if(NOT jerryscript_FOUND)
find_package(jerryscript REQUIRED)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
jerryscript::core
jerryscript::ext
jerryscript::port
)
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)
endif()
if(NOT mbedtls_FOUND)
find_package(mbedtls REQUIRED)
if(mbedtls_FOUND)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC ${MBEDTLS_LIBRARIES})
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PUBLIC ${MBEDTLS_INCLUDE_DIRS})
else()
message(FATAL_ERROR "mbedtls not found. Please ensure mbedtls is correctly installed.")
endif()
endif()
# Includes
@@ -56,9 +71,8 @@ target_sources(${DUSK_BINARY_TARGET_NAME}
# Subdirs
add_subdirectory(assert)
add_subdirectory(asset)
add_subdirectory(console)
add_subdirectory(display)
add_subdirectory(log)
add_subdirectory(display)
add_subdirectory(engine)
add_subdirectory(entity)
add_subdirectory(error)
+2 -2
View File
@@ -25,7 +25,7 @@ errorret_t assetInit(void) {
}
bool_t assetFileExists(const char_t *filename) {
assertStrLenMax(filename, ASSET_FILE_PATH_MAX, "Filename too long.");
assertStrLenMax(filename, FILENAME_MAX, "Filename too long.");
zip_int64_t idx = zip_name_locate(ASSET.zip, filename, 0);
if(idx < 0) return false;
@@ -38,7 +38,7 @@ errorret_t assetLoad(
void *params,
void *output
) {
assertStrLenMax(filename, ASSET_FILE_PATH_MAX, "Filename too long.");
assertStrLenMax(filename, FILENAME_MAX, "Filename too long.");
assertNotNull(output, "Output pointer cannot be NULL.");
assertNotNull(loader, "Asset file loader cannot be NULL.");
-2
View File
@@ -9,8 +9,6 @@
#include "error/error.h"
#include <zip.h>
#define ASSET_FILE_PATH_MAX FILENAME_MAX
typedef struct assetfile_s assetfile_t;
typedef errorret_t (*assetfileloader_t)(assetfile_t *file);
@@ -1,92 +1,82 @@
/**
* 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 before open");
assertNull(file->zipFile, "Asset file zip handle must be NULL");
assertNotNull(file->output, "Asset file output cannot be NULL");
assetscript_t *script = (assetscript_t *)file->output;
// Open the asset for buffering
errorChain(assetFileOpen(file));
// 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);
// 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);
}
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);
// 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);
}
if(script->resultOut != NULL) {
*(script->resultOut) = result;
} else {
jerry_value_free(result);
}
return closeRet;
// Close the file
return assetFileClose(file);
}
errorret_t assetScriptLoad(
const char_t *path,
jerry_value_t *resultOut
) {
errorret_t assetScriptLoad(const char_t *path, scriptcontext_t *ctx) {
assertNotNull(path, "Script path cannot be NULL");
assertNotNull(ctx, "Script context cannot be NULL");
assetscript_t script;
script.ctx = ctx;
assetscript_t scriptData;
scriptData.resultOut = resultOut;
return assetLoad(path, assetScriptLoader, NULL, &scriptData);
return assetLoad(
path,
assetScriptLoader,
NULL,
&script
);
}
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,23 +1,28 @@
/**
* 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 "script/scriptcontext.h"
#define ASSET_SCRIPT_CHUNK_SIZE 1024
#define ASSET_SCRIPT_BUFFER_SIZE 1024
typedef struct {
jerry_value_t *resultOut;
void *nothing;
} assetscriptloaderparams_t;
typedef struct {
scriptcontext_t *ctx;
char_t buffer[ASSET_SCRIPT_BUFFER_SIZE];
} assetscript_t;
/**
* Handler for script assets. Reads the full source, evaluates it with
* JerryScript, and optionally stores the result.
*
* Handler for script assets.
*
* @param file Asset file to load the script from.
* @return Any error that occurs during loading.
*/
@@ -25,14 +30,19 @@ errorret_t assetScriptLoader(assetfile_t *file);
/**
* Loads a script from the specified path.
*
* @param path Path to the script asset.
* @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.
*
* @param path Path to the script asset.
* @param ctx Script context to load the script into.
* @return Any error that occurs during loading.
*/
errorret_t assetScriptLoad(
const char_t *path,
jerry_value_t *resultOut
);
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);
-193
View File
@@ -1,193 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "console.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "util/string.h"
#include "input/input.h"
#include "log/log.h"
#include "engine/engine.h"
#include "script/scriptmanager.h"
#include "display/shader/shaderunlit.h"
#include "display/text/text.h"
#include "display/spritebatch/spritebatch.h"
console_t CONSOLE;
void consoleInit(void) {
memoryZero(&CONSOLE, sizeof(console_t));
#ifdef DUSK_CONSOLE_POSIX
threadInit(&CONSOLE.thread, consoleInputThread);
threadMutexInit(&CONSOLE.execMutex);
threadMutexInit(&CONSOLE.printMutex);
threadStartRequest(&CONSOLE.thread);
#endif
}
void consolePrint(const char_t *message, ...) {
char_t buffer[CONSOLE_LINE_MAX];
va_list args;
va_start(args, message);
int32_t len = stringFormatVA(buffer, CONSOLE_LINE_MAX, message, args);
va_end(args);
#ifdef DUSK_CONSOLE_POSIX
threadMutexLock(&CONSOLE.printMutex);
#endif
memoryMove(
CONSOLE.line[0],
CONSOLE.line[1],
(CONSOLE_HISTORY_MAX - 1) * CONSOLE_LINE_MAX
);
memoryCopy(CONSOLE.line[CONSOLE_HISTORY_MAX - 1], buffer, len + 1);
#ifdef DUSK_CONSOLE_POSIX
threadMutexUnlock(&CONSOLE.printMutex);
#endif
logDebug("%s\n", buffer);
}
void consoleExec(const char_t *line) {
assertNotNull(line, "line must not be NULL");
#ifdef DUSK_CONSOLE_POSIX
threadMutexLock(&CONSOLE.execMutex);
#endif
assertTrue(
CONSOLE.execBufferCount < CONSOLE_EXEC_BUFFER_MAX,
"Console exec buffer is full"
);
stringCopy(
CONSOLE.execBuffer[CONSOLE.execBufferCount],
line,
CONSOLE_LINE_MAX
);
CONSOLE.execBufferCount++;
#ifdef DUSK_CONSOLE_POSIX
threadMutexUnlock(&CONSOLE.execMutex);
#endif
}
void consoleUpdate(void) {
#ifdef DUSK_TIME_DYNAMIC
if(TIME.dynamicUpdate) return;
#endif
if(inputPressed(INPUT_ACTION_CONSOLE)) {
CONSOLE.visible = !CONSOLE.visible;
}
if(CONSOLE.execBufferCount == 0) return;
#ifdef DUSK_CONSOLE_POSIX
threadMutexLock(&CONSOLE.execMutex);
#endif
char_t execBuffer[CONSOLE_EXEC_BUFFER_MAX][CONSOLE_LINE_MAX];
uint32_t execBufferCount = CONSOLE.execBufferCount;
memoryCopy(execBuffer, CONSOLE.execBuffer, sizeof(execBuffer));
CONSOLE.execBufferCount = 0;
#ifdef DUSK_CONSOLE_POSIX
threadMutexUnlock(&CONSOLE.execMutex);
#endif
for(uint32_t i = 0; i < execBufferCount; i++) {
jerry_value_t result = 0;
errorret_t err = scriptManagerExec(execBuffer[i], &result);
if(err.code != ERROR_OK) {
consolePrint("Error: %s", err.state->message);
errorCatch(err);
} else if(!jerry_value_is_undefined(result)) {
jerry_value_t strVal = jerry_value_to_string(result);
char_t buf[CONSOLE_LINE_MAX];
jerry_size_t len = jerry_string_to_buffer(
strVal, JERRY_ENCODING_UTF8, (jerry_char_t*)buf, sizeof(buf) - 1
);
buf[len] = '\0';
jerry_value_free(strVal);
consolePrint("%s", buf);
}
if(result != 0) jerry_value_free(result);
}
}
errorret_t consoleDraw(void) {
if(!CONSOLE.visible) errorOk();
for(uint32_t i = 0; i < CONSOLE_HISTORY_MAX; i++) {
errorChain(textDraw(
0, FONT_TILESET_DEFAULT.tileHeight * i,
CONSOLE.line[i],
COLOR_WHITE,
&FONT_TILESET_DEFAULT,
&FONT_TEXTURE_DEFAULT
));
}
errorChain(spriteBatchFlush());
errorOk();
}
void consoleDispose(void) {
#ifdef DUSK_CONSOLE_POSIX
threadStop(&CONSOLE.thread);
threadMutexDispose(&CONSOLE.execMutex);
threadMutexDispose(&CONSOLE.printMutex);
#endif
}
#ifdef DUSK_CONSOLE_POSIX
void consoleInputThread(thread_t *thread) {
assertNotNull(thread, "Thread cannot be NULL.");
char_t line[CONSOLE_LINE_MAX];
struct pollfd pfd = {
.fd = STDIN_FILENO,
.events = POLLIN
};
while(!threadShouldStop(thread) && ENGINE.running) {
int32_t rc = poll(&pfd, 1, CONSOLE_POSIX_POLL_RATE);
if(rc == 0) continue;
if(rc < 0) {
if(errno == EINTR) continue;
assertUnreachable("poll() failed with unexpected error.");
}
if(pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) break;
if(!(pfd.revents & POLLIN)) {
pfd.revents = 0;
continue;
}
if(!fgets(line, CONSOLE_LINE_MAX, stdin)) {
if(feof(stdin)) break;
clearerr(stdin);
continue;
}
size_t len = strlen(line);
while(len && (line[len - 1] == '\n' || line[len - 1] == '\r')) {
line[--len] = '\0';
}
if(len > 0) consoleExec(line);
pfd.revents = 0;
}
}
#endif
-79
View File
@@ -1,79 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "consoledefs.h"
#include "error/error.h"
#include "dusk.h"
#ifdef DUSK_CONSOLE_POSIX
#include "thread/thread.h"
#include <poll.h>
#include <unistd.h>
#define CONSOLE_POSIX_POLL_RATE 75
#endif
typedef struct {
char_t line[CONSOLE_HISTORY_MAX][CONSOLE_LINE_MAX];
bool_t visible;
char_t execBuffer[CONSOLE_EXEC_BUFFER_MAX][CONSOLE_LINE_MAX];
uint32_t execBufferCount;
#ifdef DUSK_CONSOLE_POSIX
thread_t thread;
threadmutex_t execMutex;
threadmutex_t printMutex;
#endif
} console_t;
extern console_t CONSOLE;
/**
* Initializes the console.
*/
void consoleInit(void);
/**
* Prints a message to the console history.
*
* @param message The message to print (printf-style).
*/
void consolePrint(const char_t *message, ...);
/**
* Queues a JS string for execution on the main thread. Thread-safe.
*
* @param line The JS source line to execute.
*/
void consoleExec(const char_t *line);
/**
* Processes pending queued script lines. Call once per frame from main thread.
*/
void consoleUpdate(void);
/**
* Renders the console history to the screen (UI space).
*
* @return The error return value.
*/
errorret_t consoleDraw(void);
/**
* Disposes of the console.
*/
void consoleDispose(void);
#ifdef DUSK_CONSOLE_POSIX
/**
* Input thread handler for POSIX stdin.
*
* @param thread The thread that is running.
*/
void consoleInputThread(thread_t *thread);
#endif
-12
View File
@@ -1,12 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#define CONSOLE_LINE_MAX 512
#define CONSOLE_HISTORY_MAX 16
#define CONSOLE_EXEC_BUFFER_MAX 32
+2
View File
@@ -25,6 +25,8 @@
#include "display/shader/shaderunlit.h"
#include "time/time.h"
#include "script/module/display/moduleshader.h"
display_t DISPLAY = { 0 };
errorret_t displayInit(void) {
+5 -5
View File
@@ -13,19 +13,19 @@
#include "asset/loader/display/assettilesetloader.h"
#include "display/shader/shaderunlit.h"
texture_t FONT_TEXTURE_DEFAULT;
tileset_t FONT_TILESET_DEFAULT;
texture_t DEFAULT_FONT_TEXTURE;
tileset_t DEFAULT_FONT_TILESET;
errorret_t textInit(void) {
errorChain(assetTextureLoad(
"ui/minogram.png", &FONT_TEXTURE_DEFAULT, TEXTURE_FORMAT_RGBA
"ui/minogram.png", &DEFAULT_FONT_TEXTURE, TEXTURE_FORMAT_RGBA
));
errorChain(assetTilesetLoad("ui/minogram.dtf", &FONT_TILESET_DEFAULT));
errorChain(assetTilesetLoad("ui/minogram.dtf", &DEFAULT_FONT_TILESET));
errorOk();
}
errorret_t textDispose(void) {
errorChain(textureDispose(&FONT_TEXTURE_DEFAULT));
errorChain(textureDispose(&DEFAULT_FONT_TEXTURE));
errorOk();
}
+2 -2
View File
@@ -12,8 +12,8 @@
#define TEXT_CHAR_START '!'
extern texture_t FONT_TEXTURE_DEFAULT;
extern tileset_t FONT_TILESET_DEFAULT;
extern texture_t DEFAULT_FONT_TEXTURE;
extern tileset_t DEFAULT_FONT_TILESET;
/**
* Initializes the text system.
-1
View File
@@ -21,7 +21,6 @@
#include <cglm/cglm.h>
#include <cglm/types.h>
#include <cglm/vec2.h>
#include <jerryscript.h>
#include "duskplatform.h"
+182 -22
View File
@@ -21,20 +21,118 @@
#include "game/game.h"
#include "physics/physicsmanager.h"
#include "network/network.h"
#include "network/networkinfo.h"
#include "system/system.h"
#include "console/console.h"
double jerry_port_current_time(void) {
dusktimeepoch_t epoch = timeGetEpoch();
return epoch.time * 1000.0;
}
#include "network/httpclient.h"
int32_t jerry_port_local_tza(double unix_ms) {
(void) unix_ms;
return 0;
}
#include "display/mesh/cube.h"
#include "display/mesh/plane.h"
engine_t ENGINE;
entityid_t phBoxEnt;
componentid_t phBoxPhys;
float_t onlineSwapTime = FLT_MAX;
void goOnline();
void goOffline();
void onGETComplete(httpclient_t *client, void *user) {
sceneLog("GET request complete!\n");
sceneLog("Response status: %u\n", client->statusCode);
for(size_t i = 0; i < client->responseHeaderCount; i++) {
sceneLog("Header: %s: %s\n", client->responseHeaders[i].name, client->responseHeaders[i].value);
}
}
void onGETError(httpclient_t *client, errorret_t err, void *user) {
errorCatch(errorPrint(err));
}
void onNetworkConnected(void *user) {
onlineSwapTime = TIME.time + 3.0f;
networkinfo_t info = networkGetInfo();
if(info.type == NETWORK_TYPE_IPV4) {
sceneLog(
"Connected to network with IPv4 address: " NETWORK_INFO_FORMAT_IPV4 "\n",
info.ipv4.ip[0], info.ipv4.ip[1], info.ipv4.ip[2], info.ipv4.ip[3]
);
#ifdef DUSK_NETWORK_IPV6
} else if(info.type == NETWORK_TYPE_IPV6) {
sceneLog(
"Connected to network with IPv6 address: " NETWORK_INFO_FORMAT_IPV6 "\n",
info.ipv6.ip[0], info.ipv6.ip[1], info.ipv6.ip[2], info.ipv6.ip[3],
info.ipv6.ip[4], info.ipv6.ip[5], info.ipv6.ip[6], info.ipv6.ip[7],
info.ipv6.ip[8], info.ipv6.ip[9], info.ipv6.ip[10], info.ipv6.ip[11],
info.ipv6.ip[12], info.ipv6.ip[13], info.ipv6.ip[14], info.ipv6.ip[15]
);
#endif
}
char_t *domain = "https://google.com";
sceneLog("Online, sending GET to %s...\n", domain);
httpclient_t client;
errorret_t ret;
ret = httpclientInit(&client);
if(ret.code != ERROR_OK) {
errorCatch(errorPrint(ret));
return;
}
httpclientRequest(
&client,
"GET",
domain,
false,
NULL,
NULL,
onGETComplete,
onGETError,
NULL
);
ret = httpclientDispose(&client);
if(ret.code != ERROR_OK) {
errorCatch(errorPrint(ret));
return;
}
// sceneLog("Network connected, I will disconnect at: %.2f1.\n", onlineSwapTime);
}
void onNetworkFailed(errorret_t error, void *user) {
onlineSwapTime = TIME.time + 3.0f;
sceneLog("Failed to connect to network, will try again at %.2f1.\n", onlineSwapTime);
}
void onNetworkDisconnected(errorret_t error, void *user) {
onlineSwapTime = TIME.time + 3.0f;
sceneLog("Network disconnected, will go online at %.2f1.\n", onlineSwapTime);
errorCatch(errorPrint(error));
}
void onNetworkDisconnectFinished(void *user) {
onlineSwapTime = TIME.time + 3.0f;
sceneLog("Finished disconnecting from network, will go online at %.2f1.\n", onlineSwapTime);
}
void goOnline() {
sceneLog("Going online...\n");
networkRequestConnection(
onNetworkConnected,
onNetworkFailed,
onNetworkDisconnected,
NULL
);
}
void goOffline() {
sceneLog("Going offline...\n");
networkRequestDisconnection(onNetworkDisconnectFinished, NULL);
}
errorret_t engineInit(const int32_t argc, const char_t **argv) {
memoryZero(&ENGINE, sizeof(engine_t));
@@ -45,7 +143,6 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
// Init systems. Order is important.
errorChain(systemInit());
timeInit();
consoleInit();
errorChain(inputInit());
errorChain(assetInit());
errorChain(localeManagerInit());
@@ -55,32 +152,97 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
errorChain(sceneInit());
entityManagerInit();
physicsManagerInit();
errorChain(networkInit());
// errorChain(networkInit());
errorChain(gameInit());
sceneLog("Init done, going to queue online in 1 seconds...\n");
onlineSwapTime = TIME.time + 1.0f;
// Camera
entityid_t cam = entityManagerAdd();
componentid_t camPos = entityAddComponent(cam, COMPONENT_TYPE_POSITION);
float_t distance = 6.0f;
entityPositionLookAt(
cam, camPos,
(vec3){ 0.0f, 1.0f, 0.0f },
(vec3){ 0.0f, 1.0f, 0.0f },
(vec3){ distance, distance, distance }
);
componentid_t camCam = entityAddComponent(cam, COMPONENT_TYPE_CAMERA);
entityCameraSetZFar(cam, camCam, 100.0f);
// Floor
entityid_t floorEnt = entityManagerAdd();
componentid_t floorPos = entityAddComponent(floorEnt, COMPONENT_TYPE_POSITION);
componentid_t floorMesh = entityAddComponent(floorEnt, COMPONENT_TYPE_MESH);
componentid_t floorMat = entityAddComponent(floorEnt, COMPONENT_TYPE_MATERIAL);
componentid_t floorPhys = entityAddComponent(floorEnt, COMPONENT_TYPE_PHYSICS);
entityPositionSetPosition(floorEnt, floorPos, (vec3){ -5.0f, 0.0f, -5.0f });
entityPositionSetScale(floorEnt, floorPos, (vec3){ 10.0f, 1.0f, 10.0f });
entityMeshSetMesh(floorEnt, floorMesh, &PLANE_MESH_SIMPLE);
entityMaterialGetShaderMaterial(floorEnt, floorMat)->unlit.color = COLOR_GREEN;
entityphysics_t *floorPhysData = entityPhysicsGet(floorEnt, floorPhys);
floorPhysData->type = PHYSICS_BODY_STATIC;
floorPhysData->shape.type = PHYSICS_SHAPE_PLANE;
floorPhysData->shape.data.plane.normal[0] = 0.0f;
floorPhysData->shape.data.plane.normal[1] = 1.0f;
floorPhysData->shape.data.plane.normal[2] = 0.0f;
floorPhysData->shape.data.plane.distance = 0.0f;
// Box
phBoxEnt = entityManagerAdd();
componentid_t boxPos = entityAddComponent(phBoxEnt, COMPONENT_TYPE_POSITION);
componentid_t boxMesh = entityAddComponent(phBoxEnt, COMPONENT_TYPE_MESH);
componentid_t boxMat = entityAddComponent(phBoxEnt, COMPONENT_TYPE_MATERIAL);
phBoxPhys = entityAddComponent(phBoxEnt, COMPONENT_TYPE_PHYSICS);
entityMeshSetMesh(phBoxEnt, boxMesh, &CUBE_MESH_SIMPLE);
entityMaterialGetShaderMaterial(phBoxEnt, boxMat)->unlit.color = COLOR_RED;
entityPositionSetPosition(phBoxEnt, boxPos, (vec3){ 0.0f, 4.0f, 0.0f });
/* Run the init script. */
consolePrint("Engine initialized");
errorChain(scriptManagerExecFile("init.js", NULL));
scriptcontext_t ctx;
errorChain(scriptContextInit(&ctx));
errorChain(scriptContextExecFile(&ctx, "init.lua"));
scriptContextDispose(&ctx);
errorOk();
}
errorret_t engineUpdate(void) {
// Order here is important.
errorChain(networkUpdate());
// errorChain(networkUpdate());
timeUpdate();
inputUpdate();
consoleUpdate();
uiUpdate();
errorChain(sceneUpdate());
/* Reset the box to its start position on demand. */
if(inputIsDown(INPUT_ACTION_ACCEPT)) {
componentid_t posComp = entityGetComponent(phBoxEnt, COMPONENT_TYPE_POSITION);
entityPositionSetPosition(phBoxEnt, posComp, (vec3){ 0.0f, 4.0f, 0.0f });
entityPhysicsSetVelocity(phBoxEnt, phBoxPhys, (vec3){ 0.0f, 0.0f, 0.0f });
}
/* Step physics: positions are updated directly on POSITION components. */
physicsManagerUpdate();
errorChain(gameUpdate());
errorChain(displayUpdate());
if(inputPressed(INPUT_ACTION_RAGEQUIT)) ENGINE.running = false;
// Scene update occurs last because only after rendering would we want to do
// scene switching, refer to sceneSet() for information.
errorChain(sceneUpdate());
if(TIME.time >= onlineSwapTime) {
onlineSwapTime = FLT_MAX;
if(NETWORK.state == NETWORK_STATE_CONNECTED) {
goOffline();
} else {
goOnline();
}
}
errorOk();
}
@@ -90,15 +252,13 @@ void engineExit(void) {
}
errorret_t engineDispose(void) {
// errorChain(networkDispose());
sceneDispose();
errorChain(gameDispose());
errorChain(networkDispose());
entityManagerDispose();
localeManagerDispose();
uiDispose();
consoleDispose();
errorChain(displayDispose());
errorChain(assetDispose());
errorOk();
}
+2 -8
View File
@@ -12,14 +12,8 @@
componentdefinition_t COMPONENT_DEFINITIONS[] = {
[COMPONENT_TYPE_NULL] = { 0 },
#define X(enm, type, field, iMethod, dMethod) \
[COMPONENT_TYPE_##enm] = { \
.enumName = #enm, \
.name = #field, \
.init = iMethod, \
.dispose = dMethod \
},
#define X(enumName, type, field, iMethod, dMethod) \
[COMPONENT_TYPE_##enumName] = { .init = iMethod, .dispose = dMethod },
#include "componentlist.h"
#undef X
-2
View File
@@ -20,8 +20,6 @@ typedef union {
} componentdata_t;
typedef struct {
const char_t *enumName;
const char_t *name;
void (*init)(const entityid_t, const componentid_t);
void (*dispose)(const entityid_t, const componentid_t);
} componentdefinition_t;
+1 -2
View File
@@ -4,5 +4,4 @@
# https://opensource.org/licenses/MIT
add_subdirectory(display)
add_subdirectory(physics)
add_subdirectory(script)
add_subdirectory(physics)
@@ -6,8 +6,6 @@
*/
#include "entity/entitymanager.h"
#include "entity/entity.h"
#include "entity/component/display/entityposition.h"
#include "display/framebuffer/framebuffer.h"
#include "display/screen/screen.h"
@@ -21,6 +19,42 @@ void entityCameraInit(const entityid_t ent, const componentid_t comp) {
cam->perspective.fov = glm_rad(45.0f);
}
float_t entityCameraGetZNear(const entityid_t ent, const componentid_t comp) {
entitycamera_t *cam = (entitycamera_t *)componentGetData(
ent, comp, COMPONENT_TYPE_CAMERA
);
return cam->nearClip;
}
void entityCameraSetZNear(
const entityid_t ent,
const componentid_t comp,
const float_t zNear
) {
entitycamera_t *cam = (entitycamera_t *)componentGetData(
ent, comp, COMPONENT_TYPE_CAMERA
);
cam->nearClip = zNear;
}
float_t entityCameraGetZFar(const entityid_t ent, const componentid_t comp) {
entitycamera_t *cam = (entitycamera_t *)componentGetData(
ent, comp, COMPONENT_TYPE_CAMERA
);
return cam->farClip;
}
void entityCameraSetZFar(
const entityid_t ent,
const componentid_t comp,
const float_t zFar
) {
entitycamera_t *cam = (entitycamera_t *)componentGetData(
ent, comp, COMPONENT_TYPE_CAMERA
);
cam->farClip = zFar;
}
void entityCameraGetProjection(
const entityid_t ent,
const componentid_t comp,
@@ -58,38 +92,4 @@ void entityCameraGetProjection(
out
);
}
}
entityid_t entityCameraGetCurrent(void) {
entityid_t camEnts[ENTITY_COUNT_MAX];
componentid_t camComps[ENTITY_COUNT_MAX];
entityid_t count = componentGetEntitiesWithComponent(
COMPONENT_TYPE_CAMERA, camEnts, camComps
);
if(count == 0) return ENTITY_COUNT_MAX;
return camEnts[0];
}
void entityCameraGetForward(const entityid_t entityId, vec2 out) {
componentid_t posComp = entityGetComponent(entityId, COMPONENT_TYPE_POSITION);
entityposition_t *pos = entityPositionGet(entityId, posComp);
// View matrix column layout: M[col][row], forward = {-M[0][2], -M[1][2], -M[2][2]}
float_t fx = -pos->transform[0][2];
float_t fz = -pos->transform[2][2];
float_t len = sqrtf(fx * fx + fz * fz);
if(len > 1e-6f) { fx /= len; fz /= len; }
out[0] = fx;
out[1] = fz;
}
void entityCameraGetRight(const entityid_t entityId, vec2 out) {
componentid_t posComp = entityGetComponent(entityId, COMPONENT_TYPE_POSITION);
entityposition_t *pos = entityPositionGet(entityId, posComp);
// View matrix column layout: right = {M[0][0], M[1][0], M[2][0]}
float_t rx = pos->transform[0][0];
float_t rz = pos->transform[2][0];
float_t len = sqrtf(rx * rx + rz * rz);
if(len > 1e-6f) { rx /= len; rz /= len; }
out[0] = rx;
out[1] = rz;
}
@@ -55,24 +55,45 @@ void entityCameraGetProjection(
);
/**
* Returns the entity ID of the first active camera, or ENTITY_COUNT_MAX if none.
* Gets the near clip distance of a camera.
*
* @param ent The entity ID.
* @param comp The component ID.
* @return The near clip distance.
*/
entityid_t entityCameraGetCurrent(void);
float_t entityCameraGetZNear(const entityid_t ent, const componentid_t comp);
/**
* Gets the camera's horizontal forward direction (XZ plane) from its position
* component. Automatically finds the position component on the entity.
* Sets the near clip distance of a camera.
*
* @param entityId The camera entity ID.
* @param out Output vec2: {forwardX, forwardZ} normalized.
* @param ent The entity ID.
* @param comp The component ID.
* @param zNear The near clip distance.
*/
void entityCameraGetForward(const entityid_t entityId, vec2 out);
void entityCameraSetZNear(
const entityid_t ent,
const componentid_t comp,
const float_t zNear
);
/**
* Gets the camera's horizontal right direction (XZ plane) from its position
* component. Automatically finds the position component on the entity.
* Gets the far clip distance of a camera.
*
* @param entityId The camera entity ID.
* @param out Output vec2: {rightX, rightZ} normalized.
* @param ent The entity ID.
* @param comp The component ID.
* @return The far clip distance.
*/
void entityCameraGetRight(const entityid_t entityId, vec2 out);
float_t entityCameraGetZFar(const entityid_t ent, const componentid_t comp);
/**
* Sets the far clip distance of a camera.
*
* @param ent The entity ID.
* @param comp The component ID.
* @param zFar The far clip distance.
*/
void entityCameraSetZFar(
const entityid_t ent,
const componentid_t comp,
const float_t zFar
);
@@ -48,15 +48,4 @@ void entityMaterialSetShader(
entityId, componentId, COMPONENT_TYPE_MATERIAL
);
mat->shader = shader;
}
void entityMaterialSetColor(
const entityid_t entityId,
const componentid_t componentId,
const color_t color
) {
entitymaterial_t *mat = componentGetData(
entityId, componentId, COMPONENT_TYPE_MATERIAL
);
mat->material.unlit.color = color;
}
@@ -60,17 +60,4 @@ void entityMaterialSetShader(
const entityid_t entityId,
const componentid_t componentId,
shader_t *shader
);
/**
* Sets the unlit color for the given entity and component.
*
* @param entityId The entity ID.
* @param componentId The component ID.
* @param color The color to set.
*/
void entityMaterialSetColor(
const entityid_t entityId,
const componentid_t componentId,
const color_t color
);
+1 -105
View File
@@ -5,13 +5,7 @@
* https://opensource.org/licenses/MIT
*/
#include "entitymesh.h"
#include "entity/entitymanager.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "display/mesh/cube.h"
#include "display/mesh/plane.h"
#include "display/mesh/capsule.h"
void entityMeshInit(
const entityid_t entityId,
@@ -20,9 +14,7 @@ void entityMeshInit(
entitymesh_t *comp = componentGetData(
entityId, componentId, COMPONENT_TYPE_MESH
);
comp->mesh = &CUBE_MESH_SIMPLE;
comp->ownedVertices = NULL;
comp->ownsData = false;
comp->mesh = NULL;
}
mesh_t * entityMeshGetMesh(
@@ -44,100 +36,4 @@ void entityMeshSetMesh(
entityId, componentId, COMPONENT_TYPE_MESH
);
comp->mesh = mesh;
}
void entityMeshDispose(
const entityid_t entityId,
const componentid_t componentId
) {
entitymesh_t *comp = componentGetData(
entityId, componentId, COMPONENT_TYPE_MESH
);
if(!comp->ownsData) return;
(void)meshDispose(&comp->ownedMesh);
memoryFree(comp->ownedVertices);
comp->ownedVertices = NULL;
comp->ownsData = false;
comp->mesh = NULL;
}
errorret_t entityMeshGeneratePlane(
const entityid_t entityId,
const componentid_t componentId,
const float_t width,
const float_t height
) {
entitymesh_t *comp = componentGetData(
entityId, componentId, COMPONENT_TYPE_MESH
);
entityMeshDispose(entityId, componentId);
comp->ownedVertices = memoryAllocate(PLANE_VERTEX_COUNT * sizeof(meshvertex_t));
assertNotNull(comp->ownedVertices, "Failed to allocate plane vertices");
vec3 min = { -width * 0.5f, 0.0f, -height * 0.5f };
vec3 max = { width * 0.5f, 0.0f, height * 0.5f };
vec2 uvMin = { 0.0f, 0.0f };
vec2 uvMax = { 1.0f, 1.0f };
planeBuffer(
comp->ownedVertices,
PLANE_AXIS_XZ,
min,
max
#if MESH_ENABLE_COLOR
, COLOR_WHITE_4B
#endif
, uvMin,
uvMax
);
errorChain(meshInit(
&comp->ownedMesh,
PLANE_PRIMITIVE_TYPE,
PLANE_VERTEX_COUNT,
comp->ownedVertices
));
comp->mesh = &comp->ownedMesh;
comp->ownsData = true;
errorOk();
}
errorret_t entityMeshGenerateCapsule(
const entityid_t entityId,
const componentid_t componentId,
const float_t radius,
const float_t halfHeight
) {
entitymesh_t *comp = componentGetData(
entityId, componentId, COMPONENT_TYPE_MESH
);
entityMeshDispose(entityId, componentId);
comp->ownedVertices = memoryAllocate(CAPSULE_VERTEX_COUNT * sizeof(meshvertex_t));
assertNotNull(comp->ownedVertices, "Failed to allocate capsule vertices");
vec3 center = { 0.0f, 0.0f, 0.0f };
capsuleBuffer(
comp->ownedVertices,
center,
radius,
halfHeight,
CAPSULE_CAP_RINGS,
CAPSULE_SECTORS
#if MESH_ENABLE_COLOR
, COLOR_WHITE_4B
#endif
);
errorChain(meshInit(
&comp->ownedMesh,
CAPSULE_PRIMITIVE_TYPE,
CAPSULE_VERTEX_COUNT,
comp->ownedVertices
));
comp->mesh = &comp->ownedMesh;
comp->ownsData = true;
errorOk();
}
@@ -11,9 +11,6 @@
typedef struct {
mesh_t *mesh;
mesh_t ownedMesh;
meshvertex_t *ownedVertices;
bool_t ownsData;
} entitymesh_t;
/**
@@ -50,47 +47,4 @@ void entityMeshSetMesh(
const entityid_t entityId,
const componentid_t componentId,
mesh_t *mesh
);
/**
* Disposes the entity mesh component, freeing any owned mesh data.
*
* @param entityId The entity ID.
* @param componentId The component ID.
*/
void entityMeshDispose(
const entityid_t entityId,
const componentid_t componentId
);
/**
* Generates an XZ-aligned plane mesh owned by the component.
*
* @param entityId The entity ID.
* @param componentId The component ID.
* @param width Width of the plane along the X axis.
* @param height Height of the plane along the Z axis.
* @return Error return value.
*/
errorret_t entityMeshGeneratePlane(
const entityid_t entityId,
const componentid_t componentId,
const float_t width,
const float_t height
);
/**
* Generates a Y-axis capsule mesh owned by the component.
*
* @param entityId The entity ID.
* @param componentId The component ID.
* @param radius Radius of the cylinder and hemisphere caps.
* @param halfHeight Half-height of the cylindrical section.
* @return Error return value.
*/
errorret_t entityMeshGenerateCapsule(
const entityid_t entityId,
const componentid_t componentId,
const float_t radius,
const float_t halfHeight
);
@@ -100,25 +100,6 @@ bool_t entityPhysicsIsOnGround(
return phys->onGround;
}
void entityPhysicsSetBodyType(
const entityid_t entityId,
const componentid_t componentId,
const physicsbodytype_t type
) {
entityphysics_t *phys = entityPhysicsGet(entityId, componentId);
assertNotNull(phys, "Failed to get physics component data");
phys->type = type;
}
physicsbodytype_t entityPhysicsGetBodyType(
const entityid_t entityId,
const componentid_t componentId
) {
entityphysics_t *phys = entityPhysicsGet(entityId, componentId);
assertNotNull(phys, "Failed to get physics component data");
return phys->type;
}
void entityPhysicsDispose(
const entityid_t entityId,
const componentid_t componentId
@@ -122,31 +122,6 @@ bool_t entityPhysicsIsOnGround(
const componentid_t componentId
);
/**
* Sets the body type of the entity's physics body.
*
* @param entityId The entity ID.
* @param componentId The component ID.
* @param type The body type to set.
*/
void entityPhysicsSetBodyType(
const entityid_t entityId,
const componentid_t componentId,
const physicsbodytype_t type
);
/**
* Gets the body type of the entity's physics body.
*
* @param entityId The entity ID.
* @param componentId The component ID.
* @return The body type of the physics body.
*/
physicsbodytype_t entityPhysicsGetBodyType(
const entityid_t entityId,
const componentid_t componentId
);
/**
* Releases the body slot back to PHYSICS_WORLD. Called automatically when
* the component is disposed via the component system.
@@ -1,7 +0,0 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
+1 -7
View File
@@ -11,14 +11,8 @@
#include "entity/component/display/entitymaterial.h"
#include "entity/component/physics/entityphysics.h"
// Name (Uppercase)
// Structure
// Field name (lowercase)
// Init function (optional)
// Dispose function (optional)
X(POSITION, entityposition_t, position, entityPositionInit, NULL)
X(CAMERA, entitycamera_t, camera, entityCameraInit, NULL)
X(MESH, entitymesh_t, mesh, entityMeshInit, entityMeshDispose)
X(MESH, entitymesh_t, mesh, entityMeshInit, NULL)
X(MATERIAL, entitymaterial_t, material, entityMaterialInit, NULL)
X(PHYSICS, entityphysics_t, physics, entityPhysicsInit, entityPhysicsDispose)
+2 -2
View File
@@ -8,8 +8,8 @@
#pragma once
#include "dusk.h"
#define ENTITY_COUNT_MAX 20
#define ENTITY_COMPONENT_COUNT_MAX 8
#define ENTITY_COUNT_MAX 6
#define ENTITY_COMPONENT_COUNT_MAX 6
typedef uint8_t entityid_t;
typedef uint8_t componentid_t;
+3 -3
View File
@@ -8,7 +8,7 @@
#include "entitymanager.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "console/console.h"
#include "scene/scene.h"
entitymanager_t ENTITY_MANAGER;
@@ -19,8 +19,8 @@ void entityManagerInit(void) {
sizeof(entityid_t) * COMPONENT_TYPE_COUNT * ENTITY_COUNT_MAX
);
consolePrint(
"Entity Manager size: %zu bytes (%.2f KB)",
sceneLog(
"Entity Manager size: %zu bytes (%.2f KB)\n",
sizeof(entitymanager_t),
sizeof(entitymanager_t) / 1024.0f
);
+89 -76
View File
@@ -1,6 +1,6 @@
/**
* Copyright (c) 2026 Dominic Masters
*
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
@@ -8,7 +8,6 @@
#include "event.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "console/console.h"
void eventInit(
event_t *event,
@@ -47,16 +46,19 @@ eventsub_t eventSubscribeUser(
"Script event listener context cannot be NULL"
);
assertTrue(
user.script.funcValue != 0,
user.script.luaFunctionRef != LUA_NOREF,
"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;
@@ -71,7 +73,7 @@ eventsub_t eventSubscribe(
const eventcallback_t callback,
const void *user
) {
return eventSubscribeUser(
eventSubscribeUser(
event,
EVENT_TYPE_C,
(eventuserdata_t){ .c = { .callback = callback, .user = (void *)user } }
@@ -80,34 +82,54 @@ eventsub_t eventSubscribe(
eventsub_t eventSubscribeScriptContext(
event_t *event,
scriptmanager_t *context,
jerry_value_t funcValue
scriptcontext_t *context,
const int functionIndex
) {
assertNotNull(context, "Script context cannot be NULL");
assertTrue(funcValue != 0, "Script function value is invalid");
assertTrue(
lua_isfunction(context->luaState, functionIndex),
"Expected function at given index"
);
// 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 = 0;
uint8_t i;
i = 0;
do {
if(context->subscribedEvents[i] == event) {
alreadySubbed = true;
break;
if(context->subscribedEvents[i] != event) {
i++;
continue;
}
i++;
} while(i < SCRIPT_MANAGER_MAX_EVENT_SUBSCRIPTIONS);
if(context->subscribedEvents[i] == NULL) break;
alreadySubbed = true;
break;
} while(i < SCRIPT_CONTEXT_MAX_EVENT_SUBSCRIPTIONS);
if(!alreadySubbed) {
i = 0;
do {
if(context->subscribedEvents[i] == NULL) {
context->subscribedEvents[i] = event;
break;
if(context->subscribedEvents[i] != NULL) {
i++;
continue;
}
i++;
} while(i < SCRIPT_MANAGER_MAX_EVENT_SUBSCRIPTIONS);
context->subscribedEvents[i] = event;
break;
} while(i < SCRIPT_CONTEXT_MAX_EVENT_SUBSCRIPTIONS);
assertTrue(
i < SCRIPT_MANAGER_MAX_EVENT_SUBSCRIPTIONS,
i < SCRIPT_CONTEXT_MAX_EVENT_SUBSCRIPTIONS,
"Script context has reached maximum event subscriptions"
);
}
@@ -115,12 +137,7 @@ eventsub_t eventSubscribeScriptContext(
return eventSubscribeUser(
event,
EVENT_TYPE_SCRIPT,
(eventuserdata_t){
.script = {
.context = context,
.funcValue = funcValue
}
}
(eventuserdata_t){ .script = scriptUser }
);
}
@@ -130,34 +147,38 @@ void eventUnsubscribe(event_t *event, const eventsub_t id) {
if(event->listenerCount == 0) return;
// Find listener
uint16_t index = 0;
do {
if(event->listenerArray[index].id != id) {
index++;
continue;
if(event->listenerArray[index].id == id) {
// Found it, remove by swapping with last and reducing count
event->listenerArray[index] = event->listenerArray[--event->listenerCount];
return;
}
if(event->listenerArray[index].type == EVENT_TYPE_SCRIPT) {
jerry_value_t funcVal = event->listenerArray[index].user.script.funcValue;
if(funcVal != 0) {
jerry_value_free(funcVal);
}
}
event->listenerArray[index] = event->listenerArray[--event->listenerCount];
return;
index++;
} while(index < event->listenerCount);
// Did we find it?
if(index == event->listenerCount) return;
// Shift remaining listeners down (if any)
if(index < event->listenerCount - 1) {
memoryMove(
&event->listenerArray[index],
&event->listenerArray[index + 1],
sizeof(eventlistener_t) * (event->listenerCount - index - 1)
);
}
event->listenerCount--;
}
void eventUnsubscribeScriptContext(
event_t *event,
const scriptmanager_t *ctx
) {
void eventUnsubscribeScriptContext(event_t *event, const scriptcontext_t *ctx) {
assertNotNull(event, "Event cannot be NULL");
assertNotNull(ctx, "Script context cannot be NULL");
if(event->listenerCount == 0) return;
uint16_t i = 0;
do {
eventlistener_t *listener = &event->listenerArray[i];
@@ -168,6 +189,9 @@ void eventUnsubscribeScriptContext(
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);
}
@@ -180,55 +204,44 @@ void eventInvoke(
assertNotNull(event, "Event cannot be NULL");
if(event->listenerCount == 0) return;
event->isInvoking = true;
eventdata_t data = {
uint16_t i = 0;
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) {
jerry_value_t funcVal = listener->user.script.funcValue;
assertNotNull((void *)(uintptr_t)funcVal, "Script function value is NULL");
// Call Lua function
lua_State *L = listener->user.script.context->luaState;
assertNotNull(L, "Lua state in event listener cannot be NULL");
jerry_value_t callArgs[1];
jerry_length_t argCount = 0;
// Push function
lua_rawgeti(L, LUA_REGISTRYINDEX, listener->user.script.luaFunctionRef);
if(eventParams != NULL) {
callArgs[0] = jerry_object();
jerry_object_set_native_ptr(
callArgs[0], &JS_PTR_NATIVE_INFO, (void *)eventParams
if(eventParams != NULL && metatableName != NULL) {
lua_getmetatable(L, -1);
luaL_getmetatable(L, metatableName);
assertTrue(
lua_rawequal(L, -1, -2),
"Event parameter metatable does not match expected type"
);
argCount = 1;
}
jerry_value_t result = jerry_call(
funcVal, jerry_undefined(), callArgs, argCount
);
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);
// Call function with 1 arg, 0 return values
if(lua_pcall(L, 1, 0, 0) != LUA_OK) {
const char_t *strErr = lua_tostring(L, -1);
lua_pop(L, 1);
// Log error but continue
printf("Error invoking Lua event listener: %s\n", strErr);
}
jerry_value_free(result);
} else {
assertUnreachable("Unknown event listener type");
}
@@ -236,4 +249,4 @@ void eventInvoke(
} while(i < event->listenerCount);
event->isInvoking = false;
}
}
+3 -6
View File
@@ -83,8 +83,8 @@ eventsub_t eventSubscribe(
*/
eventsub_t eventSubscribeScriptContext(
event_t *event,
scriptmanager_t *context,
jerry_value_t funcValue
scriptcontext_t *context,
const int functionIndex
);
/**
@@ -101,10 +101,7 @@ void eventUnsubscribe(event_t *event, const eventsub_t subscription);
* @param event The event to unsubscribe from.
* @param context The script context whose listeners should be removed.
*/
void eventUnsubscribeScriptContext(
event_t *event,
const scriptmanager_t *ctx
);
void eventUnsubscribeScriptContext(event_t *event, const scriptcontext_t *ctx);
/**
* Invoke an event, calling all subscribed listeners. Optionally provide event
+5 -5
View File
@@ -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 "eventcallback.h"
#include "script/scriptmanager.h"
#include "script/scriptcontext.h"
typedef enum {
EVENT_TYPE_C = 0,
@@ -15,8 +15,8 @@ typedef enum {
} eventtype_t;
typedef struct {
scriptmanager_t *context;
jerry_value_t funcValue;
scriptcontext_t *context;
int luaFunctionRef;
} 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;
+28 -34
View File
@@ -95,27 +95,27 @@ void inputUpdate(void) {
if(TIME.dynamicUpdate) return;
#endif
// if(INPUT.eventPressed.listenerCount > 0) {
// action = &INPUT.actions[0];
// do {
// if(inputPressed(action->action)) {
// inputevent_t inputEvent = { .action = action->action };
// eventInvoke(&INPUT.eventPressed, &inputEvent, "input_mt");
// }
// action++;
// } while(action < &INPUT.actions[INPUT_ACTION_COUNT]);
// }
if(INPUT.eventPressed.listenerCount > 0) {
action = &INPUT.actions[0];
do {
if(inputPressed(action->action)) {
inputevent_t inputEvent = { .action = action->action };
eventInvoke(&INPUT.eventPressed, &inputEvent, "input_mt");
}
action++;
} while(action < &INPUT.actions[INPUT_ACTION_COUNT]);
}
// if(INPUT.eventReleased.listenerCount > 0) {
// action = &INPUT.actions[0];
// do {
// if(inputReleased(action->action)) {
// inputevent_t inputEvent = { .action = action->action };
// eventInvoke(&INPUT.eventReleased, &inputEvent, "input_mt");
// }
// action++;
// } while(action < &INPUT.actions[INPUT_ACTION_COUNT]);
// }
if(INPUT.eventReleased.listenerCount > 0) {
action = &INPUT.actions[0];
do {
if(inputReleased(action->action)) {
inputevent_t inputEvent = { .action = action->action };
eventInvoke(&INPUT.eventReleased, &inputEvent, "input_mt");
}
action++;
} while(action < &INPUT.actions[INPUT_ACTION_COUNT]);
}
}
float_t inputGetCurrentValue(const inputaction_t action) {
@@ -171,16 +171,6 @@ float_t inputAxis(const inputaction_t neg, const inputaction_t pos) {
return inputGetCurrentValue(pos) - inputGetCurrentValue(neg);
}
void inputAxis2D(
const inputaction_t negX, const inputaction_t posX,
const inputaction_t negY, const inputaction_t posY,
vec2 result
) {
assertNotNull(result, "Result vector cannot be null");
result[0] = inputAxis(negX, posX);
result[1] = inputAxis(negY, posY);
}
void inputBind(const inputbutton_t button, const inputaction_t act) {
assertTrue(
act < INPUT_ACTION_COUNT,
@@ -189,15 +179,19 @@ void inputBind(const inputbutton_t button, const inputaction_t act) {
assertTrue(act != INPUT_ACTION_NULL, "Cannot bind to NULL action");
// Get the button data for this button.
inputbuttondata_t *data = inputButtonGetData(button);
assertNotNull(data, "Input button not found");
inputbuttondata_t *data = INPUT_BUTTON_DATA;
do {
if(memoryCompare(&data->button, &button, sizeof(inputbutton_t)) == 0) {
break;
}
data++;
} while(data->name != NULL);
assertNotNull(data->name, "Input button not found");
// Bind the action.
data->action = act;
}
float_t inputDeadzone(const float_t rawValue, const float_t deadzone) {
if(rawValue < deadzone) return 0.0f;
return (rawValue - deadzone) / (1.0f - deadzone);
+1 -2
View File
@@ -5,7 +5,6 @@ LEFT,
RIGHT,
ACCEPT,
CANCEL,
RAGEQUIT,
CONSOLE,
RAGEQUIT
POINTERX,
POINTERY,
1 id id,
5 RIGHT RIGHT,
6 ACCEPT ACCEPT,
7 CANCEL CANCEL,
8 RAGEQUIT RAGEQUIT
CONSOLE
9 POINTERX POINTERX,
10 POINTERY POINTERY,
-16
View File
@@ -120,22 +120,6 @@ bool_t inputReleased(const inputaction_t action);
*/
float_t inputAxis(const inputaction_t neg, const inputaction_t pos);
/**
* Gets the values of a 2D input axis, defined by two pairs of actions (negative
* and positive for each axis).
*
* @param negX The action representing the negative direction of the X axis.
* @param posX The action representing the positive direction of the X axis.
* @param negY The action representing the negative direction of the Y axis.
* @param posY The action representing the positive direction of the Y axis.
* @param result A vec2 to store the resulting axis values (-1.0f to 1.0f).
*/
void inputAxis2D(
const inputaction_t negX, const inputaction_t posX,
const inputaction_t negY, const inputaction_t posY,
vec2 result
);
/**
* Binds an input button to an action.
*
+9 -9
View File
@@ -9,14 +9,14 @@
#include "assert/assert.h"
#include "util/string.h"
inputaction_t inputActionGetByName(const char_t *name) {
assertNotNull(name, "name must not be NULL");
// inputaction_t inputActionGetByName(const char_t *name) {
// assertNotNull(name, "name must not be NULL");
for(inputaction_t i = 0; i < INPUT_ACTION_COUNT; i++) {
if(INPUT_ACTION_IDS[i] == NULL) continue;
if(stringCompareInsensitive(INPUT_ACTION_IDS[i], name) != 0) continue;
return i;
}
// for(inputaction_t i = 0; i < INPUT_ACTION_COUNT; i++) {
// if(INPUT_ACTION_IDS[i] == NULL) continue;
// if(stringCompareInsensitive(INPUT_ACTION_IDS[i], name) != 0) continue;
// return i;
// }
return INPUT_ACTION_COUNT;
}
// return INPUT_ACTION_COUNT;
// }
+1 -1
View File
@@ -26,4 +26,4 @@ typedef struct {
* @param name The name of the input action.
* @return The input action, or INPUT_ACTION_COUNT if not found.
*/
inputaction_t inputActionGetByName(const char_t *name);
// inputaction_t inputActionGetByName(const char_t *name);
-13
View File
@@ -9,7 +9,6 @@
#include "input.h"
#include "assert/assert.h"
#include "util/string.h"
#include "util/memory.h"
inputbutton_t inputButtonGetByName(const char_t *name) {
assertNotNull(name, "name must not be NULL");
@@ -27,16 +26,4 @@ inputbutton_t inputButtonGetByName(const char_t *name) {
float_t inputButtonGetValue(const inputbutton_t button) {
return inputButtonGetValuePlatform(button);
}
inputbuttondata_t * inputButtonGetData(const inputbutton_t button) {
inputbuttondata_t *data = INPUT_BUTTON_DATA;
do {
if(memoryCompare(&data->button, &button, sizeof(inputbutton_t)) == 0) {
return data;
}
data++;
} while(data->name != NULL);
return NULL;
}
+1 -9
View File
@@ -96,12 +96,4 @@ inputbutton_t inputButtonGetByName(const char_t *name);
* @param button The input button.
* @return The current value of the input button (0.0f to 1.0f).
*/
float_t inputButtonGetValue(const inputbutton_t button);
/**
* Gets the button data for a specific input button.
*
* @param button The input button to get the data for.
* @return The button data, or NULL if not found.
*/
inputbuttondata_t * inputButtonGetData(const inputbutton_t button);
float_t inputButtonGetValue(const inputbutton_t button);
+3
View File
@@ -7,4 +7,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
network.c
networkinfo.c
networksocket.c
networktls.c
httpclient.c
)
+545
View File
@@ -0,0 +1,545 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "httpclient.h"
#include "util/memory.h"
#include "assert/assert.h"
/* ---- helpers ----------------------------------------------------------- */
static bool_t httpclientStrEqualCI(const char_t *a, const char_t *b) {
while(*a && *b) {
if(tolower((unsigned char)*a) != tolower((unsigned char)*b)) return false;
a++; b++;
}
return *a == '\0' && *b == '\0';
}
/**
* Reads exactly len bytes from the TLS stream, retrying on timeout.
* Returns an error on I/O failure or unexpected connection close.
*/
static errorret_t httpclientReadExact(
networktls_t *tls,
errorstate_t *es,
uint8_t *buf,
size_t len
) {
size_t total = 0;
while(total < len) {
size_t got = 0;
errorret_t err = networktlsRead(tls, buf + total, len - total, &got);
if(err.code != ERROR_OK) return err;
if(got == NETWORKSOCKET_RECV_CLOSED) {
errorThrowState(es, "Connection closed before all expected bytes arrived");
}
total += got;
}
errorOk();
}
/**
* Reads bytes one at a time until \r\n is found. Writes the line (without
* the terminator) into buf, null-terminates, and sets *outLen. Returns an
* error on I/O failure or if the line exceeds maxLen-1 characters.
*/
static errorret_t httpclientReadLine(
networktls_t *tls,
errorstate_t *es,
char_t *buf,
size_t maxLen,
size_t *outLen
) {
size_t len = 0;
uint8_t ch;
size_t got;
errorret_t err;
while(len < maxLen - 1) {
do {
got = 0;
err = networktlsRead(tls, &ch, 1, &got);
if(err.code != ERROR_OK) return err;
if(got == NETWORKSOCKET_RECV_CLOSED) {
errorThrowState(es, "Connection closed during header read");
}
} while(got == 0);
if(ch == '\r') {
/* consume the \n */
do {
got = 0;
err = networktlsRead(tls, &ch, 1, &got);
if(err.code != ERROR_OK) return err;
if(got == NETWORKSOCKET_RECV_CLOSED) break;
} while(got == 0);
break;
}
buf[len++] = (char_t)ch;
}
buf[len] = '\0';
*outLen = len;
errorOk();
}
/* ---- response parsing -------------------------------------------------- */
/**
* Parses status code from "HTTP/x.y NNN ..." — returns 0 on failure.
*/
static uint16_t httpclientParseStatus(const char_t *line) {
const char_t *p = strchr(line, ' ');
if(!p) return 0;
return (uint16_t)atoi(p + 1);
}
/**
* Parses "Name: Value" into the client's responseHeaders array. Trims
* leading whitespace from the value.
*/
static void httpclientParseHeader(
httpclient_t *client,
const char_t *line
) {
const char_t *colon;
const char_t *valStart;
size_t nameLen;
size_t valLen;
httpheader_t *h;
if(client->responseHeaderCount >= HTTPCLIENT_HEADER_MAX) return;
colon = strchr(line, ':');
if(!colon) return;
nameLen = (size_t)(colon - line);
if(nameLen == 0 || nameLen >= HTTPCLIENT_HEADER_NAME_MAX) return;
valStart = colon + 1;
while(*valStart == ' ' || *valStart == '\t') valStart++;
valLen = strlen(valStart);
if(valLen >= HTTPCLIENT_HEADER_VALUE_MAX) valLen = HTTPCLIENT_HEADER_VALUE_MAX - 1;
h = &client->responseHeaders[client->responseHeaderCount++];
memoryCopy(h->name, line, nameLen);
h->name[nameLen] = '\0';
memoryCopy(h->value, valStart, valLen);
h->value[valLen] = '\0';
}
static const char_t *httpclientGetResponseHeader(
const httpclient_t *client,
const char_t *name
) {
size_t i;
for(i = 0; i < client->responseHeaderCount; i++) {
if(httpclientStrEqualCI(client->responseHeaders[i].name, name)) {
return client->responseHeaders[i].value;
}
}
return NULL;
}
/* ---- body delivery ----------------------------------------------------- */
static errorret_t httpclientReadBodyFixed(
httpclient_t *client,
size_t contentLength
) {
uint8_t buf[HTTPCLIENT_BODY_BUF_SIZE];
size_t remaining = contentLength;
size_t got;
errorret_t err;
while(remaining > 0) {
size_t want = remaining < sizeof(buf) ? remaining : sizeof(buf);
err = httpclientReadExact(&client->tls, &client->errorState, buf, want);
if(err.code != ERROR_OK) return err;
if(client->onData) client->onData(client, buf, want, client->user);
remaining -= want;
got = want;
(void)got;
}
errorOk();
}
static errorret_t httpclientReadBodyChunked(httpclient_t *client) {
char_t sizeLine[32];
size_t lineLen;
errorret_t err;
uint8_t buf[HTTPCLIENT_BODY_BUF_SIZE];
for(;;) {
err = httpclientReadLine(
&client->tls, &client->errorState, sizeLine, sizeof(sizeLine), &lineLen
);
if(err.code != ERROR_OK) return err;
/* strtol parses hex chunk size; stop on terminating "0" chunk */
size_t chunkSize = (size_t)strtol(sizeLine, NULL, 16);
if(chunkSize == 0) {
/* consume trailing CRLF after the zero chunk */
httpclientReadLine(
&client->tls, &client->errorState, sizeLine, sizeof(sizeLine), &lineLen
);
break;
}
size_t remaining = chunkSize;
while(remaining > 0) {
size_t want = remaining < sizeof(buf) ? remaining : sizeof(buf);
err = httpclientReadExact(
&client->tls, &client->errorState, buf, want
);
if(err.code != ERROR_OK) return err;
if(client->onData) client->onData(client, buf, want, client->user);
remaining -= want;
}
/* consume the CRLF after chunk data */
err = httpclientReadLine(
&client->tls, &client->errorState, sizeLine, sizeof(sizeLine), &lineLen
);
if(err.code != ERROR_OK) return err;
}
errorOk();
}
static errorret_t httpclientReadBodyUntilClose(httpclient_t *client) {
uint8_t buf[HTTPCLIENT_BODY_BUF_SIZE];
size_t got;
errorret_t err;
for(;;) {
err = networktlsRead(&client->tls, buf, sizeof(buf), &got);
if(err.code != ERROR_OK) return err;
if(got == NETWORKSOCKET_RECV_CLOSED) break;
if(got > 0 && client->onData) {
client->onData(client, buf, got, client->user);
}
}
errorOk();
}
/* ---- TLS onConnect — runs the full HTTP exchange ----------------------- */
static void httpclientTlsOnConnect(networktls_t *tls, void *user) {
httpclient_t *client = (httpclient_t *)user;
char_t reqBuf[HTTPCLIENT_REQ_BUF_SIZE];
char_t lineBuf[HTTPCLIENT_HEADER_NAME_MAX + HTTPCLIENT_HEADER_VALUE_MAX + 4];
int reqLen;
size_t lineLen;
size_t i;
errorret_t err;
/* ---- build request headers ----------------------------------------- */
reqLen = snprintf(
reqBuf, sizeof(reqBuf),
"%s %s HTTP/1.1\r\n"
"Host: %s\r\n"
"Connection: close\r\n"
"User-Agent: DuskEngine/1.0\r\n",
client->method,
client->url.path[0] != '\0' ? client->url.path : "/",
client->url.host
);
for(i = 0; i < client->requestHeaderCount; i++) {
int n = snprintf(
reqBuf + reqLen, sizeof(reqBuf) - (size_t)reqLen,
"%s: %s\r\n",
client->requestHeaders[i].name,
client->requestHeaders[i].value
);
if(n > 0) reqLen += n;
}
if(client->requestBodyLen > 0) {
int n = snprintf(
reqBuf + reqLen, sizeof(reqBuf) - (size_t)reqLen,
"Content-Length: %zu\r\n",
client->requestBodyLen
);
if(n > 0) reqLen += n;
}
/* blank line terminates headers */
if((size_t)reqLen + 2 < sizeof(reqBuf)) {
reqBuf[reqLen++] = '\r';
reqBuf[reqLen++] = '\n';
}
err = networktlsWrite(tls, (const uint8_t *)reqBuf, (size_t)reqLen);
if(err.code != ERROR_OK) {
if(client->onError) client->onError(client, err, client->user);
return;
}
if(client->requestBody != NULL && client->requestBodyLen > 0) {
err = networktlsWrite(tls, client->requestBody, client->requestBodyLen);
if(err.code != ERROR_OK) {
if(client->onError) client->onError(client, err, client->user);
return;
}
}
/* ---- read response status line ------------------------------------- */
err = httpclientReadLine(
tls, &client->errorState, lineBuf, sizeof(lineBuf), &lineLen
);
if(err.code != ERROR_OK) {
if(client->onError) client->onError(client, err, client->user);
return;
}
client->statusCode = httpclientParseStatus(lineBuf);
/* ---- read response headers until blank line ------------------------ */
client->responseHeaderCount = 0;
for(;;) {
err = httpclientReadLine(
tls, &client->errorState, lineBuf, sizeof(lineBuf), &lineLen
);
if(err.code != ERROR_OK) {
if(client->onError) client->onError(client, err, client->user);
return;
}
if(lineLen == 0) break; /* blank line = end of headers */
httpclientParseHeader(client, lineBuf);
}
if(client->onHeaders) {
client->onHeaders(
client,
client->statusCode,
client->responseHeaders,
client->responseHeaderCount,
client->user
);
}
/* ---- read response body -------------------------------------------- */
/* 1xx, 204 No Content and 304 Not Modified carry no body */
bool_t noBody = (
client->statusCode < 200 ||
client->statusCode == 204 ||
client->statusCode == 304
);
if(!noBody) {
const char_t *transferEncoding =
httpclientGetResponseHeader(client, "Transfer-Encoding");
const char_t *contentLengthStr =
httpclientGetResponseHeader(client, "Content-Length");
if(transferEncoding != NULL &&
strstr(transferEncoding, "chunked") != NULL) {
err = httpclientReadBodyChunked(client);
} else if(contentLengthStr != NULL) {
size_t contentLength = (size_t)atoi(contentLengthStr);
err = httpclientReadBodyFixed(client, contentLength);
} else {
err = httpclientReadBodyUntilClose(client);
}
if(err.code != ERROR_OK) {
if(client->onError) client->onError(client, err, client->user);
return;
}
}
if(client->onComplete) client->onComplete(client, client->user);
}
static void httpclientTlsOnError(
networktls_t *tls,
errorret_t err,
void *user
) {
httpclient_t *client = (httpclient_t *)user;
if(client->onError) client->onError(client, err, client->user);
}
static void httpclientTlsOnDisconnect(networktls_t *tls, void *user) {
/* nothing — httpclientTlsOnConnect drives the full lifecycle */
}
/* ---- public API -------------------------------------------------------- */
errorret_t httpclientInit(httpclient_t *client) {
memoryZero(client, sizeof(httpclient_t));
errorChain(networktlsInit(&client->tls));
errorOk();
}
errorret_t httpclientParseUrl(
httpurl_t *url,
errorstate_t *errorState,
const char_t *urlStr
) {
const char_t *schemeEnd;
const char_t *hostStart;
const char_t *portStart;
const char_t *pathStart;
size_t hostLen;
memoryZero(url, sizeof(httpurl_t));
schemeEnd = strstr(urlStr, "://");
if(!schemeEnd) {
errorThrowState(errorState, "URL missing scheme: %s", urlStr);
}
size_t schemeLen = (size_t)(schemeEnd - urlStr);
if(schemeLen >= HTTPCLIENT_SCHEME_MAX) {
errorThrowState(errorState, "URL scheme too long");
}
memoryCopy(url->scheme, urlStr, schemeLen);
url->scheme[schemeLen] = '\0';
/* default port by scheme */
if(httpclientStrEqualCI(url->scheme, "https")) {
url->port = 443;
} else if(httpclientStrEqualCI(url->scheme, "http")) {
url->port = 80;
} else {
errorThrowState(errorState, "Unsupported URL scheme: %s", url->scheme);
}
hostStart = schemeEnd + 3;
pathStart = strchr(hostStart, '/');
portStart = strchr(hostStart, ':');
/* port present and appears before the path */
if(portStart != NULL && (pathStart == NULL || portStart < pathStart)) {
hostLen = (size_t)(portStart - hostStart);
url->port = (uint16_t)atoi(portStart + 1);
} else {
hostLen = pathStart != NULL
? (size_t)(pathStart - hostStart)
: strlen(hostStart);
}
if(hostLen == 0 || hostLen >= HTTPCLIENT_HOST_MAX) {
errorThrowState(errorState, "URL host missing or too long");
}
memoryCopy(url->host, hostStart, hostLen);
url->host[hostLen] = '\0';
if(pathStart != NULL) {
size_t pathLen = strlen(pathStart);
if(pathLen >= HTTPCLIENT_PATH_MAX) pathLen = HTTPCLIENT_PATH_MAX - 1;
memoryCopy(url->path, pathStart, pathLen);
url->path[pathLen] = '\0';
} else {
url->path[0] = '/';
url->path[1] = '\0';
}
errorOk();
}
void httpclientSetHeader(
httpclient_t *client,
const char_t *name,
const char_t *value
) {
size_t nameLen;
size_t valLen;
size_t i;
httpheader_t *h;
assertNotNull(name, "header name must not be null");
assertNotNull(value, "header value must not be null");
/* replace existing header with same name */
for(i = 0; i < client->requestHeaderCount; i++) {
if(httpclientStrEqualCI(client->requestHeaders[i].name, name)) {
h = &client->requestHeaders[i];
valLen = strlen(value);
if(valLen >= HTTPCLIENT_HEADER_VALUE_MAX) valLen = HTTPCLIENT_HEADER_VALUE_MAX - 1;
memoryCopy(h->value, value, valLen);
h->value[valLen] = '\0';
return;
}
}
assertTrue(
client->requestHeaderCount < HTTPCLIENT_HEADER_MAX,
"httpclientSetHeader: request header limit exceeded"
);
h = &client->requestHeaders[client->requestHeaderCount++];
nameLen = strlen(name);
if(nameLen >= HTTPCLIENT_HEADER_NAME_MAX) nameLen = HTTPCLIENT_HEADER_NAME_MAX - 1;
memoryCopy(h->name, name, nameLen);
h->name[nameLen] = '\0';
valLen = strlen(value);
if(valLen >= HTTPCLIENT_HEADER_VALUE_MAX) valLen = HTTPCLIENT_HEADER_VALUE_MAX - 1;
memoryCopy(h->value, value, valLen);
h->value[valLen] = '\0';
}
void httpclientRequest(
httpclient_t *client,
const char_t *method,
const char_t *url,
bool_t verifyPeer,
httpclientheadercallback_t onHeaders,
httpclientdatacallback_t onData,
httpclientcompletecallback_t onComplete,
httpclienterrorcallback_t onError,
void *user
) {
size_t methodLen;
errorret_t err;
assertNotNull(method, "method must not be null");
assertNotNull(url, "url must not be null");
methodLen = strlen(method);
if(methodLen >= HTTPCLIENT_METHOD_MAX) methodLen = HTTPCLIENT_METHOD_MAX - 1;
memoryCopy(client->method, method, methodLen);
client->method[methodLen] = '\0';
err = httpclientParseUrl(&client->url, &client->errorState, url);
if(err.code != ERROR_OK) {
if(onError) onError(client, err, user);
return;
}
client->onHeaders = onHeaders;
client->onData = onData;
client->onComplete = onComplete;
client->onError = onError;
client->user = user;
client->statusCode = 0;
client->responseHeaderCount = 0;
networktlsConnect(
&client->tls,
client->url.host,
client->url.port,
verifyPeer,
httpclientTlsOnConnect,
httpclientTlsOnError,
httpclientTlsOnDisconnect,
client
);
}
void httpclientDisconnect(httpclient_t *client) {
networktlsDisconnect(&client->tls);
}
errorret_t httpclientDispose(httpclient_t *client) {
return networktlsDispose(&client->tls);
}
+170
View File
@@ -0,0 +1,170 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "network/networktls.h"
#include "util/endian.h"
#define HTTPCLIENT_METHOD_MAX 8
#define HTTPCLIENT_URL_MAX 1024
#define HTTPCLIENT_HOST_MAX 256
#define HTTPCLIENT_PATH_MAX 768
#define HTTPCLIENT_SCHEME_MAX 8
#define HTTPCLIENT_HEADER_NAME_MAX 64
#define HTTPCLIENT_HEADER_VALUE_MAX 512
#define HTTPCLIENT_HEADER_MAX 32
#define HTTPCLIENT_HEADER_BUF_SIZE 8192
#define HTTPCLIENT_BODY_BUF_SIZE 4096
#define HTTPCLIENT_REQ_BUF_SIZE 4096
/** Parsed components of a URL. */
typedef struct {
char_t scheme[HTTPCLIENT_SCHEME_MAX];
char_t host[HTTPCLIENT_HOST_MAX];
uint16_t port;
char_t path[HTTPCLIENT_PATH_MAX];
} httpurl_t;
/** A single HTTP header (name + value pair). */
typedef struct {
char_t name[HTTPCLIENT_HEADER_NAME_MAX];
char_t value[HTTPCLIENT_HEADER_VALUE_MAX];
} httpheader_t;
typedef struct httpclient_s httpclient_t;
/**
* Called once the response status line and all headers have been parsed.
* Fired from the socket thread before any body data arrives.
*/
typedef void (*httpclientheadercallback_t)(
httpclient_t *client,
uint16_t statusCode,
const httpheader_t *headers,
size_t headerCount,
void *user
);
/**
* Called for each chunk of response body data.
* May be called multiple times per request. Fired from the socket thread.
* For binary payloads use endianReadBE32 / endianReadLE32 from util/endian.h
* to read multi-byte values with correct byte order.
*/
typedef void (*httpclientdatacallback_t)(
httpclient_t *client,
const uint8_t *data,
size_t len,
void *user
);
/** Called once when the response body has been fully received. */
typedef void (*httpclientcompletecallback_t)(
httpclient_t *client,
void *user
);
/** Called on any transport or protocol error. */
typedef void (*httpclienterrorcallback_t)(
httpclient_t *client,
errorret_t err,
void *user
);
typedef struct httpclient_s {
networktls_t tls;
errorstate_t errorState;
/* --- request config (set before httpclientRequest) --- */
char_t method[HTTPCLIENT_METHOD_MAX];
httpurl_t url;
httpheader_t requestHeaders[HTTPCLIENT_HEADER_MAX];
size_t requestHeaderCount;
/**
* Optional request body. The pointer must remain valid until onComplete or
* onError fires. The HTTP layer does not copy the body.
*/
const uint8_t *requestBody;
size_t requestBodyLen;
/* --- response state (populated during request) --- */
uint16_t statusCode;
httpheader_t responseHeaders[HTTPCLIENT_HEADER_MAX];
size_t responseHeaderCount;
/* --- callbacks (all fired from the socket thread) --- */
httpclientheadercallback_t onHeaders;
httpclientdatacallback_t onData;
httpclientcompletecallback_t onComplete;
httpclienterrorcallback_t onError;
void *user;
} httpclient_t;
/**
* Initializes an HTTP client. Must be called before any other httpclient
* function.
*/
errorret_t httpclientInit(httpclient_t *client);
/**
* Parses a URL string into its components. Supports http:// and https://.
* Port defaults to 80 (HTTP) or 443 (HTTPS) when not specified.
*
* @return ERROR_NOT_OK if the URL is malformed.
*/
errorret_t httpclientParseUrl(
httpurl_t *url,
errorstate_t *errorState,
const char_t *urlStr
);
/**
* Adds or replaces a request header. Must be called before httpclientRequest.
* Asserts if HTTPCLIENT_HEADER_MAX is exceeded.
*/
void httpclientSetHeader(
httpclient_t *client,
const char_t *name,
const char_t *value
);
/**
* Starts an asynchronous HTTPS (or plain HTTP) request. All callbacks fire
* from the socket thread, not the main thread.
*
* @param method HTTP verb ("GET", "POST", etc.).
* @param url Full URL string — parsed internally.
* @param verifyPeer Pass true to verify the server TLS certificate. Pass
* false for self-signed / development servers.
* @param onHeaders Called when response headers arrive (may be NULL).
* @param onData Called for each body chunk (may be NULL).
* @param onComplete Called when the full response is received (may be NULL).
* @param onError Called on any error (may be NULL).
* @param user Passed to all callbacks.
*/
void httpclientRequest(
httpclient_t *client,
const char_t *method,
const char_t *url,
bool_t verifyPeer,
httpclientheadercallback_t onHeaders,
httpclientdatacallback_t onData,
httpclientcompletecallback_t onComplete,
httpclienterrorcallback_t onError,
void *user
);
/**
* Requests cancellation of an in-flight request. Non-blocking.
*/
void httpclientDisconnect(httpclient_t *client);
/**
* Blocks until the request thread exits, then frees all resources.
*/
errorret_t httpclientDispose(httpclient_t *client);
+165
View File
@@ -0,0 +1,165 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "networksocket.h"
#include "util/memory.h"
#include "assert/assert.h"
static void networksocketThreadCallback(thread_t *thread) {
networksocket_t *socket = (networksocket_t *)thread->data;
errorret_t err;
bool_t hadError;
hadError = false;
err = networksocketPlatformConnect(socket);
if (err.code != ERROR_OK) {
socket->state = NETWORKSOCKET_STATE_DISCONNECTED;
if (socket->onError) socket->onError(socket, err, socket->user);
return;
}
socket->state = NETWORKSOCKET_STATE_CONNECTED;
if (socket->onConnect) socket->onConnect(socket, socket->user);
if (socket->onReceive != NULL) {
uint8_t recvBuf[NETWORKSOCKET_RECV_BUFFER_SIZE];
uint8_t localSendBuf[NETWORKSOCKET_SEND_BUFFER_SIZE];
size_t sendLen;
size_t recvLen;
while (!threadShouldStop(thread)) {
threadMutexLock(&socket->sendMutex);
sendLen = socket->sendLen;
if (sendLen > 0) {
memoryCopy(localSendBuf, socket->sendBuffer, sendLen);
socket->sendLen = 0;
}
threadMutexUnlock(&socket->sendMutex);
if (sendLen > 0) {
err = networksocketPlatformSend(socket, localSendBuf, sendLen);
if (err.code != ERROR_OK) {
hadError = true;
break;
}
}
recvLen = 0;
err = networksocketPlatformRecv(
socket, recvBuf, NETWORKSOCKET_RECV_BUFFER_SIZE, &recvLen
);
if (err.code != ERROR_OK) {
hadError = true;
break;
}
if (recvLen == NETWORKSOCKET_RECV_CLOSED) break;
if (recvLen > 0) {
socket->onReceive(socket, recvBuf, recvLen, socket->user);
}
}
}
socket->state = NETWORKSOCKET_STATE_DISCONNECTING;
networksocketPlatformDisconnect(socket);
socket->state = NETWORKSOCKET_STATE_DISCONNECTED;
if (hadError) {
if (socket->onError) socket->onError(socket, err, socket->user);
} else {
if (socket->onDisconnect) socket->onDisconnect(socket, socket->user);
}
}
errorret_t networksocketInit(networksocket_t *socket) {
memoryZero(socket, sizeof(networksocket_t));
socket->state = NETWORKSOCKET_STATE_DISCONNECTED;
threadInit(&socket->thread, networksocketThreadCallback);
socket->thread.data = socket;
threadMutexInit(&socket->sendMutex);
errorChain(networksocketPlatformInit(socket));
errorOk();
}
void networksocketConnect(
networksocket_t *socket,
const char_t *host,
uint16_t port,
networksocketcallback_t onConnect,
networksocketerrorcallback_t onError,
networksocketcallback_t onDisconnect,
networksocketrecvcallback_t onReceive,
void *user
) {
size_t hostLen;
assertNotNull(host, "host must not be null");
hostLen = strlen(host);
assertTrue(hostLen < NETWORKSOCKET_HOST_MAX, "host exceeds NETWORKSOCKET_HOST_MAX");
memoryCopy(socket->host, host, hostLen + 1);
socket->port = port;
socket->onConnect = onConnect;
socket->onError = onError;
socket->onDisconnect = onDisconnect;
socket->onReceive = onReceive;
socket->user = user;
socket->sendLen = 0;
socket->state = NETWORKSOCKET_STATE_CONNECTING;
threadStart(&socket->thread);
}
errorret_t networksocketSend(
networksocket_t *socket,
const uint8_t *data,
size_t len
) {
assertNotNull(data, "data must not be null");
assertTrue(len > 0, "len must be greater than 0");
threadMutexLock(&socket->sendMutex);
assertTrue(
socket->sendLen + len <= NETWORKSOCKET_SEND_BUFFER_SIZE,
"networksocketSend would overflow send buffer"
);
memoryCopy(socket->sendBuffer + socket->sendLen, data, len);
socket->sendLen += len;
threadMutexUnlock(&socket->sendMutex);
errorOk();
}
errorret_t networksocketWrite(
networksocket_t *socket,
const uint8_t *data,
size_t len
) {
return networksocketPlatformSend(socket, data, len);
}
errorret_t networksocketRead(
networksocket_t *socket,
uint8_t *buf,
size_t maxLen,
size_t *outLen
) {
return networksocketPlatformRecv(socket, buf, maxLen, outLen);
}
void networksocketDisconnect(networksocket_t *socket) {
threadStopRequest(&socket->thread);
}
errorret_t networksocketDispose(networksocket_t *socket) {
threadStop(&socket->thread);
errorChain(networksocketPlatformDispose(socket));
threadMutexDispose(&socket->sendMutex);
errorOk();
}
+170
View File
@@ -0,0 +1,170 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#include "thread/thread.h"
#include "network/networksocketplatform.h"
#ifndef networksocketPlatformInit
#error "networksocketPlatformInit must be defined"
#endif
#ifndef networksocketPlatformConnect
#error "networksocketPlatformConnect must be defined"
#endif
#ifndef networksocketPlatformSend
#error "networksocketPlatformSend must be defined"
#endif
#ifndef networksocketPlatformRecv
#error "networksocketPlatformRecv must be defined"
#endif
#ifndef networksocketPlatformDisconnect
#error "networksocketPlatformDisconnect must be defined"
#endif
#ifndef networksocketPlatformDispose
#error "networksocketPlatformDispose must be defined"
#endif
#define NETWORKSOCKET_HOST_MAX 256
#define NETWORKSOCKET_SEND_BUFFER_SIZE 4096
#define NETWORKSOCKET_RECV_BUFFER_SIZE 4096
/** Sentinel returned by networksocketPlatformRecv when the peer closed the connection. */
#define NETWORKSOCKET_RECV_CLOSED ((size_t)(-1))
typedef enum {
NETWORKSOCKET_STATE_DISCONNECTED,
NETWORKSOCKET_STATE_CONNECTING,
NETWORKSOCKET_STATE_CONNECTED,
NETWORKSOCKET_STATE_DISCONNECTING,
} networksocketstate_t;
typedef struct networksocket_s networksocket_t;
typedef void (*networksocketcallback_t)(
networksocket_t *socket, void *user
);
typedef void (*networksocketerrorcallback_t)(
networksocket_t *socket, errorret_t err, void *user
);
typedef void (*networksocketrecvcallback_t)(
networksocket_t *socket, const uint8_t *data, size_t len, void *user
);
typedef struct networksocket_s {
networksocketstate_t state;
errorstate_t errorState;
thread_t thread;
threadmutex_t sendMutex;
char_t host[NETWORKSOCKET_HOST_MAX];
uint16_t port;
networksocketplatform_t platform;
uint8_t sendBuffer[NETWORKSOCKET_SEND_BUFFER_SIZE];
size_t sendLen;
/**
* Called from the socket thread once TCP is established. If onReceive is
* NULL, the socket thread will exit after this returns and disconnect.
* If onReceive is set, a recv loop runs after this returns.
* Suitable entry point for TLS/HTTP layers that drive their own I/O.
*/
networksocketcallback_t onConnect;
/**
* When non-NULL, the socket thread runs a receive loop after connection
* and calls this for each incoming data chunk. When NULL, the thread only
* calls onConnect and then disconnects.
*/
networksocketrecvcallback_t onReceive;
networksocketerrorcallback_t onError;
networksocketcallback_t onDisconnect;
void *user;
} networksocket_t;
/**
* Initializes a socket structure. Must be called before any other socket
* function.
*/
errorret_t networksocketInit(networksocket_t *socket);
/**
* Starts an asynchronous connection in a dedicated thread. All callbacks are
* invoked from the socket thread, not the main thread.
*
* If onReceive is non-NULL, the thread runs a receive loop after connection
* and delivers incoming data via onReceive. If onReceive is NULL, the thread
* calls onConnect and exits (useful for TLS or request-driven I/O that
* manages its own reads via networksocketRead/networksocketWrite).
*
* @param socket Socket to connect.
* @param host Hostname or IP address to connect to.
* @param port Port number.
* @param onConnect Called once TCP is established (may be NULL).
* @param onError Called on connect or I/O error (may be NULL).
* @param onDisconnect Called on graceful disconnect (may be NULL).
* @param onReceive Called for each received chunk; NULL disables recv loop.
* @param user User data passed to all callbacks.
*/
void networksocketConnect(
networksocket_t *socket,
const char_t *host,
uint16_t port,
networksocketcallback_t onConnect,
networksocketerrorcallback_t onError,
networksocketcallback_t onDisconnect,
networksocketrecvcallback_t onReceive,
void *user
);
/**
* Queues data for sending. Thread-safe; may be called from any thread.
* The socket thread drains the queue on each recv-loop iteration.
* Asserts if the send buffer would overflow.
*/
errorret_t networksocketSend(
networksocket_t *socket,
const uint8_t *data,
size_t len
);
/**
* Writes data directly to the socket without buffering. Must be called from
* the socket's own thread (e.g., from within onConnect or a TLS bio callback).
*/
errorret_t networksocketWrite(
networksocket_t *socket,
const uint8_t *data,
size_t len
);
/**
* Reads data directly from the socket. Must be called from the socket's own
* thread. Returns 0 in *outLen on timeout (no data yet); returns
* NETWORKSOCKET_RECV_CLOSED in *outLen when the peer has closed the connection.
*/
errorret_t networksocketRead(
networksocket_t *socket,
uint8_t *buf,
size_t maxLen,
size_t *outLen
);
/**
* Requests the socket thread to stop. Non-blocking; onDisconnect fires once
* the thread has finished.
*/
void networksocketDisconnect(networksocket_t *socket);
/**
* Blocks until the socket thread has stopped, then releases platform
* resources. Safe to call on an already-disconnected socket.
*/
errorret_t networksocketDispose(networksocket_t *socket);
+288
View File
@@ -0,0 +1,288 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "networktls.h"
#include "util/memory.h"
#include "assert/assert.h"
// Define missing mbedtls net error codes if not present
#ifndef MBEDTLS_ERR_NET_SEND_FAILED
#define MBEDTLS_ERR_NET_SEND_FAILED -0x004E
#endif
#ifndef MBEDTLS_ERR_NET_RECV_FAILED
#define MBEDTLS_ERR_NET_RECV_FAILED -0x004C
#endif
/* ---- mbedTLS bio callbacks -------------------------------------------- */
#ifdef DUSK_DOLPHIN
#include <ogc/lwp_watchdog.h>
#include <mbedtls/platform_time.h>
mbedtls_ms_time_t mbedtls_ms_time(void) {
return (mbedtls_ms_time_t)ticks_to_millisecs(gettime());
}
#endif
int mbedtls_platform_get_entropy(uint8_t *output, size_t len) {
for (size_t i = 0; i < len; i++) {
output[i] = rand() & 0xFF;
}
return 0;
}
static int networktlsBioSend(
void *ctx,
const unsigned char *buf,
size_t len
) {
networktls_t *tls = (networktls_t *)ctx;
errorret_t err = networksocketWrite(&tls->socket, (const uint8_t *)buf, len);
if(err.code != ERROR_OK) return MBEDTLS_ERR_NET_SEND_FAILED;
return (int)len;
}
static int networktlsBioRecv(void *ctx, unsigned char *buf, size_t len) {
networktls_t *tls = (networktls_t *)ctx;
size_t outLen = 0;
errorret_t err = networksocketRead(
&tls->socket, (uint8_t *)buf, len, &outLen
);
if(err.code != ERROR_OK) return MBEDTLS_ERR_NET_RECV_FAILED;
if(outLen == NETWORKSOCKET_RECV_CLOSED) return 0;
if(outLen == 0) return MBEDTLS_ERR_SSL_WANT_READ;
return (int)outLen;
}
/* ---- socket callbacks (run in socket thread) --------------------------- */
static void networktlsSocketOnConnect(networksocket_t *socket, void *user) {
networktls_t *tls = (networktls_t *)user;
errorret_t errret;
int ret;
if(psa_crypto_init() != PSA_SUCCESS) {
errret = errorThrowImpl(
&tls->errorState, ERROR_NOT_OK,
__FILE__, __func__, __LINE__,
"psa_crypto_init failed"
);
if(tls->onError) tls->onError(tls, errret, tls->user);
return;
}
ret = mbedtls_ssl_config_defaults(
&tls->conf,
MBEDTLS_SSL_IS_CLIENT,
MBEDTLS_SSL_TRANSPORT_STREAM,
MBEDTLS_SSL_PRESET_DEFAULT
);
if(ret != 0) {
errret = errorThrowImpl(
&tls->errorState, ERROR_NOT_OK,
__FILE__, __func__, __LINE__,
"mbedtls_ssl_config_defaults failed: -0x%04x", -ret
);
if(tls->onError) tls->onError(tls, errret, tls->user);
return;
}
mbedtls_ssl_conf_authmode(
&tls->conf,
tls->verifyPeer ? MBEDTLS_SSL_VERIFY_REQUIRED : MBEDTLS_SSL_VERIFY_NONE
);
mbedtls_ssl_conf_ca_chain(&tls->conf, &tls->cacert, NULL);
ret = mbedtls_ssl_setup(&tls->ssl, &tls->conf);
if(ret != 0) {
errret = errorThrowImpl(
&tls->errorState, ERROR_NOT_OK,
__FILE__, __func__, __LINE__,
"mbedtls_ssl_setup failed: -0x%04x", -ret
);
if(tls->onError) tls->onError(tls, errret, tls->user);
return;
}
ret = mbedtls_ssl_set_hostname(&tls->ssl, tls->socket.host);
if(ret != 0) {
errret = errorThrowImpl(
&tls->errorState, ERROR_NOT_OK,
__FILE__, __func__, __LINE__,
"mbedtls_ssl_set_hostname failed: -0x%04x", -ret
);
if(tls->onError) tls->onError(tls, errret, tls->user);
return;
}
mbedtls_ssl_set_bio(
&tls->ssl, tls,
networktlsBioSend, networktlsBioRecv, NULL
);
/* TLS handshake loop — WANT_READ/WANT_WRITE are non-fatal retry signals */
do {
ret = mbedtls_ssl_handshake(&tls->ssl);
} while(
ret == MBEDTLS_ERR_SSL_WANT_READ ||
ret == MBEDTLS_ERR_SSL_WANT_WRITE
);
if(ret != 0) {
errret = errorThrowImpl(
&tls->errorState, ERROR_NOT_OK,
__FILE__, __func__, __LINE__,
"TLS handshake failed: -0x%04x", -ret
);
if(tls->onError) tls->onError(tls, errret, tls->user);
return;
}
/* Handshake succeeded — let the user drive I/O from this thread */
if(tls->onConnect) tls->onConnect(tls, tls->user);
mbedtls_ssl_close_notify(&tls->ssl);
}
static void networktlsSocketOnError(
networksocket_t *socket,
errorret_t err,
void *user
) {
networktls_t *tls = (networktls_t *)user;
if(tls->onError) tls->onError(tls, err, tls->user);
}
static void networktlsSocketOnDisconnect(
networksocket_t *socket,
void *user
) {
networktls_t *tls = (networktls_t *)user;
if(tls->onDisconnect) tls->onDisconnect(tls, tls->user);
}
/* ---- public API -------------------------------------------------------- */
errorret_t networktlsInit(networktls_t *tls) {
memoryZero(tls, sizeof(networktls_t));
errorChain(networksocketInit(&tls->socket));
mbedtls_ssl_init(&tls->ssl);
mbedtls_ssl_config_init(&tls->conf);
mbedtls_x509_crt_init(&tls->cacert);
errorOk();
}
errorret_t networktlsAddCACert(
networktls_t *tls,
const uint8_t *certPem,
size_t len
) {
int ret = mbedtls_x509_crt_parse(&tls->cacert, certPem, len);
if(ret != 0) {
errorThrowState(
&tls->errorState,
"Failed to parse CA certificate: -0x%04x", -ret
);
}
errorOk();
}
void networktlsConnect(
networktls_t *tls,
const char_t *host,
uint16_t port,
bool_t verifyPeer,
networktlscallback_t onConnect,
networktlserrorcallback_t onError,
networktlscallback_t onDisconnect,
void *user
) {
tls->verifyPeer = verifyPeer;
tls->onConnect = onConnect;
tls->onError = onError;
tls->onDisconnect = onDisconnect;
tls->user = user;
networksocketConnect(
&tls->socket,
host, port,
networktlsSocketOnConnect,
networktlsSocketOnError,
networktlsSocketOnDisconnect,
NULL, /* no recv loop — TLS drives all I/O from onConnect */
tls
);
}
errorret_t networktlsWrite(
networktls_t *tls,
const uint8_t *data,
size_t len
) {
size_t sent = 0;
int ret;
while(sent < len) {
ret = mbedtls_ssl_write(
&tls->ssl,
(const unsigned char *)(data + sent),
len - sent
);
if(ret == MBEDTLS_ERR_SSL_WANT_WRITE || ret == MBEDTLS_ERR_SSL_WANT_READ) {
continue;
}
if(ret < 0) {
errorThrowState(
&tls->errorState,
"TLS write failed: -0x%04x", -ret
);
}
sent += (size_t)ret;
}
errorOk();
}
errorret_t networktlsRead(
networktls_t *tls,
uint8_t *buf,
size_t maxLen,
size_t *outLen
) {
int ret;
do {
ret = mbedtls_ssl_read(&tls->ssl, (unsigned char *)buf, maxLen);
} while(
ret == MBEDTLS_ERR_SSL_WANT_READ ||
ret == MBEDTLS_ERR_SSL_WANT_WRITE
);
if(ret == 0 || ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
*outLen = NETWORKSOCKET_RECV_CLOSED;
errorOk();
}
if(ret < 0) {
errorThrowState(
&tls->errorState,
"TLS read failed: -0x%04x", -ret
);
}
*outLen = (size_t)ret;
errorOk();
}
void networktlsDisconnect(networktls_t *tls) {
networksocketDisconnect(&tls->socket);
}
errorret_t networktlsDispose(networktls_t *tls) {
errorChain(networksocketDispose(&tls->socket));
mbedtls_ssl_free(&tls->ssl);
mbedtls_ssl_config_free(&tls->conf);
mbedtls_x509_crt_free(&tls->cacert);
errorOk();
}
+108
View File
@@ -0,0 +1,108 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "network/networksocket.h"
#include <mbedtls/ssl.h>
#include <mbedtls/x509_crt.h>
#include <psa/crypto.h>
typedef struct networktls_s networktls_t;
typedef void (*networktlscallback_t)(networktls_t *tls, void *user);
typedef void (*networktlserrorcallback_t)(
networktls_t *tls, errorret_t err, void *user
);
typedef struct networktls_s {
networksocket_t socket;
errorstate_t errorState;
bool_t verifyPeer;
mbedtls_ssl_context ssl;
mbedtls_ssl_config conf;
mbedtls_x509_crt cacert;
networktlscallback_t onConnect;
networktlserrorcallback_t onError;
networktlscallback_t onDisconnect;
void *user;
} networktls_t;
/**
* Initializes the TLS context and the underlying socket.
*/
errorret_t networktlsInit(networktls_t *tls);
/**
* Adds a PEM-encoded CA certificate used to verify the server. Must be called
* before networktlsConnect when verifyPeer is true.
*/
errorret_t networktlsAddCACert(
networktls_t *tls,
const uint8_t *certPem,
size_t len
);
/**
* Starts an async TCP+TLS connection in the socket's own thread. All callbacks
* fire from that thread, not the main thread.
*
* When onConnect fires, the TLS handshake is complete and the connection is
* ready for networktlsWrite / networktlsRead. The connection is kept alive
* until onConnect returns, so all I/O should be performed synchronously within
* that callback.
*
* @param verifyPeer When true, the server certificate is verified against the
* CA certificates added via networktlsAddCACert. When false,
* certificate verification is skipped (useful for development
* or self-signed certificates).
*/
void networktlsConnect(
networktls_t *tls,
const char_t *host,
uint16_t port,
bool_t verifyPeer,
networktlscallback_t onConnect,
networktlserrorcallback_t onError,
networktlscallback_t onDisconnect,
void *user
);
/**
* Writes data through the TLS layer. Must be called from within the
* onConnect callback (i.e., from the socket thread).
*/
errorret_t networktlsWrite(
networktls_t *tls,
const uint8_t *data,
size_t len
);
/**
* Reads up to maxLen bytes from the TLS layer. Blocks until data arrives or
* the connection closes. Sets *outLen to bytes received, or
* NETWORKSOCKET_RECV_CLOSED when the peer has closed the connection.
* Must be called from within the onConnect callback.
*/
errorret_t networktlsRead(
networktls_t *tls,
uint8_t *buf,
size_t maxLen,
size_t *outLen
);
/**
* Requests disconnect. Non-blocking; onDisconnect fires once complete.
*/
void networktlsDisconnect(networktls_t *tls);
/**
* Blocks until the connection thread stops, then releases all resources.
*/
errorret_t networktlsDispose(networktls_t *tls);
+135 -274
View File
@@ -1,5 +1,5 @@
// Copyright (c) 2026 Dominic Masters
//
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
@@ -15,16 +15,56 @@
#include "display/spritebatch/spritebatch.h"
#include "display/text/text.h"
#include "display/screen/screen.h"
#include "console/console.h"
#include "util/string.h"
#include "script/scriptmanager.h"
#include "script/module/scene/modulescene.h"
scene_t SCENE;
char_t SCENE_LOG[SCENE_LOG_SIZE];
void sceneLog(const char *fmt, ...) {
char temp[512];
// 1. Format input like printf
va_list args;
va_start(args, fmt);
vsnprintf(temp, sizeof(temp), fmt, args);
va_end(args);
printf("%s", temp);
// 2. Split into lines
char *lines[64];
int line_count = 0;
char *ptr = temp;
while (*ptr && line_count < 64) {
lines[line_count++] = ptr;
char *nl = strchr(ptr, '\n');
if (!nl) break;
*nl = '\0';
ptr = nl + 1;
}
// 3. Prepend lines in reverse order (so final order is correct)
for (int i = 0; i < line_count; i++) {
char new_log[SCENE_LOG_SIZE];
snprintf(new_log, sizeof(new_log), "%s\n%s", lines[i], SCENE_LOG);
// Copy back safely
strncpy(SCENE_LOG, new_log, SCENE_LOG_SIZE - 1);
SCENE_LOG[SCENE_LOG_SIZE - 1] = '\0';
}
}
errorret_t sceneInit(void) {
memoryZero(&SCENE, sizeof(scene_t));
SCENE.scriptRef = SCENE_SCRIPT_REF_NONE;
memoryZero(SCENE_LOG, sizeof(SCENE_LOG));
sceneLog("Init\n");
errorOk();
}
@@ -34,30 +74,32 @@ errorret_t sceneUpdate(void) {
errorOk();
}
#endif
if(stringCompare(SCENE.sceneNext, SCENE.sceneCurrent) != 0) {
errorChain(sceneSetImmediate(SCENE.sceneNext));
}
if(SCENE.sceneActive) {
errorChain(moduleSceneCall("update"));
}
errorOk();
}
dusktimeepoch_t LAST;
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
);
if(camCount == 0) errorOk();
// Get meshes
entityid_t meshEnts[ENTITY_COUNT_MAX];
componentid_t meshComps[ENTITY_COUNT_MAX];
entityid_t meshCount = componentGetEntitiesWithComponent(
COMPONENT_TYPE_MESH, meshEnts, meshComps
);
if(meshCount == 0) errorOk();
// Prep Matricies
mat4 view, proj, model;
errorChain(shaderBind(&SHADER_UNLIT));
// For each camera
// For each camera.
for(entityid_t camIndex = 0; camIndex < camCount; camIndex++) {
entityid_t camEnt = camEnts[camIndex];
componentid_t camComp = camComps[camIndex];
@@ -70,271 +112,90 @@ errorret_t sceneRender(void) {
entityCameraGetProjection(camEnt, camComp, proj);
entityPositionGetTransform(camEnt, camPos, view);
// For each entity
for(entityid_t entityId = 0; entityId < ENTITY_COUNT_MAX; entityId++) {
// Does this entity have a material?
componentid_t matComp = entityGetComponent(
entityId, COMPONENT_TYPE_MATERIAL
);
if(matComp != 0xFF) {
// Yes, get the mesh
componentid_t meshComp = entityGetComponent(
entityId, COMPONENT_TYPE_MESH
);
// For each mesh.
for(entityid_t meshIndex = 0; meshIndex < meshCount; meshIndex++) {
entityid_t meshEnt = meshEnts[meshIndex];
if(meshComp == 0xFF) {
logError("Entity with material component without mesh component found\n");
continue;
}
// Yes, get the material and shader.
shadermaterial_t *material = entityMaterialGetShaderMaterial(
entityId, matComp
);
shader_t *shader = entityMaterialGetShader(entityId, matComp);
if(shader == NULL) {
logError("Entity with material component without shader found\n");
continue;
}
// Get the mesh
mesh_t *mesh = entityMeshGetMesh(entityId, meshComp);
if(mesh == NULL) {
logError("Entity with material component without mesh found\n");
continue;
}
// Get the transform.
componentid_t meshPos = entityGetComponent(
entityId, COMPONENT_TYPE_POSITION
);
if(meshPos == 0xFF) {
glm_mat4_identity(model);
} else {
entityPositionGetTransform(entityId, meshPos, model);
}
// Render the mesh.
errorChain(shaderBind(shader));
errorChain(shaderSetMatrix(shader, SHADER_UNLIT_PROJECTION, proj));
errorChain(shaderSetMatrix(shader, SHADER_UNLIT_VIEW, view));
errorChain(shaderSetMatrix(shader, SHADER_UNLIT_MODEL, model));
errorChain(shaderSetMaterial(shader, material));
errorChain(meshDraw(mesh, 0, -1));
componentid_t meshComp = meshComps[meshIndex];
mesh_t *mesh = entityMeshGetMesh(meshEnt, meshComp);
if(mesh == NULL) {
continue;
}
// No, in future there may be other renderable types.
componentid_t meshPos = entityGetComponent(
meshEnt, COMPONENT_TYPE_POSITION
);
if(meshPos == 0xFF) {
logError("Mesh entity without entity position found\n");
continue;
}
componentid_t meshMat = entityGetComponent(
meshEnt, COMPONENT_TYPE_MATERIAL
);
if(meshMat == 0xFF) {
logError("Mesh entity without material component found\n");
continue;
}
shadermaterial_t *material = entityMaterialGetShaderMaterial(
meshEnt, meshMat
);
shader_t *shader = entityMaterialGetShader(meshEnt, meshMat);
if(shader == NULL) {
logError("Mesh entity with material component without shader found\n");
continue;
}
entityPositionGetTransform(meshEnt, meshPos, model);
errorChain(shaderBind(shader));
errorChain(shaderSetMatrix(shader, SHADER_UNLIT_PROJECTION, proj));
errorChain(shaderSetMatrix(shader, SHADER_UNLIT_VIEW, view));
errorChain(shaderSetMatrix(shader, SHADER_UNLIT_MODEL, model));
errorChain(shaderSetMaterial(shader, material));
errorChain(meshDraw(mesh, 0, -1));
}
}
errorOk();
// if(camCount > 0) {
// // For each entity
// for(entityid_t entityId = 0; entityId < ENTITY_COUNT_MAX; entityId++) {
// }
// entityid_t meshEnts[ENTITY_COUNT_MAX];
// componentid_t meshComps[ENTITY_COUNT_MAX];
// entityid_t meshCount = componentGetEntitiesWithComponent(
// COMPONENT_TYPE_MESH, meshEnts, meshComps
// );
// if(meshCount > 0) {
// errorChain(shaderBind(&SHADER_UNLIT));
// for(entityid_t camIndex = 0; camIndex < camCount; camIndex++) {
// entityid_t camEnt = camEnts[camIndex];
// componentid_t camComp = camComps[camIndex];
// componentid_t camPos = entityGetComponent(camEnt, COMPONENT_TYPE_POSITION);
// if(camPos == 0xFF) {
// logError("Camera entity without entity position found\n");
// continue;
// }
// entityCameraGetProjection(camEnt, camComp, proj);
// entityPositionGetTransform(camEnt, camPos, view);
// 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;
// componentid_t meshPos = entityGetComponent(
// meshEnt, COMPONENT_TYPE_POSITION
// );
// if(meshPos == 0xFF) {
// logError("Mesh entity without entity position found\n");
// continue;
// }
// componentid_t meshMat = entityGetComponent(
// meshEnt, COMPONENT_TYPE_MATERIAL
// );
// if(meshMat == 0xFF) {
// logError("Mesh entity without material component found\n");
// continue;
// }
// shadermaterial_t *material = entityMaterialGetShaderMaterial(
// meshEnt, meshMat
// );
// shader_t *shader = entityMaterialGetShader(meshEnt, meshMat);
// if(shader == NULL) {
// logError("Mesh entity with material component without shader found\n");
// continue;
// }
// entityPositionGetTransform(meshEnt, meshPos, model);
// errorChain(shaderBind(shader));
// errorChain(shaderSetMatrix(shader, SHADER_UNLIT_PROJECTION, proj));
// errorChain(shaderSetMatrix(shader, SHADER_UNLIT_VIEW, view));
// errorChain(shaderSetMatrix(shader, SHADER_UNLIT_MODEL, model));
// errorChain(shaderSetMaterial(shader, material));
// errorChain(meshDraw(mesh, 0, -1));
// }
// }
// }
// }
// glm_ortho(
// 0.0f, SCREEN.width,
// SCREEN.height, 0.0f,
// 0.1f, 100.0f,
// proj
// );
// glm_lookat(
// (vec3){ 0.0f, 0.0f, 1.0f },
// (vec3){ 0.0f, 0.0f, 0.0f },
// (vec3){ 0.0f, 1.0f, 0.0f },
// view
// );
// glm_mat4_identity(model);
// errorChain(shaderBind(&SHADER_UNLIT));
// errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_PROJECTION, proj));
// errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, view));
// errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, model));
// {
// entityid_t sprEnts[ENTITY_COUNT_MAX];
// componentid_t sprComps[ENTITY_COUNT_MAX];
// entityid_t sprCount = componentGetEntitiesWithComponent(
// COMPONENT_TYPE_SPRITE, sprEnts, sprComps
// );
// for(entityid_t si = 0; si < sprCount; si++) {
// entitysprite_t *spr = entitySpriteGet(sprEnts[si], sprComps[si]);
// vec3 pos = { 0.0f, 0.0f, 0.0f };
// componentid_t posComp = entityGetComponent(
// sprEnts[si], COMPONENT_TYPE_POSITION
// );
// if(posComp != 0xFF) {
// entityPositionGetPosition(sprEnts[si], posComp, pos);
// }
// errorChain(shaderSetTexture(
// &SHADER_UNLIT, SHADER_UNLIT_TEXTURE, spr->texture
// ));
// #if !MESH_ENABLE_COLOR
// errorChain(shaderSetColor(
// &SHADER_UNLIT, SHADER_UNLIT_COLOR, spr->color
// ));
// #endif
// errorChain(spriteBatchPush(
// pos[0], pos[1],
// pos[0] + spr->width, pos[1] + spr->height,
// #if MESH_ENABLE_COLOR
// spr->color,
// #endif
// spr->uv[0], spr->uv[1],
// spr->uv[2], spr->uv[3]
// ));
// errorChain(spriteBatchFlush());
// }
// }
// errorChain(consoleDraw());
// // FPS
// char_t fpsText[32];
// dusktimeepoch_t now = timeGetEpoch();
// double_t delta = now.time - LAST.time;
// LAST = now;
// double_t fps = delta > 0 ? 1.0 / delta : 0.0;
// snprintf(fpsText, sizeof(fpsText), "FPS: %.2f", fps);
// errorChain(spriteBatchFlush());
// errorChain(textDraw(
// 0, 0,
// fpsText, COLOR_WHITE,
// &FONT_TILESET_DEFAULT, &FONT_TEXTURE_DEFAULT
// ));
// errorChain(spriteBatchFlush());
// errorOk();
}
errorret_t sceneSetImmediate(const char_t *scene) {
if(scene != SCENE.sceneNext) {
stringCopy(
SCENE.sceneNext,
scene == NULL ? "" : scene,
ASSET_FILE_PATH_MAX
);
}
if(SCENE.sceneActive) {
errorChain(moduleSceneCall("dispose"));
SCENE.sceneActive = false;
}
moduleSceneReset();
stringCopy(
SCENE.sceneCurrent,
scene == NULL ? "" : scene,
ASSET_FILE_PATH_MAX
// Here is where UI will go
glm_ortho(
0.0f, SCREEN.width,
SCREEN.height, 0.0f,
0.1f, 100.0f,
proj
);
if(scene != NULL) {
jerry_value_t sceneClass = SCENE_SCRIPT_REF_NONE;
errorChain(scriptManagerExecFile(scene, &sceneClass));
if(!jerry_value_is_function(sceneClass)) {
if(sceneClass != SCENE_SCRIPT_REF_NONE) jerry_value_free(sceneClass);
errorThrow("Scene '%s' must export a constructor function", scene);
}
jerry_value_t sceneObj = jerry_construct(sceneClass, NULL, 0);
jerry_value_free(sceneClass);
if(jerry_value_is_exception(sceneObj)) {
char_t errMsg[512];
moduleBaseExceptionMessage(sceneObj, errMsg, sizeof(errMsg));
jerry_value_free(sceneObj);
errorThrow("Scene '%s' constructor threw: %s", scene, errMsg);
}
SCENE.scriptRef = sceneObj;
SCENE.sceneActive = true;
}
errorOk();
}
void sceneSet(const char_t *scene) {
stringCopy(
SCENE.sceneNext,
scene == NULL ? "" : scene,
ASSET_FILE_PATH_MAX
glm_lookat(
(vec3){ 0.0f, 0.0f, 1.0f },
(vec3){ 0.0f, 0.0f, 0.0f },
(vec3){ 0.0f, 1.0f, 0.0f },
view
);
}
glm_mat4_identity(model);
errorChain(shaderBind(&SHADER_UNLIT));
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_PROJECTION, proj));
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, view));
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, model));
// errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_0TEXTURE, &DEFAULT_FONT_TEXTURE));
// errorChain(shaderSetColor(&SHADER_UNLIT, SHADER_UNLIT_COLOR, COLOR_WHITE));
errorChain(textDraw(
32, 32,
// "Hello World",
SCENE_LOG,
COLOR_WHITE,
&DEFAULT_FONT_TILESET,
&DEFAULT_FONT_TEXTURE
));
errorChain(spriteBatchFlush());
errorret_t sceneDispose(void) {
errorChain(moduleSceneCall("dispose"));
errorOk();
}
errorret_t sceneSet(const char_t *script) {
errorOk();
}
void sceneDispose(void) {
}
+21 -37
View File
@@ -1,68 +1,52 @@
/**
* Copyright (c) 2026 Dominic Masters
*
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptmanager.h"
#include "asset/assetfile.h"
#include "event/event.h"
#define SCENE_EVENT_UPDATE_MAX 16
#include "error/error.h"
typedef struct {
bool_t sceneActive;
jerry_value_t scriptRef;
char_t sceneCurrent[ASSET_FILE_PATH_MAX];
char_t sceneNext[ASSET_FILE_PATH_MAX];
void *nothing;
} scene_t;
extern scene_t SCENE;
/** Sentinel value meaning no scene script is loaded. */
#define SCENE_SCRIPT_REF_NONE ((jerry_value_t)0)
#define SCENE_LOG_SIZE 1024
extern char_t SCENE_LOG[SCENE_LOG_SIZE];
void sceneLog(const char *fmt, ...);
/**
* Initializes the scene manager.
*
* @return Any error state that happened.
* Initialize the scene subsystem.
*
* @return The error return value.
*/
errorret_t sceneInit(void);
/**
* Ticks the scene manager; may call the scene's update method.
*
* @return Any error state that happened.
* Update the current scene.
*
* @return The error return value.
*/
errorret_t sceneUpdate(void);
/**
* Renders the scene.
*
* @return Any error state that happened.
* Render the current scene.
*
* @return The error return value.
*/
errorret_t sceneRender(void);
/**
* Immediately switches scenes, disposing the current one first.
*
* @param scene Scene to switch to (asset file path).
* @return Any error state that happened.
* Set the current scene by script name.
*
* @param script The script name of the scene to set.
*/
errorret_t sceneSetImmediate(const char_t *scene);
errorret_t sceneSet(const char_t *script);
/**
* Requests a scene change on the next safe opportunity.
*
* @param scene Which scene to set.
* Dispose of the scene subsystem.
*/
void sceneSet(const char_t *scene);
/**
* Disposes of the current scene.
*
* @return Any error state that happened.
*/
errorret_t sceneDispose(void);
void sceneDispose(void);
+4 -2
View File
@@ -7,7 +7,9 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
scriptmanager.c
scriptproto.c
scriptcontext.c
scriptmodule.c
)
# Subdirectories
# Subdirectories
add_subdirectory(module)
+14
View File
@@ -0,0 +1,14 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Subdirectories
add_subdirectory(display)
add_subdirectory(event)
add_subdirectory(input)
add_subdirectory(locale)
add_subdirectory(system)
add_subdirectory(scene)
add_subdirectory(time)
add_subdirectory(ui)
@@ -1,67 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/module/modulebase.h"
#include "script/scriptproto.h"
#include "console/console.h"
static scriptproto_t MODULE_CONSOLE_PROTO;
moduleBaseFunction(moduleConsolePrint) {
char_t buf[512];
char_t msg[4096];
size_t msgLen = 0;
for(jerry_length_t i = 0; i < argc; ++i) {
jerry_value_t strVal = jerry_value_to_string(args[i]);
moduleBaseToString(strVal, buf, sizeof(buf));
jerry_value_free(strVal);
size_t partLen = strlen(buf);
if(msgLen + partLen + 1 < sizeof(msg)) {
stringCopy(msg + msgLen, buf, sizeof(msg) - msgLen);
msgLen += partLen;
}
if(i + 1 < argc && msgLen + 1 < sizeof(msg)) {
msg[msgLen++] = '\t';
msg[msgLen] = '\0';
}
}
consolePrint("%s", msg);
return jerry_undefined();
}
moduleBaseFunction(moduleConsoleGetVisible) {
return jerry_boolean(CONSOLE.visible);
}
moduleBaseFunction(moduleConsoleSetVisible) {
moduleBaseRequireArgs(1);
if(!jerry_value_is_boolean(args[0])) {
return moduleBaseThrow("Console.visible: expected boolean");
}
CONSOLE.visible = jerry_value_is_true(args[0]);
return jerry_undefined();
}
static void moduleConsole(void) {
scriptProtoInit(
&MODULE_CONSOLE_PROTO, "Console",
sizeof(uint8_t), NULL
);
scriptProtoDefineStaticFunc(
&MODULE_CONSOLE_PROTO, "print", moduleConsolePrint
);
scriptProtoDefineStaticProp(
&MODULE_CONSOLE_PROTO, "visible",
moduleConsoleGetVisible, moduleConsoleSetVisible
);
}
@@ -0,0 +1,18 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
moduleglm.c
modulespritebatch.c
moduleglm.c
modulecolor.c
moduletext.c
modulescreen.c
moduletileset.c
moduletexture.c
moduleshader.c
)
@@ -0,0 +1,186 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "modulecolor.h"
#include "display/color.h"
#include "assert/assert.h"
#include "util/string.h"
#include "time/time.h"
void moduleColor(scriptcontext_t *context) {
assertNotNull(context, "Context cannot be NULL.");
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);
lua_pushstring(context->luaState, "__newindex");
lua_pushcfunction(context->luaState, moduleColorNewIndex);
lua_settable(context->luaState, -3);
lua_pushstring(context->luaState, "__tostring");
lua_pushcfunction(context->luaState, moduleColorToString);
lua_settable(context->luaState, -3);
}
lua_pop(context->luaState, 1);
lua_register(context->luaState, "color", moduleColorFuncColor);
lua_register(context->luaState, "colorRainbow", moduleColorRainbow);
scriptContextExec(context, COLOR_SCRIPT);
}
int moduleColorFuncColor(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL.");
scriptcontext_t *context = *(scriptcontext_t **)lua_getextraspace(L);
assertNotNull(context, "Script context cannot be NULL.");
// Needs 4 channel uint8_t
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.");
}
colorchannel8_t r = (colorchannel8_t)lua_tonumber(L, 1);
colorchannel8_t g = (colorchannel8_t)lua_tonumber(L, 2);
colorchannel8_t b = (colorchannel8_t)lua_tonumber(L, 3);
colorchannel8_t a = (colorchannel8_t)lua_tonumber(L, 4);
// 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;
return 1;
}
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) {
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_pushnil(L);
return 1;
}
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(!lua_isnumber(L, 3)) {
return luaL_error(L, "color channel r must be a number.");
}
color->r = (colorchannel8_t)lua_tonumber(L, 3);
return 0;
} else if(stringCompare(key, "g") == 0) {
if(!lua_isnumber(L, 3)) {
return luaL_error(L, "color channel g must be a number.");
}
color->g = (colorchannel8_t)lua_tonumber(L, 3);
return 0;
} else if(stringCompare(key, "b") == 0) {
if(!lua_isnumber(L, 3)) {
return luaL_error(L, "color channel b must be a number.");
}
color->b = (colorchannel8_t)lua_tonumber(L, 3);
return 0;
} else if(stringCompare(key, "a") == 0) {
if(!lua_isnumber(L, 3)) {
return luaL_error(L, "color channel a must be a number.");
}
color->a = (colorchannel8_t)lua_tonumber(L, 3);
return 0;
}
return 0;
}
int moduleColorToString(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL.");
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
);
return 1;
}
int moduleColorRainbow(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL.");
// Allow time offset
float_t t = TIME.time;
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);
}
// Allow speed multiplier
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);
}
// Generate rainbow based on time.
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;
// Set metatable
luaL_getmetatable(L, "color_mt");
lua_setmetatable(L, -2);
return 1;
}
+42 -145
View File
@@ -1,158 +1,55 @@
/**
* Copyright (c) 2026 Dominic Masters
*
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/module/modulebase.h"
#include "display/color.h"
#include "time/time.h"
#include "script/scriptproto.h"
#include "script/scriptcontext.h"
static scriptproto_t MODULE_COLOR_PROTO;
/**
* Registers the color module with the given script context.
*
* @param context The script context to register the module with.
*/
void moduleColor(scriptcontext_t *context);
static inline color_t * moduleColorGet(
const jerry_call_info_t *callInfo
) {
return (color_t*)scriptProtoGetValue(
&MODULE_COLOR_PROTO, callInfo->this_value
);
}
/**
* Lua function to create a color.
*
* @param L The Lua state.
* @return Number of return values.
*/
int moduleColorFuncColor(lua_State *L);
moduleBaseFunction(moduleColorGetR) {
color_t *c = moduleColorGet(callInfo);
return c ? jerry_number(c->r) : jerry_undefined();
}
/**
* Index function for the color structure.
* @param L The Lua state.
* @return Number of return values.
*/
int moduleColorIndex(lua_State *L);
moduleBaseFunction(moduleColorGetG) {
color_t *c = moduleColorGet(callInfo);
return c ? jerry_number(c->g) : jerry_undefined();
}
/**
* New index function for the color structure.
*
* @param L The Lua state.
* @return Number of return values.
*/
int moduleColorNewIndex(lua_State *L);
moduleBaseFunction(moduleColorGetB) {
color_t *c = moduleColorGet(callInfo);
return c ? jerry_number(c->b) : jerry_undefined();
}
/**
* Color to string method for script
*
* @param L The Lua state.
* @return Number of return values.
*/
int moduleColorToString(lua_State *L);
moduleBaseFunction(moduleColorGetA) {
color_t *c = moduleColorGet(callInfo);
return c ? jerry_number(c->a) : jerry_undefined();
}
moduleBaseFunction(moduleColorSetR) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
color_t *c = moduleColorGet(callInfo);
if(!c) return jerry_undefined();
c->r = (colorchannel8_t)jerry_value_as_number(args[0]);
return args[0];
}
moduleBaseFunction(moduleColorSetG) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
color_t *c = moduleColorGet(callInfo);
if(!c) return jerry_undefined();
c->g = (colorchannel8_t)jerry_value_as_number(args[0]);
return args[0];
}
moduleBaseFunction(moduleColorSetB) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
color_t *c = moduleColorGet(callInfo);
if(!c) return jerry_undefined();
c->b = (colorchannel8_t)jerry_value_as_number(args[0]);
return args[0];
}
moduleBaseFunction(moduleColorSetA) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
color_t *c = moduleColorGet(callInfo);
if(!c) return jerry_undefined();
c->a = (colorchannel8_t)jerry_value_as_number(args[0]);
return args[0];
}
moduleBaseFunction(moduleColorToString) {
color_t *c = moduleColorGet(callInfo);
if(!c) return jerry_undefined();
char_t buf[64];
stringFormat(
buf, sizeof(buf),
"{ \"r\": %d, \"g\": %d, \"b\": %d, \"a\": %d }",
(int32_t)c->r, (int32_t)c->g,
(int32_t)c->b, (int32_t)c->a
);
return jerry_string_sz(buf);
}
static jerry_value_t moduleColorMakeObject(color_t color) {
return scriptProtoCreateValue(&MODULE_COLOR_PROTO, &color);
}
moduleBaseFunction(moduleColorConstructor) {
if(argc > 0 && !jerry_value_is_number(args[0])) {
return moduleBaseThrow("Color: r must be a number");
}
if(argc > 1 && !jerry_value_is_number(args[1])) {
return moduleBaseThrow("Color: g must be a number");
}
if(argc > 2 && !jerry_value_is_number(args[2])) {
return moduleBaseThrow("Color: b must be a number");
}
if(argc > 3 && !jerry_value_is_number(args[3])) {
return moduleBaseThrow("Color: a must be a number");
}
color_t c;
c.r = argc > 0 ? (colorchannel8_t)jerry_value_as_number(args[0]) : 255;
c.g = argc > 1 ? (colorchannel8_t)jerry_value_as_number(args[1]) : 255;
c.b = argc > 2 ? (colorchannel8_t)jerry_value_as_number(args[2]) : 255;
c.a = argc > 3 ? (colorchannel8_t)jerry_value_as_number(args[3]) : 255;
return moduleColorMakeObject(c);
}
moduleBaseFunction(moduleColorRainbow) {
float_t t;
if(argc >= 1 && jerry_value_is_number(args[0])) {
t = (float_t)jerry_value_as_number(args[0]);
} else {
t = TIME.time * 4.0f;
}
if(argc >= 2 && jerry_value_is_number(args[1])) {
t *= (float_t)jerry_value_as_number(args[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(void) {
scriptProtoInit(
&MODULE_COLOR_PROTO, "Color", sizeof(color_t), moduleColorConstructor
);
scriptProtoDefineProp(
&MODULE_COLOR_PROTO, "r", moduleColorGetR, moduleColorSetR
);
scriptProtoDefineProp(
&MODULE_COLOR_PROTO, "g", moduleColorGetG, moduleColorSetG
);
scriptProtoDefineProp(
&MODULE_COLOR_PROTO, "b", moduleColorGetB, moduleColorSetB
);
scriptProtoDefineProp(
&MODULE_COLOR_PROTO, "a", moduleColorGetA, moduleColorSetA
);
scriptProtoDefineStaticFunc(
&MODULE_COLOR_PROTO, "rainbow", moduleColorRainbow
);
scriptProtoDefineToString(&MODULE_COLOR_PROTO, moduleColorToString);
moduleBaseEval(COLOR_SCRIPT);
}
/**
* Lua function to create a rainbow color based on time.
*
* @param L The Lua state.
* @return Number of return values.
*/
int moduleColorRainbow(lua_State *L);
+283
View File
@@ -0,0 +1,283 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "moduleglm.h"
#include "assert/assert.h"
#include "util/string.h"
#include "util/memory.h"
void moduleGLM(scriptcontext_t *context) {
assertNotNull(context, "Context cannot be NULL.");
// Create metatable for vec3 structure.
if(luaL_newmetatable(context->luaState, "vec3_mt")) {
// Metatable methods
lua_pushcfunction(context->luaState, moduleVec3Index);
lua_setfield(context->luaState, -2, "__index");
lua_pushcfunction(context->luaState, moduleVec3NewIndex);
lua_setfield(context->luaState, -2, "__newindex");
lua_pushcfunction(context->luaState, moduleVec3ToString);
lua_setfield(context->luaState, -2, "__tostring");
}
lua_pop(context->luaState, 1);
if(luaL_newmetatable(context->luaState, "vec4_mt")) {
// Metatable methods
lua_pushcfunction(context->luaState, moduleVec4Index);
lua_setfield(context->luaState, -2, "__index");
lua_pushcfunction(context->luaState, moduleVec4NewIndex);
lua_setfield(context->luaState, -2, "__newindex");
lua_pushcfunction(context->luaState, moduleVec4ToString);
lua_setfield(context->luaState, -2, "__tostring");
}
lua_pop(context->luaState, 1);
lua_register(context->luaState, "vec3", moduleVec3Create);
}
int moduleVec3Create(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
vec3 *v = (vec3 *)lua_newuserdata(l, sizeof(vec3));
memoryZero(v, sizeof(vec3));
// May be expecting between 1 and 3 values.
int top = lua_gettop(l);
if(top >= 1) {
if(!lua_isnumber(l, 1)) {
luaL_error(l, "Vec3 x component must be a number.");
}
(*v)[0] = (float_t)lua_tonumber(l, 1);
}
if(top >= 2) {
if(!lua_isnumber(l, 2)) {
luaL_error(l, "Vec3 y component must be a number.");
}
(*v)[1] = (float_t)lua_tonumber(l, 2);
}
if(top >= 3) {
if(!lua_isnumber(l, 3)) {
luaL_error(l, "Vec3 z component must be a number.");
}
(*v)[2] = (float_t)lua_tonumber(l, 3);
}
// Set metatable
luaL_getmetatable(l, "vec3_mt");
lua_setmetatable(l, -2);
return 1;
}
int moduleVec3Index(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.");
vec3 *vec = (vec3 *)luaL_checkudata(l, 1, "vec3_mt");
assertNotNull(vec, "Vec3 pointer cannot be NULL.");
if(stringCompare(key, "x") == 0) {
lua_pushnumber(l, (*vec)[0]);
return 1;
} else if(stringCompare(key, "y") == 0) {
lua_pushnumber(l, (*vec)[1]);
return 1;
} else if(stringCompare(key, "z") == 0) {
lua_pushnumber(l, (*vec)[2]);
return 1;
}
}
int moduleVec3NewIndex(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.");
vec3 *vec = (vec3 *)luaL_checkudata(l, 1, "vec3_mt");
assertNotNull(vec, "Vec3 pointer cannot be NULL.");
if(stringCompare(key, "x") == 0) {
if(!lua_isnumber(l, 3)) {
luaL_error(l, "Vec3 x component must be a number.");
}
(*vec)[0] = (float_t)lua_tonumber(l, 3);
return 0;
} else if(stringCompare(key, "y") == 0) {
if(!lua_isnumber(l, 3)) {
luaL_error(l, "Vec3 y component must be a number.");
}
(*vec)[1] = (float_t)lua_tonumber(l, 3);
return 0;
} else if(stringCompare(key, "z") == 0) {
if(!lua_isnumber(l, 3)) {
luaL_error(l, "Vec3 z component must be a number.");
}
(*vec)[2] = (float_t)lua_tonumber(l, 3);
return 0;
}
luaL_error(l, "Invalid key for vec3: %s", key);
return 0;
}
int moduleVec3ToString(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
vec3 *vec = (vec3 *)luaL_checkudata(l, 1, "vec3_mt");
assertNotNull(vec, "Vec3 pointer cannot be NULL.");
char buf[128];
snprintf(
buf, sizeof(buf),
"vec3(%.3f, %.3f, %.3f)",
(*vec)[0], (*vec)[1], (*vec)[2]
);
lua_pushstring(l, buf);
return 1;
}
int moduleVec4Create(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
vec4 *v = (vec4 *)lua_newuserdata(l, sizeof(vec4));
memoryZero(v, sizeof(vec4));
// May be expecting between 1 and 4 values.
int top = lua_gettop(l);
if(top >= 1) {
if(!lua_isnumber(l, 1)) {
luaL_error(l, "Vec4 x component must be a number.");
}
(*v)[0] = (float_t)lua_tonumber(l, 1);
}
if(top >= 2) {
if(!lua_isnumber(l, 2)) {
luaL_error(l, "Vec4 y component must be a number.");
}
(*v)[1] = (float_t)lua_tonumber(l, 2);
}
if(top >= 3) {
if(!lua_isnumber(l, 3)) {
luaL_error(l, "Vec4 z component must be a number.");
}
(*v)[2] = (float_t)lua_tonumber(l, 3);
}
if(top >= 4) {
if(!lua_isnumber(l, 4)) {
luaL_error(l, "Vec4 w component must be a number.");
}
(*v)[3] = (float_t)lua_tonumber(l, 4);
}
// Set metatable
luaL_getmetatable(l, "vec4_mt");
lua_setmetatable(l, -2);
return 1;
}
int moduleVec4Index(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.");
vec4 *vec = (vec4 *)luaL_checkudata(l, 1, "vec4_mt");
assertNotNull(vec, "Vec4 pointer cannot be NULL.");
if(stringCompare(key, "x") == 0) {
lua_pushnumber(l, (*vec)[0]);
return 1;
} else if(stringCompare(key, "y") == 0) {
lua_pushnumber(l, (*vec)[1]);
return 1;
} else if(stringCompare(key, "z") == 0) {
lua_pushnumber(l, (*vec)[2]);
return 1;
} else if(stringCompare(key, "w") == 0) {
lua_pushnumber(l, (*vec)[3]);
return 1;
} else if(stringCompare(key, "u0") == 0) {
lua_pushnumber(l, (*vec)[0]);
return 1;
} else if(stringCompare(key, "v0") == 0) {
lua_pushnumber(l, (*vec)[1]);
return 1;
} else if(stringCompare(key, "u1") == 0) {
lua_pushnumber(l, (*vec)[2]);
return 1;
} else if(stringCompare(key, "v1") == 0) {
lua_pushnumber(l, (*vec)[3]);
return 1;
}
lua_pushnil(l);
return 1;
}
int moduleVec4NewIndex(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.");
vec4 *vec = (vec4 *)luaL_checkudata(l, 1, "vec4_mt");
assertNotNull(vec, "Vec4 pointer cannot be NULL.");
if(stringCompare(key, "x") == 0) {
if(!lua_isnumber(l, 3)) {
luaL_error(l, "Vec4 x component must be a number.");
}
(*vec)[0] = (float_t)lua_tonumber(l, 3);
return 0;
} else if(stringCompare(key, "y") == 0) {
if(!lua_isnumber(l, 3)) {
luaL_error(l, "Vec4 y component must be a number.");
}
(*vec)[1] = (float_t)lua_tonumber(l, 3);
return 0;
} else if(stringCompare(key, "z") == 0) {
if(!lua_isnumber(l, 3)) {
luaL_error(l, "Vec4 z component must be a number.");
}
(*vec)[2] = (float_t)lua_tonumber(l, 3);
return 0;
} else if(stringCompare(key, "w") == 0) {
if(!lua_isnumber(l, 3)) {
luaL_error(l, "Vec4 w component must be a number.");
}
(*vec)[3] = (float_t)lua_tonumber(l, 3);
return 0;
}
luaL_error(l, "Invalid key for vec4: %s", key);
return 0;
}
int moduleVec4ToString(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
vec4 *vec = (vec4 *)luaL_checkudata(l, 1, "vec4_mt");
assertNotNull(vec, "Vec4 pointer cannot be NULL.");
char buf[128];
snprintf(
buf, sizeof(buf),
"vec4(%.3f, %.3f, %.3f, %.3f)",
(*vec)[0], (*vec)[1], (*vec)[2], (*vec)[3]
);
lua_pushstring(l, buf);
return 1;
}
@@ -0,0 +1,80 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptcontext.h"
/**
* Registers the GLM module with the given script context.
*
* @param context The script context to register the module with.
*/
void moduleGLM(scriptcontext_t *context);
/**
* Creates a new vec3 structure in Lua.
*
* @param l The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleVec3Create(lua_State *l);
/**
* Lua __index metamethod for vec3 structure.
*
* @param l The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleVec3Index(lua_State *l);
/**
* Lua __newindex metamethod for vec3 structure.
*
* @param l The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleVec3NewIndex(lua_State *l);
/**
* Lua __tostring metamethod for vec3 structure.
*
* @param l The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleVec3ToString(lua_State *l);
/**
* Creates a new vec4 structure in Lua.
*
* @param l The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleVec4Create(lua_State *l);
/**
* Lua __index metamethod for vec4 structure.
*
* @param l The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleVec4Index(lua_State *l);
/**
* Lua __newindex metamethod for vec4 structure.
*
* @param l The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleVec4NewIndex(lua_State *l);
/**
* Lua __tostring metamethod for vec4 structure.
*
* @param l The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleVec4ToString(lua_State *l);
@@ -0,0 +1,43 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "modulescreen.h"
#include "assert/assert.h"
#include "display/screen/screen.h"
void moduleScreen(scriptcontext_t *context) {
assertNotNull(context, "Context cannot be NULL.");
lua_register(context->luaState, "screenGetWidth", moduleScreenGetWidth);
lua_register(context->luaState, "screenGetHeight", moduleScreenGetHeight);
lua_register(context->luaState, "screenSetBackground", moduleScreenSetBackground);
}
int moduleScreenGetWidth(lua_State *L) {
assertNotNull(L, "Lua state is null");
lua_pushnumber(L, SCREEN.width);
return 1;
}
int moduleScreenGetHeight(lua_State *L) {
assertNotNull(L, "Lua state is null");
lua_pushnumber(L, SCREEN.height);
return 1;
}
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;
}
color_t *color = (color_t*)luaL_checkudata(L, 1, "color_mt");
SCREEN.background = *color;
return 0;
}
+29 -63
View File
@@ -1,74 +1,40 @@
/**
* Copyright (c) 2026 Dominic Masters
*
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/module/display/modulecolor.h"
#include "display/screen/screen.h"
#include "script/scriptcontext.h"
static scriptproto_t MODULE_SCREEN_PROTO;
/**
* Registers the Screen module with the given script context.
*
* @param context The script context to register the module with.
*/
void moduleScreen(scriptcontext_t *context);
moduleBaseFunction(moduleScreenGetWidth) {
return jerry_number(SCREEN.width);
}
/**
* Gets the current screen width.
*
* @param L The Lua state.
* @return Count of return values.
*/
int moduleScreenGetWidth(lua_State *L);
moduleBaseFunction(moduleScreenGetHeight) {
return jerry_number(SCREEN.height);
}
/**
* Gets the current screen height.
*
* @param L The Lua state.
* @return Count of return values.
*/
int moduleScreenGetHeight(lua_State *L);
moduleBaseFunction(moduleScreenGetAspect) {
return jerry_number(SCREEN.aspect);
}
moduleBaseFunction(moduleScreenGetBackground) {
return moduleColorMakeObject(SCREEN.background);
}
moduleBaseFunction(moduleScreenSetBackground) {
if(argc < 1 || !jerry_value_is_object(args[0])) {
return moduleBaseThrow("Screen background color must be a color object");
}
color_t *color = (color_t*)scriptProtoGetValue(
&MODULE_COLOR_PROTO, args[0]
);
if(!color) {
return moduleBaseThrow("Background must be a valid color object");
}
memoryCopy(&SCREEN.background, color, sizeof(color_t));
return jerry_undefined();
}
moduleBaseFunction(moduleScreenToString) {
char_t buf[128];
stringFormat(
buf, sizeof(buf),
"{ \"width\": %d, \"height\": %d, \"aspect\": %.2f }",
SCREEN.width, SCREEN.height, SCREEN.aspect
);
return jerry_string_sz(buf);
}
static void moduleScreen(void) {
scriptProtoInit(
&MODULE_SCREEN_PROTO, "Screen", sizeof(screen_t), NULL
);
scriptProtoDefineProp(
&MODULE_SCREEN_PROTO, "width", moduleScreenGetWidth, NULL
);
scriptProtoDefineProp(
&MODULE_SCREEN_PROTO, "height", moduleScreenGetHeight, NULL
);
scriptProtoDefineProp(
&MODULE_SCREEN_PROTO, "aspect", moduleScreenGetAspect, NULL
);
scriptProtoDefineProp(
&MODULE_SCREEN_PROTO, "background",
moduleScreenGetBackground, moduleScreenSetBackground
);
scriptProtoDefineToString(&MODULE_SCREEN_PROTO, moduleScreenToString);
}
/**
* Sets the screen background color.
*
* @param L The Lua state.
* @return Count of return values.
*/
int moduleScreenSetBackground(lua_State *L);
@@ -0,0 +1,119 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "moduleshader.h"
#include "assert/assert.h"
#include "display/shader/shader.h"
#include "display/shader/shaderunlit.h"
void moduleShader(scriptcontext_t *context) {
assertNotNull(context, "Context cannot be NULL.");
// Shader unlit defs
lua_pushlightuserdata(context->luaState, &SHADER_UNLIT);
lua_setglobal(context->luaState, "SHADER_UNLIT");
lua_pushstring(context->luaState, SHADER_UNLIT_PROJECTION);
lua_setglobal(context->luaState, "SHADER_UNLIT_PROJECTION");
lua_pushstring(context->luaState, SHADER_UNLIT_VIEW);
lua_setglobal(context->luaState, "SHADER_UNLIT_VIEW");
lua_pushstring(context->luaState, SHADER_UNLIT_MODEL);
lua_setglobal(context->luaState, "SHADER_UNLIT_MODEL");
lua_pushstring(context->luaState, SHADER_UNLIT_TEXTURE);
lua_setglobal(context->luaState, "SHADER_UNLIT_TEXTURE");
// Shader methods
lua_register(context->luaState, "shaderBind", moduleShaderBind);
lua_register(context->luaState, "shaderSetMatrix", moduleShaderSetMatrix);
lua_register(context->luaState, "shaderSetTexture", moduleShaderSetTexture);
}
int moduleShaderBind(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
// Should be passed a shader userdata pointer only.
shader_t *shader = (shader_t *)lua_touserdata(l, 1);
assertNotNull(shader, "Shader pointer cannot be NULL.");
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 0;
}
int moduleShaderSetMatrix(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
// Expect shader, string and matrix.
if(!lua_isuserdata(l, 1)) {
luaL_error(l, "First argument must be a shader_mt userdata.");
return 0;
}
if(!lua_isstring(l, 2)) {
luaL_error(l, "Second argument must be a string.");
return 0;
}
if(!lua_isuserdata(l, 3)) {
luaL_error(l, "Third argument must be a mat4_mt userdata.");
return 0;
}
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.");
mat4 *mat = (mat4 *)lua_touserdata(l, 3);
assertNotNull(mat, "Matrix pointer cannot be NULL.");
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 0;
}
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.");
texture_t *texture;
// Texture can be Nil or a pointer, if not nil it must be a texture pointer.
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;
}
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 0;
}
@@ -0,0 +1,42 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptcontext.h"
/**
* Register shader functions to the given script context.
*
* @param context The script context to register shader functions to.
*/
void moduleShader(scriptcontext_t *context);
/**
* Script binding for binding a shader.
*
* @param l The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleShaderBind(lua_State *l);
/**
* Script binding for setting a matrix uniform in a shader.
*
* @param l The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleShaderSetMatrix(lua_State *l);
/**
* Script binding for setting a texture uniform in a shader.
*
* @param l The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleShaderSetTexture(lua_State *l);
errorret_t doThing();
@@ -0,0 +1,105 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "modulespritebatch.h"
#include "display/spritebatch/spritebatch.h"
#include "assert/assert.h"
void moduleSpriteBatch(scriptcontext_t *context) {
lua_register(context->luaState, "spriteBatchFlush", moduleSpriteBatchFlush);
lua_register(context->luaState, "spriteBatchClear", moduleSpriteBatchClear);
lua_register(context->luaState, "spriteBatchPush", moduleSpriteBatchPush);
}
int moduleSpriteBatchFlush(lua_State *L) {
assertNotNull(L, "Lua state is null");
spriteBatchFlush();
return 0;
}
int moduleSpriteBatchClear(lua_State *L) {
assertNotNull(L, "Lua state is null");
spriteBatchClear();
return 0;
}
int moduleSpriteBatchPush(lua_State *L) {
assertNotNull(L, "Lua state is null");
// MinX, MinY, MaxX, MaxY
if(
!lua_isnumber(L, 1) || !lua_isnumber(L, 2) || !lua_isnumber(L, 3) ||
!lua_isnumber(L, 4)
) {
return luaL_error(L, "Sprite coordinates must be numbers");
}
// Color (struct) or nil for white
color_t *color = NULL;
if(lua_gettop(L) < 5 || lua_isnil(L, 5)) {
// Allow NULL
} else if(lua_isuserdata(L, 5)) {
color = (color_t*)luaL_checkudata(L, 5, "color_mt");
} else {
return luaL_error(L, "Sprite color must be a color struct or nil");
}
// Optional UV min and maxes, defaults to 0,0 -> 1,1
float_t u0 = 0.0f;
float_t v0 = 0.0f;
float_t u1 = 1.0f;
float_t v1 = 1.0f;
if(lua_gettop(L) >= 7) {
if(!lua_isnumber(L, 6) || !lua_isnumber(L, 7)) {
return luaL_error(L, "Sprite UV min coordinates must be numbers");
}
u0 = (float_t)lua_tonumber(L, 6);
v0 = (float_t)lua_tonumber(L, 7);
}
if(lua_gettop(L) >= 9) {
if(!lua_isnumber(L, 8) || !lua_isnumber(L, 9)) {
return luaL_error(L, "Sprite UV max coordinates must be numbers");
}
u1 = (float_t)lua_tonumber(L, 8);
v1 = (float_t)lua_tonumber(L, 9);
}
float_t minX = (float_t)lua_tonumber(L, 1);
float_t minY = (float_t)lua_tonumber(L, 2);
float_t maxX = (float_t)lua_tonumber(L, 3);
float_t maxY = (float_t)lua_tonumber(L, 4);
errorret_t ret = spriteBatchPush(
minX,
minY,
maxX,
maxY,
#if MESH_ENABLE_COLOR
color == NULL ? COLOR_WHITE : *color,
#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 0;
}
@@ -0,0 +1,40 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptcontext.h"
/**
* Register sprite batch functions to the given script context.
*
* @param context The script context to register sprite batch functions to.
*/
void moduleSpriteBatch(scriptcontext_t *context);
/**
* Script binding for flushing the sprite batch.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleSpriteBatchFlush(lua_State *L);
/**
* Script binding for clearing the sprite batch.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleSpriteBatchClear(lua_State *L);
/**
* Script binding for pushing a sprite to the sprite batch.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleSpriteBatchPush(lua_State *L);
@@ -0,0 +1,92 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "moduletext.h"
#include "assert/assert.h"
#include "display/text/text.h"
#include "display/spritebatch/spritebatch.h"
void moduleText(scriptcontext_t *context) {
assertNotNull(context, "Script context is null");
lua_register(context->luaState, "textDraw", moduleTextDraw);
lua_register(context->luaState, "textMeasure", moduleTextMeasure);
}
int moduleTextDraw(lua_State *L) {
assertNotNull(L, "Lua state is null");
// Position.
if(!lua_isnumber(L, 1)) {
return luaL_error(L, "X position must be a number");
}
if(!lua_isnumber(L, 2)) {
return luaL_error(L, "Y position must be a number");
}
const float_t x = (float_t)lua_tonumber(L, 1);
const float_t y = (float_t)lua_tonumber(L, 2);
// String
if(!lua_isstring(L, 3)) {
return luaL_error(L, "Text to draw must be a string");
}
const char_t *text = (const char_t*)lua_tostring(L, 3);
// Optional color
color_t *color = NULL;
if(lua_gettop(L) < 4 || lua_isnil(L, 4)) {
// Allow NULL
} else if(lua_isuserdata(L, 4)) {
color = (color_t*)luaL_checkudata(L, 4, "color_mt");
} else {
return luaL_error(L, "Sprite color must be a color struct or nil");
}
// For now, use the default font tileset and texture.
errorret_t ret = textDraw(
x,
y,
text,
color == NULL ? COLOR_WHITE : *color,
&DEFAULT_FONT_TILESET,
&DEFAULT_FONT_TEXTURE
);
if(ret.code != ERROR_OK) {
errorCatch(errorPrint(ret));
luaL_error(L, "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");
}
}
int moduleTextMeasure(lua_State *L) {
assertNotNull(L, "Lua state is null");
// String
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);
int32_t width = 0;
int32_t height = 0;
textMeasure(
text,
&DEFAULT_FONT_TILESET,
&width,
&height
);
lua_pushnumber(L, width);
lua_pushnumber(L, height);
return 2;
}
@@ -0,0 +1,32 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptcontext.h"
/**
* Register text rendering functions to the given script context.
*
* @param context The script context to register text functions to.
*/
void moduleText(scriptcontext_t *context);
/**
* Script binding for drawing text.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleTextDraw(lua_State *L);
/**
* Script binding for measuring text dimensions.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleTextMeasure(lua_State *L);
@@ -0,0 +1,119 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "moduletexture.h"
#include "assert/assert.h"
#include "display/texture/texture.h"
#include "asset/loader/display/assettextureloader.h"
#include "util/memory.h"
#include "util/string.h"
void moduleTexture(scriptcontext_t *ctx) {
assertNotNull(ctx, "Script context cannot be null");
lua_State *l = ctx->luaState;
assertNotNull(l, "Lua state cannot be null");
// Create metatable for texture structure.
if(luaL_newmetatable(l, "texture_mt") == 1) {
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");
}
// Texture formats
lua_pushnumber(l, TEXTURE_FORMAT_RGBA);
lua_setglobal(l, "TEXTURE_FORMAT_RGBA");
lua_register(ctx->luaState, "textureLoad", moduleTextureLoad);
}
int moduleTextureIndex(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.");
const char *key = luaL_checkstring(l, 2);
assertNotNull(key, "Key cannot be NULL.");
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;
}
lua_pushnil(l);
return 1;
}
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;
}
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;
}
int moduleTextureLoad(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
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;
}
const char_t *filename = lua_tostring(l, 1);
assertNotNull(filename, "Filename cannot be NULL.");
assertStrLenMin(filename, 1, "Filename cannot be empty.");
// Create texture owned to lua
texture_t *tex = (texture_t *)lua_newuserdata(l, 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;
}
// Set metatable
luaL_getmetatable(l, "texture_mt");
lua_setmetatable(l, -2);
// Return the texture
return 1;
}
@@ -0,0 +1,15 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptcontext.h"
void moduleTexture(scriptcontext_t *ctx);
int moduleTextureIndex(lua_State *l);
int moduleTextureToString(lua_State *l);
int moduleTextureGC(lua_State *l);
int moduleTextureLoad(lua_State *l);
@@ -0,0 +1,164 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "moduletileset.h"
#include "assert/assert.h"
#include "display/texture/tileset.h"
#include "util/memory.h"
#include "util/string.h"
#include "asset/loader/display/assettilesetloader.h"
void moduleTileset(scriptcontext_t *ctx) {
assertNotNull(ctx, "Script context cannot be NULL");
// Tileset metatable
if(luaL_newmetatable(ctx->luaState, "tileset_mt")) {
lua_pushcfunction(ctx->luaState, moduleTilesetIndex);
lua_setfield(ctx->luaState, -2, "__index");
lua_pushcfunction(ctx->luaState, moduleTilesetToString);
lua_setfield(ctx->luaState, -2, "__tostring");
}
lua_pop(ctx->luaState, 1); // Pop the metatable
lua_register(ctx->luaState, "tilesetLoad", moduleTilesetLoad);
lua_register(ctx->luaState, "tilesetTileGetUV", moduleTilesetTileGetUV);
lua_register(
ctx->luaState, "tilesetPositionGetUV", moduleTilesetPositionGetUV
);
}
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.");
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;
}
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
);
return 1;
}
int moduleTilesetTileGetUV(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
if(!lua_isuserdata(l, 1)) {
luaL_error(l, "First argument must be a tileset userdata.");
return 0;
}
tileset_t *ts = (tileset_t *)luaL_checkudata(l, 1, "tileset_mt");
assertNotNull(ts, "Tileset pointer cannot be NULL.");
if(!lua_isnumber(l, 2)) {
luaL_error(l, "Second arguments must be tile index.");
return 0;
}
uint16_t tileIndex = (uint16_t)lua_tonumber(l, 2);
// Create vec4 that lua owns
vec4 *uv = (vec4 *)lua_newuserdata(l, sizeof(vec4));
tilesetTileGetUV(ts, tileIndex, *uv);
// Set metatable
luaL_getmetatable(l, "vec4_mt");
lua_setmetatable(l, -2);
return 1;
}
int moduleTilesetPositionGetUV(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
if(!lua_isuserdata(l, 1)) {
luaL_error(l, "First argument must be a tileset userdata.");
return 0;
}
tileset_t *ts = (tileset_t *)luaL_checkudata(l, 1, "tileset_mt");
assertNotNull(ts, "Tileset pointer cannot be NULL.");
if(!lua_isnumber(l, 2)) {
luaL_error(l, "Second arguments must be column number.");
return 0;
}
uint16_t column = (uint16_t)lua_tonumber(l, 2);
if(!lua_isnumber(l, 3)) {
luaL_error(l, "Third arguments must be row number.");
return 0;
}
uint16_t row = (uint16_t)lua_tonumber(l, 3);
// Create vec4 that lua owns
vec4 *uv = (vec4 *)lua_newuserdata(l, sizeof(vec4));
tilesetPositionGetUV(ts, column, row, *uv);
// Set metatable
luaL_getmetatable(l, "vec4_mt");
lua_setmetatable(l, -2);
return 1;
}
int moduleTilesetLoad(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
if(!lua_isstring(l, 1)) {
luaL_error(l, "First argument must be a string filename.");
return 0;
}
const char_t *filename = lua_tostring(l, 1);
assertNotNull(filename, "Filename cannot be NULL.");
assertStrLenMin(filename, 1, "Filename cannot be empty.");
// Create texture owned to lua
tileset_t *tileset = (tileset_t *)lua_newuserdata(l, sizeof(tileset_t));
memoryZero(tileset, sizeof(tileset_t));
errorret_t ret = assetTilesetLoad(filename, tileset);
if(ret.code != ERROR_OK) {
errorCatch(errorPrint(ret));
luaL_error(l, "Failed to load tileset asset: %s", filename);
return 0;
}
// Set metatable
luaL_getmetatable(l, "tileset_mt");
lua_setmetatable(l, -2);
// Return the tileset
return 1;
}
@@ -0,0 +1,56 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptcontext.h"
/**
* Registers the tileset module in the scripting context.
*
* @param ctx The scripting context to register the module in.
*/
void moduleTileset(scriptcontext_t *ctx);
/**
* __index metamethod for tileset userdata.
*
* @param l The Lua state.
* @return The number of return values on the Lua stack.
*/
int moduleTilesetIndex(lua_State *l);
/**
* __tostring metamethod for tileset userdata.
*
* @param l The Lua state.
* @return The number of return values on the Lua stack.
*/
int moduleTilesetToString(lua_State *l);
/**
* Lua function to get the UV coordinates for a tile index in a tileset.
*
* @param l The Lua state.
* @return The number of return values on the Lua stack.
*/
int moduleTilesetTileGetUV(lua_State *l);
/**
* Lua function to get the UV coordinates for a tile position in a tileset.
*
* @param l The Lua state.
* @return The number of return values on the Lua stack.
*/
int moduleTilesetPositionGetUV(lua_State *l);
/**
* Lua function to load a tileset from a texture and tile dimensions.
*
* @param l The Lua state.
* @return The number of return values on the Lua stack.
*/
int moduleTilesetLoad(lua_State *l);
@@ -1,29 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/module/modulebase.h"
#include "script/scriptproto.h"
#include "engine/engine.h"
static scriptproto_t MODULE_ENGINE_PROTO;
moduleBaseFunction(moduleEngineExit) {
ENGINE.running = false;
return jerry_undefined();
}
static void moduleEngine(void) {
scriptProtoInit(
&MODULE_ENGINE_PROTO, "Engine",
sizeof(uint8_t), NULL
);
scriptProtoDefineStaticFunc(
&MODULE_ENGINE_PROTO, "exit", moduleEngineExit
);
}
@@ -1,244 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/module/modulebase.h"
#include "script/scriptproto.h"
#include "entity/entity.h"
#include "entity/component/display/entitycamera.h"
#include "moduleentityposition.h"
static scriptproto_t MODULE_ENTITY_CAMERA_PROTO;
static entitycamera_t * moduleEntityCameraGet(
const jerry_call_info_t *callInfo
) {
componenthandle_t *h = scriptProtoGetValue(
&MODULE_ENTITY_CAMERA_PROTO, callInfo->this_value
);
if(!h) return NULL;
return (entitycamera_t*)componentGetData(
h->eid, h->cid, COMPONENT_TYPE_CAMERA
);
}
moduleBaseFunction(moduleEntityCameraGetZNear) {
entitycamera_t *cam = moduleEntityCameraGet(callInfo);
if(!cam) return jerry_undefined();
return jerry_number(cam->nearClip);
}
moduleBaseFunction(moduleEntityCameraGetZFar) {
entitycamera_t *cam = moduleEntityCameraGet(callInfo);
if(!cam) return jerry_undefined();
return jerry_number(cam->farClip);
}
moduleBaseFunction(moduleEntityCameraGetOrthoTop) {
entitycamera_t *cam = moduleEntityCameraGet(callInfo);
if(!cam || cam->projType != ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) {
return jerry_undefined();
}
return jerry_number(cam->orthographic.top);
}
moduleBaseFunction(moduleEntityCameraGetOrthoBottom) {
entitycamera_t *cam = moduleEntityCameraGet(callInfo);
if(!cam || cam->projType != ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) {
return jerry_undefined();
}
return jerry_number(cam->orthographic.bottom);
}
moduleBaseFunction(moduleEntityCameraGetOrthoLeft) {
entitycamera_t *cam = moduleEntityCameraGet(callInfo);
if(!cam || cam->projType != ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) {
return jerry_undefined();
}
return jerry_number(cam->orthographic.left);
}
moduleBaseFunction(moduleEntityCameraGetOrthoRight) {
entitycamera_t *cam = moduleEntityCameraGet(callInfo);
if(!cam || cam->projType != ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) {
return jerry_undefined();
}
return jerry_number(cam->orthographic.right);
}
moduleBaseFunction(moduleEntityCameraGetFov) {
entitycamera_t *cam = moduleEntityCameraGet(callInfo);
if(!cam || (
cam->projType != ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE &&
cam->projType != ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED
)) {
return jerry_undefined();
}
return jerry_number(cam->perspective.fov);
}
moduleBaseFunction(moduleEntityCameraGetProjectionType) {
entitycamera_t *cam = moduleEntityCameraGet(callInfo);
if(!cam) return jerry_undefined();
return jerry_number(cam->projType);
}
moduleBaseFunction(moduleEntityCameraSetZNear) {
if(argc < 1 || !jerry_value_is_number(args[0])) {
return moduleBaseThrow("Expected a number");
}
entitycamera_t *cam = moduleEntityCameraGet(callInfo);
if(!cam) return jerry_undefined();
cam->nearClip = (float_t)jerry_value_as_number(args[0]);
return args[0];
}
moduleBaseFunction(moduleEntityCameraSetZFar) {
if(argc < 1 || !jerry_value_is_number(args[0])) {
return moduleBaseThrow("Expected a number");
}
entitycamera_t *cam = moduleEntityCameraGet(callInfo);
if(!cam) return jerry_undefined();
cam->farClip = (float_t)jerry_value_as_number(args[0]);
return args[0];
}
moduleBaseFunction(moduleEntityCameraSetOrthoTop) {
if(argc < 1 || !jerry_value_is_number(args[0])) {
return moduleBaseThrow("Expected a number");
}
entitycamera_t *cam = moduleEntityCameraGet(callInfo);
if(!cam || cam->projType != ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) {
return jerry_undefined();
}
cam->orthographic.top = (float_t)jerry_value_as_number(args[0]);
return args[0];
}
moduleBaseFunction(moduleEntityCameraSetOrthoBottom) {
if(argc < 1 || !jerry_value_is_number(args[0])) {
return moduleBaseThrow("Expected a number");
}
entitycamera_t *cam = moduleEntityCameraGet(callInfo);
if(!cam || cam->projType != ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) {
return jerry_undefined();
}
cam->orthographic.bottom = (float_t)jerry_value_as_number(args[0]);
return args[0];
}
moduleBaseFunction(moduleEntityCameraSetOrthoLeft) {
if(argc < 1 || !jerry_value_is_number(args[0])) {
return moduleBaseThrow("Expected a number");
}
entitycamera_t *cam = moduleEntityCameraGet(callInfo);
if(!cam || cam->projType != ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) {
return jerry_undefined();
}
cam->orthographic.left = (float_t)jerry_value_as_number(args[0]);
return args[0];
}
moduleBaseFunction(moduleEntityCameraSetOrthoRight) {
if(argc < 1 || !jerry_value_is_number(args[0])) {
return moduleBaseThrow("Expected a number");
}
entitycamera_t *cam = moduleEntityCameraGet(callInfo);
if(!cam || cam->projType != ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) {
return jerry_undefined();
}
cam->orthographic.right = (float_t)jerry_value_as_number(args[0]);
return args[0];
}
moduleBaseFunction(moduleEntityCameraSetFov) {
if(argc < 1 || !jerry_value_is_number(args[0])) {
return moduleBaseThrow("Expected a number");
}
entitycamera_t *cam = moduleEntityCameraGet(callInfo);
if(!cam || (
cam->projType != ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE &&
cam->projType != ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED
)) {
return jerry_undefined();
}
cam->perspective.fov = (float_t)jerry_value_as_number(args[0]);
return args[0];
}
moduleBaseFunction(moduleEntityCameraSetProjectionType) {
if(argc < 1 || !jerry_value_is_number(args[0])) {
return moduleBaseThrow("Expected a number");
}
entitycamera_t *cam = moduleEntityCameraGet(callInfo);
if(!cam) return jerry_undefined();
int32_t projType = (int32_t)jerry_value_as_number(args[0]);
if(
projType < ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE ||
projType > ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC
) {
return moduleBaseThrow("Invalid projection type");
}
cam->projType = (entitycameraprojectiontype_t)projType;
return args[0];
}
moduleBaseFunction(moduleEntityCameraAdd) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
entityid_t id = (entityid_t)jerry_value_as_number(args[0]);
componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_CAMERA);
componenthandle_t h = { .eid = id, .cid = comp };
return scriptProtoCreateValue(&MODULE_ENTITY_CAMERA_PROTO, &h);
}
static void moduleEntityCAMERA(void) {
scriptProtoInit(
&MODULE_ENTITY_CAMERA_PROTO, NULL, sizeof(componenthandle_t), NULL
);
scriptProtoDefineProp(
&MODULE_ENTITY_CAMERA_PROTO, "zNear",
moduleEntityCameraGetZNear, moduleEntityCameraSetZNear
);
scriptProtoDefineProp(
&MODULE_ENTITY_CAMERA_PROTO, "zFar",
moduleEntityCameraGetZFar, moduleEntityCameraSetZFar
);
scriptProtoDefineProp(
&MODULE_ENTITY_CAMERA_PROTO, "orthoTop",
moduleEntityCameraGetOrthoTop, moduleEntityCameraSetOrthoTop
);
scriptProtoDefineProp(
&MODULE_ENTITY_CAMERA_PROTO, "orthoBottom",
moduleEntityCameraGetOrthoBottom, moduleEntityCameraSetOrthoBottom
);
scriptProtoDefineProp(
&MODULE_ENTITY_CAMERA_PROTO, "orthoLeft",
moduleEntityCameraGetOrthoLeft, moduleEntityCameraSetOrthoLeft
);
scriptProtoDefineProp(
&MODULE_ENTITY_CAMERA_PROTO, "orthoRight",
moduleEntityCameraGetOrthoRight, moduleEntityCameraSetOrthoRight
);
scriptProtoDefineProp(
&MODULE_ENTITY_CAMERA_PROTO, "fov",
moduleEntityCameraGetFov, moduleEntityCameraSetFov
);
scriptProtoDefineProp(
&MODULE_ENTITY_CAMERA_PROTO, "projectionType",
moduleEntityCameraGetProjectionType, moduleEntityCameraSetProjectionType
);
moduleBaseSetInt(
"CAMERA_TYPE_ORTHOGRAPHIC",
ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC
);
moduleBaseSetInt(
"CAMERA_TYPE_PERSPECTIVE",
ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE
);
}
@@ -1,91 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/module/modulebase.h"
#include "script/scriptproto.h"
#include "entity/entity.h"
#include "entity/component/display/entitymaterial.h"
#include "display/color.h"
#include "moduleentityposition.h"
static scriptproto_t MODULE_ENTITY_MATERIAL_PROTO;
static entitymaterial_t * moduleEntityMaterialGet(
const jerry_call_info_t *callInfo
) {
componenthandle_t *h = scriptProtoGetValue(
&MODULE_ENTITY_MATERIAL_PROTO, callInfo->this_value
);
if(!h) return NULL;
return (entitymaterial_t*)componentGetData(
h->eid, h->cid, COMPONENT_TYPE_MATERIAL
);
}
moduleBaseFunction(moduleEntityMaterialSetColor) {
moduleBaseRequireArgs(1);
if(!jerry_value_is_object(args[0])) {
return moduleBaseThrow("expected color object");
}
entitymaterial_t *mat = moduleEntityMaterialGet(callInfo);
if(!mat) return jerry_undefined();
jerry_value_t key;
jerry_value_t v;
color_t col;
key = jerry_string_sz("r");
v = jerry_object_get(args[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[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[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[0], key);
jerry_value_free(key);
col.a = (colorchannel8_t)jerry_value_as_number(v);
jerry_value_free(v);
mat->material.unlit.color = col;
return jerry_undefined();
}
moduleBaseFunction(moduleEntityMaterialAdd) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
entityid_t id = (entityid_t)jerry_value_as_number(args[0]);
componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_MATERIAL);
componenthandle_t h = { .eid = id, .cid = comp };
return scriptProtoCreateValue(&MODULE_ENTITY_MATERIAL_PROTO, &h);
}
static void moduleEntityMATERIAL(void) {
scriptProtoInit(
&MODULE_ENTITY_MATERIAL_PROTO,
NULL,
sizeof(componenthandle_t),
NULL
);
scriptProtoDefineFunc(
&MODULE_ENTITY_MATERIAL_PROTO,
"setColor",
moduleEntityMaterialSetColor
);
}
@@ -1,40 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/module/modulebase.h"
#include "script/scriptproto.h"
#include "entity/entity.h"
#include "entity/component/display/entitymesh.h"
#include "moduleentityposition.h"
static scriptproto_t MODULE_ENTITY_MESH_PROTO;
static entitymesh_t * moduleEntityMeshGet(
const jerry_call_info_t *callInfo
) {
componenthandle_t *h = scriptProtoGetValue(
&MODULE_ENTITY_MESH_PROTO, callInfo->this_value
);
if(!h) return NULL;
return (entitymesh_t*)componentGetData(h->eid, h->cid, COMPONENT_TYPE_MESH);
}
moduleBaseFunction(moduleEntityMeshAdd) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
entityid_t id = (entityid_t)jerry_value_as_number(args[0]);
componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_MESH);
componenthandle_t h = { .eid = id, .cid = comp };
return scriptProtoCreateValue(&MODULE_ENTITY_MESH_PROTO, &h);
}
static void moduleEntityMESH(void) {
scriptProtoInit(
&MODULE_ENTITY_MESH_PROTO, NULL,
sizeof(componenthandle_t), NULL
);
}
@@ -1,183 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/module/modulebase.h"
#include "script/module/math/modulevec3ref.h"
#include "script/scriptproto.h"
#include "entity/entity.h"
#include "entity/component/physics/entityphysics.h"
#include "moduleentityposition.h"
static scriptproto_t MODULE_ENTITY_PHYSICS_PROTO;
static entityphysics_t * moduleEntityPhysicsGet(
const jerry_call_info_t *callInfo
) {
componenthandle_t *h = scriptProtoGetValue(
&MODULE_ENTITY_PHYSICS_PROTO, callInfo->this_value
);
if(!h) return NULL;
return entityPhysicsGet(h->eid, h->cid);
}
moduleBaseFunction(moduleEntityPhysicsGetVelocity) {
entityphysics_t *phys = moduleEntityPhysicsGet(callInfo);
if(!phys) return jerry_undefined();
return moduleVec3RefPush(phys->velocity, NULL, NULL);
}
moduleBaseFunction(moduleEntityPhysicsSetVelocity) {
moduleBaseRequireArgs(1);
entityphysics_t *phys = moduleEntityPhysicsGet(callInfo);
if(!phys) return jerry_undefined();
vec3 v;
if(!moduleVec3AnyCheck(args[0], v)) {
return moduleBaseThrow("expected Vec3");
}
glm_vec3_copy(v, phys->velocity);
return jerry_undefined();
}
moduleBaseFunction(moduleEntityPhysicsGetOnGround) {
entityphysics_t *phys = moduleEntityPhysicsGet(callInfo);
if(!phys) return jerry_undefined();
return jerry_boolean(phys->onGround);
}
moduleBaseFunction(moduleEntityPhysicsGetBodyType) {
entityphysics_t *phys = moduleEntityPhysicsGet(callInfo);
if(!phys) return jerry_undefined();
return jerry_number((double)phys->type);
}
moduleBaseFunction(moduleEntityPhysicsSetBodyType) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
entityphysics_t *phys = moduleEntityPhysicsGet(callInfo);
if(!phys) return jerry_undefined();
phys->type = (physicsbodytype_t)(int32_t)jerry_value_as_number(args[0]);
return jerry_undefined();
}
moduleBaseFunction(moduleEntityPhysicsApplyImpulse) {
moduleBaseRequireArgs(1);
entityphysics_t *phys = moduleEntityPhysicsGet(callInfo);
if(!phys) return jerry_undefined();
if(phys->type == PHYSICS_BODY_STATIC) return jerry_undefined();
vec3 impulse;
if(!moduleVec3Check(args[0], impulse)) {
return moduleBaseThrow("expected Vec3 impulse");
}
glm_vec3_add(phys->velocity, impulse, phys->velocity);
return jerry_undefined();
}
moduleBaseFunction(moduleEntityPhysicsSetShapeCube) {
moduleBaseRequireArgs(1);
entityphysics_t *phys = moduleEntityPhysicsGet(callInfo);
if(!phys) return jerry_undefined();
vec3 half;
if(!moduleVec3Check(args[0], half)) {
return moduleBaseThrow("expected Vec3 halfExtents");
}
phys->shape.type = PHYSICS_SHAPE_CUBE;
glm_vec3_copy(half, phys->shape.data.cube.halfExtents);
return jerry_undefined();
}
moduleBaseFunction(moduleEntityPhysicsSetShapeSphere) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
entityphysics_t *phys = moduleEntityPhysicsGet(callInfo);
if(!phys) return jerry_undefined();
phys->shape.type = PHYSICS_SHAPE_SPHERE;
phys->shape.data.sphere.radius = (float_t)jerry_value_as_number(args[0]);
return jerry_undefined();
}
moduleBaseFunction(moduleEntityPhysicsSetShapeCapsule) {
moduleBaseRequireArgs(2);
moduleBaseRequireNumber(0); moduleBaseRequireNumber(1);
entityphysics_t *phys = moduleEntityPhysicsGet(callInfo);
if(!phys) return jerry_undefined();
phys->shape.type = PHYSICS_SHAPE_CAPSULE;
phys->shape.data.capsule.radius = (float_t)jerry_value_as_number(args[0]);
phys->shape.data.capsule.halfHeight =
(float_t)jerry_value_as_number(args[1]);
return jerry_undefined();
}
moduleBaseFunction(moduleEntityPhysicsSetShapePlane) {
moduleBaseRequireArgs(2); moduleBaseRequireNumber(1);
entityphysics_t *phys = moduleEntityPhysicsGet(callInfo);
if(!phys) return jerry_undefined();
vec3 normal;
if(!moduleVec3Check(args[0], normal)) {
return moduleBaseThrow("expected Vec3 normal");
}
phys->shape.type = PHYSICS_SHAPE_PLANE;
glm_vec3_copy(normal, phys->shape.data.plane.normal);
phys->shape.data.plane.distance = (float_t)jerry_value_as_number(args[1]);
return jerry_undefined();
}
moduleBaseFunction(moduleEntityPhysicsAdd) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
entityid_t id = (entityid_t)jerry_value_as_number(args[0]);
componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_PHYSICS);
componenthandle_t h = { .eid = id, .cid = comp };
return scriptProtoCreateValue(&MODULE_ENTITY_PHYSICS_PROTO, &h);
}
static void moduleEntityPHYSICS(void) {
scriptProtoInit(
&MODULE_ENTITY_PHYSICS_PROTO, NULL,
sizeof(componenthandle_t), NULL
);
scriptProtoDefineProp(
&MODULE_ENTITY_PHYSICS_PROTO, "velocity",
moduleEntityPhysicsGetVelocity, moduleEntityPhysicsSetVelocity
);
scriptProtoDefineProp(
&MODULE_ENTITY_PHYSICS_PROTO, "onGround",
moduleEntityPhysicsGetOnGround, NULL
);
scriptProtoDefineProp(
&MODULE_ENTITY_PHYSICS_PROTO, "bodyType",
moduleEntityPhysicsGetBodyType, moduleEntityPhysicsSetBodyType
);
scriptProtoDefineFunc(
&MODULE_ENTITY_PHYSICS_PROTO, "applyImpulse",
moduleEntityPhysicsApplyImpulse
);
scriptProtoDefineFunc(
&MODULE_ENTITY_PHYSICS_PROTO, "setShapeCube",
moduleEntityPhysicsSetShapeCube
);
scriptProtoDefineFunc(
&MODULE_ENTITY_PHYSICS_PROTO, "setShapeSphere",
moduleEntityPhysicsSetShapeSphere
);
scriptProtoDefineFunc(
&MODULE_ENTITY_PHYSICS_PROTO, "setShapeCapsule",
moduleEntityPhysicsSetShapeCapsule
);
scriptProtoDefineFunc(
&MODULE_ENTITY_PHYSICS_PROTO, "setShapePlane",
moduleEntityPhysicsSetShapePlane
);
moduleBaseSetInt("PHYSICS_BODY_STATIC", PHYSICS_BODY_STATIC);
moduleBaseSetInt("PHYSICS_BODY_DYNAMIC", PHYSICS_BODY_DYNAMIC);
moduleBaseSetInt("PHYSICS_BODY_KINEMATIC", PHYSICS_BODY_KINEMATIC);
moduleBaseSetInt("PHYSICS_SHAPE_CUBE", PHYSICS_SHAPE_CUBE);
moduleBaseSetInt("PHYSICS_SHAPE_SPHERE", PHYSICS_SHAPE_SPHERE);
moduleBaseSetInt("PHYSICS_SHAPE_CAPSULE", PHYSICS_SHAPE_CAPSULE);
moduleBaseSetInt("PHYSICS_SHAPE_PLANE", PHYSICS_SHAPE_PLANE);
}
@@ -1,147 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/module/modulebase.h"
#include "script/module/math/modulevec3ref.h"
#include "script/scriptproto.h"
#include "entity/entity.h"
#include "entity/component/display/entityposition.h"
// Shared component handle struct — defined once, used by all component modules.
#ifndef COMPONENT_HANDLE_DEFINED
#define COMPONENT_HANDLE_DEFINED
typedef struct {
entityid_t eid;
componentid_t cid;
} componenthandle_t;
#endif
static scriptproto_t MODULE_ENTITY_POSITION_PROTO;
static entityposition_t * moduleEntityPositionGet(
const jerry_call_info_t *callInfo
) {
componenthandle_t *h = scriptProtoGetValue(
&MODULE_ENTITY_POSITION_PROTO, callInfo->this_value
);
if(!h) return NULL;
return entityPositionGet(h->eid, h->cid);
}
moduleBaseFunction(moduleEntityPositionGetPosition) {
entityposition_t *pos = moduleEntityPositionGet(callInfo);
if(!pos) return jerry_undefined();
return moduleVec3RefPush(
pos->position, (void(*)(void*))entityPositionRebuild, pos
);
}
moduleBaseFunction(moduleEntityPositionSetPosition) {
moduleBaseRequireArgs(1);
entityposition_t *pos = moduleEntityPositionGet(callInfo);
if(!pos) return jerry_undefined();
vec3 v;
if(!moduleVec3AnyCheck(args[0], v)) {
return moduleBaseThrow("expected Vec3");
}
glm_vec3_copy(v, pos->position);
entityPositionRebuild(pos);
return jerry_undefined();
}
moduleBaseFunction(moduleEntityPositionGetRotation) {
entityposition_t *pos = moduleEntityPositionGet(callInfo);
if(!pos) return jerry_undefined();
return moduleVec3RefPush(
pos->rotation, (void(*)(void*))entityPositionRebuild, pos
);
}
moduleBaseFunction(moduleEntityPositionSetRotation) {
moduleBaseRequireArgs(1);
entityposition_t *pos = moduleEntityPositionGet(callInfo);
if(!pos) return jerry_undefined();
vec3 v;
if(!moduleVec3AnyCheck(args[0], v)) {
return moduleBaseThrow("expected Vec3");
}
glm_vec3_copy(v, pos->rotation);
entityPositionRebuild(pos);
return jerry_undefined();
}
moduleBaseFunction(moduleEntityPositionGetScale) {
entityposition_t *pos = moduleEntityPositionGet(callInfo);
if(!pos) return jerry_undefined();
return moduleVec3RefPush(
pos->scale, (void(*)(void*))entityPositionRebuild, pos
);
}
moduleBaseFunction(moduleEntityPositionSetScale) {
moduleBaseRequireArgs(1);
entityposition_t *pos = moduleEntityPositionGet(callInfo);
if(!pos) return jerry_undefined();
vec3 v;
if(!moduleVec3AnyCheck(args[0], v)) {
return moduleBaseThrow("expected Vec3");
}
glm_vec3_copy(v, pos->scale);
entityPositionRebuild(pos);
return jerry_undefined();
}
moduleBaseFunction(moduleEntityPositionLookAt) {
moduleBaseRequireArgs(1);
entityposition_t *pos = moduleEntityPositionGet(callInfo);
if(!pos) return jerry_undefined();
vec3 target;
if(!moduleVec3AnyCheck(args[0], target)) {
return moduleBaseThrow("expected Vec3 target");
}
vec3 up = { 0.0f, 1.0f, 0.0f };
if(argc >= 2 && !moduleVec3AnyCheck(args[1], up)) {
return moduleBaseThrow("expected Vec3 up");
}
glm_lookat(pos->position, target, up, pos->transform);
entityPositionDecompose(pos);
return jerry_undefined();
}
moduleBaseFunction(moduleEntityPositionAdd) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
entityid_t id = (entityid_t)jerry_value_as_number(args[0]);
componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_POSITION);
componenthandle_t h = { .eid = id, .cid = comp };
return scriptProtoCreateValue(&MODULE_ENTITY_POSITION_PROTO, &h);
}
static void moduleEntityPOSITION(void) {
scriptProtoInit(
&MODULE_ENTITY_POSITION_PROTO, NULL,
sizeof(componenthandle_t), NULL
);
scriptProtoDefineProp(
&MODULE_ENTITY_POSITION_PROTO, "position",
moduleEntityPositionGetPosition, moduleEntityPositionSetPosition
);
scriptProtoDefineProp(
&MODULE_ENTITY_POSITION_PROTO, "rotation",
moduleEntityPositionGetRotation, moduleEntityPositionSetRotation
);
scriptProtoDefineProp(
&MODULE_ENTITY_POSITION_PROTO, "scale",
moduleEntityPositionGetScale, moduleEntityPositionSetScale
);
scriptProtoDefineFunc(
&MODULE_ENTITY_POSITION_PROTO, "lookAt",
moduleEntityPositionLookAt
);
}
@@ -1,205 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/module/modulebase.h"
#include "script/scriptproto.h"
#include "entity/entity.h"
#include "entity/entitymanager.h"
#include "component/moduleentityposition.h"
#include "component/moduleentitycamera.h"
#include "component/moduleentitymesh.h"
#include "component/moduleentitymaterial.h"
#include "component/moduleentityphysics.h"
typedef struct {
entityid_t id;
} entityscript_t;
static scriptproto_t MODULE_ENTITY_PROTO;
// Getters
moduleBaseFunction(moduleEntityGetId) {
entityscript_t *inst = (entityscript_t*)scriptProtoGetValue(
&MODULE_ENTITY_PROTO, callInfo->this_value
);
if(!inst) return jerry_undefined();
return jerry_number(inst->id);
}
// Getter defined for each component type
static jerry_value_t moduleEntityGetComponent(
const jerry_call_info_t *callInfo,
const jerry_value_t args[],
const jerry_length_t argc,
const componenttype_t type,
scriptproto_t *proto
) {
assertNotNull(callInfo, "Call info must not be null");
assertTrue(argc >= 0, "Argc must be non-negative");
assertTrue(
type > COMPONENT_TYPE_NULL && type < COMPONENT_TYPE_COUNT,
"Invalid component type"
);
entityid_t entityId;
componentid_t compId;
// Get the entity script data.
entityscript_t *inst = (entityscript_t*)scriptProtoGetValue(
&MODULE_ENTITY_PROTO, callInfo->this_value
);
if(!inst) return jerry_undefined();
entityId = inst->id;
// Find the component ID of the requested type.
compId = entityGetComponent(entityId, type);
if(compId == 0xFF) {
return jerry_undefined();
}
componenthandle_t h = { .eid = entityId, .cid = compId };
return scriptProtoCreateValue(proto, &h);
}
#define X(enumName, type, field, init, dispose) \
moduleBaseFunction(moduleEntityGet##enumName) { \
return moduleEntityGetComponent( \
callInfo, \
args, \
argc, \
COMPONENT_TYPE_##enumName, \
&MODULE_ENTITY_##enumName##_PROTO \
); \
}
#include "entity/componentlist.h"
#undef X
moduleBaseFunction(moduleEntityToString) {
entityscript_t *inst = (entityscript_t*)scriptProtoGetValue(
&MODULE_ENTITY_PROTO, callInfo->this_value
);
if(!inst) return jerry_string_sz("Entity(?)");
char_t components[128];
size_t clen = 0;
bool_t first = true;
for(componenttype_t t = 1; t < COMPONENT_TYPE_COUNT; t++) {
if(entityGetComponent(inst->id, t) == 0xFF) continue;
if(!first) {
stringCopy(components + clen, ", ", sizeof(components) - clen);
clen += 2;
}
const char_t *name = COMPONENT_DEFINITIONS[t].enumName;
stringCopy(components + clen, name, sizeof(components) - clen);
clen += strlen(name);
first = false;
}
char_t buf[256];
if(first) {
stringFormat(
buf, sizeof(buf),
"{ \"id\": %d, \"components\": [] }", inst->id
);
} else {
stringFormat(
buf, sizeof(buf),
"{ \"id\": %d, \"components\": [ %s ] }", inst->id, components
);
}
return jerry_string_sz(buf);
}
// Methods
moduleBaseFunction(moduleEntityConstructor) {
entityscript_t *inst = (entityscript_t*)memoryAllocate(
sizeof(entityscript_t)
);
inst->id = entityManagerAdd();
jerry_object_set_native_ptr(
callInfo->this_value, &MODULE_ENTITY_PROTO.info, inst
);
return jerry_undefined();
}
moduleBaseFunction(moduleEntityAddComponent) {
if(argc < 1 || !jerry_value_is_number(args[0])) {
return moduleBaseThrow("Entity.add: expected a valid component type");
}
componenttype_t type = (componenttype_t)jerry_value_as_number(args[0]);
if(type <= COMPONENT_TYPE_NULL || type >= COMPONENT_TYPE_COUNT) {
return moduleBaseThrow("Entity.add: invalid component type");
}
// Get the entity script data.
entityscript_t *inst = (entityscript_t*)scriptProtoGetValue(
&MODULE_ENTITY_PROTO, callInfo->this_value
);
if(!inst) return moduleBaseThrow("Entity.add: invalid entity");
componentid_t id = entityAddComponent(inst->id, type);
return jerry_number(id);
}
moduleBaseFunction(moduleEntityDisposeMethod) {
entityscript_t *inst = (entityscript_t*)scriptProtoGetValue(
&MODULE_ENTITY_PROTO, callInfo->this_value
);
if(!inst) return jerry_undefined();
entityDispose(inst->id);
return jerry_undefined();
}
static void moduleEntity(void) {
// Init the entity prototype
scriptProtoInit(
&MODULE_ENTITY_PROTO,
"Entity",
sizeof(entityscript_t),
moduleEntityConstructor
);
scriptProtoDefineToString(&MODULE_ENTITY_PROTO, moduleEntityToString);
// Entity Methods
scriptProtoDefineFunc(
&MODULE_ENTITY_PROTO, "add", moduleEntityAddComponent
);
scriptProtoDefineFunc(
&MODULE_ENTITY_PROTO, "dispose", moduleEntityDisposeMethod
);
// Entity props
scriptProtoDefineProp(
&MODULE_ENTITY_PROTO, "id", moduleEntityGetId, NULL
);
// Init component type modules.
char_t buffer[64];
#define X(enumName, type, field, iMethod, dMethod) \
moduleEntity##enumName(); \
scriptProtoDefineProp( \
&MODULE_ENTITY_PROTO, \
COMPONENT_DEFINITIONS[COMPONENT_TYPE_##enumName].name, \
moduleEntityGet##enumName, \
NULL \
); \
snprintf( \
buffer, sizeof(buffer), \
"%s = %d\n", \
#enumName, \
COMPONENT_TYPE_##enumName \
); \
moduleBaseEval(buffer);
#include "entity/componentlist.h"
#undef X
}
@@ -0,0 +1,10 @@
# 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
moduleevent.c
)
@@ -0,0 +1,45 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "moduleevent.h"
#include "event/event.h"
#include "engine/engine.h"
#include "assert/assert.h"
int moduleEventSubscribe(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
// State has user pointer to owning scriptcontext_t
scriptcontext_t *context = *(scriptcontext_t **)lua_getextraspace(L);
assertNotNull(context, "Script context cannot be NULL");
// Expecting event pointer
if(!lua_islightuserdata(L, 1)) {
luaL_error(L, "eventSubscribe: Expected event pointer as first argument");
return 0;
}
// Expecting callback function (Lua function)
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);
// Pass back to lua.
lua_pushnumber(L, id);
return 1;
}
void moduleEvent(scriptcontext_t *context) {
// Reg functions
lua_register(context->luaState, "eventSubscribe", moduleEventSubscribe);
}
@@ -0,0 +1,21 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptcontext.h"
/**
* Register event functions to the given script context.
*
* @param context The script context to register event functions to.
*/
void moduleEvent(scriptcontext_t *context);
/**
* Script binding for subscribing to an event.
*/
int moduleEventSubscribe(lua_State *L);
@@ -0,0 +1,10 @@
# 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
moduleinput.c
)
+217
View File
@@ -0,0 +1,217 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "moduleinput.h"
#include "input/input.h"
#include "assert/assert.h"
#include "util/string.h"
void moduleInput(scriptcontext_t *context) {
assertNotNull(context, "Script context cannot be NULL");
// Setup enums.
scriptContextExec(context, INPUT_ACTION_SCRIPT);
// Input values.
scriptContextExec(context,
""
#ifdef DUSK_INPUT_KEYBOARD
"INPUT_KEYBOARD = true\n"
#endif
#ifdef DUSK_INPUT_GAMEPAD
"INPUT_GAMEPAD = true\n"
#endif
#ifdef DUSK_INPUT_POINTER
"INPUT_POINTER = true\n"
#endif
#ifdef DUSK_INPUT_TOUCH
"INPUT_TOUCH = true\n"
#endif
);
// Metatable
if(luaL_newmetatable(context->luaState, "input_mt")) {
lua_pushcfunction(context->luaState, moduleInputIndex);
lua_setfield(context->luaState, -2, "__index");
}
lua_pop(context->luaState, 1);
// Events
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
lua_register(context->luaState, "inputBind", moduleInputBind);
lua_register(context->luaState, "inputIsDown", moduleInputIsDown);
lua_register(context->luaState, "inputPressed", moduleInputPressed);
lua_register(context->luaState, "inputReleased", moduleInputReleased);
lua_register(context->luaState, "inputGetValue", moduleInputGetValue);
lua_register(context->luaState, "inputAxis", moduleInputAxis);
}
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) {
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;
}
int moduleInputBind(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
// Requires action and button.
if(!lua_isstring(L, 1)) {
luaL_error(L, "inputBind: Expected button name as first argument");
return 0;
}
// Expect action ID
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;
}
// Validate action
if(action < INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
luaL_error(L, "inputBind: Invalid action ID %d with str %s", action, strBtn);
return 0;
}
// Get button by name
inputbutton_t btn = inputButtonGetByName(strBtn);
if(btn.type == INPUT_BUTTON_TYPE_NONE) {
luaL_error(L, "inputBind: Invalid button name '%s'", strBtn);
return 0;
}
inputBind(btn, action);
return 0;
}
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);
if(action < INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
luaL_error(L, "inputIsDown: Invalid action ID %d", action);
return 0;
}
lua_pushboolean(L, inputIsDown(action));
return 1;
}
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);
if(action < INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
luaL_error(L, "inputPressed: Invalid action ID %d", action);
return 0;
}
lua_pushboolean(L, inputPressed(action));
return 1;
}
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);
if(action < INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
luaL_error(L, "inputReleased: Invalid action ID %d", action);
return 0;
}
lua_pushboolean(L, inputReleased(action));
return 1;
}
int moduleInputGetValue(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
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);
if(action < INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
luaL_error(L, "inputGetValue: Invalid action ID %d", action);
return 0;
}
lua_pushnumber(L, inputGetCurrentValue(action));
return 1;
}
int moduleInputAxis(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
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);
if(neg < INPUT_ACTION_NULL || neg >= INPUT_ACTION_COUNT) {
luaL_error(L, "inputAxis: Invalid negative action ID %d", neg);
return 0;
}
if(pos < INPUT_ACTION_NULL || pos >= INPUT_ACTION_COUNT) {
luaL_error(L, "inputAxis: Invalid positive action ID %d", pos);
return 0;
}
lua_pushnumber(L, inputAxis(neg, pos));
return 1;
}
+57 -147
View File
@@ -1,161 +1,71 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/module/modulebase.h"
#include "script/scriptproto.h"
#include "script/module/math/modulevec2.h"
#include "input/input.h"
#include "script/scriptcontext.h"
static scriptproto_t MODULE_INPUT_PROTO;
/**
* Register input functions to the given script context.
*
* @param context The script context to register input functions to.
*/
void moduleInput(scriptcontext_t *context);
// Static Methods
moduleBaseFunction(moduleInputBind) {
moduleBaseRequireArgs(2);
moduleBaseRequireString(0);
moduleBaseRequireNumber(1);
/**
* Getter for input structure fields.
*
* @param l The Lua state.
*/
int moduleInputIndex(lua_State *l);
char_t strBtn[128];
moduleBaseToString(args[0], strBtn, sizeof(strBtn));
if(strBtn[0] == '\0') {
return moduleBaseThrow("Input.bind: button name cannot be empty");
}
/**
* Script binding for binding an input button to an action.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleInputBind(lua_State *L);
const inputaction_t action = (inputaction_t)jerry_value_as_number(args[1]);
if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
return moduleBaseThrow("Input.bind: invalid action");
}
/**
* Script binding for checking if an input action is currently pressed.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleInputIsDown(lua_State *L);
inputbutton_t btn = inputButtonGetByName(strBtn);
if(btn.type == INPUT_BUTTON_TYPE_NONE) {
return moduleBaseThrow("Input.bind: invalid button name");
}
/**
* Script binding for checking if an input action was pressed this frame.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleInputPressed(lua_State *L);
inputBind(btn, action);
return jerry_undefined();
}
/**
* Script binding for checking if an input action was released this frame.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleInputReleased(lua_State *L);
moduleBaseFunction(moduleInputIsDown) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
const inputaction_t action = (inputaction_t)jerry_value_as_number(args[0]);
if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
return moduleBaseThrow("Input.isDown: invalid action");
}
return jerry_boolean(inputIsDown(action));
}
/**
* Script binding for getting the value of an input axis.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleInputGetValue(lua_State *L);
moduleBaseFunction(moduleInputPressed) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
const inputaction_t action = (inputaction_t)jerry_value_as_number(args[0]);
if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
return moduleBaseThrow("Input.pressed: invalid action");
}
return jerry_boolean(inputPressed(action));
}
moduleBaseFunction(moduleInputReleased) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
const inputaction_t action = (inputaction_t)jerry_value_as_number(args[0]);
if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
return moduleBaseThrow("Input.released: invalid action");
}
return jerry_boolean(inputReleased(action));
}
moduleBaseFunction(moduleInputGetValue) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
const inputaction_t action = (inputaction_t)jerry_value_as_number(args[0]);
if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
return moduleBaseThrow("Input.getValue: invalid action");
}
return jerry_number(inputGetCurrentValue(action));
}
moduleBaseFunction(moduleInputAxis) {
moduleBaseRequireArgs(2);
moduleBaseRequireNumber(0); moduleBaseRequireNumber(1);
const inputaction_t neg = (inputaction_t)jerry_value_as_number(args[0]);
const inputaction_t pos = (inputaction_t)jerry_value_as_number(args[1]);
if(neg <= INPUT_ACTION_NULL || neg >= INPUT_ACTION_COUNT) {
return moduleBaseThrow("Input.axis: invalid negative action");
}
if(pos <= INPUT_ACTION_NULL || pos >= INPUT_ACTION_COUNT) {
return moduleBaseThrow("Input.axis: invalid positive action");
}
return jerry_number(inputAxis(neg, pos));
}
moduleBaseFunction(moduleInputAxis2D) {
moduleBaseRequireArgs(4);
moduleBaseRequireNumber(0); moduleBaseRequireNumber(1);
moduleBaseRequireNumber(2); moduleBaseRequireNumber(3);
const inputaction_t negX = (inputaction_t)jerry_value_as_number(args[0]);
const inputaction_t posX = (inputaction_t)jerry_value_as_number(args[1]);
const inputaction_t negY = (inputaction_t)jerry_value_as_number(args[2]);
const inputaction_t posY = (inputaction_t)jerry_value_as_number(args[3]);
if(negX <= INPUT_ACTION_NULL || negX >= INPUT_ACTION_COUNT) {
return moduleBaseThrow("Input.axis2D: invalid negX action");
}
if(posX <= INPUT_ACTION_NULL || posX >= INPUT_ACTION_COUNT) {
return moduleBaseThrow("Input.axis2D: invalid posX action");
}
if(negY <= INPUT_ACTION_NULL || negY >= INPUT_ACTION_COUNT) {
return moduleBaseThrow("Input.axis2D: invalid negY action");
}
if(posY <= INPUT_ACTION_NULL || posY >= INPUT_ACTION_COUNT) {
return moduleBaseThrow("Input.axis2D: invalid posY action");
}
vec2 result;
inputAxis2D(negX, posX, negY, posY, result);
return moduleVec2Push(result);
}
static void moduleInput(void) {
moduleBaseEval(INPUT_ACTION_SCRIPT);
moduleBaseEval(
""
#ifdef DUSK_INPUT_KEYBOARD
"var INPUT_KEYBOARD = true;\n"
#endif
#ifdef DUSK_INPUT_GAMEPAD
"var INPUT_GAMEPAD = true;\n"
#endif
#ifdef DUSK_INPUT_POINTER
"var INPUT_POINTER = true;\n"
#endif
#ifdef DUSK_INPUT_TOUCH
"var INPUT_TOUCH = true;\n"
#endif
);
scriptProtoInit(
&MODULE_INPUT_PROTO, "Input", sizeof(uint8_t), NULL
);
scriptProtoDefineStaticFunc(
&MODULE_INPUT_PROTO, "bind", moduleInputBind
);
scriptProtoDefineStaticFunc(
&MODULE_INPUT_PROTO, "isDown", moduleInputIsDown
);
scriptProtoDefineStaticFunc(
&MODULE_INPUT_PROTO, "pressed", moduleInputPressed
);
scriptProtoDefineStaticFunc(
&MODULE_INPUT_PROTO, "released", moduleInputReleased
);
scriptProtoDefineStaticFunc(
&MODULE_INPUT_PROTO, "getValue", moduleInputGetValue
);
scriptProtoDefineStaticFunc(
&MODULE_INPUT_PROTO, "axis", moduleInputAxis
);
scriptProtoDefineStaticFunc(
&MODULE_INPUT_PROTO, "axis2D", moduleInputAxis2D
);
}
/**
* Script binding for inputAxis.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleInputAxis(lua_State *L);
@@ -1,9 +1,10 @@
# Copyright (c) 2026 Dominic Masters
#
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
timedolphin.c
)
modulelocale.c
)
@@ -0,0 +1,99 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "modulelocale.h"
#include "locale/localemanager.h"
#include "assert/assert.h"
void moduleLocale(scriptcontext_t *context) {
assertNotNull(context, "Script context cannot be NULL");
// Execute the locale script definitions.
lua_register(context->luaState, "localeGetText", moduleLocaleGetText);
}
int moduleLocaleGetText(lua_State *L) {
// Expect string param for the id
if(!lua_isstring(L, 1)) {
luaL_error(L, "Expected message ID as first argument");
return 0;
}
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;
}
// Optional plural param, default to 0
int32_t plural = 0;
int top = lua_gettop(L);
int argStart = 2;
if(top >= 2 && !lua_isnil(L, 2)) {
if(!lua_isnumber(L, 2)) {
luaL_error(L, "Expected plural as second argument");
return 0;
}
plural = (int32_t)lua_tointeger(L, 2);
if(plural < 0) {
luaL_error(L, "Plural cannot be negative");
return 0;
}
argStart = 3;
}
// Build structured arg list from remaining Lua args
size_t argCount = (top >= argStart) ? (size_t)(top - argStart + 1) : 0;
#define MODULE_LOCALE_MAX_ARGS 16
assetlocalearg_t argsStack[MODULE_LOCALE_MAX_ARGS];
assetlocalearg_t *args = argsStack;
if(argCount > MODULE_LOCALE_MAX_ARGS) {
luaL_error(L, "Too many args (max %d)", MODULE_LOCALE_MAX_ARGS);
return 0;
}
for(size_t i = 0; i < argCount; ++i) {
int luaIndex = argStart + (int)i;
if(lua_isinteger(L, luaIndex)) {
args[i].type = ASSET_LOCALE_ARG_INT;
args[i].intValue = (int32_t)lua_tonumber(L, luaIndex);
} else if(lua_isnumber(L, luaIndex)) {
args[i].type = ASSET_LOCALE_ARG_FLOAT;
args[i].floatValue = lua_tonumber(L, luaIndex);
} else if(lua_isstring(L, luaIndex)) {
args[i].type = ASSET_LOCALE_ARG_STRING;
args[i].stringValue = lua_tostring(L, luaIndex);
} else {
luaL_error(L, "Unsupported localization argument type");
return 0;
}
}
char_t buffer[1024];
errorret_t err = localeManagerGetTextArgs(
id,
buffer,
sizeof(buffer),
plural,
args,
argCount
);
if(err.code != ERROR_OK) {
errorCatch(errorPrint(err));
luaL_error(L, "Failed to get localized text for ID '%s'", id);
return 0;
}
lua_pushstring(L, buffer);
return 1;
}
@@ -0,0 +1,24 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptcontext.h"
/**
* Register locale functions to the given script context.
*
* @param context The script context to register locale functions to.
*/
void moduleLocale(scriptcontext_t *context);
/**
* Script binding for getting a localized string.
*
* @param L The Lua state.
* @return The number of return values on the Lua stack.
*/
int moduleLocaleGetText(lua_State *L);
-229
View File
@@ -1,229 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/module/modulebase.h"
#include "script/scriptproto.h"
#include "cglm/cglm.h"
#include "modulevec3.h"
#include "modulevec4.h"
static scriptproto_t MODULE_MAT4_PROTO;
static inline void * moduleMatGet(const jerry_call_info_t *callInfo) {
return scriptProtoGetValue(&MODULE_MAT4_PROTO, callInfo->this_value);
}
moduleBaseFunction(moduleMatConstructor) {
float_t (*ptr)[4] = (float_t (*)[4])memoryAllocate(sizeof(mat4));
glm_mat4_identity(ptr);
jerry_object_set_native_ptr(
callInfo->this_value, &MODULE_MAT4_PROTO.info, ptr
);
return jerry_undefined();
}
moduleBaseFunction(moduleMatMul) {
moduleBaseRequireArgs(1);
float_t (*a)[4] = (float_t (*)[4])moduleMatGet(callInfo);
if(!a) return moduleBaseThrow("Mat4.mul: invalid this");
float_t (*b)[4] = (float_t (*)[4])scriptProtoGetValue(
&MODULE_MAT4_PROTO, args[0]
);
if(!b) return moduleBaseThrow("Mat4.mul: argument must be a Mat4");
mat4 r;
glm_mat4_mul(a, b, r);
return scriptProtoCreateValue(&MODULE_MAT4_PROTO, r);
}
moduleBaseFunction(moduleMatTranspose) {
float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo);
if(!m) return moduleBaseThrow("Mat4.transpose: invalid this");
mat4 r;
glm_mat4_transpose_to(m, r);
return scriptProtoCreateValue(&MODULE_MAT4_PROTO, r);
}
moduleBaseFunction(moduleMatInverse) {
float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo);
if(!m) return moduleBaseThrow("Mat4.inverse: invalid this");
mat4 r;
glm_mat4_inv(m, r);
return scriptProtoCreateValue(&MODULE_MAT4_PROTO, r);
}
moduleBaseFunction(moduleMatDeterminant) {
float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo);
if(!m) return moduleBaseThrow("Mat4.determinant: invalid this");
return jerry_number(glm_mat4_det(m));
}
moduleBaseFunction(moduleMatMulVec3) {
moduleBaseRequireArgs(1);
float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo);
if(!m) return moduleBaseThrow("Mat4.mulVec3: invalid this");
vec3 vin;
if(!moduleVec3Check(args[0], vin)) {
return moduleBaseThrow("Mat4.mulVec3: argument must be a Vec3");
}
float_t w = (argc >= 2 && jerry_value_is_number(args[1]))
? (float_t)jerry_value_as_number(args[1]) : 1.0f;
vec3 vout;
glm_mat4_mulv3(m, vin, w, vout);
return moduleVec3Push(vout);
}
moduleBaseFunction(moduleMatMulVec4) {
moduleBaseRequireArgs(1);
float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo);
if(!m) return moduleBaseThrow("Mat4.mulVec4: invalid this");
vec4 vin;
if(!moduleVec4Check(args[0], vin)) {
return moduleBaseThrow("Mat4.mulVec4: argument must be a Vec4");
}
vec4 vout;
glm_mat4_mulv(m, vin, vout);
return moduleVec4Push(vout);
}
moduleBaseFunction(moduleMatTranslate) {
moduleBaseRequireArgs(1);
float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo);
if(!m) return moduleBaseThrow("Mat4.translate: invalid this");
vec3 tv;
if(!moduleVec3Check(args[0], tv)) {
return moduleBaseThrow("Mat4.translate: argument must be a Vec3");
}
mat4 r;
glm_mat4_copy(m, r);
glm_translate(r, tv);
return scriptProtoCreateValue(&MODULE_MAT4_PROTO, r);
}
moduleBaseFunction(moduleMatScale) {
moduleBaseRequireArgs(1);
float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo);
if(!m) return moduleBaseThrow("Mat4.scale: invalid this");
vec3 sv;
if(!moduleVec3Check(args[0], sv)) {
return moduleBaseThrow("Mat4.scale: argument must be a Vec3");
}
mat4 r;
glm_mat4_copy(m, r);
glm_scale(r, sv);
return scriptProtoCreateValue(&MODULE_MAT4_PROTO, r);
}
moduleBaseFunction(moduleMatStaticIdentity) {
mat4 r;
glm_mat4_identity(r);
return scriptProtoCreateValue(&MODULE_MAT4_PROTO, r);
}
moduleBaseFunction(moduleMatStaticPerspective) {
moduleBaseRequireArgs(4);
moduleBaseRequireNumber(0); moduleBaseRequireNumber(1);
moduleBaseRequireNumber(2); moduleBaseRequireNumber(3);
mat4 r;
glm_perspective(
(float_t)jerry_value_as_number(args[0]),
(float_t)jerry_value_as_number(args[1]),
(float_t)jerry_value_as_number(args[2]),
(float_t)jerry_value_as_number(args[3]),
r
);
return scriptProtoCreateValue(&MODULE_MAT4_PROTO, r);
}
moduleBaseFunction(moduleMatStaticLookAt) {
moduleBaseRequireArgs(3);
vec3 eye, center, up;
if(!moduleVec3Check(args[0], eye)) {
return moduleBaseThrow("Mat4.lookAt: eye must be a Vec3");
}
if(!moduleVec3Check(args[1], center)) {
return moduleBaseThrow("Mat4.lookAt: center must be a Vec3");
}
if(!moduleVec3Check(args[2], up)) {
return moduleBaseThrow("Mat4.lookAt: up must be a Vec3");
}
mat4 r;
glm_lookat(eye, center, up, r);
return scriptProtoCreateValue(&MODULE_MAT4_PROTO, r);
}
moduleBaseFunction(moduleMatToString) {
float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo);
if(!m) return jerry_string_sz("Mat4(?)");
char_t buf[256];
stringFormat(
buf, sizeof(buf),
"Mat4([%g,%g,%g,%g], [%g,%g,%g,%g],"
" [%g,%g,%g,%g], [%g,%g,%g,%g])",
m[0][0], m[0][1], m[0][2], m[0][3],
m[1][0], m[1][1], m[1][2], m[1][3],
m[2][0], m[2][1], m[2][2], m[2][3],
m[3][0], m[3][1], m[3][2], m[3][3]
);
return jerry_string_sz(buf);
}
static inline jerry_value_t moduleMat4Push(float (*m)[4]) {
return scriptProtoCreateValue(&MODULE_MAT4_PROTO, m);
}
static inline bool_t moduleMat4Check(jerry_value_t val, float (*out)[4]) {
float_t (*m)[4] = (float_t (*)[4])scriptProtoGetValue(
&MODULE_MAT4_PROTO, val
);
if(!m) return false;
glm_mat4_copy(m, out);
return true;
}
static void moduleMat4(void) {
scriptProtoInit(
&MODULE_MAT4_PROTO, "Mat4", sizeof(mat4), moduleMatConstructor
);
scriptProtoDefineToString(&MODULE_MAT4_PROTO, moduleMatToString);
scriptProtoDefineFunc(
&MODULE_MAT4_PROTO, "mul", moduleMatMul
);
scriptProtoDefineFunc(
&MODULE_MAT4_PROTO, "transpose", moduleMatTranspose
);
scriptProtoDefineFunc(
&MODULE_MAT4_PROTO, "inverse", moduleMatInverse
);
scriptProtoDefineFunc(
&MODULE_MAT4_PROTO, "determinant", moduleMatDeterminant
);
scriptProtoDefineFunc(
&MODULE_MAT4_PROTO, "mulVec3", moduleMatMulVec3
);
scriptProtoDefineFunc(
&MODULE_MAT4_PROTO, "mulVec4", moduleMatMulVec4
);
scriptProtoDefineFunc(
&MODULE_MAT4_PROTO, "translate", moduleMatTranslate
);
scriptProtoDefineFunc(
&MODULE_MAT4_PROTO, "scale", moduleMatScale
);
scriptProtoDefineStaticFunc(
&MODULE_MAT4_PROTO, "identity", moduleMatStaticIdentity
);
scriptProtoDefineStaticFunc(
&MODULE_MAT4_PROTO, "perspective", moduleMatStaticPerspective
);
scriptProtoDefineStaticFunc(
&MODULE_MAT4_PROTO, "lookAt", moduleMatStaticLookAt
);
}
-22
View File
@@ -1,22 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/module/modulebase.h"
#include "modulevec2.h"
#include "modulevec3.h"
#include "modulevec3ref.h"
#include "modulevec4.h"
#include "modulemat4.h"
static void moduleMath(void) {
moduleVec2();
moduleVec3();
moduleVec3Ref();
moduleVec4();
moduleMat4();
}

Some files were not shown because too many files have changed in this diff Show More