50 Commits

Author SHA1 Message Date
YourWishes d373de7a29 Some adjustments 2026-05-01 13:44:51 -05:00
YourWishes 1efa9a9f7b More cleanup 2026-05-01 09:43:50 -05:00
YourWishes 0fb3ba2f91 Cleanup, prepping for example game stuff 2026-04-30 23:43:49 -05:00
YourWishes 3b4c5b5153 Added FPS meter 2026-04-30 23:34:32 -05:00
YourWishes 9293aeeec8 Fix position 2026-04-30 23:18:36 -05:00
YourWishes 03ae83b119 More cleanup? 2026-04-30 23:07:17 -05:00
YourWishes abd63cc6cf More cleanup 2026-04-30 22:40:32 -05:00
YourWishes 2e43aa2c44 Bit more cleanup 2026-04-30 20:03:44 -05:00
YourWishes 3d984e13c2 Module input improvements 2026-04-29 23:40:01 -05:00
YourWishes 010900fe21 Better again. 2026-04-29 23:26:21 -05:00
YourWishes ffed626447 More cleanup 2026-04-29 22:39:47 -05:00
YourWishes 61f69af35a Refactor pass 1 2026-04-29 14:53:35 -05:00
YourWishes bd248ee91c Build script on PSP, Dolphin and Engine. 2026-04-28 21:34:09 -05:00
YourWishes 194255bffe Fix merge conflcits 2026-04-28 14:02:59 -05:00
YourWishes 52ee627079 Merge branch 'jerryscript' into playertest 2026-04-28 14:02:53 -05:00
YourWishes bd4200e707 Finished getting JerryScript on all the platforms. 2026-04-28 13:59:46 -05:00
YourWishes 73e7d6c7f3 Add epoch 2026-04-28 10:33:23 -05:00
YourWishes a41b0e916b prog 2026-04-28 08:04:01 -05:00
YourWishes 19f2a2c616 Bit more consistent but still far from perfect 2026-04-27 09:14:14 -05:00
YourWishes 998601f722 Playertest: scene/script system refactor and Wii ABI fix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 23:30:08 -05:00
YourWishes 7c3386cf3e Add entity scripting 2026-04-20 17:01:12 -05:00
YourWishes d161182997 Entity modules 2026-04-20 16:50:16 -05:00
YourWishes 1646dc2dbd Fixed build 2026-04-20 15:43:18 -05:00
YourWishes b640295be2 Scene script 2026-04-20 15:34:24 -05:00
YourWishes b89ae2391b Reg console. 2026-04-20 14:31:22 -05:00
YourWishes a0fad441d0 Updated bind command 2026-04-20 12:59:25 -05:00
YourWishes 340084dac3 Removed console aliases 2026-04-20 12:49:06 -05:00
YourWishes c78135aa09 Fixed bugs with console 2026-04-20 12:05:35 -05:00
YourWishes d19f8bbd30 Restored console, has a bug 2026-04-20 09:26:25 -05:00
YourWishes 4205899f5a No idea why gamecube is crashing, disabling this for now 2026-04-18 21:57:57 -05:00
YourWishes 7dd3940770 Moved code to dolphin for network 2026-04-18 17:41:30 -05:00
YourWishes 00d94e3015 Slight wii improvements 2026-04-18 16:01:53 -05:00
YourWishes 7bacb3ee2b Testing on real wii hardware some more 2026-04-18 15:59:25 -05:00
YourWishes 8e49be5ac4 Testing some wii rendering bugs 2026-04-18 15:29:40 -05:00
YourWishes 3b94598d2c Fixed dolphin matricies the ugly way 2026-04-18 00:36:35 -05:00
YourWishes bddc9af3b6 "Improved" Dolphin matricies slightly 2026-04-18 00:32:50 -05:00
YourWishes 2451d73a7c Improved Wii aspect ratio significantly 2026-04-17 23:49:39 -05:00
YourWishes 1dd2efa182 Dolphin compiles, network untested 2026-04-17 22:53:49 -05:00
YourWishes acea610773 Disable curl on linux 2026-04-17 22:53:29 -05:00
YourWishes 8f2f1fd496 Added network info 2026-04-17 17:00:03 -05:00
YourWishes 39c775872a PSP networking matches linux now. 2026-04-17 16:32:45 -05:00
YourWishes bdb3cbd109 Fixed crash for cross/cancel logic 2026-04-17 16:02:45 -05:00
YourWishes ff84ce2b04 Added PSP Accept/Cance 2026-04-17 15:28:03 -05:00
YourWishes 225f405592 PSP Networking refactor 2026-04-17 14:57:10 -05:00
YourWishes 715ecffa18 Taking a break on net 2026-04-16 06:38:56 -05:00
YourWishes e51cdc8992 PSP net code first pass 2026-04-15 15:50:43 -05:00
YourWishes 133685ea37 Linux HTTP implementation 2026-04-15 15:11:44 -05:00
YourWishes 6aff98d555 Fix PSP build 2026-04-15 06:10:38 -05:00
YourWishes acdc524284 bit more accurate 2026-04-15 06:04:30 -05:00
YourWishes 1ee5ec7b43 Vita builds for the first time 2026-04-15 05:52:30 -05:00
225 changed files with 7939 additions and 3695 deletions
+16
View File
@@ -53,6 +53,22 @@ jobs:
path: ./git-artifcats/Dusk path: ./git-artifcats/Dusk
if-no-files-found: error 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: build-knulli:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
+3 -1
View File
@@ -4,11 +4,13 @@
# https://opensource.org/licenses/MIT # https://opensource.org/licenses/MIT
# Setup # Setup
cmake_minimum_required(VERSION 3.18) cmake_minimum_required(VERSION 3.13)
set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) 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) option(DUSK_BUILD_TESTS "Enable tests" OFF)
+22
View File
@@ -0,0 +1,22 @@
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
@@ -0,0 +1,21 @@
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
@@ -0,0 +1,77 @@
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
@@ -1,76 +0,0 @@
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("circle", INPUT_ACTION_CANCEL)
inputBind("cross", INPUT_ACTION_ACCEPT)
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
@@ -0,0 +1,32 @@
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;
+96
View File
@@ -0,0 +1,96 @@
# 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})
+1 -22
View File
@@ -4,6 +4,7 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_INPUT_GAMEPAD DUSK_INPUT_GAMEPAD
DUSK_DISPLAY_WIDTH=640 DUSK_DISPLAY_WIDTH=640
DUSK_DISPLAY_HEIGHT=480 DUSK_DISPLAY_HEIGHT=480
DUSK_THREAD_PTHREAD
) )
# Custom compiler flags # Custom compiler flags
@@ -21,31 +22,9 @@ set(CGLM_SHARED OFF CACHE BOOL "Build cglm shared" FORCE)
set(CGLM_STATIC ON CACHE BOOL "Build cglm static" FORCE) set(CGLM_STATIC ON CACHE BOOL "Build cglm static" FORCE)
find_package(cglm REQUIRED) 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 # Link libraries
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
cglm cglm
liblua
m m
fat fat
PkgConfig::zip PkgConfig::zip
+6 -1
View File
@@ -2,4 +2,9 @@ include(cmake/targets/dolphin.cmake)
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_GAMECUBE DUSK_GAMECUBE
) )
# Link libraries
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
# bba
)
+6 -1
View File
@@ -1,6 +1,7 @@
# Find link platform-specific libraries # Find link platform-specific libraries
find_package(SDL2 REQUIRED) find_package(SDL2 REQUIRED)
find_package(OpenGL REQUIRED) find_package(OpenGL REQUIRED)
# find_package(CURL REQUIRED)
# Setup endianess at compile time to optimize. # Setup endianess at compile time to optimize.
include(TestBigEndian) include(TestBigEndian)
@@ -16,18 +17,20 @@ else()
endif() endif()
# Link required libraries. # Link required libraries.
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
SDL2 SDL2
pthread pthread
OpenGL::GL OpenGL::GL
GL GL
m m
# CURL::libcurl
) )
# Define platform-specific macros. # Define platform-specific macros.
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_SDL2 DUSK_SDL2
DUSK_OPENGL DUSK_OPENGL
DUSK_CONSOLE_POSIX
# DUSK_OPENGL_LEGACY # DUSK_OPENGL_LEGACY
DUSK_LINUX DUSK_LINUX
DUSK_DISPLAY_SIZE_DYNAMIC DUSK_DISPLAY_SIZE_DYNAMIC
@@ -38,4 +41,6 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_INPUT_POINTER DUSK_INPUT_POINTER
DUSK_INPUT_GAMEPAD DUSK_INPUT_GAMEPAD
DUSK_TIME_DYNAMIC DUSK_TIME_DYNAMIC
DUSK_NETWORK_IPV6
DUSK_THREAD_PTHREAD
) )
+26 -3
View File
@@ -1,6 +1,19 @@
# 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(SDL2 REQUIRED)
find_package(OpenGL REQUIRED) find_package(OpenGL REQUIRED)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC target_link_libraries(${DUSK_BINARY_TARGET_NAME} PUBLIC
${SDL2_LIBRARIES} ${SDL2_LIBRARIES}
SDL2 SDL2
pthread pthread
@@ -24,13 +37,22 @@ target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
pspvfpu pspvfpu
pspvram pspvram
psphprm psphprm
pspnet
pspnet_inet
pspnet_apctl
psphttp
pspssl
jerryscript::core
jerryscript::ext
jerryscript::port
) )
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE target_include_directories(${DUSK_BINARY_TARGET_NAME} PRIVATE
${SDL2_INCLUDE_DIRS} ${SDL2_INCLUDE_DIRS}
) )
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC target_compile_definitions(${DUSK_BINARY_TARGET_NAME} PUBLIC
DUSK_SDL2 DUSK_SDL2
DUSK_OPENGL DUSK_OPENGL
DUSK_PSP DUSK_PSP
@@ -39,6 +61,7 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_OPENGL_LEGACY DUSK_OPENGL_LEGACY
DUSK_DISPLAY_WIDTH=480 DUSK_DISPLAY_WIDTH=480
DUSK_DISPLAY_HEIGHT=272 DUSK_DISPLAY_HEIGHT=272
DUSK_THREAD_PTHREAD
) )
# Postbuild, create .pbp file for PSP. # Postbuild, create .pbp file for PSP.
+88
View File
@@ -0,0 +1,88 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
if(NOT DEFINED ENV{VITASDK})
message(FATAL_ERROR "VITASDK environment variable is not set.")
endif()
include("$ENV{VITASDK}/share/vita.cmake" REQUIRED)
set(VITA_APP_NAME "Dusk")
set(VITA_TITLEID "DUSK00001")
set(VITA_VERSION "01.00")
find_package(SDL2 REQUIRED)
# Custom flags for cglm
set(CGLM_SHARED OFF CACHE BOOL "Build cglm shared" FORCE)
set(CGLM_STATIC ON CACHE BOOL "Build cglm static" FORCE)
find_package(cglm REQUIRED)
# Link libraries
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
${SDL2_LIBRARIES}
cglm
SDL2
SDL2main
zip
bz2
z
zstd
crypto
lzma
m
pthread
stdc++
vitaGL
mathneon
vitashark
kubridge_stub
SceAppMgr_stub
SceAudio_stub
SceCtrl_stub
SceCommonDialog_stub
SceDisplay_stub
SceKernelDmacMgr_stub
SceGxm_stub
SceShaccCg_stub
SceSysmodule_stub
ScePower_stub
SceTouch_stub
SceVshBridge_stub
SceIofilemgr_stub
SceShaccCgExt
libtaihen_stub.a
# SceKernel_stub
SceAppUtil_stub
SceHid_stub
SceRtc_stub
)
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
${SDL2_INCLUDE_DIRS}
)
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_SDL2
DUSK_OPENGL
DUSK_VITA
DUSK_INPUT_GAMEPAD
DUSK_PLATFORM_ENDIAN_LITTLE
DUSK_OPENGL_LEGACY
DUSK_DISPLAY_WIDTH=960
DUSK_DISPLAY_HEIGHT=544
)
# Post-build: create SELF from the ELF binary (UNSAFE = homebrew, no signing)
vita_create_self(${DUSK_BINARY_TARGET_NAME}.self ${DUSK_BINARY_TARGET_NAME} UNSAFE)
# Post-build: package SELF + assets into a .vpk installable on the Vita
vita_create_vpk(${DUSK_BINARY_TARGET_NAME}.vpk ${VITA_TITLEID} ${DUSK_BINARY_TARGET_NAME}.self
VERSION ${VITA_VERSION}
NAME ${VITA_APP_NAME}
FILE ${DUSK_ASSETS_ZIP} dusk.dsk
)
+13
View File
@@ -0,0 +1,13 @@
FROM vitasdk/vitasdk:latest
WORKDIR /workdir
# Install vitaGL and its dependencies (vitashark, SceShaccCg) via vdpm
RUN which vdpm
# Install Python (needed for Dusk code generation tools)
RUN apk add --no-cache \
python3 \
py3-pip \
py3-dotenv
VOLUME ["/workdir"]
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
docker build -t dusk-vita -f docker/vita/Dockerfile .
docker run --rm -v $(pwd):/workdir dusk-vita /bin/bash -c "./scripts/build-vita.sh"
+13
View File
@@ -0,0 +1,13 @@
#!/bin/bash
if [ -z "$VITASDK" ]; then
echo "VITASDK environment variable is not set. Please set it to the path of your VitaSDK installation."
exit 1
fi
mkdir -p build-vita
cd build-vita
cmake \
-DCMAKE_TOOLCHAIN_FILE=$VITASDK/share/vita.toolchain.cmake \
-DDUSK_TARGET_SYSTEM=vita \
..
make -j$(nproc)
+6 -1
View File
@@ -16,7 +16,12 @@ elseif(DUSK_TARGET_SYSTEM STREQUAL "psp")
add_subdirectory(dusksdl2) add_subdirectory(dusksdl2)
add_subdirectory(duskgl) add_subdirectory(duskgl)
elseif(DUSK_TARGET_SYSTEM STREQUAL "gamecube" OR DUSK_TARGET_SYSTEM STREQUAL "wii") elseif(DUSK_TARGET_SYSTEM STREQUAL "vita")
add_subdirectory(duskvita)
add_subdirectory(dusksdl2)
add_subdirectory(duskgl)
elseif(DUSK_TARGET_SYSTEM STREQUAL "wii" OR DUSK_TARGET_SYSTEM STREQUAL "gamecube")
add_subdirectory(duskdolphin) add_subdirectory(duskdolphin)
endif() endif()
+12 -17
View File
@@ -32,18 +32,13 @@ if(NOT yyjson_FOUND)
endif() endif()
endif() endif()
if(NOT Lua_FOUND) if(NOT jerryscript_FOUND)
find_package(Lua REQUIRED) find_package(jerryscript REQUIRED)
if(Lua_FOUND AND NOT TARGET Lua::Lua) target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
add_library(Lua::Lua INTERFACE IMPORTED) jerryscript::core
set_target_properties( jerryscript::ext
Lua::Lua jerryscript::port
PROPERTIES )
INTERFACE_INCLUDE_DIRECTORIES "${LUA_INCLUDE_DIR}"
INTERFACE_LINK_LIBRARIES "${LUA_LIBRARIES}"
)
endif()
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC Lua::Lua)
endif() endif()
# Includes # Includes
@@ -61,8 +56,9 @@ target_sources(${DUSK_BINARY_TARGET_NAME}
# Subdirs # Subdirs
add_subdirectory(assert) add_subdirectory(assert)
add_subdirectory(asset) add_subdirectory(asset)
add_subdirectory(log) add_subdirectory(console)
add_subdirectory(display) add_subdirectory(display)
add_subdirectory(log)
add_subdirectory(engine) add_subdirectory(engine)
add_subdirectory(entity) add_subdirectory(entity)
add_subdirectory(error) add_subdirectory(error)
@@ -72,10 +68,9 @@ add_subdirectory(locale)
add_subdirectory(physics) add_subdirectory(physics)
add_subdirectory(scene) add_subdirectory(scene)
add_subdirectory(script) add_subdirectory(script)
add_subdirectory(system)
add_subdirectory(time) add_subdirectory(time)
add_subdirectory(ui) add_subdirectory(ui)
add_subdirectory(network)
add_subdirectory(util) add_subdirectory(util)
add_subdirectory(thread)
# if(DUSK_TARGET_SYSTEM STREQUAL "linux" OR DUSK_TARGET_SYSTEM STREQUAL "psp")
# add_subdirectory(thread)
# endif()
+2 -2
View File
@@ -25,7 +25,7 @@ errorret_t assetInit(void) {
} }
bool_t assetFileExists(const char_t *filename) { bool_t assetFileExists(const char_t *filename) {
assertStrLenMax(filename, FILENAME_MAX, "Filename too long."); assertStrLenMax(filename, ASSET_FILE_PATH_MAX, "Filename too long.");
zip_int64_t idx = zip_name_locate(ASSET.zip, filename, 0); zip_int64_t idx = zip_name_locate(ASSET.zip, filename, 0);
if(idx < 0) return false; if(idx < 0) return false;
@@ -38,7 +38,7 @@ errorret_t assetLoad(
void *params, void *params,
void *output void *output
) { ) {
assertStrLenMax(filename, FILENAME_MAX, "Filename too long."); assertStrLenMax(filename, ASSET_FILE_PATH_MAX, "Filename too long.");
assertNotNull(output, "Output pointer cannot be NULL."); assertNotNull(output, "Output pointer cannot be NULL.");
assertNotNull(loader, "Asset file loader cannot be NULL."); assertNotNull(loader, "Asset file loader cannot be NULL.");
+2
View File
@@ -9,6 +9,8 @@
#include "error/error.h" #include "error/error.h"
#include <zip.h> #include <zip.h>
#define ASSET_FILE_PATH_MAX FILENAME_MAX
typedef struct assetfile_s assetfile_t; typedef struct assetfile_s assetfile_t;
typedef errorret_t (*assetfileloader_t)(assetfile_t *file); typedef errorret_t (*assetfileloader_t)(assetfile_t *file);
@@ -1,82 +1,92 @@
/** /**
* Copyright (c) 2026 Dominic Masters * Copyright (c) 2026 Dominic Masters
* *
* This software is released under the MIT License. * This software is released under the MIT License.
* https://opensource.org/licenses/MIT * https://opensource.org/licenses/MIT
*/ */
#include "assetscriptloader.h" #include "assetscriptloader.h"
#include "assert/assert.h" #include "assert/assert.h"
#include <stdlib.h>
#include <zip.h>
errorret_t assetScriptLoader(assetfile_t *file) { errorret_t assetScriptLoader(assetfile_t *file) {
assertNotNull(file, "Asset file cannot be NULL"); assertNotNull(file, "Asset file cannot be NULL");
assertNull(file->zipFile, "Asset file zip handle must be NULL"); assertNull(file->zipFile, "Asset file zip handle must be NULL before open");
assertNotNull(file->output, "Asset file output cannot be NULL"); assertNotNull(file->output, "Asset file output cannot be NULL");
assetscript_t *script = (assetscript_t *)file->output; assetscript_t *script = (assetscript_t *)file->output;
// Open the asset for buffering
errorChain(assetFileOpen(file)); errorChain(assetFileOpen(file));
// Request loading // Accumulate full source into a dynamically grown buffer.
if(!lua_load( size_t srcLen = 0;
script->ctx->luaState, size_t capacity = ASSET_SCRIPT_CHUNK_SIZE;
assetScriptReader, char_t *src = (char_t *)malloc(capacity + 1);
file, if(!src) {
file->filename, assetFileClose(file);
NULL errorThrow("Out of memory reading script: %s", file->filename);
) == 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);
} }
// Now loaded, exec while(1) {
if(lua_pcall(script->ctx->luaState, 0, LUA_MULTRET, 0) != LUA_OK) { if(srcLen + ASSET_SCRIPT_CHUNK_SIZE > capacity) {
const char_t *strErr = lua_tostring(script->ctx->luaState, -1); capacity = srcLen + ASSET_SCRIPT_CHUNK_SIZE;
lua_pop(script->ctx->luaState, 1); char_t *tmp = (char_t *)realloc(src, capacity + 1);
errorThrow("Failed to execute Lua script: %s", strErr); if(!tmp) {
free(src);
assetFileClose(file);
errorThrow("Out of memory reading script: %s", file->filename);
}
src = tmp;
}
zip_int64_t n = zip_fread(
file->zipFile, src + srcLen, ASSET_SCRIPT_CHUNK_SIZE
);
if(n <= 0) break;
srcLen += (size_t)n;
}
src[srcLen] = '\0';
errorret_t closeRet = assetFileClose(file);
jerry_value_t result = jerry_eval(
(const jerry_char_t *)src,
srcLen,
JERRY_PARSE_NO_OPTS
);
free(src);
if(jerry_value_is_exception(result)) {
jerry_value_t errVal = jerry_exception_value(result, false);
jerry_value_t errStr = jerry_value_to_string(errVal);
char_t buf[256];
jerry_size_t len = jerry_string_to_buffer(
errStr, JERRY_ENCODING_UTF8, (jerry_char_t *)buf, sizeof(buf) - 1
);
buf[len] = '\0';
jerry_value_free(errStr);
jerry_value_free(errVal);
jerry_value_free(result);
errorThrow("Script error in '%s': %s", file->filename, buf);
} }
// Close the file if(script->resultOut != NULL) {
return assetFileClose(file); *(script->resultOut) = result;
} else {
jerry_value_free(result);
}
return closeRet;
} }
errorret_t assetScriptLoad(const char_t *path, scriptcontext_t *ctx) { errorret_t assetScriptLoad(
const char_t *path,
jerry_value_t *resultOut
) {
assertNotNull(path, "Script path cannot be NULL"); assertNotNull(path, "Script path cannot be NULL");
assertNotNull(ctx, "Script context cannot be NULL");
assetscript_t script;
script.ctx = ctx;
return assetLoad( assetscript_t scriptData;
path, scriptData.resultOut = resultOut;
assetScriptLoader,
NULL, return assetLoad(path, assetScriptLoader, NULL, &scriptData);
&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,28 +1,23 @@
/** /**
* Copyright (c) 2026 Dominic Masters * Copyright (c) 2026 Dominic Masters
* *
* This software is released under the MIT License. * This software is released under the MIT License.
* https://opensource.org/licenses/MIT * https://opensource.org/licenses/MIT
*/ */
#pragma once #pragma once
#include "asset/asset.h" #include "asset/asset.h"
#include "script/scriptcontext.h"
#define ASSET_SCRIPT_BUFFER_SIZE 1024 #define ASSET_SCRIPT_CHUNK_SIZE 1024
typedef struct { typedef struct {
void *nothing; jerry_value_t *resultOut;
} assetscriptloaderparams_t;
typedef struct {
scriptcontext_t *ctx;
char_t buffer[ASSET_SCRIPT_BUFFER_SIZE];
} assetscript_t; } assetscript_t;
/** /**
* Handler for script assets. * Handler for script assets. Reads the full source, evaluates it with
* * JerryScript, and optionally stores the result.
*
* @param file Asset file to load the script from. * @param file Asset file to load the script from.
* @return Any error that occurs during loading. * @return Any error that occurs during loading.
*/ */
@@ -30,19 +25,14 @@ errorret_t assetScriptLoader(assetfile_t *file);
/** /**
* Loads a script from the specified path. * Loads a script from the specified path.
* *
* @param path Path to the script asset. * @param path Path to the script asset.
* @param ctx Script context to load the script into. * @param resultOut Optional out-parameter for the script return value.
* Caller must call jerry_value_free() if non-NULL.
* Pass NULL to discard the return value.
* @return Any error that occurs during loading. * @return Any error that occurs during loading.
*/ */
errorret_t assetScriptLoad(const char_t *path, scriptcontext_t *ctx); errorret_t assetScriptLoad(
const char_t *path,
/** jerry_value_t *resultOut
* 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);
@@ -1,10 +1,9 @@
# Copyright (c) 2026 Dominic Masters # Copyright (c) 2026 Dominic Masters
# #
# This software is released under the MIT License. # This software is released under the MIT License.
# https://opensource.org/licenses/MIT # https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME} target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC PUBLIC
modulelocale.c console.c
) )
+193
View File
@@ -0,0 +1,193 @@
/**
* 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
@@ -0,0 +1,79 @@
/**
* 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
@@ -0,0 +1,12 @@
/**
* 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
+1 -6
View File
@@ -25,8 +25,6 @@
#include "display/shader/shaderunlit.h" #include "display/shader/shaderunlit.h"
#include "time/time.h" #include "time/time.h"
#include "script/module/display/moduleshader.h"
display_t DISPLAY = { 0 }; display_t DISPLAY = { 0 };
errorret_t displayInit(void) { errorret_t displayInit(void) {
@@ -64,7 +62,7 @@ errorret_t displayInit(void) {
glm_perspective( glm_perspective(
glm_rad(45.0f), glm_rad(45.0f),
(float_t)SCREEN.width / (float_t)SCREEN.height, SCREEN.aspect,
0.1f, 0.1f,
100.0f, 100.0f,
proj proj
@@ -101,9 +99,6 @@ errorret_t displayUpdate(void) {
errorChain(sceneRender()); errorChain(sceneRender());
// Render UI
// uiRender();
// Finish up // Finish up
screenUnbind(); screenUnbind();
screenRender(); screenRender();
@@ -40,6 +40,17 @@ uint32_t frameBufferGetHeight(const framebuffer_t *framebuffer) {
return frameBufferPlatformGetHeight(framebuffer); return frameBufferPlatformGetHeight(framebuffer);
} }
float_t frameBufferGetAspect(const framebuffer_t *framebuffer) {
#ifdef frameBufferPlatformGetAspect
return frameBufferPlatformGetAspect(framebuffer);
#endif
uint32_t width = frameBufferGetWidth(framebuffer);
uint32_t height = frameBufferGetHeight(framebuffer);
if(height == 0) return 1.0f; // Avoid divide by zero, just return 1:1 aspect.
return (float_t)width / (float_t)height;
}
void frameBufferClear(const uint8_t flags, const color_t color) { void frameBufferClear(const uint8_t flags, const color_t color) {
frameBufferPlatformClear(flags, color); frameBufferPlatformClear(flags, color);
} }
@@ -58,6 +58,16 @@ uint32_t frameBufferGetWidth(const framebuffer_t *framebuffer);
*/ */
uint32_t frameBufferGetHeight(const framebuffer_t *framebuffer); uint32_t frameBufferGetHeight(const framebuffer_t *framebuffer);
/**
* Returns the aspect ratio of the framebuffer. This is ALMOST always just
* the width / height, however some platforms may choose to override this if
* they have stretched styled back buffers, e.g. 640x480 stretched.
*
* @param framebuffer The framebuffer to get the aspect ratio of.
* @return The aspect ratio of the framebuffer.
*/
float_t frameBufferGetAspect(const framebuffer_t *framebuffer);
/** /**
* Binds the framebuffer for rendering, or the backbuffer if the framebuffer * Binds the framebuffer for rendering, or the backbuffer if the framebuffer
* provided is NULL. * provided is NULL.
+4 -4
View File
@@ -52,7 +52,7 @@ errorret_t screenBind() {
// Screen mode backbuffer uses the full display size // Screen mode backbuffer uses the full display size
SCREEN.width = frameBufferGetWidth(FRAMEBUFFER_BOUND); SCREEN.width = frameBufferGetWidth(FRAMEBUFFER_BOUND);
SCREEN.height = frameBufferGetHeight(FRAMEBUFFER_BOUND); SCREEN.height = frameBufferGetHeight(FRAMEBUFFER_BOUND);
SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height; SCREEN.aspect = frameBufferGetAspect(FRAMEBUFFER_BOUND);
// No needd for a framebuffer. // No needd for a framebuffer.
#ifdef DUSK_DISPLAY_SIZE_DYNAMIC #ifdef DUSK_DISPLAY_SIZE_DYNAMIC
@@ -100,8 +100,7 @@ errorret_t screenBind() {
int32_t fbWidth, fbHeight; int32_t fbWidth, fbHeight;
fbWidth = frameBufferGetWidth(FRAMEBUFFER_BOUND); fbWidth = frameBufferGetWidth(FRAMEBUFFER_BOUND);
fbHeight = frameBufferGetHeight(FRAMEBUFFER_BOUND); fbHeight = frameBufferGetHeight(FRAMEBUFFER_BOUND);
float_t currentAspect = frameBufferGetAspect(FRAMEBUFFER_BOUND);
float_t currentAspect = (float_t)fbWidth / (float_t)fbHeight;
if(currentAspect == SCREEN.aspectRatio.ratio) { if(currentAspect == SCREEN.aspectRatio.ratio) {
// No need to use framebuffer. // No need to use framebuffer.
SCREEN.width = fbWidth; SCREEN.width = fbWidth;
@@ -129,13 +128,14 @@ errorret_t screenBind() {
if(SCREEN.framebufferReady) { if(SCREEN.framebufferReady) {
// Is current framebuffer the correct size? // Is current framebuffer the correct size?
int32_t curFbWidth, curFbHeight; int32_t curFbWidth, curFbHeight;
float_t curFbAspect = frameBufferGetAspect(&SCREEN.framebuffer);
curFbWidth = frameBufferGetWidth(&SCREEN.framebuffer); curFbWidth = frameBufferGetWidth(&SCREEN.framebuffer);
curFbHeight = frameBufferGetHeight(&SCREEN.framebuffer); curFbHeight = frameBufferGetHeight(&SCREEN.framebuffer);
if(curFbWidth == newFbWidth && curFbHeight == newFbHeight) { if(curFbWidth == newFbWidth && curFbHeight == newFbHeight) {
// Correct size, nothing to do. // Correct size, nothing to do.
SCREEN.width = newFbWidth; SCREEN.width = newFbWidth;
SCREEN.height = newFbHeight; SCREEN.height = newFbHeight;
SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height; SCREEN.aspect = curFbAspect;
errorChain(frameBufferBind(&SCREEN.framebuffer)); errorChain(frameBufferBind(&SCREEN.framebuffer));
errorOk(); errorOk();
} }
+11 -8
View File
@@ -13,19 +13,19 @@
#include "asset/loader/display/assettilesetloader.h" #include "asset/loader/display/assettilesetloader.h"
#include "display/shader/shaderunlit.h" #include "display/shader/shaderunlit.h"
texture_t DEFAULT_FONT_TEXTURE; texture_t FONT_TEXTURE_DEFAULT;
tileset_t DEFAULT_FONT_TILESET; tileset_t FONT_TILESET_DEFAULT;
errorret_t textInit(void) { errorret_t textInit(void) {
errorChain(assetTextureLoad( errorChain(assetTextureLoad(
"ui/minogram.png", &DEFAULT_FONT_TEXTURE, TEXTURE_FORMAT_RGBA "ui/minogram.png", &FONT_TEXTURE_DEFAULT, TEXTURE_FORMAT_RGBA
)); ));
errorChain(assetTilesetLoad("ui/minogram.dtf", &DEFAULT_FONT_TILESET)); errorChain(assetTilesetLoad("ui/minogram.dtf", &FONT_TILESET_DEFAULT));
errorOk(); errorOk();
} }
errorret_t textDispose(void) { errorret_t textDispose(void) {
errorChain(textureDispose(&DEFAULT_FONT_TEXTURE)); errorChain(textureDispose(&FONT_TEXTURE_DEFAULT));
errorOk(); errorOk();
} }
@@ -70,9 +70,7 @@ errorret_t textDraw(
const float_t x, const float_t x,
const float_t y, const float_t y,
const char_t *text, const char_t *text,
#if MESH_ENABLE_COLOR const color_t color,
const color_t color,
#endif
const tileset_t *tileset, const tileset_t *tileset,
texture_t *texture texture_t *texture
) { ) {
@@ -83,6 +81,11 @@ errorret_t textDraw(
errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, texture)); errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, texture));
#if MESH_ENABLE_COLOR
#else
errorChain(shaderSetColor(&SHADER_UNLIT, SHADER_UNLIT_COLOR, color));
#endif
// errorChain(spriteBatchPush( // errorChain(spriteBatchPush(
// // texture, // // texture,
// posX, posY, // posX, posY,
+3 -5
View File
@@ -12,8 +12,8 @@
#define TEXT_CHAR_START '!' #define TEXT_CHAR_START '!'
extern texture_t DEFAULT_FONT_TEXTURE; extern texture_t FONT_TEXTURE_DEFAULT;
extern tileset_t DEFAULT_FONT_TILESET; extern tileset_t FONT_TILESET_DEFAULT;
/** /**
* Initializes the text system. * Initializes the text system.
@@ -66,9 +66,7 @@ errorret_t textDraw(
const float_t x, const float_t x,
const float_t y, const float_t y,
const char_t *text, const char_t *text,
#if MESH_ENABLE_COLOR const color_t color,
const color_t color,
#endif
const tileset_t *tileset, const tileset_t *tileset,
texture_t *texture texture_t *texture
); );
+1
View File
@@ -21,6 +21,7 @@
#include <cglm/cglm.h> #include <cglm/cglm.h>
#include <cglm/types.h> #include <cglm/types.h>
#include <cglm/vec2.h> #include <cglm/vec2.h>
#include <jerryscript.h>
#include "duskplatform.h" #include "duskplatform.h"
+28 -69
View File
@@ -20,15 +20,22 @@
#include "entity/component/physics/entityphysics.h" #include "entity/component/physics/entityphysics.h"
#include "game/game.h" #include "game/game.h"
#include "physics/physicsmanager.h" #include "physics/physicsmanager.h"
#include "display/mesh/cube.h" #include "network/network.h"
#include "display/mesh/plane.h" #include "system/system.h"
#include "console/console.h"
double jerry_port_current_time(void) {
dusktimeepoch_t epoch = timeGetEpoch();
return epoch.time * 1000.0;
}
int32_t jerry_port_local_tza(double unix_ms) {
(void) unix_ms;
return 0;
}
engine_t ENGINE; engine_t ENGINE;
/* Kept module-level only because engineUpdate needs them for the reset. */
static entityid_t phBoxEnt;
static componentid_t phBoxPhys;
errorret_t engineInit(const int32_t argc, const char_t **argv) { errorret_t engineInit(const int32_t argc, const char_t **argv) {
memoryZero(&ENGINE, sizeof(engine_t)); memoryZero(&ENGINE, sizeof(engine_t));
ENGINE.running = true; ENGINE.running = true;
@@ -36,7 +43,9 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
ENGINE.argv = argv; ENGINE.argv = argv;
// Init systems. Order is important. // Init systems. Order is important.
errorChain(systemInit());
timeInit(); timeInit();
consoleInit();
errorChain(inputInit()); errorChain(inputInit());
errorChain(assetInit()); errorChain(assetInit());
errorChain(localeManagerInit()); errorChain(localeManagerInit());
@@ -46,86 +55,33 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
errorChain(sceneInit()); errorChain(sceneInit());
entityManagerInit(); entityManagerInit();
physicsManagerInit(); physicsManagerInit();
errorChain(networkInit());
errorChain(gameInit()); errorChain(gameInit());
/* ---- 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, distance * 6.0f);
/* ---- Static floor (visual + physics) ---- */
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;
/* ---- Dynamic 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;
/* Physics position lives in the POSITION component. CUBE_MESH_SIMPLE is
* centred at origin (-0.5..0.5), so entity position == physics centre. */
entityPositionSetPosition(phBoxEnt, boxPos, (vec3){ 0.0f, 4.0f, 0.0f });
/* Run the init script. */ /* Run the init script. */
scriptcontext_t ctx; consolePrint("Engine initialized");
errorChain(scriptContextInit(&ctx)); errorChain(scriptManagerExecFile("init.js", NULL));
errorChain(scriptContextExecFile(&ctx, "init.lua"));
scriptContextDispose(&ctx);
errorOk(); errorOk();
} }
errorret_t engineUpdate(void) { errorret_t engineUpdate(void) {
// Order here is important.
errorChain(networkUpdate());
timeUpdate(); timeUpdate();
inputUpdate(); inputUpdate();
consoleUpdate();
uiUpdate(); 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(); physicsManagerUpdate();
errorChain(gameUpdate()); errorChain(gameUpdate());
errorChain(displayUpdate()); errorChain(displayUpdate());
if(inputPressed(INPUT_ACTION_RAGEQUIT)) ENGINE.running = false; 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());
errorOk(); errorOk();
} }
@@ -136,10 +92,13 @@ void engineExit(void) {
errorret_t engineDispose(void) { errorret_t engineDispose(void) {
sceneDispose(); sceneDispose();
errorChain(gameDispose()); errorChain(gameDispose());
errorChain(networkDispose());
entityManagerDispose(); entityManagerDispose();
localeManagerDispose(); localeManagerDispose();
uiDispose(); uiDispose();
consoleDispose();
errorChain(displayDispose()); errorChain(displayDispose());
errorChain(assetDispose()); errorChain(assetDispose());
errorOk(); errorOk();
} }
+8 -2
View File
@@ -12,8 +12,14 @@
componentdefinition_t COMPONENT_DEFINITIONS[] = { componentdefinition_t COMPONENT_DEFINITIONS[] = {
[COMPONENT_TYPE_NULL] = { 0 }, [COMPONENT_TYPE_NULL] = { 0 },
#define X(enumName, type, field, iMethod, dMethod) \ #define X(enm, type, field, iMethod, dMethod) \
[COMPONENT_TYPE_##enumName] = { .init = iMethod, .dispose = dMethod }, [COMPONENT_TYPE_##enm] = { \
.enumName = #enm, \
.name = #field, \
.init = iMethod, \
.dispose = dMethod \
},
#include "componentlist.h" #include "componentlist.h"
#undef X #undef X
+2
View File
@@ -20,6 +20,8 @@ typedef union {
} componentdata_t; } componentdata_t;
typedef struct { typedef struct {
const char_t *enumName;
const char_t *name;
void (*init)(const entityid_t, const componentid_t); void (*init)(const entityid_t, const componentid_t);
void (*dispose)(const entityid_t, const componentid_t); void (*dispose)(const entityid_t, const componentid_t);
} componentdefinition_t; } componentdefinition_t;
+2 -1
View File
@@ -4,4 +4,5 @@
# https://opensource.org/licenses/MIT # https://opensource.org/licenses/MIT
add_subdirectory(display) add_subdirectory(display)
add_subdirectory(physics) add_subdirectory(physics)
add_subdirectory(script)
@@ -6,6 +6,8 @@
*/ */
#include "entity/entitymanager.h" #include "entity/entitymanager.h"
#include "entity/entity.h"
#include "entity/component/display/entityposition.h"
#include "display/framebuffer/framebuffer.h" #include "display/framebuffer/framebuffer.h"
#include "display/screen/screen.h" #include "display/screen/screen.h"
@@ -19,42 +21,6 @@ void entityCameraInit(const entityid_t ent, const componentid_t comp) {
cam->perspective.fov = glm_rad(45.0f); 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( void entityCameraGetProjection(
const entityid_t ent, const entityid_t ent,
const componentid_t comp, const componentid_t comp,
@@ -92,4 +58,38 @@ void entityCameraGetProjection(
out 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,45 +55,24 @@ void entityCameraGetProjection(
); );
/** /**
* Gets the near clip distance of a camera. * Returns the entity ID of the first active camera, or ENTITY_COUNT_MAX if none.
*
* @param ent The entity ID.
* @param comp The component ID.
* @return The near clip distance.
*/ */
float_t entityCameraGetZNear(const entityid_t ent, const componentid_t comp); entityid_t entityCameraGetCurrent(void);
/** /**
* Sets the near clip distance of a camera. * Gets the camera's horizontal forward direction (XZ plane) from its position
* component. Automatically finds the position component on the entity.
* *
* @param ent The entity ID. * @param entityId The camera entity ID.
* @param comp The component ID. * @param out Output vec2: {forwardX, forwardZ} normalized.
* @param zNear The near clip distance.
*/ */
void entityCameraSetZNear( void entityCameraGetForward(const entityid_t entityId, vec2 out);
const entityid_t ent,
const componentid_t comp,
const float_t zNear
);
/** /**
* Gets the far clip distance of a camera. * Gets the camera's horizontal right direction (XZ plane) from its position
* component. Automatically finds the position component on the entity.
* *
* @param ent The entity ID. * @param entityId The camera entity ID.
* @param comp The component ID. * @param out Output vec2: {rightX, rightZ} normalized.
* @return The far clip distance.
*/ */
float_t entityCameraGetZFar(const entityid_t ent, const componentid_t comp); void entityCameraGetRight(const entityid_t entityId, vec2 out);
/**
* 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,4 +48,15 @@ void entityMaterialSetShader(
entityId, componentId, COMPONENT_TYPE_MATERIAL entityId, componentId, COMPONENT_TYPE_MATERIAL
); );
mat->shader = shader; 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,4 +60,17 @@ void entityMaterialSetShader(
const entityid_t entityId, const entityid_t entityId,
const componentid_t componentId, const componentid_t componentId,
shader_t *shader 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
); );
+105 -1
View File
@@ -5,7 +5,13 @@
* https://opensource.org/licenses/MIT * https://opensource.org/licenses/MIT
*/ */
#include "entitymesh.h"
#include "entity/entitymanager.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( void entityMeshInit(
const entityid_t entityId, const entityid_t entityId,
@@ -14,7 +20,9 @@ void entityMeshInit(
entitymesh_t *comp = componentGetData( entitymesh_t *comp = componentGetData(
entityId, componentId, COMPONENT_TYPE_MESH entityId, componentId, COMPONENT_TYPE_MESH
); );
comp->mesh = NULL; comp->mesh = &CUBE_MESH_SIMPLE;
comp->ownedVertices = NULL;
comp->ownsData = false;
} }
mesh_t * entityMeshGetMesh( mesh_t * entityMeshGetMesh(
@@ -36,4 +44,100 @@ void entityMeshSetMesh(
entityId, componentId, COMPONENT_TYPE_MESH entityId, componentId, COMPONENT_TYPE_MESH
); );
comp->mesh = 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,6 +11,9 @@
typedef struct { typedef struct {
mesh_t *mesh; mesh_t *mesh;
mesh_t ownedMesh;
meshvertex_t *ownedVertices;
bool_t ownsData;
} entitymesh_t; } entitymesh_t;
/** /**
@@ -47,4 +50,47 @@ void entityMeshSetMesh(
const entityid_t entityId, const entityid_t entityId,
const componentid_t componentId, const componentid_t componentId,
mesh_t *mesh 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,6 +100,25 @@ bool_t entityPhysicsIsOnGround(
return phys->onGround; 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( void entityPhysicsDispose(
const entityid_t entityId, const entityid_t entityId,
const componentid_t componentId const componentid_t componentId
@@ -122,6 +122,31 @@ bool_t entityPhysicsIsOnGround(
const componentid_t componentId 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 * Releases the body slot back to PHYSICS_WORLD. Called automatically when
* the component is disposed via the component system. * the component is disposed via the component system.
@@ -0,0 +1,7 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
+7 -1
View File
@@ -11,8 +11,14 @@
#include "entity/component/display/entitymaterial.h" #include "entity/component/display/entitymaterial.h"
#include "entity/component/physics/entityphysics.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(POSITION, entityposition_t, position, entityPositionInit, NULL)
X(CAMERA, entitycamera_t, camera, entityCameraInit, NULL) X(CAMERA, entitycamera_t, camera, entityCameraInit, NULL)
X(MESH, entitymesh_t, mesh, entityMeshInit, NULL) X(MESH, entitymesh_t, mesh, entityMeshInit, entityMeshDispose)
X(MATERIAL, entitymaterial_t, material, entityMaterialInit, NULL) X(MATERIAL, entitymaterial_t, material, entityMaterialInit, NULL)
X(PHYSICS, entityphysics_t, physics, entityPhysicsInit, entityPhysicsDispose) X(PHYSICS, entityphysics_t, physics, entityPhysicsInit, entityPhysicsDispose)
+2 -2
View File
@@ -8,8 +8,8 @@
#pragma once #pragma once
#include "dusk.h" #include "dusk.h"
#define ENTITY_COUNT_MAX 64 #define ENTITY_COUNT_MAX 20
#define ENTITY_COMPONENT_COUNT_MAX 16 #define ENTITY_COMPONENT_COUNT_MAX 8
typedef uint8_t entityid_t; typedef uint8_t entityid_t;
typedef uint8_t componentid_t; typedef uint8_t componentid_t;
+3 -2
View File
@@ -8,6 +8,7 @@
#include "entitymanager.h" #include "entitymanager.h"
#include "assert/assert.h" #include "assert/assert.h"
#include "util/memory.h" #include "util/memory.h"
#include "console/console.h"
entitymanager_t ENTITY_MANAGER; entitymanager_t ENTITY_MANAGER;
@@ -18,8 +19,8 @@ void entityManagerInit(void) {
sizeof(entityid_t) * COMPONENT_TYPE_COUNT * ENTITY_COUNT_MAX sizeof(entityid_t) * COMPONENT_TYPE_COUNT * ENTITY_COUNT_MAX
); );
printf( consolePrint(
"Entity Manager size is currently: %zu bytes (%.2f KB)\n", "Entity Manager size: %zu bytes (%.2f KB)",
sizeof(entitymanager_t), sizeof(entitymanager_t),
sizeof(entitymanager_t) / 1024.0f sizeof(entitymanager_t) / 1024.0f
); );
+76 -89
View File
@@ -1,6 +1,6 @@
/** /**
* Copyright (c) 2026 Dominic Masters * Copyright (c) 2026 Dominic Masters
* *
* This software is released under the MIT License. * This software is released under the MIT License.
* https://opensource.org/licenses/MIT * https://opensource.org/licenses/MIT
*/ */
@@ -8,6 +8,7 @@
#include "event.h" #include "event.h"
#include "assert/assert.h" #include "assert/assert.h"
#include "util/memory.h" #include "util/memory.h"
#include "console/console.h"
void eventInit( void eventInit(
event_t *event, event_t *event,
@@ -46,19 +47,16 @@ eventsub_t eventSubscribeUser(
"Script event listener context cannot be NULL" "Script event listener context cannot be NULL"
); );
assertTrue( assertTrue(
user.script.luaFunctionRef != LUA_NOREF, user.script.funcValue != 0,
"Script event listener function reference is invalid" "Script event listener function reference is invalid"
); );
} else { } else {
assertUnreachable("Unknown event listener type"); assertUnreachable("Unknown event listener type");
} }
// Gen a new ID
eventsub_t id = event->nextId++; eventsub_t id = event->nextId++;
// Did we wrap?
assertTrue(event->nextId != 0, "Event subscription ID overflow"); assertTrue(event->nextId != 0, "Event subscription ID overflow");
// Append listener
eventlistener_t *listener = &event->listenerArray[event->listenerCount++]; eventlistener_t *listener = &event->listenerArray[event->listenerCount++];
memoryZero(listener, sizeof(eventlistener_t)); memoryZero(listener, sizeof(eventlistener_t));
listener->user = user; listener->user = user;
@@ -73,7 +71,7 @@ eventsub_t eventSubscribe(
const eventcallback_t callback, const eventcallback_t callback,
const void *user const void *user
) { ) {
eventSubscribeUser( return eventSubscribeUser(
event, event,
EVENT_TYPE_C, EVENT_TYPE_C,
(eventuserdata_t){ .c = { .callback = callback, .user = (void *)user } } (eventuserdata_t){ .c = { .callback = callback, .user = (void *)user } }
@@ -82,54 +80,34 @@ eventsub_t eventSubscribe(
eventsub_t eventSubscribeScriptContext( eventsub_t eventSubscribeScriptContext(
event_t *event, event_t *event,
scriptcontext_t *context, scriptmanager_t *context,
const int functionIndex jerry_value_t funcValue
) { ) {
assertNotNull(context, "Script context cannot be NULL"); assertNotNull(context, "Script context cannot be NULL");
assertTrue( assertTrue(funcValue != 0, "Script function value is invalid");
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; bool_t alreadySubbed = false;
uint8_t i; uint8_t i = 0;
i = 0;
do { do {
if(context->subscribedEvents[i] != event) { if(context->subscribedEvents[i] == event) {
i++; alreadySubbed = true;
continue; break;
} }
i++;
if(context->subscribedEvents[i] == NULL) break; } while(i < SCRIPT_MANAGER_MAX_EVENT_SUBSCRIPTIONS);
alreadySubbed = true;
break;
} while(i < SCRIPT_CONTEXT_MAX_EVENT_SUBSCRIPTIONS);
if(!alreadySubbed) { if(!alreadySubbed) {
i = 0; i = 0;
do { do {
if(context->subscribedEvents[i] != NULL) { if(context->subscribedEvents[i] == NULL) {
i++; context->subscribedEvents[i] = event;
continue; break;
} }
i++;
context->subscribedEvents[i] = event; } while(i < SCRIPT_MANAGER_MAX_EVENT_SUBSCRIPTIONS);
break;
} while(i < SCRIPT_CONTEXT_MAX_EVENT_SUBSCRIPTIONS);
assertTrue( assertTrue(
i < SCRIPT_CONTEXT_MAX_EVENT_SUBSCRIPTIONS, i < SCRIPT_MANAGER_MAX_EVENT_SUBSCRIPTIONS,
"Script context has reached maximum event subscriptions" "Script context has reached maximum event subscriptions"
); );
} }
@@ -137,7 +115,12 @@ eventsub_t eventSubscribeScriptContext(
return eventSubscribeUser( return eventSubscribeUser(
event, event,
EVENT_TYPE_SCRIPT, EVENT_TYPE_SCRIPT,
(eventuserdata_t){ .script = scriptUser } (eventuserdata_t){
.script = {
.context = context,
.funcValue = funcValue
}
}
); );
} }
@@ -147,38 +130,34 @@ void eventUnsubscribe(event_t *event, const eventsub_t id) {
if(event->listenerCount == 0) return; if(event->listenerCount == 0) return;
// Find listener
uint16_t index = 0; uint16_t index = 0;
do { do {
if(event->listenerArray[index].id == id) { if(event->listenerArray[index].id != id) {
// Found it, remove by swapping with last and reducing count index++;
event->listenerArray[index] = event->listenerArray[--event->listenerCount]; continue;
return;
} }
index++;
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;
} while(index < event->listenerCount); } 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 scriptcontext_t *ctx) { void eventUnsubscribeScriptContext(
event_t *event,
const scriptmanager_t *ctx
) {
assertNotNull(event, "Event cannot be NULL"); assertNotNull(event, "Event cannot be NULL");
assertNotNull(ctx, "Script context cannot be NULL"); assertNotNull(ctx, "Script context cannot be NULL");
if(event->listenerCount == 0) return; if(event->listenerCount == 0) return;
uint16_t i = 0; uint16_t i = 0;
do { do {
eventlistener_t *listener = &event->listenerArray[i]; eventlistener_t *listener = &event->listenerArray[i];
@@ -189,9 +168,6 @@ void eventUnsubscribeScriptContext(event_t *event, const scriptcontext_t *ctx) {
i++; i++;
continue; 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); eventUnsubscribe(event, listener->id);
} while(i < event->listenerCount); } while(i < event->listenerCount);
} }
@@ -204,44 +180,55 @@ void eventInvoke(
assertNotNull(event, "Event cannot be NULL"); assertNotNull(event, "Event cannot be NULL");
if(event->listenerCount == 0) return; if(event->listenerCount == 0) return;
event->isInvoking = true; event->isInvoking = true;
uint16_t i = 0; eventdata_t data = {
eventdata_t data ={
.event = event, .event = event,
.eventParams = eventParams, .eventParams = eventParams,
}; };
uint16_t i = 0;
do { do {
eventlistener_t *listener = &event->listenerArray[i]; eventlistener_t *listener = &event->listenerArray[i];
if(listener->type == EVENT_TYPE_C) { if(listener->type == EVENT_TYPE_C) {
listener->user.c.callback(&data, listener->user.c); listener->user.c.callback(&data, listener->user.c);
} else if(listener->type == EVENT_TYPE_SCRIPT) { } else if(listener->type == EVENT_TYPE_SCRIPT) {
// Call Lua function jerry_value_t funcVal = listener->user.script.funcValue;
lua_State *L = listener->user.script.context->luaState; assertNotNull((void *)(uintptr_t)funcVal, "Script function value is NULL");
assertNotNull(L, "Lua state in event listener cannot be NULL");
// Push function jerry_value_t callArgs[1];
lua_rawgeti(L, LUA_REGISTRYINDEX, listener->user.script.luaFunctionRef); jerry_length_t argCount = 0;
if(eventParams != NULL && metatableName != NULL) { if(eventParams != NULL) {
lua_getmetatable(L, -1); callArgs[0] = jerry_object();
luaL_getmetatable(L, metatableName); jerry_object_set_native_ptr(
assertTrue( callArgs[0], &JS_PTR_NATIVE_INFO, (void *)eventParams
lua_rawequal(L, -1, -2),
"Event parameter metatable does not match expected type"
); );
argCount = 1;
} }
// Call function with 1 arg, 0 return values jerry_value_t result = jerry_call(
if(lua_pcall(L, 1, 0, 0) != LUA_OK) { funcVal, jerry_undefined(), callArgs, argCount
const char_t *strErr = lua_tostring(L, -1); );
lua_pop(L, 1);
// Log error but continue if(argCount > 0) jerry_value_free(callArgs[0]);
printf("Error invoking Lua event listener: %s\n", strErr);
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);
} }
jerry_value_free(result);
} else { } else {
assertUnreachable("Unknown event listener type"); assertUnreachable("Unknown event listener type");
} }
@@ -249,4 +236,4 @@ void eventInvoke(
} while(i < event->listenerCount); } while(i < event->listenerCount);
event->isInvoking = false; event->isInvoking = false;
} }
+6 -3
View File
@@ -83,8 +83,8 @@ eventsub_t eventSubscribe(
*/ */
eventsub_t eventSubscribeScriptContext( eventsub_t eventSubscribeScriptContext(
event_t *event, event_t *event,
scriptcontext_t *context, scriptmanager_t *context,
const int functionIndex jerry_value_t funcValue
); );
/** /**
@@ -101,7 +101,10 @@ void eventUnsubscribe(event_t *event, const eventsub_t subscription);
* @param event The event to unsubscribe from. * @param event The event to unsubscribe from.
* @param context The script context whose listeners should be removed. * @param context The script context whose listeners should be removed.
*/ */
void eventUnsubscribeScriptContext(event_t *event, const scriptcontext_t *ctx); void eventUnsubscribeScriptContext(
event_t *event,
const scriptmanager_t *ctx
);
/** /**
* Invoke an event, calling all subscribed listeners. Optionally provide event * Invoke an event, calling all subscribed listeners. Optionally provide event
+5 -5
View File
@@ -1,13 +1,13 @@
/** /**
* Copyright (c) 2026 Dominic Masters * Copyright (c) 2026 Dominic Masters
* *
* This software is released under the MIT License. * This software is released under the MIT License.
* https://opensource.org/licenses/MIT * https://opensource.org/licenses/MIT
*/ */
#pragma once #pragma once
#include "eventcallback.h" #include "eventcallback.h"
#include "script/scriptcontext.h" #include "script/scriptmanager.h"
typedef enum { typedef enum {
EVENT_TYPE_C = 0, EVENT_TYPE_C = 0,
@@ -15,8 +15,8 @@ typedef enum {
} eventtype_t; } eventtype_t;
typedef struct { typedef struct {
scriptcontext_t *context; scriptmanager_t *context;
int luaFunctionRef; jerry_value_t funcValue;
} eventscript_t; } eventscript_t;
typedef struct eventc_s { typedef struct eventc_s {
@@ -27,4 +27,4 @@ typedef struct eventc_s {
typedef union eventuserdata_u { typedef union eventuserdata_u {
eventscript_t script; eventscript_t script;
eventc_t c; eventc_t c;
} eventuserdata_t; } eventuserdata_t;
+34 -28
View File
@@ -95,27 +95,27 @@ void inputUpdate(void) {
if(TIME.dynamicUpdate) return; if(TIME.dynamicUpdate) return;
#endif #endif
if(INPUT.eventPressed.listenerCount > 0) { // if(INPUT.eventPressed.listenerCount > 0) {
action = &INPUT.actions[0]; // action = &INPUT.actions[0];
do { // do {
if(inputPressed(action->action)) { // if(inputPressed(action->action)) {
inputevent_t inputEvent = { .action = action->action }; // inputevent_t inputEvent = { .action = action->action };
eventInvoke(&INPUT.eventPressed, &inputEvent, "input_mt"); // eventInvoke(&INPUT.eventPressed, &inputEvent, "input_mt");
} // }
action++; // action++;
} while(action < &INPUT.actions[INPUT_ACTION_COUNT]); // } while(action < &INPUT.actions[INPUT_ACTION_COUNT]);
} // }
if(INPUT.eventReleased.listenerCount > 0) { // if(INPUT.eventReleased.listenerCount > 0) {
action = &INPUT.actions[0]; // action = &INPUT.actions[0];
do { // do {
if(inputReleased(action->action)) { // if(inputReleased(action->action)) {
inputevent_t inputEvent = { .action = action->action }; // inputevent_t inputEvent = { .action = action->action };
eventInvoke(&INPUT.eventReleased, &inputEvent, "input_mt"); // eventInvoke(&INPUT.eventReleased, &inputEvent, "input_mt");
} // }
action++; // action++;
} while(action < &INPUT.actions[INPUT_ACTION_COUNT]); // } while(action < &INPUT.actions[INPUT_ACTION_COUNT]);
} // }
} }
float_t inputGetCurrentValue(const inputaction_t action) { float_t inputGetCurrentValue(const inputaction_t action) {
@@ -171,6 +171,16 @@ float_t inputAxis(const inputaction_t neg, const inputaction_t pos) {
return inputGetCurrentValue(pos) - inputGetCurrentValue(neg); 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) { void inputBind(const inputbutton_t button, const inputaction_t act) {
assertTrue( assertTrue(
act < INPUT_ACTION_COUNT, act < INPUT_ACTION_COUNT,
@@ -179,19 +189,15 @@ void inputBind(const inputbutton_t button, const inputaction_t act) {
assertTrue(act != INPUT_ACTION_NULL, "Cannot bind to NULL action"); assertTrue(act != INPUT_ACTION_NULL, "Cannot bind to NULL action");
// Get the button data for this button. // Get the button data for this button.
inputbuttondata_t *data = INPUT_BUTTON_DATA; inputbuttondata_t *data = inputButtonGetData(button);
do { assertNotNull(data, "Input button not found");
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. // Bind the action.
data->action = act; data->action = act;
} }
float_t inputDeadzone(const float_t rawValue, const float_t deadzone) { float_t inputDeadzone(const float_t rawValue, const float_t deadzone) {
if(rawValue < deadzone) return 0.0f; if(rawValue < deadzone) return 0.0f;
return (rawValue - deadzone) / (1.0f - deadzone); return (rawValue - deadzone) / (1.0f - deadzone);
+2 -1
View File
@@ -5,6 +5,7 @@ LEFT,
RIGHT, RIGHT,
ACCEPT, ACCEPT,
CANCEL, CANCEL,
RAGEQUIT RAGEQUIT,
CONSOLE,
POINTERX, POINTERX,
POINTERY, POINTERY,
1 id, id
5 RIGHT, RIGHT
6 ACCEPT, ACCEPT
7 CANCEL, CANCEL
8 RAGEQUIT RAGEQUIT
9 CONSOLE
10 POINTERX, POINTERX
11 POINTERY, POINTERY
+16
View File
@@ -120,6 +120,22 @@ bool_t inputReleased(const inputaction_t action);
*/ */
float_t inputAxis(const inputaction_t neg, const inputaction_t pos); 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. * Binds an input button to an action.
* *
+9 -9
View File
@@ -9,14 +9,14 @@
#include "assert/assert.h" #include "assert/assert.h"
#include "util/string.h" #include "util/string.h"
// inputaction_t inputActionGetByName(const char_t *name) { inputaction_t inputActionGetByName(const char_t *name) {
// assertNotNull(name, "name must not be NULL"); assertNotNull(name, "name must not be NULL");
// for(inputaction_t i = 0; i < INPUT_ACTION_COUNT; i++) { for(inputaction_t i = 0; i < INPUT_ACTION_COUNT; i++) {
// if(INPUT_ACTION_IDS[i] == NULL) continue; if(INPUT_ACTION_IDS[i] == NULL) continue;
// if(stringCompareInsensitive(INPUT_ACTION_IDS[i], name) != 0) continue; if(stringCompareInsensitive(INPUT_ACTION_IDS[i], name) != 0) continue;
// return i; 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. * @param name The name of the input action.
* @return The input action, or INPUT_ACTION_COUNT if not found. * @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,6 +9,7 @@
#include "input.h" #include "input.h"
#include "assert/assert.h" #include "assert/assert.h"
#include "util/string.h" #include "util/string.h"
#include "util/memory.h"
inputbutton_t inputButtonGetByName(const char_t *name) { inputbutton_t inputButtonGetByName(const char_t *name) {
assertNotNull(name, "name must not be NULL"); assertNotNull(name, "name must not be NULL");
@@ -26,4 +27,16 @@ inputbutton_t inputButtonGetByName(const char_t *name) {
float_t inputButtonGetValue(const inputbutton_t button) { float_t inputButtonGetValue(const inputbutton_t button) {
return inputButtonGetValuePlatform(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;
} }
+9 -1
View File
@@ -96,4 +96,12 @@ inputbutton_t inputButtonGetByName(const char_t *name);
* @param button The input button. * @param button The input button.
* @return The current value of the input button (0.0f to 1.0f). * @return The current value of the input button (0.0f to 1.0f).
*/ */
float_t inputButtonGetValue(const inputbutton_t button); 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);
@@ -1,10 +1,10 @@
# Copyright (c) 2026 Dominic Masters # Copyright (c) 2026 Dominic Masters
# #
# This software is released under the MIT License. # This software is released under the MIT License.
# https://opensource.org/licenses/MIT # https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME} target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC PUBLIC
modulesystem.c network.c
) networkinfo.c
)
+116
View File
@@ -0,0 +1,116 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "network.h"
#include "util/memory.h"
#include "assert/assert.h"
#include "log/log.h"
network_t NETWORK;
errorret_t networkInit() {
memoryZero(&NETWORK, sizeof(network_t));
NETWORK.errorState.code = ERROR_OK;
NETWORK.onDisconnect = NULL;
return networkPlatformInit();
}
errorret_t networkUpdate() {
errorChain(networkPlatformUpdate());
if(NETWORK.state == NETWORK_STATE_CONNECTED && !networkIsConnected()) {
NETWORK.state = NETWORK_STATE_DISCONNECTED;
if(NETWORK.onDisconnect) {
errorret_t ret;
if(NETWORK.errorState.code == ERROR_OK) {
ret = errorThrowImpl(
&NETWORK.errorState,
ERROR_NOT_OK,
__FILE__, __func__, __LINE__,
"Network connection lost"
);
} else {
ret.code = NETWORK.errorState.code;
ret.state = &NETWORK.errorState;
}
NETWORK.onDisconnect(ret, NETWORK.disconnectUser);
}
}
errorOk();
}
bool_t networkIsConnected() {
return networkPlatformIsConnected();
}
void networkRequestConnection(
void (*onConnected)(void *user),
void (*onFailed)(errorret_t error, void *user),
void (*onDisconnect)(errorret_t error, void *user),
void *user
) {
assertNotNull(onConnected, "onConnected callback must not be null");
assertNotNull(onFailed, "onFailed callback must not be null");
assertNotNull(onDisconnect, "onDisconnect callback must not be null");
NETWORK.state = NETWORK_STATE_CONNECTING;
NETWORK.onDisconnect = onDisconnect;
NETWORK.disconnectUser = user;
#ifndef networkPlatformRequestConnection
// This is a platform cannot be requested to go online, this would basically
// be for platforms like Linux or Windows where the OS is responsible for
// maintaining the network connection.
if(networkIsConnected()) {
NETWORK.state = NETWORK_STATE_CONNECTED;
onConnected(user);
} else {
errorret_t ret = errorThrowImpl(
&NETWORK.errorState,
ERROR_NOT_OK,
__FILE__, __func__, __LINE__,
"No network connection available"
);
onFailed(ret, user);
}
#else
networkPlatformRequestConnection(onConnected, onFailed, onDisconnect, user);
#endif
}
void networkRequestDisconnection(
void (*onComplete)(void *user),
void *user
) {
assertNotNull(onComplete, "onComplete callback must not be null");
NETWORK.state = NETWORK_STATE_DISCONNECTING;
#ifndef networkPlatformRequestDisconnection
NETWORK.state = NETWORK_STATE_DISCONNECTED;
onComplete(user);
#else
networkPlatformRequestDisconnection(onComplete, user);
#endif
}
void networkDisconnectedDuringDispose(void *u) {
logDebug("Network disconnected during dispose\n");
}
errorret_t networkDispose() {
if(NETWORK.state == NETWORK_STATE_CONNECTED) {
networkRequestDisconnection(networkDisconnectedDuringDispose, NULL);
}
errorChain(networkPlatformDispose());
errorOk();
}
+126
View File
@@ -0,0 +1,126 @@
/**
* 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 "network/networkplatform.h"
#ifndef networkPlatformInit
#error "networkPlatformInit must be defined"
#endif
#ifndef networkPlatformUpdate
#error "networkPlatformUpdate must be defined"
#endif
#ifndef networkPlatformDispose
#error "networkPlatformDispose must be defined"
#endif
#ifndef networkPlatformIsConnected
#error "networkPlatformIsConnected must be defined"
#endif
typedef enum {
NETWORK_STATE_DISCONNECTED,
NETWORK_STATE_CONNECTING,
NETWORK_STATE_CONNECTED,
NETWORK_STATE_DISCONNECTING,
} networkstate_t;
typedef struct {
networkplatform_t platform;
errorstate_t errorState;
networkstate_t state;
void (*onDisconnect)(errorret_t error, void *user);
void *disconnectUser;
} network_t;
extern network_t NETWORK;
/**
* Initializes the network system. This will NOT connect to the network.
*
* @return An error code indicating success or failure.
*/
errorret_t networkInit();
/**
* Updates the network manager, dispatching any completed async request
* callbacks on the main thread.
*
* @return An error code indicating success or failure.
*/
errorret_t networkUpdate();
/**
* Disposes of the network manager. This will NOT disconnect from the network.
*
* @return An error code indicating success or failure.
*/
errorret_t networkDispose();
/**
* Returns true if the system is connected to AN network, this doesn't mean that
* requests will succeed, doesn't mean there's internet, just that we could
* possibly make network requests. If this is false, this usually means
* something like;
* - A network cable is not connnected
* - No Wi-Fi Connection has been established
* - No IP Address has been assigned
*
* That kinda stuff.
*
* In future I will probably have REASONS for why it's not connected, for
* example;
* - On PSP you need to "request" network access
* - On GameCube, network settings need to be defined, including DHCP, etc.
* - On Windows, this may require additional permissions
*
* @return True if some network connection is detected.
*/
bool_t networkIsConnected();
/**
* See networkIsConnected for a bit more info, but this is for some
* platforms (mainly PSP) to request the system to start doing networking.
*
* You should only call this once and assume that it is "pending" until either
* onComplete or onFailed is invoked. If you call this twice it is undefined
* behavior.
*
* onDisconnect must be provided, and is called whenever the network is lost
* after a successful connection. This will NOT be called if disconnect is
* manually triggered, but WILL if an error occurs, or a network stack bug, etc.
*
* @param onConnected Callback to invoke when the network is connected.
* @param onFailed Callback to invoke if the network connection fails.
* @param onDisconnect Called after a successful connection, when disconnected.
* @param user User data to pass to the callbacks.
*/
void networkRequestConnection(
void (*onConnected)(void *user),
void (*onFailed)(errorret_t error, void *user),
void (*onDisconnect)(errorret_t error, void *user),
void *user
);
/**
* Requests the system to disconnect from the network. This is basically just
* for PSP, but I guess it could be used on other platforms in future if they
* have some kind of "network connection mode" that needs to be exited.
*
* You should only call this once and assume that it is "pending" until
* onComplete is invoked. If you call this twice it is undefined behavior.
*
* If it fails, you still get onComplete called, but may fail if you try to
* reconnect later unfortunately.
*
* @param onComplete Callback to invoke when the network is disconnected.
* @param user User data to pass to the callback.
*/
void networkRequestDisconnection(
void (*onComplete)(void *user),
void *user
);
+24
View File
@@ -0,0 +1,24 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "network.h"
#include "networkinfo.h"
#include "network/networkplatform.h"
#include "assert/assert.h"
#ifndef networkPlatformGetInfo
#error "networkPlatformGetInfo must be defined"
#endif
networkinfo_t networkGetInfo() {
assertTrue(
NETWORK.state == NETWORK_STATE_CONNECTED,
"networkGetInfo called when not connected"
);
return networkPlatformGetInfo();
}
+57
View File
@@ -0,0 +1,57 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#define NETWORK_INFO_IPV4_DNS_COUNT_MAX 2
#define NETWORK_INFO_IPV4_OCTET_COUNT 4
#define NETWORK_INFO_IPV6_OCTET_COUNT 16
#define NETWORK_INFO_IPV6_DNS_COUNT_MAX 2
#define NETWORK_INFO_FORMAT_IPV4 "%u.%u.%u.%u"
#define NETWORK_INFO_FORMAT_IPV6 \
"%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x"
typedef enum {
NETWORK_TYPE_IPV4,
#ifdef DUSK_NETWORK_IPV6
NETWORK_TYPE_IPV6,
#endif
} networktype_t;
typedef struct {
uint8_t ip[NETWORK_INFO_IPV4_OCTET_COUNT];
// uint8_t subnet[NETWORK_INFO_IPV4_OCTET_COUNT];
// uint8_t gateway[NETWORK_INFO_IPV4_OCTET_COUNT];
// uint8_t dns[NETWORK_INFO_IPV4_OCTET_COUNT][NETWORK_INFO_IPV4_DNS_COUNT_MAX];
} networkinfoipv4_t;
#ifdef DUSK_NETWORK_IPV6
typedef struct {
uint8_t ip[NETWORK_INFO_IPV6_OCTET_COUNT];
// uint8_t subnet[NETWORK_INFO_IPV6_OCTET_COUNT];
// uint8_t gateway[NETWORK_INFO_IPV6_OCTET_COUNT];
// uint8_t dns[NETWORK_INFO_IPV6_OCTET_COUNT][NETWORK_INFO_IPV6_DNS_COUNT_MAX];
} networkinfoipv6_t;
#endif
typedef struct {
networktype_t type;
union {
networkinfoipv4_t ipv4;
#ifdef DUSK_NETWORK_IPV6
networkinfoipv6_t ipv6;
#endif
};
} networkinfo_t;
/**
* Returns the network information for the currently active network connection.
*
* @return Network information for the currently active network connection.
*/
networkinfo_t networkGetInfo();
+277 -59
View File
@@ -1,5 +1,5 @@
// Copyright (c) 2026 Dominic Masters // Copyright (c) 2026 Dominic Masters
// //
// This software is released under the MIT License. // This software is released under the MIT License.
// https://opensource.org/licenses/MIT // https://opensource.org/licenses/MIT
@@ -12,12 +12,19 @@
#include "entity/entitymanager.h" #include "entity/entitymanager.h"
#include "display/shader/shaderunlit.h" #include "display/shader/shaderunlit.h"
#include "display/mesh/cube.h" #include "display/mesh/cube.h"
#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; scene_t SCENE;
errorret_t sceneInit(void) { errorret_t sceneInit(void) {
memoryZero(&SCENE, sizeof(scene_t)); memoryZero(&SCENE, sizeof(scene_t));
SCENE.scriptRef = SCENE_SCRIPT_REF_NONE;
errorOk(); errorOk();
} }
@@ -27,31 +34,30 @@ errorret_t sceneUpdate(void) {
errorOk(); errorOk();
} }
#endif #endif
if(stringCompare(SCENE.sceneNext, SCENE.sceneCurrent) != 0) {
errorChain(sceneSetImmediate(SCENE.sceneNext));
}
if(SCENE.sceneActive) {
errorChain(moduleSceneCall("update"));
}
errorOk(); errorOk();
} }
dusktimeepoch_t LAST;
errorret_t sceneRender(void) { errorret_t sceneRender(void) {
// Get Cameras
entityid_t camEnts[ENTITY_COUNT_MAX]; entityid_t camEnts[ENTITY_COUNT_MAX];
componentid_t camComps[ENTITY_COUNT_MAX]; componentid_t camComps[ENTITY_COUNT_MAX];
entityid_t camCount = componentGetEntitiesWithComponent( entityid_t camCount = componentGetEntitiesWithComponent(
COMPONENT_TYPE_CAMERA, camEnts, camComps 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; mat4 view, proj, model;
errorChain(shaderBind(&SHADER_UNLIT));
// For each camera
for(entityid_t camIndex = 0; camIndex < camCount; camIndex++) { for(entityid_t camIndex = 0; camIndex < camCount; camIndex++) {
entityid_t camEnt = camEnts[camIndex]; entityid_t camEnt = camEnts[camIndex];
componentid_t camComp = camComps[camIndex]; componentid_t camComp = camComps[camIndex];
@@ -64,59 +70,271 @@ errorret_t sceneRender(void) {
entityCameraGetProjection(camEnt, camComp, proj); entityCameraGetProjection(camEnt, camComp, proj);
entityPositionGetTransform(camEnt, camPos, view); entityPositionGetTransform(camEnt, camPos, view);
// For each mesh. // For each entity
for(entityid_t meshIndex = 0; meshIndex < meshCount; meshIndex++) { for(entityid_t entityId = 0; entityId < ENTITY_COUNT_MAX; entityId++) {
entityid_t meshEnt = meshEnts[meshIndex]; // Does this entity have a material?
componentid_t matComp = entityGetComponent(
componentid_t meshComp = meshComps[meshIndex]; entityId, COMPONENT_TYPE_MATERIAL
mesh_t *mesh = entityMeshGetMesh(meshEnt, meshComp);
if(mesh == NULL) {
continue;
}
componentid_t meshPos = entityGetComponent(
meshEnt, COMPONENT_TYPE_POSITION
); );
if(meshPos == 0xFF) { if(matComp != 0xFF) {
logError("Mesh entity without entity position found\n"); // Yes, get the mesh
componentid_t meshComp = entityGetComponent(
entityId, COMPONENT_TYPE_MESH
);
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));
continue; continue;
} }
componentid_t meshMat = entityGetComponent( // No, in future there may be other renderable types.
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(); 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
);
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();
} }
errorret_t sceneSet(const char_t *script) { void sceneSet(const char_t *scene) {
errorOk(); stringCopy(
SCENE.sceneNext,
scene == NULL ? "" : scene,
ASSET_FILE_PATH_MAX
);
} }
void sceneDispose(void) { errorret_t sceneDispose(void) {
errorChain(moduleSceneCall("dispose"));
} errorOk();
}
+38 -18
View File
@@ -1,48 +1,68 @@
/** /**
* Copyright (c) 2026 Dominic Masters * Copyright (c) 2026 Dominic Masters
* *
* This software is released under the MIT License. * This software is released under the MIT License.
* https://opensource.org/licenses/MIT * https://opensource.org/licenses/MIT
*/ */
#pragma once #pragma once
#include "error/error.h" #include "script/scriptmanager.h"
#include "asset/assetfile.h"
#include "event/event.h"
#define SCENE_EVENT_UPDATE_MAX 16
typedef struct { typedef struct {
void *nothing; bool_t sceneActive;
jerry_value_t scriptRef;
char_t sceneCurrent[ASSET_FILE_PATH_MAX];
char_t sceneNext[ASSET_FILE_PATH_MAX];
} scene_t; } scene_t;
extern scene_t SCENE; extern scene_t SCENE;
/** Sentinel value meaning no scene script is loaded. */
#define SCENE_SCRIPT_REF_NONE ((jerry_value_t)0)
/** /**
* Initialize the scene subsystem. * Initializes the scene manager.
* *
* @return The error return value. * @return Any error state that happened.
*/ */
errorret_t sceneInit(void); errorret_t sceneInit(void);
/** /**
* Update the current scene. * Ticks the scene manager; may call the scene's update method.
* *
* @return The error return value. * @return Any error state that happened.
*/ */
errorret_t sceneUpdate(void); errorret_t sceneUpdate(void);
/** /**
* Render the current scene. * Renders the scene.
* *
* @return The error return value. * @return Any error state that happened.
*/ */
errorret_t sceneRender(void); errorret_t sceneRender(void);
/** /**
* Set the current scene by script name. * Immediately switches scenes, disposing the current one first.
* *
* @param script The script name of the scene to set. * @param scene Scene to switch to (asset file path).
* @return Any error state that happened.
*/ */
errorret_t sceneSet(const char_t *script); errorret_t sceneSetImmediate(const char_t *scene);
/** /**
* Dispose of the scene subsystem. * Requests a scene change on the next safe opportunity.
*
* @param scene Which scene to set.
*/ */
void sceneDispose(void); void sceneSet(const char_t *scene);
/**
* Disposes of the current scene.
*
* @return Any error state that happened.
*/
errorret_t sceneDispose(void);
+2 -4
View File
@@ -7,9 +7,7 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME} target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC PUBLIC
scriptmanager.c scriptmanager.c
scriptcontext.c scriptproto.c
scriptmodule.c
) )
# Subdirectories # Subdirectories
add_subdirectory(module)
-14
View File
@@ -1,14 +0,0 @@
# 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)
@@ -0,0 +1,67 @@
/**
* 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
);
}
@@ -1,18 +0,0 @@
# 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
)
@@ -1,186 +0,0 @@
/**
* 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;
}
+145 -42
View File
@@ -1,55 +1,158 @@
/** /**
* Copyright (c) 2026 Dominic Masters * Copyright (c) 2026 Dominic Masters
* *
* This software is released under the MIT License. * This software is released under the MIT License.
* https://opensource.org/licenses/MIT * https://opensource.org/licenses/MIT
*/ */
#pragma once #pragma once
#include "script/scriptcontext.h" #include "script/module/modulebase.h"
#include "display/color.h"
#include "time/time.h"
#include "script/scriptproto.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(
* Lua function to create a color. const jerry_call_info_t *callInfo
* ) {
* @param L The Lua state. return (color_t*)scriptProtoGetValue(
* @return Number of return values. &MODULE_COLOR_PROTO, callInfo->this_value
*/ );
int moduleColorFuncColor(lua_State *L); }
/** moduleBaseFunction(moduleColorGetR) {
* Index function for the color structure. color_t *c = moduleColorGet(callInfo);
* @param L The Lua state. return c ? jerry_number(c->r) : jerry_undefined();
* @return Number of return values. }
*/
int moduleColorIndex(lua_State *L);
/** moduleBaseFunction(moduleColorGetG) {
* New index function for the color structure. color_t *c = moduleColorGet(callInfo);
* return c ? jerry_number(c->g) : jerry_undefined();
* @param L The Lua state. }
* @return Number of return values.
*/
int moduleColorNewIndex(lua_State *L);
/** moduleBaseFunction(moduleColorGetB) {
* Color to string method for script color_t *c = moduleColorGet(callInfo);
* return c ? jerry_number(c->b) : jerry_undefined();
* @param L The Lua state. }
* @return Number of return values.
*/
int moduleColorToString(lua_State *L);
/** moduleBaseFunction(moduleColorGetA) {
* Lua function to create a rainbow color based on time. color_t *c = moduleColorGet(callInfo);
* return c ? jerry_number(c->a) : jerry_undefined();
* @param L The Lua state. }
* @return Number of return values.
*/ moduleBaseFunction(moduleColorSetR) {
int moduleColorRainbow(lua_State *L); 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);
}
-283
View File
@@ -1,283 +0,0 @@
/**
* 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;
}
@@ -1,80 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptcontext.h"
/**
* 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);
@@ -1,43 +0,0 @@
/**
* 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;
}
+63 -29
View File
@@ -1,40 +1,74 @@
/** /**
* Copyright (c) 2026 Dominic Masters * Copyright (c) 2026 Dominic Masters
* *
* This software is released under the MIT License. * This software is released under the MIT License.
* https://opensource.org/licenses/MIT * https://opensource.org/licenses/MIT
*/ */
#pragma once #pragma once
#include "script/scriptcontext.h" #include "script/module/display/modulecolor.h"
#include "display/screen/screen.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) {
* Gets the current screen width. return jerry_number(SCREEN.width);
* }
* @param L The Lua state.
* @return Count of return values.
*/
int moduleScreenGetWidth(lua_State *L);
/** moduleBaseFunction(moduleScreenGetHeight) {
* Gets the current screen height. return jerry_number(SCREEN.height);
* }
* @param L The Lua state.
* @return Count of return values.
*/
int moduleScreenGetHeight(lua_State *L);
/** moduleBaseFunction(moduleScreenGetAspect) {
* Sets the screen background color. return jerry_number(SCREEN.aspect);
* }
* @param L The Lua state.
* @return Count of return values. moduleBaseFunction(moduleScreenGetBackground) {
*/ return moduleColorMakeObject(SCREEN.background);
int moduleScreenSetBackground(lua_State *L); }
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);
}
@@ -1,119 +0,0 @@
/**
* 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;
}
@@ -1,42 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptcontext.h"
/**
* 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();
@@ -1,105 +0,0 @@
/**
* 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;
}
@@ -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/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);
@@ -1,94 +0,0 @@
/**
* 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,
#if MESH_ENABLE_COLOR
color == NULL ? COLOR_WHITE : *color,
#endif
&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;
}
@@ -1,32 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptcontext.h"
/**
* 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);
@@ -1,119 +0,0 @@
/**
* 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;
}
@@ -1,15 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptcontext.h"
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);
@@ -1,164 +0,0 @@
/**
* 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;
}
@@ -1,56 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptcontext.h"
/**
* 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);
@@ -0,0 +1,29 @@
/**
* 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
);
}
@@ -0,0 +1,244 @@
/**
* 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
);
}
@@ -0,0 +1,91 @@
/**
* 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
);
}
@@ -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/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
);
}
@@ -0,0 +1,183 @@
/**
* 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);
}
@@ -0,0 +1,147 @@
/**
* 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
);
}
@@ -0,0 +1,205 @@
/**
* 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
}
@@ -1,10 +0,0 @@
# 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
)
@@ -1,45 +0,0 @@
/**
* 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);
}
@@ -1,21 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptcontext.h"
/**
* 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);
@@ -1,10 +0,0 @@
# 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
@@ -1,217 +0,0 @@
/**
* 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;
}
+147 -57
View File
@@ -1,71 +1,161 @@
/** /**
* Copyright (c) 2025 Dominic Masters * Copyright (c) 2026 Dominic Masters
* *
* This software is released under the MIT License. * This software is released under the MIT License.
* https://opensource.org/licenses/MIT * https://opensource.org/licenses/MIT
*/ */
#pragma once #pragma once
#include "script/scriptcontext.h" #include "script/module/modulebase.h"
#include "script/scriptproto.h"
#include "script/module/math/modulevec2.h"
#include "input/input.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
* Getter for input structure fields. moduleBaseFunction(moduleInputBind) {
* moduleBaseRequireArgs(2);
* @param l The Lua state. moduleBaseRequireString(0);
*/ moduleBaseRequireNumber(1);
int moduleInputIndex(lua_State *l);
/** char_t strBtn[128];
* Script binding for binding an input button to an action. moduleBaseToString(args[0], strBtn, sizeof(strBtn));
* if(strBtn[0] == '\0') {
* @param L The Lua state. return moduleBaseThrow("Input.bind: button name cannot be empty");
* @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]);
* Script binding for checking if an input action is currently pressed. if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
* return moduleBaseThrow("Input.bind: invalid action");
* @param L The Lua state. }
* @return Number of return values on the Lua stack.
*/
int moduleInputIsDown(lua_State *L);
/** inputbutton_t btn = inputButtonGetByName(strBtn);
* Script binding for checking if an input action was pressed this frame. if(btn.type == INPUT_BUTTON_TYPE_NONE) {
* return moduleBaseThrow("Input.bind: invalid button name");
* @param L The Lua state. }
* @return Number of return values on the Lua stack.
*/
int moduleInputPressed(lua_State *L);
/** inputBind(btn, action);
* Script binding for checking if an input action was released this frame. return jerry_undefined();
* }
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleInputReleased(lua_State *L);
/** moduleBaseFunction(moduleInputIsDown) {
* Script binding for getting the value of an input axis. moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
* const inputaction_t action = (inputaction_t)jerry_value_as_number(args[0]);
* @param L The Lua state. if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
* @return Number of return values on the Lua stack. return moduleBaseThrow("Input.isDown: invalid action");
*/ }
int moduleInputGetValue(lua_State *L); return jerry_boolean(inputIsDown(action));
}
/** moduleBaseFunction(moduleInputPressed) {
* Script binding for inputAxis. moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
* const inputaction_t action = (inputaction_t)jerry_value_as_number(args[0]);
* @param L The Lua state. if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
* @return Number of return values on the Lua stack. return moduleBaseThrow("Input.pressed: invalid action");
*/ }
int moduleInputAxis(lua_State *L); 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
);
}
@@ -1,99 +0,0 @@
/**
* 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;
}

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