1 Commits

Author SHA1 Message Date
YourWishes 643a8077dd Socket first pass 2026-04-19 18:53:09 -05:00
252 changed files with 4703 additions and 8657 deletions
+15 -15
View File
@@ -53,21 +53,21 @@ jobs:
path: ./git-artifcats/Dusk
if-no-files-found: error
# build-vita:
# runs-on: ubuntu-latest
# steps:
# - name: Checkout repository
# uses: actions/checkout@v6
# - name: Set up Docker
# uses: docker/setup-docker-action@v5
# - name: Build Vita
# run: ./scripts/build-vita-docker.sh
# - name: Upload Vita binary
# uses: actions/upload-artifact@v6
# with:
# name: dusk-vita
# path: build-vita/Dusk.vpk
# if-no-files-found: error
build-vita:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker
uses: docker/setup-docker-action@v5
- name: Build Vita
run: ./scripts/build-vita-docker.sh
- name: Upload Vita binary
uses: actions/upload-artifact@v6
with:
name: dusk-vita
path: build-vita/Dusk.vpk
if-no-files-found: error
build-knulli:
runs-on: ubuntu-latest
+1 -3
View File
@@ -4,13 +4,11 @@
# https://opensource.org/licenses/MIT
# Setup
cmake_minimum_required(VERSION 3.13)
cmake_minimum_required(VERSION 3.18)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules)
cmake_policy(SET CMP0079 NEW)
# set(FETCHCONTENT_UPDATES_DISCONNECTED ON)
option(DUSK_BUILD_TESTS "Enable tests" OFF)
+13
View File
@@ -0,0 +1,13 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef struct {
void *nothing;
} inventory_t;
-35
View File
@@ -1,35 +0,0 @@
var SPEED = 3.0;
var DURATION = 2.0;
function MoveCubeCutscene(params) {
Cutscene.call(this);
this.cube = params.cube;
var startX = this.cube.position.position.x;
this.animLeft = new Animation([
{ time: 0.0, value: startX, easing: Easing.outQuad },
{ time: DURATION, value: startX - SPEED * DURATION }
]);
this.animRight = new Animation([
{ time: 0.0, value: startX - SPEED * DURATION, easing: Easing.inOutQuad },
{ time: DURATION, value: startX }
]);
}
MoveCubeCutscene.prototype = Object.create(Cutscene.prototype);
MoveCubeCutscene.prototype.constructor = MoveCubeCutscene;
MoveCubeCutscene.prototype.update = function() {
if(!this.animLeft.complete) {
this.cube.position.position.x = this.animLeft.update(TIME.delta);
} else {
this.cube.position.position.x = this.animRight.update(TIME.delta);
if(this.animRight.complete) {
Cutscene.finish();
}
}
};
module = MoveCubeCutscene;
-25
View File
@@ -1,25 +0,0 @@
var OverworldEntity = include('entities/OverworldEntity.js');
function CubeEntity() {
OverworldEntity.call(this);
this.add(MESH);
this.add(MATERIAL);
this.cubeMesh = Mesh.createCube();
this.mesh.mesh = this.cubeMesh;
}
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.color = Color.rainbow();
};
module = CubeEntity;
-21
View File
@@ -1,21 +0,0 @@
function OverworldEntity() {
Entity.call(this);
this.add(POSITION);
}
OverworldEntity.prototype = Object.create(Entity.prototype);
OverworldEntity.prototype.constructor = OverworldEntity;
OverworldEntity.prototype.update = function() {
// var speed = 3.0;
// var move = Input.axis2D(INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT, INPUT_ACTION_UP, INPUT_ACTION_DOWN);
// this.position.position.x += move.x * speed * TIME.delta;
// this.position.position.z += move.y * speed * TIME.delta;
}
OverworldEntity.prototype.dispose = function() {
// Nothing to dispose
}
module = OverworldEntity;
-77
View File
@@ -1,77 +0,0 @@
Console.visible = true;
// Default input bindings.
if (typeof PSP !== 'undefined') {
Input.bind("up", INPUT_ACTION_UP);
Input.bind("down", INPUT_ACTION_DOWN);
Input.bind("left", INPUT_ACTION_LEFT);
Input.bind("right", INPUT_ACTION_RIGHT);
Input.bind("accept", INPUT_ACTION_ACCEPT);
Input.bind("cancel", INPUT_ACTION_CANCEL);
Input.bind("select", INPUT_ACTION_RAGEQUIT);
Input.bind("lstick_up", INPUT_ACTION_UP);
Input.bind("lstick_down", INPUT_ACTION_DOWN);
Input.bind("lstick_left", INPUT_ACTION_LEFT);
Input.bind("lstick_right", INPUT_ACTION_RIGHT);
Input.bind("triangle", INPUT_ACTION_CONSOLE);
} else if (typeof DOLPHIN !== 'undefined') {
Input.bind("up", INPUT_ACTION_UP);
Input.bind("down", INPUT_ACTION_DOWN);
Input.bind("left", INPUT_ACTION_LEFT);
Input.bind("right", INPUT_ACTION_RIGHT);
Input.bind("b", INPUT_ACTION_CANCEL);
Input.bind("a", INPUT_ACTION_ACCEPT);
Input.bind("z", INPUT_ACTION_CONSOLE);
Input.bind("lstick_up", INPUT_ACTION_UP);
Input.bind("lstick_down", INPUT_ACTION_DOWN);
Input.bind("lstick_left", INPUT_ACTION_LEFT);
Input.bind("lstick_right", INPUT_ACTION_RIGHT);
} else if (typeof LINUX !== 'undefined') {
if (typeof INPUT_KEYBOARD !== 'undefined') {
Input.bind("w", INPUT_ACTION_UP);
Input.bind("s", INPUT_ACTION_DOWN);
Input.bind("a", INPUT_ACTION_LEFT);
Input.bind("d", INPUT_ACTION_RIGHT);
Input.bind("left", INPUT_ACTION_LEFT);
Input.bind("right", INPUT_ACTION_RIGHT);
Input.bind("up", INPUT_ACTION_UP);
Input.bind("down", INPUT_ACTION_DOWN);
Input.bind("enter", INPUT_ACTION_ACCEPT);
Input.bind("e", INPUT_ACTION_ACCEPT);
Input.bind("q", INPUT_ACTION_CANCEL);
Input.bind("escape", INPUT_ACTION_RAGEQUIT);
Input.bind("`", INPUT_ACTION_CONSOLE);
}
if (typeof INPUT_GAMEPAD !== 'undefined') {
Input.bind("gamepad_up", INPUT_ACTION_UP);
Input.bind("gamepad_down", INPUT_ACTION_DOWN);
Input.bind("gamepad_left", INPUT_ACTION_LEFT);
Input.bind("gamepad_right", INPUT_ACTION_RIGHT);
Input.bind("gamepad_a", INPUT_ACTION_ACCEPT);
Input.bind("gamepad_b", INPUT_ACTION_CANCEL);
Input.bind("gamepad_back", INPUT_ACTION_RAGEQUIT);
Input.bind("gamepad_lstick_up", INPUT_ACTION_UP);
Input.bind("gamepad_lstick_down", INPUT_ACTION_DOWN);
Input.bind("gamepad_lstick_left", INPUT_ACTION_LEFT);
Input.bind("gamepad_lstick_right", INPUT_ACTION_RIGHT);
}
if (typeof INPUT_POINTER !== 'undefined') {
Input.bind("mouse_x", INPUT_ACTION_POINTERX);
Input.bind("mouse_y", INPUT_ACTION_POINTERY);
}
} else {
print("Unknown platform, no default input bindings set.");
}
Scene.set('scenes/cube.js');
+76
View File
@@ -0,0 +1,76 @@
module('input')
module('platform')
module('scene')
module('locale')
-- Default Input bindings.
if PSP then
inputBind("up", INPUT_ACTION_UP)
inputBind("down", INPUT_ACTION_DOWN)
inputBind("left", INPUT_ACTION_LEFT)
inputBind("right", INPUT_ACTION_RIGHT)
inputBind("accept", INPUT_ACTION_ACCEPT)
inputBind("cancel", INPUT_ACTION_CANCEL)
inputBind("select", INPUT_ACTION_RAGEQUIT)
inputBind("lstick_up", INPUT_ACTION_UP)
inputBind("lstick_down", INPUT_ACTION_DOWN)
inputBind("lstick_left", INPUT_ACTION_LEFT)
inputBind("lstick_right", INPUT_ACTION_RIGHT)
elseif DOLPHIN then
inputBind("up", INPUT_ACTION_UP)
inputBind("down", INPUT_ACTION_DOWN)
inputBind("left", INPUT_ACTION_LEFT)
inputBind("right", INPUT_ACTION_RIGHT)
inputBind("b", INPUT_ACTION_CANCEL)
inputBind("a", INPUT_ACTION_ACCEPT)
inputBind("z", INPUT_ACTION_RAGEQUIT)
inputBind("lstick_up", INPUT_ACTION_UP)
inputBind("lstick_down", INPUT_ACTION_DOWN)
inputBind("lstick_left", INPUT_ACTION_LEFT)
inputBind("lstick_right", INPUT_ACTION_RIGHT)
elseif LINUX then
if INPUT_KEYBOARD then
inputBind("w", INPUT_ACTION_UP)
inputBind("s", INPUT_ACTION_DOWN)
inputBind("a", INPUT_ACTION_LEFT)
inputBind("d", INPUT_ACTION_RIGHT)
inputBind("left", INPUT_ACTION_LEFT)
inputBind("right", INPUT_ACTION_RIGHT)
inputBind("up", INPUT_ACTION_UP)
inputBind("down", INPUT_ACTION_DOWN)
inputBind("enter", INPUT_ACTION_ACCEPT)
inputBind("e", INPUT_ACTION_ACCEPT)
inputBind("q", INPUT_ACTION_CANCEL)
inputBind("escape", INPUT_ACTION_RAGEQUIT)
end
if INPUT_GAMEPAD then
inputBind("gamepad_up", INPUT_ACTION_UP)
inputBind("gamepad_down", INPUT_ACTION_DOWN)
inputBind("gamepad_left", INPUT_ACTION_LEFT)
inputBind("gamepad_right", INPUT_ACTION_RIGHT)
inputBind("gamepad_a", INPUT_ACTION_ACCEPT)
inputBind("gamepad_b", INPUT_ACTION_CANCEL)
inputBind("gamepad_back", INPUT_ACTION_RAGEQUIT)
inputBind("gamepad_lstick_up", INPUT_ACTION_UP)
inputBind("gamepad_lstick_down", INPUT_ACTION_DOWN)
inputBind("gamepad_lstick_left", INPUT_ACTION_LEFT)
inputBind("gamepad_lstick_right", INPUT_ACTION_RIGHT)
end
if INPUT_POINTER then
inputBind("mouse_x", INPUT_ACTION_POINTERX)
inputBind("mouse_y", INPUT_ACTION_POINTERY)
end
else
print("Unknown platform, no default input bindings set.")
end
-50
View File
@@ -1,50 +0,0 @@
var CubeEntity = include('entities/CubeEntity.js');
var MoveCubeCutscene = include('cutscenes/MoveCubeCutscene.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.inputEnabled = false;
var scene = this;
Cutscene.play(new MoveCubeCutscene({ cube: this.cube })).then(function() {
scene.inputEnabled = true;
});
Textbox.setText(
"Hello! This is a visual novel textbox. It automatically " +
"wraps long lines and splits into pages when the content " +
"is too tall to fit. Press advance to continue...\t" +
"This is a second paragraph on a new page."
);
}
CubeScene.prototype = Object.create(Scene.prototype);
CubeScene.prototype.constructor = CubeScene;
CubeScene.prototype.update = function() {
if(this.inputEnabled) {
this.cube.update();
}
};
CubeScene.prototype.dispose = function() {
Cutscene.stop();
this.cam.dispose();
this.cube.dispose();
this.spriteEnt.dispose();
};
module = CubeScene;
-6
View File
@@ -1,6 +0,0 @@
module = {
render() {
Text.draw(0, 0, "Hello World");
SpriteBatch.flush();
}
};
-96
View File
@@ -1,96 +0,0 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Turn things off we don't need
set(JERRY_CMDLINE OFF CACHE BOOL "" FORCE)
set(JERRY_EXT ON CACHE BOOL "" FORCE)
set(JERRY_DEBUGGER OFF CACHE BOOL "" FORCE)
set(JERRY_BUILTIN_DATE OFF CACHE BOOL "" FORCE)
set(ENABLE_LTO OFF CACHE BOOL "" FORCE)
# Fetch Jerry
include(FetchContent)
FetchContent_Declare(
jerryscript
GIT_REPOSITORY https://git.wish.moe/YourWishes/jerryscript
GIT_TAG float32-fix
)
FetchContent_MakeAvailable(jerryscript)
# Mark found
set(jerryscript_FOUND ON)
# Define targets
if(TARGET jerryscript-core)
set(JERRY_CORE_TARGET jerryscript-core)
elseif(TARGET jerry-core)
set(JERRY_CORE_TARGET jerry-core)
endif()
if(TARGET jerryscript-ext)
set(JERRY_EXT_TARGET jerryscript-ext)
elseif(TARGET jerry-ext)
set(JERRY_EXT_TARGET jerry-ext)
endif()
if(TARGET jerryscript-port-default)
set(JERRY_PORT_TARGET jerryscript-port-default)
elseif(TARGET jerry-port-default)
set(JERRY_PORT_TARGET jerry-port-default)
elseif(TARGET jerryscript-port)
set(JERRY_PORT_TARGET jerryscript-port)
elseif(TARGET jerry-port)
set(JERRY_PORT_TARGET jerry-port)
endif()
if(NOT JERRY_CORE_TARGET)
message(FATAL_ERROR "JerryScript core target not found")
endif()
if(NOT JERRY_EXT_TARGET)
message(FATAL_ERROR "JerryScript ext target not found")
endif()
if(NOT JERRY_PORT_TARGET)
message(FATAL_ERROR "JerryScript port target not found")
endif()
foreach(tgt IN ITEMS
${JERRY_CORE_TARGET}
${JERRY_EXT_TARGET}
${JERRY_PORT_TARGET}
)
if(TARGET ${tgt})
set_property(TARGET ${tgt} PROPERTY INTERPROCEDURAL_OPTIMIZATION OFF)
target_compile_definitions(${JERRY_CORE_TARGET} PRIVATE
JERRY_NUMBER_TYPE_FLOAT64=0
JERRY_BUILTIN_DATE=0
)
endif()
endforeach()
# Export include dirs through the targets
target_include_directories(${JERRY_CORE_TARGET} INTERFACE
${jerryscript_SOURCE_DIR}/jerry-core/include
)
target_include_directories(${JERRY_EXT_TARGET} INTERFACE
${jerryscript_SOURCE_DIR}/jerry-ext/include
)
target_include_directories(${JERRY_PORT_TARGET} INTERFACE
${jerryscript_SOURCE_DIR}/jerry-port/default/include
)
# Suppress JerryScript-only warning
if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(${JERRY_CORE_TARGET} PRIVATE
-Wno-error
)
endif()
add_library(jerryscript::core ALIAS ${JERRY_CORE_TARGET})
add_library(jerryscript::ext ALIAS ${JERRY_EXT_TARGET})
add_library(jerryscript::port ALIAS ${JERRY_PORT_TARGET})
+22 -1
View File
@@ -4,7 +4,6 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_INPUT_GAMEPAD
DUSK_DISPLAY_WIDTH=640
DUSK_DISPLAY_HEIGHT=480
DUSK_THREAD_PTHREAD
)
# Custom compiler flags
@@ -22,9 +21,31 @@ set(CGLM_SHARED OFF CACHE BOOL "Build cglm shared" FORCE)
set(CGLM_STATIC ON CACHE BOOL "Build cglm static" FORCE)
find_package(cglm REQUIRED)
# Compile lua
include(FetchContent)
FetchContent_Declare(
liblua
URL https://www.lua.org/ftp/lua-5.5.0.tar.gz
)
FetchContent_MakeAvailable(liblua)
set(LUA_SRC_DIR "${liblua_SOURCE_DIR}/src")
set(LUA_C_FILES
lapi.c lauxlib.c lbaselib.c lcode.c lcorolib.c lctype.c ldblib.c ldebug.c
ldo.c ldump.c lfunc.c lgc.c linit.c liolib.c llex.c lmathlib.c lmem.c
loadlib.c lobject.c lopcodes.c loslib.c lparser.c lstate.c lstring.c
lstrlib.c ltable.c ltablib.c ltm.c lundump.c lutf8lib.c lvm.c lzio.c
)
list(TRANSFORM LUA_C_FILES PREPEND "${LUA_SRC_DIR}/")
add_library(liblua STATIC ${LUA_C_FILES})
target_include_directories(liblua PUBLIC "${LUA_SRC_DIR}")
target_compile_definitions(liblua PRIVATE LUA_USE_C89)
add_library(lua::lua ALIAS liblua)
set(Lua_FOUND TRUE CACHE BOOL "Lua found" FORCE)
# Link libraries
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
cglm
liblua
m
fat
PkgConfig::zip
+1 -2
View File
@@ -30,7 +30,6 @@ target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_SDL2
DUSK_OPENGL
DUSK_CONSOLE_POSIX
# DUSK_OPENGL_LEGACY
DUSK_LINUX
DUSK_DISPLAY_SIZE_DYNAMIC
@@ -42,5 +41,5 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_INPUT_GAMEPAD
DUSK_TIME_DYNAMIC
DUSK_NETWORK_IPV6
DUSK_THREAD_PTHREAD
THREAD_PTHREAD=1
)
+4 -21
View File
@@ -1,19 +1,6 @@
# Fixes some problems building JerryScript
set(CMAKE_AR "$ENV{PSPDEV}/bin/psp-ar" CACHE FILEPATH "" FORCE)
set(CMAKE_RANLIB "$ENV{PSPDEV}/bin/psp-ranlib" CACHE FILEPATH "" FORCE)
set(CMAKE_C_COMPILER_AR "$ENV{PSPDEV}/bin/psp-ar" CACHE FILEPATH "" FORCE)
set(CMAKE_C_COMPILER_RANLIB "$ENV{PSPDEV}/bin/psp-ranlib" CACHE FILEPATH "" FORCE)
set(CMAKE_C_ARCHIVE_CREATE "$ENV{PSPDEV}/bin/psp-ar qc <TARGET> <LINK_FLAGS> <OBJECTS>")
set(CMAKE_C_ARCHIVE_APPEND "$ENV{PSPDEV}/bin/psp-ar q <TARGET> <LINK_FLAGS> <OBJECTS>")
set(CMAKE_C_ARCHIVE_FINISH "$ENV{PSPDEV}/bin/psp-ranlib <TARGET>")
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION OFF CACHE BOOL "" FORCE)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_C OFF CACHE BOOL "" FORCE)
set(JERRY_LTO OFF CACHE BOOL "" FORCE)
find_package(jerryscript REQUIRED)
find_package(SDL2 REQUIRED)
find_package(OpenGL REQUIRED)
target_link_libraries(${DUSK_BINARY_TARGET_NAME} PUBLIC
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
${SDL2_LIBRARIES}
SDL2
pthread
@@ -42,17 +29,13 @@ target_link_libraries(${DUSK_BINARY_TARGET_NAME} PUBLIC
pspnet_apctl
psphttp
pspssl
jerryscript::core
jerryscript::ext
jerryscript::port
)
target_include_directories(${DUSK_BINARY_TARGET_NAME} PRIVATE
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
${SDL2_INCLUDE_DIRS}
)
target_compile_definitions(${DUSK_BINARY_TARGET_NAME} PUBLIC
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_SDL2
DUSK_OPENGL
DUSK_PSP
@@ -61,7 +44,7 @@ target_compile_definitions(${DUSK_BINARY_TARGET_NAME} PUBLIC
DUSK_OPENGL_LEGACY
DUSK_DISPLAY_WIDTH=480
DUSK_DISPLAY_HEIGHT=272
DUSK_THREAD_PTHREAD
THREAD_PTHREAD=1
)
# Postbuild, create .pbp file for PSP.
+22
View File
@@ -20,9 +20,31 @@ set(CGLM_SHARED OFF CACHE BOOL "Build cglm shared" FORCE)
set(CGLM_STATIC ON CACHE BOOL "Build cglm static" FORCE)
find_package(cglm REQUIRED)
# Compile lua
include(FetchContent)
FetchContent_Declare(
liblua
URL https://www.lua.org/ftp/lua-5.5.0.tar.gz
)
FetchContent_MakeAvailable(liblua)
set(LUA_SRC_DIR "${liblua_SOURCE_DIR}/src")
set(LUA_C_FILES
lapi.c lauxlib.c lbaselib.c lcode.c lcorolib.c lctype.c ldblib.c ldebug.c
ldo.c ldump.c lfunc.c lgc.c linit.c liolib.c llex.c lmathlib.c lmem.c
loadlib.c lobject.c lopcodes.c loslib.c lparser.c lstate.c lstring.c
lstrlib.c ltable.c ltablib.c ltm.c lundump.c lutf8lib.c lvm.c lzio.c
)
list(TRANSFORM LUA_C_FILES PREPEND "${LUA_SRC_DIR}/")
add_library(liblua STATIC ${LUA_C_FILES})
target_include_directories(liblua PUBLIC "${LUA_SRC_DIR}")
target_compile_definitions(liblua PRIVATE LUA_USE_C89)
add_library(lua::lua ALIAS liblua)
set(Lua_FOUND TRUE CACHE BOOL "Lua found" FORCE)
# Link libraries
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
${SDL2_LIBRARIES}
liblua
cglm
SDL2
SDL2main
+1 -1
View File
@@ -14,8 +14,8 @@ RUN apt-get install -y \
python3-dotenv \
python3-pyqt5 \
python3-opengl \
liblua5.3-dev \
xz-utils \
liblzma-dev \
libbz2-dev \
zlib1g-dev \
libzip-dev \
+1
View File
@@ -4,6 +4,7 @@
# https://opensource.org/licenses/MIT
add_subdirectory(dusk)
add_subdirectory(duskrpg)
if(DUSK_TARGET_SYSTEM STREQUAL "linux" OR DUSK_TARGET_SYSTEM STREQUAL "knulli")
add_subdirectory(dusklinux)
+13 -12
View File
@@ -32,14 +32,19 @@ if(NOT yyjson_FOUND)
endif()
endif()
if(NOT jerryscript_FOUND)
find_package(jerryscript REQUIRED)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
jerryscript::core
jerryscript::ext
jerryscript::port
if(NOT Lua_FOUND)
find_package(Lua REQUIRED)
if(Lua_FOUND AND NOT TARGET Lua::Lua)
add_library(Lua::Lua INTERFACE IMPORTED)
set_target_properties(
Lua::Lua
PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${LUA_INCLUDE_DIR}"
INTERFACE_LINK_LIBRARIES "${LUA_LIBRARIES}"
)
endif()
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC Lua::Lua)
endif()
# Includes
target_include_directories(${DUSK_LIBRARY_TARGET_NAME}
@@ -54,18 +59,14 @@ target_sources(${DUSK_BINARY_TARGET_NAME}
)
# Subdirs
add_subdirectory(animation)
add_subdirectory(assert)
add_subdirectory(asset)
add_subdirectory(cutscene)
add_subdirectory(item)
add_subdirectory(story)
add_subdirectory(console)
add_subdirectory(display)
add_subdirectory(log)
add_subdirectory(display)
add_subdirectory(engine)
add_subdirectory(entity)
add_subdirectory(error)
add_subdirectory(event)
add_subdirectory(input)
add_subdirectory(locale)
add_subdirectory(physics)
-75
View File
@@ -1,75 +0,0 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "animation.h"
#include "assert/assert.h"
#include "util/memory.h"
#include <math.h>
void animationInit(animation_t *anim) {
memoryZero(anim, sizeof(animation_t));
}
void animationAddKeyframe(
animation_t *anim,
float_t time,
float_t value,
easingtype_t easing
) {
assertTrue(
anim->keyframeCount < ANIMATION_KEYFRAME_COUNT_MAX,
"Keyframe count exceeds ANIMATION_KEYFRAME_COUNT_MAX"
);
uint8_t i = anim->keyframeCount++;
anim->keyframes[i].time = time;
anim->keyframes[i].value = value;
anim->keyframes[i].easing = easing;
if(time > anim->duration) anim->duration = time;
}
float_t animationGetValue(const animation_t *anim) {
if(anim->keyframeCount == 0) return 0.0f;
uint8_t last = anim->keyframeCount - 1;
if(anim->keyframeCount == 1) return anim->keyframes[0].value;
if(anim->time <= anim->keyframes[0].time) return anim->keyframes[0].value;
if(anim->time >= anim->keyframes[last].time) {
return anim->keyframes[last].value;
}
for(uint8_t i = 0; i < last; i++) {
const keyframe_t *a = &anim->keyframes[i];
const keyframe_t *b = &anim->keyframes[i + 1];
if(anim->time < a->time || anim->time >= b->time) continue;
float_t t = (anim->time - a->time) / (b->time - a->time);
t = easingApply(a->easing, t);
return a->value + (b->value - a->value) * t;
}
return anim->keyframes[last].value;
}
float_t animationUpdate(animation_t *anim, float_t delta) {
if(anim->complete && !anim->loop) return animationGetValue(anim);
anim->time += delta;
if(anim->duration > 0.0f && anim->time >= anim->duration) {
if(anim->loop) {
anim->time = fmodf(anim->time, anim->duration);
} else {
anim->time = anim->duration;
anim->complete = true;
}
}
return animationGetValue(anim);
}
void animationReset(animation_t *anim) {
anim->time = 0.0f;
anim->complete = false;
}
-63
View File
@@ -1,63 +0,0 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "keyframe.h"
#include "error/error.h"
typedef struct {
keyframe_t keyframes[ANIMATION_KEYFRAME_COUNT_MAX];
uint8_t keyframeCount;
float_t time;
float_t duration;
bool_t loop;
bool_t complete;
} animation_t;
/**
* Zeroes an animation struct.
*
* @param anim The animation to initialize.
*/
void animationInit(animation_t *anim);
/**
* Appends a keyframe. Keyframes must be added in ascending time order.
*
* @param anim The animation to modify.
* @param time Time in seconds for this keyframe.
* @param value Value at this keyframe.
* @param easing Easing applied to the segment after this keyframe.
*/
void animationAddKeyframe(
animation_t *anim,
float_t time,
float_t value,
easingtype_t easing
);
/**
* Advances the animation by delta seconds and returns the current value.
*
* @param anim The animation to update.
* @param delta Seconds elapsed since last update.
* @return Interpolated value at the new time.
*/
float_t animationUpdate(animation_t *anim, float_t delta);
/**
* Returns the interpolated value at the current time without advancing.
*
* @param anim The animation to read.
* @return Interpolated value.
*/
float_t animationGetValue(const animation_t *anim);
/**
* Resets the animation to the beginning.
*
* @param anim The animation to reset.
*/
void animationReset(animation_t *anim);
-106
View File
@@ -1,106 +0,0 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "easing.h"
#include "assert/assert.h"
#include <math.h>
#define EASING_PI 3.14159265358979323846f
#define EASING_C1 1.70158f
#define EASING_C2 (EASING_C1 * 1.525f)
#define EASING_C3 (EASING_C1 + 1.0f)
float_t easingLinear(float_t t) {
return t;
}
float_t easingInSine(float_t t) {
return 1.0f - cosf(t * EASING_PI * 0.5f);
}
float_t easingOutSine(float_t t) {
return sinf(t * EASING_PI * 0.5f);
}
float_t easingInOutSine(float_t t) {
return -(cosf(EASING_PI * t) - 1.0f) * 0.5f;
}
float_t easingInQuad(float_t t) {
return t * t;
}
float_t easingOutQuad(float_t t) {
float_t u = 1.0f - t;
return 1.0f - u * u;
}
float_t easingInOutQuad(float_t t) {
if(t < 0.5f) return 2.0f * t * t;
float_t u = -2.0f * t + 2.0f;
return 1.0f - u * u * 0.5f;
}
float_t easingInCubic(float_t t) {
return t * t * t;
}
float_t easingOutCubic(float_t t) {
float_t u = 1.0f - t;
return 1.0f - u * u * u;
}
float_t easingInOutCubic(float_t t) {
if(t < 0.5f) return 4.0f * t * t * t;
float_t u = -2.0f * t + 2.0f;
return 1.0f - u * u * u * 0.5f;
}
float_t easingInQuart(float_t t) {
return t * t * t * t;
}
float_t easingOutQuart(float_t t) {
float_t u = 1.0f - t;
return 1.0f - u * u * u * u;
}
float_t easingInOutQuart(float_t t) {
if(t < 0.5f) return 8.0f * t * t * t * t;
float_t u = -2.0f * t + 2.0f;
return 1.0f - u * u * u * u * 0.5f;
}
float_t easingInBack(float_t t) {
return EASING_C3 * t * t * t - EASING_C1 * t * t;
}
float_t easingOutBack(float_t t) {
float_t u = t - 1.0f;
return 1.0f + EASING_C3 * u * u * u + EASING_C1 * u * u;
}
float_t easingInOutBack(float_t t) {
if(t < 0.5f) {
float_t u = 2.0f * t;
return u * u * ((EASING_C2 + 1.0f) * u - EASING_C2) * 0.5f;
}
float_t u = 2.0f * t - 2.0f;
return (u * u * ((EASING_C2 + 1.0f) * u + EASING_C2) + 2.0f) * 0.5f;
}
const easingfn_t EASING_FUNCTIONS[EASING_COUNT] = {
easingLinear,
easingInSine, easingOutSine, easingInOutSine,
easingInQuad, easingOutQuad, easingInOutQuad,
easingInCubic, easingOutCubic, easingInOutCubic,
easingInQuart, easingOutQuart, easingInOutQuart,
easingInBack, easingOutBack, easingInOutBack,
};
float_t easingApply(easingtype_t type, float_t t) {
assertTrue(type < EASING_COUNT, "Invalid easing type");
return EASING_FUNCTIONS[type](t);
}
-49
View File
@@ -1,49 +0,0 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "dusk.h"
#define EASING_LINEAR 0
#define EASING_IN_SINE 1
#define EASING_OUT_SINE 2
#define EASING_IN_OUT_SINE 3
#define EASING_IN_QUAD 4
#define EASING_OUT_QUAD 5
#define EASING_IN_OUT_QUAD 6
#define EASING_IN_CUBIC 7
#define EASING_OUT_CUBIC 8
#define EASING_IN_OUT_CUBIC 9
#define EASING_IN_QUART 10
#define EASING_OUT_QUART 11
#define EASING_IN_OUT_QUART 12
#define EASING_IN_BACK 13
#define EASING_OUT_BACK 14
#define EASING_IN_OUT_BACK 15
#define EASING_COUNT 16
typedef uint8_t easingtype_t;
typedef float_t (*easingfn_t)(float_t t);
extern const easingfn_t EASING_FUNCTIONS[EASING_COUNT];
float_t easingApply(easingtype_t type, float_t t);
float_t easingLinear(float_t t);
float_t easingInSine(float_t t);
float_t easingOutSine(float_t t);
float_t easingInOutSine(float_t t);
float_t easingInQuad(float_t t);
float_t easingOutQuad(float_t t);
float_t easingInOutQuad(float_t t);
float_t easingInCubic(float_t t);
float_t easingOutCubic(float_t t);
float_t easingInOutCubic(float_t t);
float_t easingInQuart(float_t t);
float_t easingOutQuart(float_t t);
float_t easingInOutQuart(float_t t);
float_t easingInBack(float_t t);
float_t easingOutBack(float_t t);
float_t easingInOutBack(float_t t);
-15
View File
@@ -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 "easing.h"
#define ANIMATION_KEYFRAME_COUNT_MAX 16
typedef struct {
float_t time;
float_t value;
easingtype_t easing;
} keyframe_t;
+2 -2
View File
@@ -25,7 +25,7 @@ errorret_t assetInit(void) {
}
bool_t assetFileExists(const char_t *filename) {
assertStrLenMax(filename, ASSET_FILE_PATH_MAX, "Filename too long.");
assertStrLenMax(filename, FILENAME_MAX, "Filename too long.");
zip_int64_t idx = zip_name_locate(ASSET.zip, filename, 0);
if(idx < 0) return false;
@@ -38,7 +38,7 @@ errorret_t assetLoad(
void *params,
void *output
) {
assertStrLenMax(filename, ASSET_FILE_PATH_MAX, "Filename too long.");
assertStrLenMax(filename, FILENAME_MAX, "Filename too long.");
assertNotNull(output, "Output pointer cannot be NULL.");
assertNotNull(loader, "Asset file loader cannot be NULL.");
+4 -14
View File
@@ -146,9 +146,7 @@ size_t assetFileLineReaderUnreadBytes(const assetfilelinereader_t *reader) {
return reader->bufferEnd - reader->bufferStart;
}
const uint8_t *assetFileLineReaderUnreadPtr(
const assetfilelinereader_t *reader
) {
const uint8_t *assetFileLineReaderUnreadPtr(const assetfilelinereader_t *reader) {
assertNotNull(reader, "Reader cannot be NULL.");
assertNotNull(reader->readBuffer, "Read buffer cannot be NULL.");
return reader->readBuffer + reader->bufferStart;
@@ -179,16 +177,11 @@ static errorret_t assetFileLineReaderAppend(
static void assetFileLineReaderTerminate(assetfilelinereader_t *reader) {
assertNotNull(reader, "Reader cannot be NULL.");
assertNotNull(reader->outBuffer, "Out buffer cannot be NULL.");
assertTrue(
reader->lineLength < reader->outBufferSize,
"Line length exceeds out buffer."
);
assertTrue(reader->lineLength < reader->outBufferSize, "Line length exceeds out buffer.");
reader->outBuffer[reader->lineLength] = '\0';
}
static ssize_t assetFileLineReaderFindNewline(
const assetfilelinereader_t *reader
) {
static ssize_t assetFileLineReaderFindNewline(const assetfilelinereader_t *reader) {
size_t i;
assertNotNull(reader, "Reader cannot be NULL.");
@@ -291,10 +284,7 @@ errorret_t assetFileLineReaderNext(assetfilelinereader_t *reader) {
errorret_t ret;
/* strip CR in CRLF */
if(
chunkLength > 0 &&
reader->readBuffer[(size_t)newlineIndex - 1] == '\r'
) {
if(chunkLength > 0 && reader->readBuffer[(size_t)newlineIndex - 1] == '\r') {
chunkLength--;
}
-2
View File
@@ -9,8 +9,6 @@
#include "error/error.h"
#include <zip.h>
#define ASSET_FILE_PATH_MAX FILENAME_MAX
typedef struct assetfile_s assetfile_t;
typedef errorret_t (*assetfileloader_t)(assetfile_t *file);
@@ -152,7 +152,7 @@ errorret_t assetLocaleParseHeader(
if(pluralIndex >= localeFile->pluralStateCount - 1) {
errorThrow(
"Too many plural conditions. Expected %d clauses for nplurals=%d.",
"Too many plural conditions. Expected %d conditional clauses for nplurals=%d.",
localeFile->pluralStateCount - 1,
localeFile->pluralStateCount
);
@@ -7,86 +7,76 @@
#include "assetscriptloader.h"
#include "assert/assert.h"
#include <stdlib.h>
#include <zip.h>
errorret_t assetScriptLoader(assetfile_t *file) {
assertNotNull(file, "Asset file cannot be NULL");
assertNull(file->zipFile, "Asset file zip handle must be NULL before open");
assertNull(file->zipFile, "Asset file zip handle must be NULL");
assertNotNull(file->output, "Asset file output cannot be NULL");
assetscript_t *script = (assetscript_t *)file->output;
// Open the asset for buffering
errorChain(assetFileOpen(file));
// Accumulate full source into a dynamically grown buffer.
size_t srcLen = 0;
size_t capacity = ASSET_SCRIPT_CHUNK_SIZE;
char_t *src = (char_t *)malloc(capacity + 1);
if(!src) {
assetFileClose(file);
errorThrow("Out of memory reading script: %s", file->filename);
// Request loading
if(!lua_load(
script->ctx->luaState,
assetScriptReader,
file,
file->filename,
NULL
) == LUA_OK) {
const char_t *strErr = lua_tostring(script->ctx->luaState, -1);
lua_pop(script->ctx->luaState, 1);
errorThrow("Failed to load Lua script: %s", strErr);
}
while(1) {
if(srcLen + ASSET_SCRIPT_CHUNK_SIZE > capacity) {
capacity = srcLen + ASSET_SCRIPT_CHUNK_SIZE;
char_t *tmp = (char_t *)realloc(src, capacity + 1);
if(!tmp) {
free(src);
assetFileClose(file);
errorThrow("Out of memory reading script: %s", file->filename);
}
src = tmp;
}
zip_int64_t n = zip_fread(
file->zipFile, src + srcLen, ASSET_SCRIPT_CHUNK_SIZE
);
if(n <= 0) break;
srcLen += (size_t)n;
}
src[srcLen] = '\0';
errorret_t closeRet = assetFileClose(file);
jerry_value_t result = jerry_eval(
(const jerry_char_t *)src,
srcLen,
JERRY_PARSE_NO_OPTS
);
free(src);
if(jerry_value_is_exception(result)) {
jerry_value_t errVal = jerry_exception_value(result, false);
jerry_value_t errStr = jerry_value_to_string(errVal);
char_t buf[256];
jerry_size_t len = jerry_string_to_buffer(
errStr, JERRY_ENCODING_UTF8, (jerry_char_t *)buf, sizeof(buf) - 1
);
buf[len] = '\0';
jerry_value_free(errStr);
jerry_value_free(errVal);
jerry_value_free(result);
errorThrow("Script error in '%s': %s", file->filename, buf);
// Now loaded, exec
if(lua_pcall(script->ctx->luaState, 0, LUA_MULTRET, 0) != LUA_OK) {
const char_t *strErr = lua_tostring(script->ctx->luaState, -1);
lua_pop(script->ctx->luaState, 1);
errorThrow("Failed to execute Lua script: %s", strErr);
}
if(script->resultOut != NULL) {
*(script->resultOut) = result;
} else {
jerry_value_free(result);
// Close the file
return assetFileClose(file);
}
return closeRet;
}
errorret_t assetScriptLoad(
const char_t *path,
jerry_value_t *resultOut
) {
errorret_t assetScriptLoad(const char_t *path, scriptcontext_t *ctx) {
assertNotNull(path, "Script path cannot be NULL");
assertNotNull(ctx, "Script context cannot be NULL");
assetscript_t scriptData;
scriptData.resultOut = resultOut;
assetscript_t script;
script.ctx = ctx;
return assetLoad(path, assetScriptLoader, NULL, &scriptData);
return assetLoad(
path,
assetScriptLoader,
NULL,
&script
);
}
const char_t * assetScriptReader(lua_State* L, void* data, size_t* size) {
assetfile_t *file = (assetfile_t*)data;
assertNotNull(file, "Script asset file cannot be NULL");
assertNotNull(file->zipFile, "Script asset zip handle cannot be NULL");
assertNotNull(file->output, "Script asset output cannot be NULL");
assetscript_t *script = (assetscript_t *)file->output;
assertNotNull(script, "Script asset output cannot be NULL");
zip_int64_t read = zip_fread(
file->zipFile,
script->buffer,
sizeof(script->buffer)
);
if(read < 0) {
*size = 0;
return NULL;
}
*size = (size_t)read;
return script->buffer;
}
@@ -7,16 +7,21 @@
#pragma once
#include "asset/asset.h"
#include "script/scriptcontext.h"
#define ASSET_SCRIPT_CHUNK_SIZE 1024
#define ASSET_SCRIPT_BUFFER_SIZE 1024
typedef struct {
jerry_value_t *resultOut;
void *nothing;
} assetscriptloaderparams_t;
typedef struct {
scriptcontext_t *ctx;
char_t buffer[ASSET_SCRIPT_BUFFER_SIZE];
} assetscript_t;
/**
* Handler for script assets. Reads the full source, evaluates it with
* JerryScript, and optionally stores the result.
* Handler for script assets.
*
* @param file Asset file to load the script from.
* @return Any error that occurs during loading.
@@ -27,12 +32,17 @@ errorret_t assetScriptLoader(assetfile_t *file);
* Loads a script from the specified path.
*
* @param path Path to the script asset.
* @param resultOut Optional out-parameter for the script return value.
* Caller must call jerry_value_free() if non-NULL.
* Pass NULL to discard the return value.
* @param ctx Script context to load the script into.
* @return Any error that occurs during loading.
*/
errorret_t assetScriptLoad(
const char_t *path,
jerry_value_t *resultOut
);
errorret_t assetScriptLoad(const char_t *path, scriptcontext_t *ctx);
/**
* Reader function for Lua to read script data from the asset.
*
* @param L Lua state.
* @param data Pointer to the scriptcontext_t structure.
* @param size Pointer to store the size of the read data.
* @return Pointer to the read data buffer.
*/
const char_t * assetScriptReader(lua_State* L, void* data, size_t* size);
-192
View File
@@ -1,192 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "console.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "util/string.h"
#include "input/input.h"
#include "log/log.h"
#include "engine/engine.h"
#include "script/scriptmanager.h"
#include "display/shader/shaderunlit.h"
#include "display/text/text.h"
#include "display/spritebatch/spritebatch.h"
console_t CONSOLE;
void consoleInit(void) {
memoryZero(&CONSOLE, sizeof(console_t));
#ifdef DUSK_CONSOLE_POSIX
threadInit(&CONSOLE.thread, consoleInputThread);
threadMutexInit(&CONSOLE.execMutex);
threadMutexInit(&CONSOLE.printMutex);
threadStartRequest(&CONSOLE.thread);
#endif
}
void consolePrint(const char_t *message, ...) {
char_t buffer[CONSOLE_LINE_MAX];
va_list args;
va_start(args, message);
int32_t len = stringFormatVA(buffer, CONSOLE_LINE_MAX, message, args);
va_end(args);
#ifdef DUSK_CONSOLE_POSIX
threadMutexLock(&CONSOLE.printMutex);
#endif
memoryMove(
CONSOLE.line[0],
CONSOLE.line[1],
(CONSOLE_HISTORY_MAX - 1) * CONSOLE_LINE_MAX
);
memoryCopy(CONSOLE.line[CONSOLE_HISTORY_MAX - 1], buffer, len + 1);
#ifdef DUSK_CONSOLE_POSIX
threadMutexUnlock(&CONSOLE.printMutex);
#endif
logDebug("%s\n", buffer);
}
void consoleExec(const char_t *line) {
assertNotNull(line, "line must not be NULL");
#ifdef DUSK_CONSOLE_POSIX
threadMutexLock(&CONSOLE.execMutex);
#endif
assertTrue(
CONSOLE.execBufferCount < CONSOLE_EXEC_BUFFER_MAX,
"Console exec buffer is full"
);
stringCopy(
CONSOLE.execBuffer[CONSOLE.execBufferCount],
line,
CONSOLE_LINE_MAX
);
CONSOLE.execBufferCount++;
#ifdef DUSK_CONSOLE_POSIX
threadMutexUnlock(&CONSOLE.execMutex);
#endif
}
void consoleUpdate(void) {
#ifdef DUSK_TIME_DYNAMIC
if(TIME.dynamicUpdate) return;
#endif
if(inputPressed(INPUT_ACTION_CONSOLE)) {
CONSOLE.visible = !CONSOLE.visible;
}
if(CONSOLE.execBufferCount == 0) return;
#ifdef DUSK_CONSOLE_POSIX
threadMutexLock(&CONSOLE.execMutex);
#endif
char_t execBuffer[CONSOLE_EXEC_BUFFER_MAX][CONSOLE_LINE_MAX];
uint32_t execBufferCount = CONSOLE.execBufferCount;
memoryCopy(execBuffer, CONSOLE.execBuffer, sizeof(execBuffer));
CONSOLE.execBufferCount = 0;
#ifdef DUSK_CONSOLE_POSIX
threadMutexUnlock(&CONSOLE.execMutex);
#endif
for(uint32_t i = 0; i < execBufferCount; i++) {
jerry_value_t result = 0;
errorret_t err = scriptManagerExec(execBuffer[i], &result);
if(err.code != ERROR_OK) {
consolePrint("Error: %s", err.state->message);
errorCatch(err);
} else if(!jerry_value_is_undefined(result)) {
jerry_value_t strVal = jerry_value_to_string(result);
char_t buf[CONSOLE_LINE_MAX];
jerry_size_t len = jerry_string_to_buffer(
strVal, JERRY_ENCODING_UTF8, (jerry_char_t*)buf, sizeof(buf) - 1
);
buf[len] = '\0';
jerry_value_free(strVal);
consolePrint("%s", buf);
}
if(result != 0) jerry_value_free(result);
}
}
errorret_t consoleDraw(void) {
if(!CONSOLE.visible) errorOk();
for(uint32_t i = 0; i < CONSOLE_HISTORY_MAX; i++) {
errorChain(textDraw(
0, FONT_TILESET_DEFAULT.tileHeight * i,
CONSOLE.line[i],
COLOR_WHITE,
&FONT_TILESET_DEFAULT,
&FONT_TEXTURE_DEFAULT
));
}
return spriteBatchFlush();
}
void consoleDispose(void) {
#ifdef DUSK_CONSOLE_POSIX
threadStop(&CONSOLE.thread);
threadMutexDispose(&CONSOLE.execMutex);
threadMutexDispose(&CONSOLE.printMutex);
#endif
}
#ifdef DUSK_CONSOLE_POSIX
void consoleInputThread(thread_t *thread) {
assertNotNull(thread, "Thread cannot be NULL.");
char_t line[CONSOLE_LINE_MAX];
struct pollfd pfd = {
.fd = STDIN_FILENO,
.events = POLLIN
};
while(!threadShouldStop(thread) && ENGINE.running) {
int32_t rc = poll(&pfd, 1, CONSOLE_POSIX_POLL_RATE);
if(rc == 0) continue;
if(rc < 0) {
if(errno == EINTR) continue;
assertUnreachable("poll() failed with unexpected error.");
}
if(pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) break;
if(!(pfd.revents & POLLIN)) {
pfd.revents = 0;
continue;
}
if(!fgets(line, CONSOLE_LINE_MAX, stdin)) {
if(feof(stdin)) break;
clearerr(stdin);
continue;
}
size_t len = strlen(line);
while(len && (line[len - 1] == '\n' || line[len - 1] == '\r')) {
line[--len] = '\0';
}
if(len > 0) consoleExec(line);
pfd.revents = 0;
}
}
#endif
-79
View File
@@ -1,79 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "consoledefs.h"
#include "error/error.h"
#include "dusk.h"
#ifdef DUSK_CONSOLE_POSIX
#include "thread/thread.h"
#include <poll.h>
#include <unistd.h>
#define CONSOLE_POSIX_POLL_RATE 75
#endif
typedef struct {
char_t line[CONSOLE_HISTORY_MAX][CONSOLE_LINE_MAX];
bool_t visible;
char_t execBuffer[CONSOLE_EXEC_BUFFER_MAX][CONSOLE_LINE_MAX];
uint32_t execBufferCount;
#ifdef DUSK_CONSOLE_POSIX
thread_t thread;
threadmutex_t execMutex;
threadmutex_t printMutex;
#endif
} console_t;
extern console_t CONSOLE;
/**
* Initializes the console.
*/
void consoleInit(void);
/**
* Prints a message to the console history.
*
* @param message The message to print (printf-style).
*/
void consolePrint(const char_t *message, ...);
/**
* Queues a JS string for execution on the main thread. Thread-safe.
*
* @param line The JS source line to execute.
*/
void consoleExec(const char_t *line);
/**
* Processes pending queued script lines. Call once per frame from main thread.
*/
void consoleUpdate(void);
/**
* Renders the console history to the screen (UI space).
*
* @return The error return value.
*/
errorret_t consoleDraw(void);
/**
* Disposes of the console.
*/
void consoleDispose(void);
#ifdef DUSK_CONSOLE_POSIX
/**
* Input thread handler for POSIX stdin.
*
* @param thread The thread that is running.
*/
void consoleInputThread(thread_t *thread);
#endif
-12
View File
@@ -1,12 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#define CONSOLE_LINE_MAX 512
#define CONSOLE_HISTORY_MAX 16
#define CONSOLE_EXEC_BUFFER_MAX 32
-187
View File
@@ -1,187 +0,0 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "cutscene.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "script/module/modulebase.h"
#include "script/module/cutscene/modulecutscene.h"
#include "console/console.h"
#include "time/time.h"
cutscene_t CUTSCENE;
static errorret_t cutsceneCallMethod(const char_t *method) {
if(CUTSCENE.scriptRef == CUTSCENE_SCRIPT_REF_NONE) errorOk();
jerry_value_t key = jerry_string_sz(method);
jerry_value_t fn = jerry_object_get(CUTSCENE.scriptRef, key);
jerry_value_free(key);
if(!jerry_value_is_function(fn)) {
jerry_value_free(fn);
errorOk();
}
jerry_value_t result = jerry_call(fn, CUTSCENE.scriptRef, NULL, 0);
jerry_value_free(fn);
if(jerry_value_is_exception(result)) {
cutsceneevent_t *event = &CUTSCENE.events[CUTSCENE.eventCurrent];
char_t errMsg[512];
moduleBaseExceptionMessage(result, errMsg, sizeof(errMsg));
jerry_value_free(result);
errorThrow(
"Cutscene event '%s' %s failed: %s",
event->script.script, method, errMsg
);
}
jerry_value_free(result);
errorOk();
}
static errorret_t cutsceneEventStart(void) {
cutsceneevent_t *event = &CUTSCENE.events[CUTSCENE.eventCurrent];
if(event->type == CUTSCENE_EVENT_TYPE_NATIVE) {
if(event->native.onStart) errorChain(event->native.onStart());
} else if(event->type == CUTSCENE_EVENT_TYPE_SCRIPT) {
jerry_value_t eventClass = CUTSCENE_SCRIPT_REF_NONE;
errorChain(scriptManagerExecFile(event->script.script, &eventClass));
if(!jerry_value_is_function(eventClass)) {
if(eventClass != CUTSCENE_SCRIPT_REF_NONE) jerry_value_free(eventClass);
errorThrow(
"Cutscene event '%s' must export a constructor function",
event->script.script
);
}
jerry_value_t eventObj = jerry_construct(eventClass, NULL, 0);
jerry_value_free(eventClass);
if(jerry_value_is_exception(eventObj)) {
char_t errMsg[512];
moduleBaseExceptionMessage(eventObj, errMsg, sizeof(errMsg));
jerry_value_free(eventObj);
errorThrow(
"Cutscene event '%s' constructor threw: %s",
event->script.script, errMsg
);
}
CUTSCENE.scriptRef = eventObj;
errorChain(cutsceneCallMethod("onStart"));
}
errorOk();
}
static errorret_t cutsceneEventEnd(void) {
cutsceneevent_t *event = &CUTSCENE.events[CUTSCENE.eventCurrent];
if(event->type == CUTSCENE_EVENT_TYPE_NATIVE) {
if(event->native.onEnd) errorChain(event->native.onEnd());
} else if(event->type == CUTSCENE_EVENT_TYPE_SCRIPT) {
errorret_t err = cutsceneCallMethod("onEnd");
if(CUTSCENE.scriptRef != CUTSCENE_SCRIPT_REF_NONE) {
jerry_value_free(CUTSCENE.scriptRef);
CUTSCENE.scriptRef = CUTSCENE_SCRIPT_REF_NONE;
}
errorChain(err);
}
errorOk();
}
errorret_t cutsceneInit(void) {
memoryZero(&CUTSCENE, sizeof(cutscene_t));
CUTSCENE.scriptRef = CUTSCENE_SCRIPT_REF_NONE;
CUTSCENE.activeRef = CUTSCENE_SCRIPT_REF_NONE;
consolePrint("Cutscene init");
errorOk();
}
errorret_t cutsceneUpdate(void) {
#ifdef DUSK_TIME_DYNAMIC
if(TIME.dynamicUpdate) {
errorOk();
}
#endif
errorChain(moduleCutsceneUpdate());
if(!CUTSCENE.active) errorOk();
cutsceneevent_t *event = &CUTSCENE.events[CUTSCENE.eventCurrent];
if(event->type == CUTSCENE_EVENT_TYPE_NATIVE) {
if(event->native.onUpdate) errorChain(event->native.onUpdate());
} else if(event->type == CUTSCENE_EVENT_TYPE_SCRIPT) {
errorChain(cutsceneCallMethod("onUpdate"));
}
errorOk();
}
errorret_t cutscenePlay(
const cutsceneevent_t *events,
const uint8_t eventCount
) {
assertNotNull(events, "Events cannot be null");
assertTrue(eventCount > 0, "Event count must be greater than zero");
assertTrue(
eventCount <= CUTSCENE_EVENT_COUNT_MAX,
"Event count exceeds CUTSCENE_EVENT_COUNT_MAX"
);
if(CUTSCENE.active) {
errorChain(cutsceneStop());
}
memoryCopy(CUTSCENE.events, events, sizeof(cutsceneevent_t) * eventCount);
CUTSCENE.eventCount = eventCount;
CUTSCENE.eventCurrent = 0;
CUTSCENE.active = true;
errorChain(cutsceneEventStart());
consolePrint("Cutscene play");
errorOk();
}
errorret_t cutsceneAdvance(void) {
if(!CUTSCENE.active) errorOk();
errorChain(cutsceneEventEnd());
CUTSCENE.eventCurrent++;
if(CUTSCENE.eventCurrent >= CUTSCENE.eventCount) {
CUTSCENE.active = false;
errorOk();
}
errorChain(cutsceneEventStart());
consolePrint("Cutscene advance");
errorOk();
}
errorret_t cutsceneStop(void) {
if(!CUTSCENE.active) errorOk();
errorChain(cutsceneEventEnd());
CUTSCENE.active = false;
consolePrint("Cutscene stop");
errorOk();
}
errorret_t cutsceneDispose(void) {
errorChain(cutsceneStop());
errorOk();
}
bool_t cutsceneIsActive(void) {
return CUTSCENE.active;
}
-105
View File
@@ -1,105 +0,0 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "script/scriptmanager.h"
#include "error/error.h"
#define CUTSCENE_EVENT_COUNT_MAX 16
#define CUTSCENE_SCRIPT_REF_NONE ((jerry_value_t)0)
typedef enum {
CUTSCENE_EVENT_TYPE_NULL,
CUTSCENE_EVENT_TYPE_NATIVE,
CUTSCENE_EVENT_TYPE_SCRIPT,
CUTSCENE_EVENT_TYPE_COUNT
} cutsceneeventtype_t;
typedef struct {
cutsceneeventtype_t type;
union {
struct {
errorret_t (*onStart)(void);
errorret_t (*onEnd)(void);
errorret_t (*onUpdate)(void);
} native;
struct {
const char_t *script;
} script;
};
} cutsceneevent_t;
typedef struct {
cutsceneevent_t events[CUTSCENE_EVENT_COUNT_MAX];
uint8_t eventCount;
uint8_t eventCurrent;
bool_t active;
jerry_value_t scriptRef;
jerry_value_t activeRef;
} cutscene_t;
extern cutscene_t CUTSCENE;
/**
* Initializes the cutscene manager.
*
* @return Any error state that happened.
*/
errorret_t cutsceneInit(void);
/**
* Ticks the active cutscene event, calling its onUpdate callback.
* Does nothing when no cutscene is playing.
*
* @return Any error state that happened.
*/
errorret_t cutsceneUpdate(void);
/**
* Copies the given event array and begins playing from the first
* event. If a cutscene is already playing it is stopped first.
*
* @param events Array of events to copy.
* @param eventCount Number of events. Must be > 0 and
* <= CUTSCENE_EVENT_COUNT_MAX.
* @return Any error state that happened.
*/
errorret_t cutscenePlay(
const cutsceneevent_t *events,
const uint8_t eventCount
);
/**
* Ends the current event and starts the next one.
* Marks the cutscene as inactive after the last event ends.
* Does nothing when no cutscene is playing.
*
* @return Any error state that happened.
*/
errorret_t cutsceneAdvance(void);
/**
* Ends the current event and stops the cutscene immediately.
* Does nothing when no cutscene is playing.
*
* @return Any error state that happened.
*/
errorret_t cutsceneStop(void);
/**
* Disposes of the cutscene manager, stopping any active cutscene.
*
* @return Any error state that happened.
*/
errorret_t cutsceneDispose(void);
/**
* Returns whether a cutscene is currently playing.
*
* @return true if a cutscene is active.
*/
bool_t cutsceneIsActive(void);
+2 -8
View File
@@ -25,6 +25,8 @@
#include "display/shader/shaderunlit.h"
#include "time/time.h"
#include "script/module/display/moduleshader.h"
display_t DISPLAY = { 0 };
errorret_t displayInit(void) {
@@ -33,7 +35,6 @@ errorret_t displayInit(void) {
#ifdef displayPlatformInit
errorChain(displayPlatformInit());
#endif
errorChain(displaySetState((displaystate_t){ .flags = 0 }));
errorChain(textureInit(
&TEXTURE_WHITE, 4, 4,
TEXTURE_FORMAT_RGBA, (texturedata_t){ .rgbaColors = TEXTURE_WHITE_PIXELS }
@@ -111,13 +112,6 @@ errorret_t displayUpdate(void) {
errorOk();
}
errorret_t displaySetState(displaystate_t state) {
#ifdef displayPlatformSetState
errorChain(displayPlatformSetState(state));
#endif
errorOk();
}
errorret_t displayDispose(void) {
errorChain(shaderDispose(&SHADER_UNLIT));
errorChain(spriteBatchDispose());
-11
View File
@@ -40,26 +40,15 @@ extern display_t DISPLAY;
/**
* Initializes the display system.
* @return An errorret_t indicating success or failure.
*/
errorret_t displayInit(void);
/**
* Tells the display system to actually draw the frame.
* @return An errorret_t indicating success or failure.
*/
errorret_t displayUpdate(void);
/**
* Sets the display state.
*
* @param state The state to set.
* @return An errorret_t indicating success or failure.
*/
errorret_t displaySetState(displaystate_t state);
/**
* Disposes of the display system.
* @return An errorret_t indicating success or failure.
*/
errorret_t displayDispose(void);
-17
View File
@@ -1,17 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#define DISPLAY_STATE_FLAG_CULL (1 << 0)
#define DISPLAY_STATE_FLAG_DEPTH_TEST (1 << 1)
#define DISPLAY_STATE_FLAG_BLEND (1 << 2)
typedef struct {
uint8_t flags;
} displaystate_t;
+4 -12
View File
@@ -119,12 +119,8 @@ void capsuleBuffer(
{
const float_t yTop = cy + halfHeight;
const float_t yBot = cy - halfHeight;
const float_t vTop = (
1.0f - (float_t)capRings / (float_t)(2 * capRings + 1)
);
const float_t vBot = (
1.0f - (float_t)(capRings + 1) / (float_t)(2 * capRings + 1)
);
const float_t vTop = 1.0f - (float_t)capRings / (float_t)(2 * capRings + 1);
const float_t vBot = 1.0f - (float_t)(capRings + 1) / (float_t)(2 * capRings + 1);
for(int32_t j = 0; j < sectors; j++) {
const float_t t1 = (float_t)j * sectorStep;
@@ -156,12 +152,8 @@ void capsuleBuffer(
const float_t lxz1 = radius * cosf(phi1);
const float_t lxz2 = radius * cosf(phi2);
const float_t v1 = (
1.0f - (float_t)(capRings + 1 + i) / (float_t)(2 * capRings + 1)
);
const float_t v2 = (
1.0f - (float_t)(capRings + 1 + i + 1) / (float_t)(2 * capRings + 1)
);
const float_t v1 = 1.0f - (float_t)(capRings + 1 + i) / (float_t)(2 * capRings + 1);
const float_t v2 = 1.0f - (float_t)(capRings + 1 + i + 1) / (float_t)(2 * capRings + 1);
for(int32_t j = 0; j < sectors; j++) {
const float_t t1 = (float_t)j * sectorStep;
+1 -1
View File
@@ -28,7 +28,7 @@ errorret_t cubeInit();
* Buffers a 3D axis-aligned cube into the provided vertex array.
* Writes CUBE_VERTEX_COUNT vertices (6 faces x 6 vertices, CCW winding).
*
* @param vertices The vertex array to buffer into.
* @param vertices The vertex array to buffer into (must hold CUBE_VERTEX_COUNT).
* @param min The minimum XYZ corner of the cube.
* @param max The maximum XYZ corner of the cube.
* @param color The color applied to all vertices.
+2 -6
View File
@@ -34,14 +34,10 @@ errorret_t meshFlush(
#ifdef meshFlushPlatform
assertNotNull(mesh, "Mesh cannot be NULL");
assertTrue(vertexOffset >= 0, "Vertex offset must be non-negative.");
assertTrue(
vertexCount == -1 || vertexCount > 0, "Vertex count incorrect."
);
assertTrue(vertexCount == -1 || vertexCount > 0, "Vertex count incorrect.");
int32_t vertCount = meshGetVertexCount(mesh);
assertTrue(
vertexOffset < (vertCount - 1), "Need at least one vert to draw"
);
assertTrue(vertexOffset < (vertCount - 1), "Need at least one vert to draw");
int32_t drawCount = vertexCount;
if(vertexCount == -1) {
+7 -1
View File
@@ -9,6 +9,12 @@
#include "display/mesh/mesh.h"
#include "display/color.h"
/**
* Vertex layout:
* 2 triangular end-caps (3 verts each) = 6
* 3 rectangular side faces (6 verts each) = 18
* Total = 24
*/
#define TRIPRISM_VERTEX_COUNT 24
#define TRIPRISM_PRIMITIVE_TYPE MESH_PRIMITIVE_TYPE_TRIANGLES
@@ -29,7 +35,7 @@ errorret_t triPrismInit();
* the prism is extruded along the Z axis between minZ and maxZ.
* Writes TRIPRISM_VERTEX_COUNT (24) vertices (CCW winding).
*
* @param vertices Vertex array to write into.
* @param vertices Vertex array to write into (must hold TRIPRISM_VERTEX_COUNT).
* @param x0,y0 First triangle vertex (XY).
* @param x1,y1 Second triangle vertex (XY).
* @param x2,y2 Third triangle vertex (XY).
+2 -2
View File
@@ -8,9 +8,9 @@
#pragma once
#include "display/mesh/quad.h"
#define SPRITEBATCH_SPRITES_MAX 256
#define SPRITEBATCH_SPRITES_MAX 32
#define SPRITEBATCH_VERTEX_COUNT (SPRITEBATCH_SPRITES_MAX * QUAD_VERTEX_COUNT)
#define SPRITEBATCH_FLUSH_COUNT 8
#define SPRITEBATCH_FLUSH_COUNT 4
#define SPRITEBATCH_SPRITES_MAX_PER_FLUSH (\
SPRITEBATCH_SPRITES_MAX / SPRITEBATCH_FLUSH_COUNT \
)
+5 -5
View File
@@ -13,19 +13,19 @@
#include "asset/loader/display/assettilesetloader.h"
#include "display/shader/shaderunlit.h"
texture_t FONT_TEXTURE_DEFAULT;
tileset_t FONT_TILESET_DEFAULT;
texture_t DEFAULT_FONT_TEXTURE;
tileset_t DEFAULT_FONT_TILESET;
errorret_t textInit(void) {
errorChain(assetTextureLoad(
"ui/minogram.png", &FONT_TEXTURE_DEFAULT, TEXTURE_FORMAT_RGBA
"ui/minogram.png", &DEFAULT_FONT_TEXTURE, TEXTURE_FORMAT_RGBA
));
errorChain(assetTilesetLoad("ui/minogram.dtf", &FONT_TILESET_DEFAULT));
errorChain(assetTilesetLoad("ui/minogram.dtf", &DEFAULT_FONT_TILESET));
errorOk();
}
errorret_t textDispose(void) {
errorChain(textureDispose(&FONT_TEXTURE_DEFAULT));
errorChain(textureDispose(&DEFAULT_FONT_TEXTURE));
errorOk();
}
+2 -2
View File
@@ -12,8 +12,8 @@
#define TEXT_CHAR_START '!'
extern texture_t FONT_TEXTURE_DEFAULT;
extern tileset_t FONT_TILESET_DEFAULT;
extern texture_t DEFAULT_FONT_TEXTURE;
extern tileset_t DEFAULT_FONT_TILESET;
/**
* Initializes the text system.
-1
View File
@@ -21,7 +21,6 @@
#include <cglm/cglm.h>
#include <cglm/types.h>
#include <cglm/vec2.h>
#include <jerryscript.h>
#include "duskplatform.h"
+172 -34
View File
@@ -12,31 +12,112 @@
#include "locale/localemanager.h"
#include "display/display.h"
#include "scene/scene.h"
#include "cutscene/cutscene.h"
#include "asset/asset.h"
#include "ui/ui.h"
#include "ui/uitextbox.h"
#include "script/scriptmanager.h"
#include "assert/assert.h"
#include "entity/entitymanager.h"
#include "entity/component/physics/entityphysics.h"
#include "game/game.h"
#include "physics/physicsmanager.h"
#include "network/network.h"
#include "network/networkinfo.h"
#include "network/networksocketclient.h"
#include "system/system.h"
#include "console/console.h"
#include "item/backpack.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;
}
#include "display/mesh/cube.h"
#include "display/mesh/plane.h"
engine_t ENGINE;
entityid_t phBoxEnt;
componentid_t phBoxPhys;
networksocketclient_t sockClient;
float_t onlineSwapTime = FLT_MAX;
void goOnline();
void goOffline();
void onSocketConnected(void *user) {
sceneLog("Socket connected.\n");
}
void onSocketError(errorret_t error, void *user) {
sceneLog("Socket error: %s\n", error.state->message);
errorCatch(errorPrint(error));
}
void onSocketDisconnected(void *user) {
sceneLog("Socket disconnected.\n");
}
void onNetworkConnected(void *user) {
onlineSwapTime = TIME.time + 1.5f;
networkinfo_t info = networkGetInfo();
if(info.type == NETWORK_TYPE_IPV4) {
sceneLog(
"Connected to network with IPv4 address: " NETWORK_INFO_FORMAT_IPV4 "\n",
info.ipv4.ip[0], info.ipv4.ip[1], info.ipv4.ip[2], info.ipv4.ip[3]
);
#ifdef DUSK_NETWORK_IPV6
} else if(info.type == NETWORK_TYPE_IPV6) {
sceneLog(
"Connected to network with IPv6 address: " NETWORK_INFO_FORMAT_IPV6 "\n",
info.ipv6.ip[0], info.ipv6.ip[1], info.ipv6.ip[2], info.ipv6.ip[3],
info.ipv6.ip[4], info.ipv6.ip[5], info.ipv6.ip[6], info.ipv6.ip[7],
info.ipv6.ip[8], info.ipv6.ip[9], info.ipv6.ip[10], info.ipv6.ip[11],
info.ipv6.ip[12], info.ipv6.ip[13], info.ipv6.ip[14], info.ipv6.ip[15]
);
#endif
}
sceneLog("Network connected, opening socket: %.2f1.\n", onlineSwapTime);
networkSocketClientInit(
&sockClient,
"google.com",
443,
NULL,
onSocketConnected,
onSocketError,
onSocketDisconnected
);
onlineSwapTime = FLT_MAX;
// sceneLog("Network connected, I will disconnect at: %.2f1.\n", onlineSwapTime);
}
void onNetworkFailed(errorret_t error, void *user) {
onlineSwapTime = TIME.time + 3.0f;
sceneLog("Failed to connect to network, will try again at %.2f1.\n", onlineSwapTime);
}
void onNetworkDisconnected(errorret_t error, void *user) {
onlineSwapTime = TIME.time + 3.0f;
sceneLog("Network disconnected, will go online at %.2f1.\n", onlineSwapTime);
errorCatch(errorPrint(error));
}
void onNetworkDisconnectFinished(void *user) {
onlineSwapTime = TIME.time + 3.0f;
sceneLog("Finished disconnecting from network, will go online at %.2f1.\n", onlineSwapTime);
}
void goOnline() {
sceneLog("Going online...\n");
networkRequestConnection(
onNetworkConnected,
onNetworkFailed,
onNetworkDisconnected,
NULL
);
}
void goOffline() {
sceneLog("Going offline...\n");
networkRequestDisconnection(onNetworkDisconnectFinished, NULL);
}
errorret_t engineInit(const int32_t argc, const char_t **argv) {
memoryZero(&ENGINE, sizeof(engine_t));
@@ -47,46 +128,106 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
// Init systems. Order is important.
errorChain(systemInit());
timeInit();
consoleInit();
errorChain(inputInit());
errorChain(assetInit());
errorChain(localeManagerInit());
errorChain(scriptManagerInit());
errorChain(displayInit());
errorChain(uiInit());
errorChain(uiTextboxInit());
errorChain(cutsceneInit());
errorChain(sceneInit());
entityManagerInit();
backpackInit();
physicsManagerInit();
errorChain(networkInit());
// errorChain(networkInit());
errorChain(gameInit());
onlineSwapTime = TIME.time + 1.0f;
sceneLog("Init done, going to queue online at %.2f1.\n", onlineSwapTime);
// Camera
entityid_t cam = entityManagerAdd();
componentid_t camPos = entityAddComponent(cam, COMPONENT_TYPE_POSITION);
float_t distance = 6.0f;
entityPositionLookAt(
cam, camPos,
(vec3){ 0.0f, 1.0f, 0.0f },
(vec3){ 0.0f, 1.0f, 0.0f },
(vec3){ distance, distance, distance }
);
componentid_t camCam = entityAddComponent(cam, COMPONENT_TYPE_CAMERA);
entityCameraSetZFar(cam, camCam, 100.0f);
// Floor
entityid_t floorEnt = entityManagerAdd();
componentid_t floorPos = entityAddComponent(floorEnt, COMPONENT_TYPE_POSITION);
componentid_t floorMesh = entityAddComponent(floorEnt, COMPONENT_TYPE_MESH);
componentid_t floorMat = entityAddComponent(floorEnt, COMPONENT_TYPE_MATERIAL);
componentid_t floorPhys = entityAddComponent(floorEnt, COMPONENT_TYPE_PHYSICS);
entityPositionSetPosition(floorEnt, floorPos, (vec3){ -5.0f, 0.0f, -5.0f });
entityPositionSetScale(floorEnt, floorPos, (vec3){ 10.0f, 1.0f, 10.0f });
entityMeshSetMesh(floorEnt, floorMesh, &PLANE_MESH_SIMPLE);
entityMaterialGetShaderMaterial(floorEnt, floorMat)->unlit.color = COLOR_GREEN;
entityphysics_t *floorPhysData = entityPhysicsGet(floorEnt, floorPhys);
floorPhysData->type = PHYSICS_BODY_STATIC;
floorPhysData->shape.type = PHYSICS_SHAPE_PLANE;
floorPhysData->shape.data.plane.normal[0] = 0.0f;
floorPhysData->shape.data.plane.normal[1] = 1.0f;
floorPhysData->shape.data.plane.normal[2] = 0.0f;
floorPhysData->shape.data.plane.distance = 0.0f;
// Box
phBoxEnt = entityManagerAdd();
componentid_t boxPos = entityAddComponent(phBoxEnt, COMPONENT_TYPE_POSITION);
componentid_t boxMesh = entityAddComponent(phBoxEnt, COMPONENT_TYPE_MESH);
componentid_t boxMat = entityAddComponent(phBoxEnt, COMPONENT_TYPE_MATERIAL);
phBoxPhys = entityAddComponent(phBoxEnt, COMPONENT_TYPE_PHYSICS);
entityMeshSetMesh(phBoxEnt, boxMesh, &CUBE_MESH_SIMPLE);
entityMaterialGetShaderMaterial(phBoxEnt, boxMat)->unlit.color = COLOR_RED;
entityPositionSetPosition(phBoxEnt, boxPos, (vec3){ 0.0f, 4.0f, 0.0f });
/* Run the init script. */
consolePrint("Engine initialized");
errorChain(scriptManagerExecFile("init.js", NULL));
scriptcontext_t ctx;
errorChain(scriptContextInit(&ctx));
errorChain(scriptContextExecFile(&ctx, "init.lua"));
scriptContextDispose(&ctx);
errorOk();
}
errorret_t engineUpdate(void) {
// Order here is important.
errorChain(networkUpdate());
// errorChain(networkUpdate());
timeUpdate();
inputUpdate();
consoleUpdate();
uiUpdate();
errorChain(uiTextboxUpdate());
errorChain(sceneUpdate());
/* Reset the box to its start position on demand. */
if(inputIsDown(INPUT_ACTION_ACCEPT)) {
componentid_t posComp = entityGetComponent(phBoxEnt, COMPONENT_TYPE_POSITION);
entityPositionSetPosition(phBoxEnt, posComp, (vec3){ 0.0f, 4.0f, 0.0f });
entityPhysicsSetVelocity(phBoxEnt, phBoxPhys, (vec3){ 0.0f, 0.0f, 0.0f });
}
/* Step physics: positions are updated directly on POSITION components. */
physicsManagerUpdate();
errorChain(gameUpdate());
errorChain(displayUpdate());
if(inputPressed(INPUT_ACTION_RAGEQUIT)) ENGINE.running = false;
// Scene update occurs last because only after rendering would we want to do
// scene switching, refer to sceneSet() for information.
errorChain(cutsceneUpdate());
errorChain(sceneUpdate());
if(TIME.time >= onlineSwapTime) {
onlineSwapTime = FLT_MAX;
if(NETWORK.state == NETWORK_STATE_CONNECTED) {
goOffline();
} else {
goOnline();
}
}
errorOk();
}
@@ -96,16 +237,13 @@ void engineExit(void) {
}
errorret_t engineDispose(void) {
uiTextboxDispose();
cutsceneDispose();
// errorChain(networkDispose());
sceneDispose();
errorChain(networkDispose());
errorChain(gameDispose());
entityManagerDispose();
localeManagerDispose();
uiDispose();
consoleDispose();
errorChain(displayDispose());
errorChain(assetDispose());
errorOk();
}
-1
View File
@@ -36,4 +36,3 @@ errorret_t engineUpdate(void);
* Shuts down the engine.
*/
errorret_t engineDispose(void);
+2 -8
View File
@@ -12,14 +12,8 @@
componentdefinition_t COMPONENT_DEFINITIONS[] = {
[COMPONENT_TYPE_NULL] = { 0 },
#define X(enm, type, field, iMethod, dMethod) \
[COMPONENT_TYPE_##enm] = { \
.enumName = #enm, \
.name = #field, \
.init = iMethod, \
.dispose = dMethod \
},
#define X(enumName, type, field, iMethod, dMethod) \
[COMPONENT_TYPE_##enumName] = { .init = iMethod, .dispose = dMethod },
#include "componentlist.h"
#undef X
-2
View File
@@ -20,8 +20,6 @@ typedef union {
} componentdata_t;
typedef struct {
const char_t *enumName;
const char_t *name;
void (*init)(const entityid_t, const componentid_t);
void (*dispose)(const entityid_t, const componentid_t);
} componentdefinition_t;
-1
View File
@@ -5,4 +5,3 @@
add_subdirectory(display)
add_subdirectory(physics)
add_subdirectory(script)
@@ -6,8 +6,6 @@
*/
#include "entity/entitymanager.h"
#include "entity/entity.h"
#include "entity/component/display/entityposition.h"
#include "display/framebuffer/framebuffer.h"
#include "display/screen/screen.h"
@@ -21,6 +19,42 @@ void entityCameraInit(const entityid_t ent, const componentid_t comp) {
cam->perspective.fov = glm_rad(45.0f);
}
float_t entityCameraGetZNear(const entityid_t ent, const componentid_t comp) {
entitycamera_t *cam = (entitycamera_t *)componentGetData(
ent, comp, COMPONENT_TYPE_CAMERA
);
return cam->nearClip;
}
void entityCameraSetZNear(
const entityid_t ent,
const componentid_t comp,
const float_t zNear
) {
entitycamera_t *cam = (entitycamera_t *)componentGetData(
ent, comp, COMPONENT_TYPE_CAMERA
);
cam->nearClip = zNear;
}
float_t entityCameraGetZFar(const entityid_t ent, const componentid_t comp) {
entitycamera_t *cam = (entitycamera_t *)componentGetData(
ent, comp, COMPONENT_TYPE_CAMERA
);
return cam->farClip;
}
void entityCameraSetZFar(
const entityid_t ent,
const componentid_t comp,
const float_t zFar
) {
entitycamera_t *cam = (entitycamera_t *)componentGetData(
ent, comp, COMPONENT_TYPE_CAMERA
);
cam->farClip = zFar;
}
void entityCameraGetProjection(
const entityid_t ent,
const componentid_t comp,
@@ -59,38 +93,3 @@ void entityCameraGetProjection(
);
}
}
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,25 +55,45 @@ void entityCameraGetProjection(
);
/**
* Returns the entity ID of the first active camera, or ENTITY_COUNT_MAX if
* none are active.
* Gets the near clip distance of a camera.
*
* @param ent The entity ID.
* @param comp The component ID.
* @return The near clip distance.
*/
entityid_t entityCameraGetCurrent(void);
float_t entityCameraGetZNear(const entityid_t ent, const componentid_t comp);
/**
* Gets the camera's horizontal forward direction (XZ plane) from its position
* component. Automatically finds the position component on the entity.
* Sets the near clip distance of a camera.
*
* @param entityId The camera entity ID.
* @param out Output vec2: {forwardX, forwardZ} normalized.
* @param ent The entity ID.
* @param comp The component ID.
* @param zNear The near clip distance.
*/
void entityCameraGetForward(const entityid_t entityId, vec2 out);
void entityCameraSetZNear(
const entityid_t ent,
const componentid_t comp,
const float_t zNear
);
/**
* Gets the camera's horizontal right direction (XZ plane) from its position
* component. Automatically finds the position component on the entity.
* Gets the far clip distance of a camera.
*
* @param entityId The camera entity ID.
* @param out Output vec2: {rightX, rightZ} normalized.
* @param ent The entity ID.
* @param comp The component ID.
* @return The far clip distance.
*/
void entityCameraGetRight(const entityid_t entityId, vec2 out);
float_t entityCameraGetZFar(const entityid_t ent, const componentid_t comp);
/**
* Sets the far clip distance of a camera.
*
* @param ent The entity ID.
* @param comp The component ID.
* @param zFar The far clip distance.
*/
void entityCameraSetZFar(
const entityid_t ent,
const componentid_t comp,
const float_t zFar
);
@@ -49,14 +49,3 @@ void entityMaterialSetShader(
);
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;
}
@@ -61,16 +61,3 @@ void entityMaterialSetShader(
const componentid_t componentId,
shader_t *shader
);
/**
* Sets the unlit color for the given entity and component.
*
* @param entityId The entity ID.
* @param componentId The component ID.
* @param color The color to set.
*/
void entityMaterialSetColor(
const entityid_t entityId,
const componentid_t componentId,
const color_t color
);
@@ -5,7 +5,6 @@
* https://opensource.org/licenses/MIT
*/
#include "entitymesh.h"
#include "entity/entitymanager.h"
void entityMeshInit(
@@ -38,9 +37,3 @@ void entityMeshSetMesh(
);
comp->mesh = mesh;
}
void entityMeshDispose(
const entityid_t entityId,
const componentid_t componentId
) {
}
+1 -12
View File
@@ -41,21 +41,10 @@ mesh_t * entityMeshGetMesh(
*
* @param entityId The entity ID.
* @param componentId The component ID.
* @param mesh A pointer to the mesh to associate.
* @param mesh A pointer to the mesh to associate with the entity mesh component.
*/
void entityMeshSetMesh(
const entityid_t entityId,
const componentid_t componentId,
mesh_t *mesh
);
/**
* Disposes the entity mesh component.
*
* @param entityId The entity ID.
* @param componentId The component ID.
*/
void entityMeshDispose(
const entityid_t entityId,
const componentid_t componentId
);
@@ -100,25 +100,6 @@ bool_t entityPhysicsIsOnGround(
return phys->onGround;
}
void entityPhysicsSetBodyType(
const entityid_t entityId,
const componentid_t componentId,
const physicsbodytype_t type
) {
entityphysics_t *phys = entityPhysicsGet(entityId, componentId);
assertNotNull(phys, "Failed to get physics component data");
phys->type = type;
}
physicsbodytype_t entityPhysicsGetBodyType(
const entityid_t entityId,
const componentid_t componentId
) {
entityphysics_t *phys = entityPhysicsGet(entityId, componentId);
assertNotNull(phys, "Failed to get physics component data");
return phys->type;
}
void entityPhysicsDispose(
const entityid_t entityId,
const componentid_t componentId
@@ -122,31 +122,6 @@ bool_t entityPhysicsIsOnGround(
const componentid_t componentId
);
/**
* Sets the body type of the entity's physics body.
*
* @param entityId The entity ID.
* @param componentId The component ID.
* @param type The body type to set.
*/
void entityPhysicsSetBodyType(
const entityid_t entityId,
const componentid_t componentId,
const physicsbodytype_t type
);
/**
* Gets the body type of the entity's physics body.
*
* @param entityId The entity ID.
* @param componentId The component ID.
* @return The body type of the physics body.
*/
physicsbodytype_t entityPhysicsGetBodyType(
const entityid_t entityId,
const componentid_t componentId
);
/**
* Releases the body slot back to PHYSICS_WORLD. Called automatically when
* the component is disposed via the component system.
+1 -7
View File
@@ -11,14 +11,8 @@
#include "entity/component/display/entitymaterial.h"
#include "entity/component/physics/entityphysics.h"
// Name (Uppercase)
// Structure
// Field name (lowercase)
// Init function (optional)
// Dispose function (optional)
X(POSITION, entityposition_t, position, entityPositionInit, NULL)
X(CAMERA, entitycamera_t, camera, entityCameraInit, NULL)
X(MESH, entitymesh_t, mesh, entityMeshInit, entityMeshDispose)
X(MESH, entitymesh_t, mesh, entityMeshInit, NULL)
X(MATERIAL, entitymaterial_t, material, entityMaterialInit, NULL)
X(PHYSICS, entityphysics_t, physics, entityPhysicsInit, entityPhysicsDispose)
+2 -2
View File
@@ -8,8 +8,8 @@
#pragma once
#include "dusk.h"
#define ENTITY_COUNT_MAX 20
#define ENTITY_COMPONENT_COUNT_MAX 8
#define ENTITY_COUNT_MAX 6
#define ENTITY_COMPONENT_COUNT_MAX 6
typedef uint8_t entityid_t;
typedef uint8_t componentid_t;
+3 -3
View File
@@ -8,7 +8,7 @@
#include "entitymanager.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "console/console.h"
#include "scene/scene.h"
entitymanager_t ENTITY_MANAGER;
@@ -19,8 +19,8 @@ void entityManagerInit(void) {
sizeof(entityid_t) * COMPONENT_TYPE_COUNT * ENTITY_COUNT_MAX
);
consolePrint(
"Entity Manager size: %zu bytes (%.2f KB)",
sceneLog(
"Entity Manager size: %zu bytes (%.2f KB)\n",
sizeof(entitymanager_t),
sizeof(entitymanager_t) / 1024.0f
);
+1 -3
View File
@@ -83,9 +83,7 @@ errorret_t errorChainImpl(
assertNotNull(retval.state->message, "Message cannot be NULL");
// Create a new line string.
int32_t newLineLen = snprintf(
NULL, 0, ERROR_LINE_FORMAT, file, line, function
);
int32_t newLineLen = snprintf(NULL, 0, ERROR_LINE_FORMAT, file, line, function);
assertTrue(newLineLen >= 0, "Line formatting failed");
char_t *newLine = (char_t *)memoryAllocate(newLineLen + 1);
snprintf(newLine, newLineLen + 1, ERROR_LINE_FORMAT, file, line, function);
@@ -3,7 +3,8 @@
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
timelinux.c
event.c
)
+252
View File
@@ -0,0 +1,252 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "event.h"
#include "assert/assert.h"
#include "util/memory.h"
void eventInit(
event_t *event,
eventlistener_t *array,
const uint16_t maxListeners
) {
assertNotNull(event, "Event cannot be NULL");
assertNotNull(array, "Listener array cannot be NULL");
assertTrue(maxListeners > 0, "Max listeners must be greater than 0");
memoryZero(event, sizeof(event_t));
event->listenerArray = array;
event->maxListeners = maxListeners;
}
eventsub_t eventSubscribeUser(
event_t *event,
const eventtype_t type,
const eventuserdata_t user
) {
assertNotNull(event, "Event cannot be NULL");
assertTrue(
event->listenerCount < event->maxListeners,
"Maximum number of listeners reached"
);
if(type == EVENT_TYPE_C) {
assertNotNull(
user.c.callback,
"C event listener callback cannot be NULL"
);
} else if(type == EVENT_TYPE_SCRIPT) {
assertNotNull(
user.script.context,
"Script event listener context cannot be NULL"
);
assertTrue(
user.script.luaFunctionRef != LUA_NOREF,
"Script event listener function reference is invalid"
);
} else {
assertUnreachable("Unknown event listener type");
}
// Gen a new ID
eventsub_t id = event->nextId++;
// Did we wrap?
assertTrue(event->nextId != 0, "Event subscription ID overflow");
// Append listener
eventlistener_t *listener = &event->listenerArray[event->listenerCount++];
memoryZero(listener, sizeof(eventlistener_t));
listener->user = user;
listener->id = id;
listener->type = type;
return id;
}
eventsub_t eventSubscribe(
event_t *event,
const eventcallback_t callback,
const void *user
) {
eventSubscribeUser(
event,
EVENT_TYPE_C,
(eventuserdata_t){ .c = { .callback = callback, .user = (void *)user } }
);
}
eventsub_t eventSubscribeScriptContext(
event_t *event,
scriptcontext_t *context,
const int functionIndex
) {
assertNotNull(context, "Script context cannot be NULL");
assertTrue(
lua_isfunction(context->luaState, functionIndex),
"Expected function at given index"
);
// Create a reference to the function
lua_pushvalue(context->luaState, functionIndex);
int funcRef = luaL_ref(context->luaState, LUA_REGISTRYINDEX);
eventscript_t scriptUser = {
.context = context,
.luaFunctionRef = funcRef
};
// Note to the context that it is now a part of this event
bool_t alreadySubbed = false;
uint8_t i;
i = 0;
do {
if(context->subscribedEvents[i] != event) {
i++;
continue;
}
if(context->subscribedEvents[i] == NULL) break;
alreadySubbed = true;
break;
} while(i < SCRIPT_CONTEXT_MAX_EVENT_SUBSCRIPTIONS);
if(!alreadySubbed) {
i = 0;
do {
if(context->subscribedEvents[i] != NULL) {
i++;
continue;
}
context->subscribedEvents[i] = event;
break;
} while(i < SCRIPT_CONTEXT_MAX_EVENT_SUBSCRIPTIONS);
assertTrue(
i < SCRIPT_CONTEXT_MAX_EVENT_SUBSCRIPTIONS,
"Script context has reached maximum event subscriptions"
);
}
return eventSubscribeUser(
event,
EVENT_TYPE_SCRIPT,
(eventuserdata_t){ .script = scriptUser }
);
}
void eventUnsubscribe(event_t *event, const eventsub_t id) {
assertNotNull(event, "Event cannot be NULL");
assertFalse(event->isInvoking, "Cannot unsubscribe while invoking event");
if(event->listenerCount == 0) return;
// Find listener
uint16_t index = 0;
do {
if(event->listenerArray[index].id == id) {
// Found it, remove by swapping with last and reducing count
event->listenerArray[index] = event->listenerArray[--event->listenerCount];
return;
}
index++;
} while(index < event->listenerCount);
// Did we find it?
if(index == event->listenerCount) return;
// Shift remaining listeners down (if any)
if(index < event->listenerCount - 1) {
memoryMove(
&event->listenerArray[index],
&event->listenerArray[index + 1],
sizeof(eventlistener_t) * (event->listenerCount - index - 1)
);
}
event->listenerCount--;
}
void eventUnsubscribeScriptContext(event_t *event, const scriptcontext_t *ctx) {
assertNotNull(event, "Event cannot be NULL");
assertNotNull(ctx, "Script context cannot be NULL");
if(event->listenerCount == 0) return;
uint16_t i = 0;
do {
eventlistener_t *listener = &event->listenerArray[i];
if(
listener->type != EVENT_TYPE_SCRIPT ||
listener->user.script.context != ctx
) {
i++;
continue;
}
// This listener belongs to the context and will need to go away. This will
// in turn decrement the listener count so we don't increment i here.
eventUnsubscribe(event, listener->id);
} while(i < event->listenerCount);
}
void eventInvoke(
event_t *event,
const void *eventParams,
const char_t *metatableName
) {
assertNotNull(event, "Event cannot be NULL");
if(event->listenerCount == 0) return;
event->isInvoking = true;
uint16_t i = 0;
eventdata_t data ={
.event = event,
.eventParams = eventParams,
};
do {
eventlistener_t *listener = &event->listenerArray[i];
if(listener->type == EVENT_TYPE_C) {
listener->user.c.callback(&data, listener->user.c);
} else if(listener->type == EVENT_TYPE_SCRIPT) {
// Call Lua function
lua_State *L = listener->user.script.context->luaState;
assertNotNull(L, "Lua state in event listener cannot be NULL");
// Push function
lua_rawgeti(L, LUA_REGISTRYINDEX, listener->user.script.luaFunctionRef);
if(eventParams != NULL && metatableName != NULL) {
lua_getmetatable(L, -1);
luaL_getmetatable(L, metatableName);
assertTrue(
lua_rawequal(L, -1, -2),
"Event parameter metatable does not match expected type"
);
}
// Call function with 1 arg, 0 return values
if(lua_pcall(L, 1, 0, 0) != LUA_OK) {
const char_t *strErr = lua_tostring(L, -1);
lua_pop(L, 1);
// Log error but continue
printf("Error invoking Lua event listener: %s\n", strErr);
}
} else {
assertUnreachable("Unknown event listener type");
}
i++;
} while(i < event->listenerCount);
event->isInvoking = false;
}
+121
View File
@@ -0,0 +1,121 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "eventuser.h"
typedef struct event_s event_t;
typedef struct eventdata_s {
const void *eventParams;
const event_t *event;
} eventdata_t;
typedef struct eventlistener_s {
eventuserdata_t user;
eventtype_t type;
uint16_t id;
} eventlistener_t;
typedef uint16_t eventsub_t;
typedef struct event_s {
eventlistener_t *listenerArray;
uint16_t listenerCount;
uint16_t maxListeners;
eventsub_t nextId;
bool_t isInvoking;
} event_t;
/**
* Initialize an event structure.
*
* @param event The event to initialize.
* @param array The array to hold event listeners.
* @param maxListeners The maximum number of listeners.
*/
void eventInit(
event_t *event,
eventlistener_t *array,
const uint16_t maxListeners
);
/**
* Subscribe to an event.
*
* @param event The event to subscribe to.
* @param type The type of the event (C or Lua).
* @param user User data to pass to the callback.
* @return The subscription ID, used to unsubscribe later.
*/
eventsub_t eventSubscribeUser(
event_t *event,
const eventtype_t type,
const eventuserdata_t user
);
/**
* Subscribe to an event with a C function callback.
*
* @param event The event to subscribe to.
* @param callback The C function callback.
* @param user User data pointer to pass to the callback.
* @return The subscription ID, used to unsubscribe later.
*/
eventsub_t eventSubscribe(
event_t *event,
const eventcallback_t callback,
const void *user
);
/**
* Subscribe to an event with a script function callback.
*
* @param event The event to subscribe to.
* @param context The script context.
* @param functionIndex The index of the Lua function on the stack.
* @return The subscription ID, used to unsubscribe later.
*/
eventsub_t eventSubscribeScriptContext(
event_t *event,
scriptcontext_t *context,
const int functionIndex
);
/**
* Unsubscribe from an event.
*
* @param event The event to unsubscribe from.
* @param subscription The subscription ID to remove.
*/
void eventUnsubscribe(event_t *event, const eventsub_t subscription);
/**
* Unsubscribe all event listeners associated with a script context.
*
* @param event The event to unsubscribe from.
* @param context The script context whose listeners should be removed.
*/
void eventUnsubscribeScriptContext(event_t *event, const scriptcontext_t *ctx);
/**
* Invoke an event, calling all subscribed listeners. Optionally provide event
* parameters, and if desired pass a metatable name. Event listeners will be
* able to read event params, and if metatable name is provided the script
* listeners will lookup metatable information matching that name to access the
* params as a structure within the script.
*
* @param event The event to invoke.
* @param eventParams Parameters to pass to the event listeners.
* @param metatableName Metatable name. See details.
*/
void eventInvoke(
event_t *event,
const void *eventParams,
const char_t *metatableName
);
+14
View File
@@ -0,0 +1,14 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef struct eventdata_s eventdata_t;
typedef struct eventc_s eventc_t;
typedef void (*eventcallback_t)(eventdata_t *data, eventc_t user);
+30
View File
@@ -0,0 +1,30 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "eventcallback.h"
#include "script/scriptcontext.h"
typedef enum {
EVENT_TYPE_C = 0,
EVENT_TYPE_SCRIPT = 1
} eventtype_t;
typedef struct {
scriptcontext_t *context;
int luaFunctionRef;
} eventscript_t;
typedef struct eventc_s {
void *user;
eventcallback_t callback;
} eventc_t;
typedef union eventuserdata_u {
eventscript_t script;
eventc_t c;
} eventuserdata_t;
+37 -14
View File
@@ -27,6 +27,13 @@ errorret_t inputInit(void) {
errorChain(inputInitPlatform());
#endif
eventInit(
&INPUT.eventPressed, INPUT.pressedListeners, INPUT_LISTENER_PRESSED_MAX
);
eventInit(
&INPUT.eventReleased, INPUT.releasedListeners, INPUT_LISTENER_RELEASED_MAX
);
errorOk();
}
@@ -87,6 +94,28 @@ void inputUpdate(void) {
#ifdef DUSK_TIME_DYNAMIC
if(TIME.dynamicUpdate) return;
#endif
if(INPUT.eventPressed.listenerCount > 0) {
action = &INPUT.actions[0];
do {
if(inputPressed(action->action)) {
inputevent_t inputEvent = { .action = action->action };
eventInvoke(&INPUT.eventPressed, &inputEvent, "input_mt");
}
action++;
} while(action < &INPUT.actions[INPUT_ACTION_COUNT]);
}
if(INPUT.eventReleased.listenerCount > 0) {
action = &INPUT.actions[0];
do {
if(inputReleased(action->action)) {
inputevent_t inputEvent = { .action = action->action };
eventInvoke(&INPUT.eventReleased, &inputEvent, "input_mt");
}
action++;
} while(action < &INPUT.actions[INPUT_ACTION_COUNT]);
}
}
float_t inputGetCurrentValue(const inputaction_t action) {
@@ -142,16 +171,6 @@ float_t inputAxis(const inputaction_t neg, const inputaction_t pos) {
return inputGetCurrentValue(pos) - inputGetCurrentValue(neg);
}
void inputAxis2D(
const inputaction_t negX, const inputaction_t posX,
const inputaction_t negY, const inputaction_t posY,
vec2 result
) {
assertNotNull(result, "Result vector cannot be null");
result[0] = inputAxis(negX, posX);
result[1] = inputAxis(negY, posY);
}
void inputBind(const inputbutton_t button, const inputaction_t act) {
assertTrue(
act < INPUT_ACTION_COUNT,
@@ -160,15 +179,19 @@ void inputBind(const inputbutton_t button, const inputaction_t act) {
assertTrue(act != INPUT_ACTION_NULL, "Cannot bind to NULL action");
// Get the button data for this button.
inputbuttondata_t *data = inputButtonGetData(button);
assertNotNull(data, "Input button not found");
inputbuttondata_t *data = INPUT_BUTTON_DATA;
do {
if(memoryCompare(&data->button, &button, sizeof(inputbutton_t)) == 0) {
break;
}
data++;
} while(data->name != NULL);
assertNotNull(data->name, "Input button not found");
// Bind the action.
data->action = act;
}
float_t inputDeadzone(const float_t rawValue, const float_t deadzone) {
if(rawValue < deadzone) return 0.0f;
return (rawValue - deadzone) / (1.0f - deadzone);
+1 -2
View File
@@ -5,7 +5,6 @@ LEFT,
RIGHT,
ACCEPT,
CANCEL,
RAGEQUIT,
CONSOLE,
RAGEQUIT
POINTERX,
POINTERY,
1 id id,
5 RIGHT RIGHT,
6 ACCEPT ACCEPT,
7 CANCEL CANCEL,
8 RAGEQUIT RAGEQUIT
CONSOLE
9 POINTERX POINTERX,
10 POINTERY POINTERY,
+10 -16
View File
@@ -9,13 +9,23 @@
#include "error/error.h"
#include "inputbutton.h"
#include "inputaction.h"
#include "event/event.h"
#define INPUT_LISTENER_PRESSED_MAX 16
#define INPUT_LISTENER_RELEASED_MAX INPUT_LISTENER_PRESSED_MAX
typedef struct {
const inputaction_t action;
} inputevent_t;
typedef struct {
inputactiondata_t actions[INPUT_ACTION_COUNT];
eventlistener_t pressedListeners[INPUT_LISTENER_PRESSED_MAX];
event_t eventPressed;
eventlistener_t releasedListeners[INPUT_LISTENER_RELEASED_MAX];
event_t eventReleased;
inputplatform_t platform;
} input_t;
@@ -110,22 +120,6 @@ bool_t inputReleased(const inputaction_t action);
*/
float_t inputAxis(const inputaction_t neg, const inputaction_t pos);
/**
* Gets the values of a 2D input axis, defined by two pairs of actions (negative
* and positive for each axis).
*
* @param negX The action representing the negative direction of the X axis.
* @param posX The action representing the positive direction of the X axis.
* @param negY The action representing the negative direction of the Y axis.
* @param posY The action representing the positive direction of the Y axis.
* @param result A vec2 to store the resulting axis values (-1.0f to 1.0f).
*/
void inputAxis2D(
const inputaction_t negX, const inputaction_t posX,
const inputaction_t negY, const inputaction_t posY,
vec2 result
);
/**
* Binds an input button to an action.
*
+9 -9
View File
@@ -9,14 +9,14 @@
#include "assert/assert.h"
#include "util/string.h"
inputaction_t inputActionGetByName(const char_t *name) {
assertNotNull(name, "name must not be NULL");
// inputaction_t inputActionGetByName(const char_t *name) {
// assertNotNull(name, "name must not be NULL");
for(inputaction_t i = 0; i < INPUT_ACTION_COUNT; i++) {
if(INPUT_ACTION_IDS[i] == NULL) continue;
if(stringCompareInsensitive(INPUT_ACTION_IDS[i], name) != 0) continue;
return i;
}
// for(inputaction_t i = 0; i < INPUT_ACTION_COUNT; i++) {
// if(INPUT_ACTION_IDS[i] == NULL) continue;
// if(stringCompareInsensitive(INPUT_ACTION_IDS[i], name) != 0) continue;
// return i;
// }
return INPUT_ACTION_COUNT;
}
// return INPUT_ACTION_COUNT;
// }
+1 -1
View File
@@ -26,4 +26,4 @@ typedef struct {
* @param name The name of the input action.
* @return The input action, or INPUT_ACTION_COUNT if not found.
*/
inputaction_t inputActionGetByName(const char_t *name);
// inputaction_t inputActionGetByName(const char_t *name);
-13
View File
@@ -9,7 +9,6 @@
#include "input.h"
#include "assert/assert.h"
#include "util/string.h"
#include "util/memory.h"
inputbutton_t inputButtonGetByName(const char_t *name) {
assertNotNull(name, "name must not be NULL");
@@ -28,15 +27,3 @@ inputbutton_t inputButtonGetByName(const char_t *name) {
float_t inputButtonGetValue(const inputbutton_t button) {
return inputButtonGetValuePlatform(button);
}
inputbuttondata_t * inputButtonGetData(const inputbutton_t button) {
inputbuttondata_t *data = INPUT_BUTTON_DATA;
do {
if(memoryCompare(&data->button, &button, sizeof(inputbutton_t)) == 0) {
return data;
}
data++;
} while(data->name != NULL);
return NULL;
}
-8
View File
@@ -97,11 +97,3 @@ inputbutton_t inputButtonGetByName(const char_t *name);
* @return The current value of the input button (0.0f to 1.0f).
*/
float_t inputButtonGetValue(const inputbutton_t button);
/**
* Gets the button data for a specific input button.
*
* @param button The input button to get the data for.
* @return The button data, or NULL if not found.
*/
inputbuttondata_t * inputButtonGetData(const inputbutton_t button);
+1
View File
@@ -7,4 +7,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
network.c
networkinfo.c
networksocketclient.c
)
+25
View File
@@ -0,0 +1,25 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
typedef struct {
const char_t *key;
const char_t *value;
} networkhttpheader_t;
typedef struct {
} networkhttprequest_t;
void networkHTTP(
const char_t *url,
const char_t *method,
void *user,
void (*onComplete)(void *user),
void (*onError)(errorret_t error, void *user)
);
+6
View File
@@ -25,11 +25,17 @@ typedef enum {
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
+44
View File
@@ -0,0 +1,44 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "networksocketclient.h"
#include "assert/assert.h"
#include "util/memory.h"
void networkSocketClientInit(
networksocketclient_t *client,
const char_t *host,
uint16_t port,
void *user,
void (*onConnect)(void *user),
void (*onError)(errorret_t error, void *user),
void (*onDisconnect)(void *user)
) {
assertNotNull(client, "Client cannot be NULL");
assertStrLenMin(host, 1, "Host cannot be empty");
assertNotNull(onConnect, "onConnect callback cannot be NULL");
assertNotNull(onError, "onError callback cannot be NULL");
assertNotNull(onDisconnect, "onDisconnect callback cannot be NULL");
memoryZero(client, sizeof(networksocketclient_t));
client->user = user;
client->onConnect = onConnect;
client->onError = onError;
client->onDisconnect = onDisconnect;
client->state = NETWORK_SOCKET_CLIENT_STATE_CONNECTING;
// Pass to platform for implementation.
networkSocketClientPlatformInit(
client,
host,
port,
user,
onConnect,
onError,
onDisconnect
);
}
+50
View File
@@ -0,0 +1,50 @@
/**
* 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/networksocketclientplatform.h"
#ifndef networkSocketClientPlatformInit
#error "Define networkSocketClientPlatformInit"
#endif
typedef enum {
NETWORK_SOCKET_CLIENT_STATE_DISCONNECTED,
NETWORK_SOCKET_CLIENT_STATE_CONNECTING,
} networksocketclientstate_t;
typedef struct networksocketclient_s {
void *user;
void (*onConnect)(void *user);
void (*onError)(errorret_t error, void *user);
void (*onDisconnect)(void *user);
networksocketclientstate_t state;
networksocketclientplatform_t platform;
errorstate_t errorState;
} networksocketclient_t;
/**
* Initializes a network socket client connection.
*
* @param client The client struct to initialize.
* @param host The hostname or IP address to connect to.
* @param port The port number to connect to.
* @param user User data to pass to callbacks.
* @param onConnect Callback for when the connection is established.
* @param onError Callback for when an error occurs.
* @param onDisconnect Callback for when the connection is disconnected.
*/
void networkSocketClientInit(
networksocketclient_t *client,
const char_t *host,
uint16_t port,
void *user,
void (*onConnect)(void *user),
void (*onError)(errorret_t error, void *user),
void (*onDisconnect)(void *user)
);
+1 -1
View File
@@ -45,7 +45,7 @@ void physicsWorldStep(const float_t dt) {
}
/* Phase 1: integrate dynamic bodies (gravity + velocity → position).
* Writes directly to pos->position, matrix rebuilt at end. */
* Writes directly to pos->position matrix rebuilt at end. */
for(entityid_t i = 0; i < physCount; i++) {
if(!positions[i]) continue;
entityphysics_t *phys = physBodies[i];
+104 -129
View File
@@ -11,19 +11,60 @@
#include "display/screen/screen.h"
#include "entity/entitymanager.h"
#include "display/shader/shaderunlit.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"
#include "script/module/cutscene/modulecutscene.h"
#include "ui/ui.h"
scene_t SCENE;
char_t SCENE_LOG[SCENE_LOG_SIZE];
void sceneLog(const char *fmt, ...) {
char temp[512];
// 1. Format input like printf
va_list args;
va_start(args, fmt);
vsnprintf(temp, sizeof(temp), fmt, args);
va_end(args);
printf("%s", temp);
// 2. Split into lines
char *lines[64];
int line_count = 0;
char *ptr = temp;
while (*ptr && line_count < 64) {
lines[line_count++] = ptr;
char *nl = strchr(ptr, '\n');
if (!nl) break;
*nl = '\0';
ptr = nl + 1;
}
// 3. Prepend lines in reverse order (so final order is correct)
for (int i = 0; i < line_count; i++) {
char new_log[SCENE_LOG_SIZE];
snprintf(new_log, sizeof(new_log), "%s\n%s", lines[i], SCENE_LOG);
// Copy back safely
strncpy(SCENE_LOG, new_log, SCENE_LOG_SIZE - 1);
SCENE_LOG[SCENE_LOG_SIZE - 1] = '\0';
}
}
errorret_t sceneInit(void) {
memoryZero(&SCENE, sizeof(scene_t));
SCENE.scriptRef = SCENE_SCRIPT_REF_NONE;
memoryZero(SCENE_LOG, sizeof(SCENE_LOG));
sceneLog("Init\n");
errorOk();
}
@@ -34,35 +75,31 @@ errorret_t sceneUpdate(void) {
}
#endif
if(stringCompare(SCENE.sceneNext, SCENE.sceneCurrent) != 0) {
errorChain(sceneSetImmediate(SCENE.sceneNext));
}
if(SCENE.sceneActive) {
errorChain(moduleSceneCall("update"));
}
errorOk();
}
dusktimeepoch_t LAST;
errorret_t sceneRender(void) {
// Get Cameras
entityid_t camEnts[ENTITY_COUNT_MAX];
componentid_t camComps[ENTITY_COUNT_MAX];
entityid_t camCount = componentGetEntitiesWithComponent(
COMPONENT_TYPE_CAMERA, camEnts, camComps
);
if(camCount == 0) errorOk();
shader_t *shaderCurrent = NULL;
// Get meshes
entityid_t meshEnts[ENTITY_COUNT_MAX];
componentid_t meshComps[ENTITY_COUNT_MAX];
entityid_t meshCount = componentGetEntitiesWithComponent(
COMPONENT_TYPE_MESH, meshEnts, meshComps
);
if(meshCount == 0) errorOk();
// Prep Matricies
mat4 view, proj, model;
errorChain(shaderBind(&SHADER_UNLIT));
errorChain(displaySetState((displaystate_t){
// .flags = DISPLAY_STATE_FLAG_CULL | DISPLAY_STATE_FLAG_DEPTH_TEST
.flags = 0
}));
// For each camera
// For each camera.
for(entityid_t camIndex = 0; camIndex < camCount; camIndex++) {
entityid_t camEnt = camEnts[camIndex];
componentid_t camComp = camComps[camIndex];
@@ -75,67 +112,53 @@ errorret_t sceneRender(void) {
entityCameraGetProjection(camEnt, camComp, proj);
entityPositionGetTransform(camEnt, camPos, view);
// For each entity (I could iterate only over entities with mesh/material)
// but in future I may have different renderable types.
for(entityid_t entityId = 0; entityId < ENTITY_COUNT_MAX; entityId++) {
// Does this entity have a material?
componentid_t matComp = entityGetComponent(
entityId, COMPONENT_TYPE_MATERIAL
);
if(matComp != 0xFF) {
// Yes, get the mesh
componentid_t meshComp = entityGetComponent(
entityId, COMPONENT_TYPE_MESH
);
if(meshComp == 0xFF) {
logError("Entity with material but no mesh found\n");
continue;
}
mesh_t *mesh = entityMeshGetMesh(entityId, meshComp);
// For each mesh.
for(entityid_t meshIndex = 0; meshIndex < meshCount; meshIndex++) {
entityid_t meshEnt = meshEnts[meshIndex];
componentid_t meshComp = meshComps[meshIndex];
mesh_t *mesh = entityMeshGetMesh(meshEnt, meshComp);
if(mesh == NULL) {
logError("Entity with material but no mesh 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 but no shader found\n");
continue;
}
// Get the transform.
componentid_t meshPos = entityGetComponent(
entityId, COMPONENT_TYPE_POSITION
meshEnt, COMPONENT_TYPE_POSITION
);
if(meshPos == 0xFF) {
glm_mat4_identity(model);
} else {
entityPositionGetTransform(entityId, meshPos, model);
logError("Mesh entity without entity position found\n");
continue;
}
// Render the mesh.
if(shaderCurrent != shader) {
shaderCurrent = shader;
errorChain(shaderBind(shaderCurrent));
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));
continue;
}
// No, in future there may be other renderable types.
}
}
// UI Rendering
// Here is where UI will go
glm_ortho(
0.0f, SCREEN.width,
SCREEN.height, 0.0f,
@@ -154,73 +177,25 @@ errorret_t sceneRender(void) {
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_PROJECTION, proj));
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, view));
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, model));
errorChain(displaySetState((displaystate_t){
.flags = DISPLAY_STATE_FLAG_BLEND
}));
errorChain(uiRender());
// errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_0TEXTURE, &DEFAULT_FONT_TEXTURE));
// errorChain(shaderSetColor(&SHADER_UNLIT, SHADER_UNLIT_COLOR, COLOR_WHITE));
errorChain(textDraw(
32, 32,
// "Hello World",
SCENE_LOG,
COLOR_WHITE,
&DEFAULT_FONT_TILESET,
&DEFAULT_FONT_TEXTURE
));
errorChain(spriteBatchFlush());
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;
}
moduleCutsceneReset();
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;
}
errorret_t sceneSet(const char_t *script) {
errorOk();
}
void sceneSet(const char_t *scene) {
stringCopy(
SCENE.sceneNext,
scene == NULL ? "" : scene,
ASSET_FILE_PATH_MAX
);
}
void sceneDispose(void) {
errorret_t sceneDispose(void) {
errorChain(moduleSceneCall("dispose"));
errorOk();
}
+16 -31
View File
@@ -6,62 +6,47 @@
*/
#pragma once
#include "script/scriptmanager.h"
#include "asset/assetfile.h"
#define SCENE_EVENT_UPDATE_MAX 16
#include "error/error.h"
typedef struct {
bool_t sceneActive;
jerry_value_t scriptRef;
char_t sceneCurrent[ASSET_FILE_PATH_MAX];
char_t sceneNext[ASSET_FILE_PATH_MAX];
void *nothing;
} scene_t;
extern scene_t SCENE;
/** Sentinel value meaning no scene script is loaded. */
#define SCENE_SCRIPT_REF_NONE ((jerry_value_t)0)
#define SCENE_LOG_SIZE 1024
extern char_t SCENE_LOG[SCENE_LOG_SIZE];
void sceneLog(const char *fmt, ...);
/**
* Initializes the scene manager.
* Initialize the scene subsystem.
*
* @return Any error state that happened.
* @return The error return value.
*/
errorret_t sceneInit(void);
/**
* Ticks the scene manager; may call the scene's update method.
* Update the current scene.
*
* @return Any error state that happened.
* @return The error return value.
*/
errorret_t sceneUpdate(void);
/**
* Renders the scene.
* Render the current scene.
*
* @return Any error state that happened.
* @return The error return value.
*/
errorret_t sceneRender(void);
/**
* Immediately switches scenes, disposing the current one first.
* Set the current scene by script name.
*
* @param scene Scene to switch to (asset file path).
* @return Any error state that happened.
* @param script The script name of the scene to set.
*/
errorret_t sceneSetImmediate(const char_t *scene);
errorret_t sceneSet(const char_t *script);
/**
* Requests a scene change on the next safe opportunity.
*
* @param scene Which scene to set.
* Dispose of the scene subsystem.
*/
void sceneSet(const char_t *scene);
/**
* Disposes of the current scene.
*
* @return Any error state that happened.
*/
errorret_t sceneDispose(void);
void sceneDispose(void);
+3 -1
View File
@@ -7,7 +7,9 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
scriptmanager.c
scriptproto.c
scriptcontext.c
scriptmodule.c
)
# Subdirectories
add_subdirectory(module)
+14
View File
@@ -0,0 +1,14 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Subdirectories
add_subdirectory(display)
add_subdirectory(event)
add_subdirectory(input)
add_subdirectory(locale)
add_subdirectory(system)
add_subdirectory(scene)
add_subdirectory(time)
add_subdirectory(ui)
@@ -1,293 +0,0 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "script/module/modulebase.h"
#include "script/scriptproto.h"
#include "animation/animation.h"
#include "util/memory.h"
static scriptproto_t MODULE_ANIMATION_PROTO;
static inline animation_t *moduleAnimationGet(
const jerry_call_info_t *callInfo
) {
return (animation_t *)scriptProtoGetValue(
&MODULE_ANIMATION_PROTO, callInfo->this_value
);
}
// Extracts an easingtype_t from a JS value. Accepts either a plain number
// or an Easing.xxx function (which has a .type numeric property).
static easingtype_t moduleAnimationReadEasing(
const jerry_value_t val
) {
if(jerry_value_is_number(val)) {
return (easingtype_t)(uint32_t)jerry_value_as_number(val);
}
if(jerry_value_is_object(val) || jerry_value_is_function(val)) {
jerry_value_t key = jerry_string_sz("type");
jerry_value_t typeVal = jerry_object_get(val, key);
jerry_value_free(key);
easingtype_t result = EASING_LINEAR;
if(jerry_value_is_number(typeVal)) {
result = (easingtype_t)(uint32_t)jerry_value_as_number(typeVal);
}
jerry_value_free(typeVal);
return result;
}
return EASING_LINEAR;
}
// Fires onReach callbacks for any keyframes crossed between prevTime and
// newTime. Returns an exception value on error, or jerry_undefined().
static jerry_value_t moduleAnimationFireKeyframes(
const jerry_value_t thisVal,
float_t prevTime,
float_t newTime
) {
jerry_value_t kfKey = jerry_string_sz("_keyframes");
jerry_value_t kfArr = jerry_object_get(thisVal, kfKey);
jerry_value_free(kfKey);
if(!jerry_value_is_array(kfArr)) {
jerry_value_free(kfArr);
return jerry_undefined();
}
jerry_length_t len = jerry_array_length(kfArr);
for(jerry_length_t i = 0; i < len; i++) {
jerry_value_t kfObj = jerry_object_get_index(kfArr, i);
jerry_value_t tKey = jerry_string_sz("time");
jerry_value_t tVal = jerry_object_get(kfObj, tKey);
jerry_value_free(tKey);
float_t kfTime = (float_t)jerry_value_as_number(tVal);
jerry_value_free(tVal);
if(prevTime < kfTime && newTime >= kfTime) {
jerry_value_t cbKey = jerry_string_sz("onReach");
jerry_value_t cb = jerry_object_get(kfObj, cbKey);
jerry_value_free(cbKey);
if(jerry_value_is_function(cb)) {
jerry_value_t r = jerry_call(cb, thisVal, NULL, 0);
jerry_value_free(cb);
if(jerry_value_is_exception(r)) {
jerry_value_free(kfObj);
jerry_value_free(kfArr);
return r;
}
jerry_value_free(r);
} else {
jerry_value_free(cb);
}
}
jerry_value_free(kfObj);
}
jerry_value_free(kfArr);
return jerry_undefined();
}
// Fires onComplete once. Returns an exception on error or jerry_undefined().
static jerry_value_t moduleAnimationFireComplete(
const jerry_value_t thisVal
) {
jerry_value_t firedKey = jerry_string_sz("_completeFired");
jerry_value_t firedVal = jerry_object_get(thisVal, firedKey);
bool_t alreadyFired = jerry_value_is_true(firedVal);
jerry_value_free(firedVal);
if(alreadyFired) {
jerry_value_free(firedKey);
return jerry_undefined();
}
jerry_value_t trueVal = jerry_boolean(true);
jerry_object_set(thisVal, firedKey, trueVal);
jerry_value_free(firedKey);
jerry_value_free(trueVal);
jerry_value_t cbKey = jerry_string_sz("onComplete");
jerry_value_t cb = jerry_object_get(thisVal, cbKey);
jerry_value_free(cbKey);
if(jerry_value_is_function(cb)) {
jerry_value_t r = jerry_call(cb, thisVal, NULL, 0);
jerry_value_free(cb);
if(jerry_value_is_exception(r)) return r;
jerry_value_free(r);
} else {
jerry_value_free(cb);
}
return jerry_undefined();
}
moduleBaseFunction(moduleAnimationConstructor) {
animation_t *anim = (animation_t *)memoryAllocate(sizeof(animation_t));
animationInit(anim);
if(argc > 0 && jerry_value_is_boolean(args[argc - 1])) {
anim->loop = jerry_value_is_true(args[argc - 1]);
}
if(argc > 0 && jerry_value_is_array(args[0])) {
jerry_length_t len = jerry_array_length(args[0]);
for(jerry_length_t i = 0; i < len; i++) {
jerry_value_t kf = jerry_object_get_index(args[0], i);
jerry_value_t tKey = jerry_string_sz("time");
jerry_value_t vKey = jerry_string_sz("value");
jerry_value_t eKey = jerry_string_sz("easing");
float_t t = (float_t)jerry_value_as_number(
jerry_object_get(kf, tKey)
);
float_t v = (float_t)jerry_value_as_number(
jerry_object_get(kf, vKey)
);
jerry_value_t eVal = jerry_object_get(kf, eKey);
easingtype_t e = moduleAnimationReadEasing(eVal);
jerry_value_free(tKey);
jerry_value_free(vKey);
jerry_value_free(eKey);
jerry_value_free(eVal);
jerry_value_free(kf);
animationAddKeyframe(anim, t, v, e);
}
// Store the JS keyframes array for onReach callback detection.
jerry_value_t kfKey = jerry_string_sz("_keyframes");
jerry_object_set(callInfo->this_value, kfKey, args[0]);
jerry_value_free(kfKey);
}
jerry_value_t firedKey = jerry_string_sz("_completeFired");
jerry_value_t falseVal = jerry_boolean(false);
jerry_object_set(callInfo->this_value, firedKey, falseVal);
jerry_value_free(firedKey);
jerry_value_free(falseVal);
jerry_object_set_native_ptr(
callInfo->this_value, &MODULE_ANIMATION_PROTO.info, anim
);
return jerry_undefined();
}
moduleBaseFunction(moduleAnimationUpdate) {
animation_t *anim = moduleAnimationGet(callInfo);
if(!anim) return moduleBaseThrow("Invalid Animation instance");
if(argc < 1 || !jerry_value_is_number(args[0])) {
return moduleBaseThrow("update() expects a number delta");
}
float_t prevTime = anim->time;
float_t delta = (float_t)jerry_value_as_number(args[0]);
float_t value = animationUpdate(anim, delta);
jerry_value_t kfResult = moduleAnimationFireKeyframes(
callInfo->this_value, prevTime, anim->time
);
if(jerry_value_is_exception(kfResult)) return kfResult;
jerry_value_free(kfResult);
if(anim->complete) {
jerry_value_t cResult = moduleAnimationFireComplete(
callInfo->this_value
);
if(jerry_value_is_exception(cResult)) return cResult;
jerry_value_free(cResult);
}
return jerry_number((double)value);
}
moduleBaseFunction(moduleAnimationGetValue) {
animation_t *anim = moduleAnimationGet(callInfo);
if(!anim) return moduleBaseThrow("Invalid Animation instance");
return jerry_number((double)animationGetValue(anim));
}
moduleBaseFunction(moduleAnimationReset) {
animation_t *anim = moduleAnimationGet(callInfo);
if(!anim) return moduleBaseThrow("Invalid Animation instance");
animationReset(anim);
jerry_value_t key = jerry_string_sz("_completeFired");
jerry_value_t falseVal = jerry_boolean(false);
jerry_object_set(callInfo->this_value, key, falseVal);
jerry_value_free(key);
jerry_value_free(falseVal);
return jerry_undefined();
}
moduleBaseFunction(moduleAnimationGetComplete) {
animation_t *anim = moduleAnimationGet(callInfo);
return anim ? jerry_boolean(anim->complete) : jerry_boolean(false);
}
moduleBaseFunction(moduleAnimationGetLoop) {
animation_t *anim = moduleAnimationGet(callInfo);
return anim ? jerry_boolean(anim->loop) : jerry_boolean(false);
}
moduleBaseFunction(moduleAnimationSetLoop) {
animation_t *anim = moduleAnimationGet(callInfo);
if(!anim) return moduleBaseThrow("Invalid Animation instance");
if(argc < 1) return moduleBaseThrow("Expected boolean");
anim->loop = jerry_value_is_true(args[0]);
return jerry_undefined();
}
moduleBaseFunction(moduleAnimationGetTime) {
animation_t *anim = moduleAnimationGet(callInfo);
return anim ? jerry_number((double)anim->time) : jerry_number(0.0);
}
moduleBaseFunction(moduleAnimationGetDuration) {
animation_t *anim = moduleAnimationGet(callInfo);
return anim ? jerry_number((double)anim->duration) : jerry_number(0.0);
}
static void moduleAnimation(void) {
scriptProtoInit(
&MODULE_ANIMATION_PROTO,
"Animation",
sizeof(animation_t),
moduleAnimationConstructor
);
scriptProtoDefineFunc(
&MODULE_ANIMATION_PROTO, "update", moduleAnimationUpdate
);
scriptProtoDefineFunc(
&MODULE_ANIMATION_PROTO, "getValue", moduleAnimationGetValue
);
scriptProtoDefineFunc(
&MODULE_ANIMATION_PROTO, "reset", moduleAnimationReset
);
scriptProtoDefineProp(
&MODULE_ANIMATION_PROTO, "complete",
moduleAnimationGetComplete, NULL
);
scriptProtoDefineProp(
&MODULE_ANIMATION_PROTO, "loop",
moduleAnimationGetLoop, moduleAnimationSetLoop
);
scriptProtoDefineProp(
&MODULE_ANIMATION_PROTO, "time",
moduleAnimationGetTime, NULL
);
scriptProtoDefineProp(
&MODULE_ANIMATION_PROTO, "duration",
moduleAnimationGetDuration, NULL
);
}
@@ -1,70 +0,0 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "script/module/modulebase.h"
#include "script/scriptproto.h"
#include "animation/easing.h"
static scriptproto_t MODULE_EASING_PROTO;
// Generate one handler per easing curve.
#define EASING_MODULE_TABLE \
X(Linear, "linear", EASING_LINEAR, easingLinear) \
X(InSine, "inSine", EASING_IN_SINE, easingInSine) \
X(OutSine, "outSine", EASING_OUT_SINE, easingOutSine) \
X(InOutSine, "inOutSine", EASING_IN_OUT_SINE, easingInOutSine) \
X(InQuad, "inQuad", EASING_IN_QUAD, easingInQuad) \
X(OutQuad, "outQuad", EASING_OUT_QUAD, easingOutQuad) \
X(InOutQuad, "inOutQuad", EASING_IN_OUT_QUAD, easingInOutQuad) \
X(InCubic, "inCubic", EASING_IN_CUBIC, easingInCubic) \
X(OutCubic, "outCubic", EASING_OUT_CUBIC, easingOutCubic) \
X(InOutCubic, "inOutCubic", EASING_IN_OUT_CUBIC, easingInOutCubic) \
X(InQuart, "inQuart", EASING_IN_QUART, easingInQuart) \
X(OutQuart, "outQuart", EASING_OUT_QUART, easingOutQuart) \
X(InOutQuart, "inOutQuart", EASING_IN_OUT_QUART, easingInOutQuart) \
X(InBack, "inBack", EASING_IN_BACK, easingInBack) \
X(OutBack, "outBack", EASING_OUT_BACK, easingOutBack) \
X(InOutBack, "inOutBack", EASING_IN_OUT_BACK, easingInOutBack)
#define X(CName, jsName, type, fn) \
moduleBaseFunction(moduleEasing##CName) { \
if(argc < 1 || !jerry_value_is_number(args[0])) { \
return moduleBaseThrow("Expected number t"); \
} \
float_t t = (float_t)jerry_value_as_number(args[0]); \
return jerry_number((double)fn(t)); \
}
EASING_MODULE_TABLE
#undef X
// Adds a callable easing function with a .type property to the Easing object.
static void moduleEasingRegister(
const char_t *jsName,
uint8_t type,
jerry_external_handler_t fn
) {
jerry_value_t fnVal = jerry_function_external(fn);
jerry_value_t typeKey = jerry_string_sz("type");
jerry_value_t typeVal = jerry_number((double)type);
jerry_object_set(fnVal, typeKey, typeVal);
jerry_value_free(typeKey);
jerry_value_free(typeVal);
jerry_value_t nameKey = jerry_string_sz(jsName);
jerry_object_set(MODULE_EASING_PROTO.prototype, nameKey, fnVal);
jerry_value_free(nameKey);
jerry_value_free(fnVal);
}
static void moduleEasing(void) {
scriptProtoInit(&MODULE_EASING_PROTO, "Easing", sizeof(uint8_t), NULL);
#define X(CName, jsName, type, fn) \
moduleEasingRegister(jsName, type, moduleEasing##CName);
EASING_MODULE_TABLE
#undef X
}
@@ -1,67 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/module/modulebase.h"
#include "script/scriptproto.h"
#include "console/console.h"
static scriptproto_t MODULE_CONSOLE_PROTO;
moduleBaseFunction(moduleConsolePrint) {
char_t buf[512];
char_t msg[4096];
size_t msgLen = 0;
for(jerry_length_t i = 0; i < argc; ++i) {
jerry_value_t strVal = jerry_value_to_string(args[i]);
moduleBaseToString(strVal, buf, sizeof(buf));
jerry_value_free(strVal);
size_t partLen = strlen(buf);
if(msgLen + partLen + 1 < sizeof(msg)) {
stringCopy(msg + msgLen, buf, sizeof(msg) - msgLen);
msgLen += partLen;
}
if(i + 1 < argc && msgLen + 1 < sizeof(msg)) {
msg[msgLen++] = '\t';
msg[msgLen] = '\0';
}
}
consolePrint("%s", msg);
return jerry_undefined();
}
moduleBaseFunction(moduleConsoleGetVisible) {
return jerry_boolean(CONSOLE.visible);
}
moduleBaseFunction(moduleConsoleSetVisible) {
if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
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,204 +0,0 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "script/module/modulebase.h"
#include "script/scriptproto.h"
#include "cutscene/cutscene.h"
#include "error/error.h"
static scriptproto_t MODULE_CUTSCENE_PROTO;
/**
* Disposes the active JS cutscene without firing the then callback.
* Safe to call when no cutscene is active.
*/
static void moduleCutsceneReset(void) {
if(CUTSCENE.activeRef == CUTSCENE_SCRIPT_REF_NONE) return;
jerry_value_t key = jerry_string_sz("dispose");
jerry_value_t fn = jerry_object_get(CUTSCENE.activeRef, key);
jerry_value_free(key);
if(jerry_value_is_function(fn)) {
jerry_value_t result = jerry_call(fn, CUTSCENE.activeRef, NULL, 0);
jerry_value_free(fn);
jerry_value_free(result);
} else {
jerry_value_free(fn);
}
jerry_value_free(CUTSCENE.activeRef);
CUTSCENE.activeRef = CUTSCENE_SCRIPT_REF_NONE;
}
/**
* Ticks the active JS cutscene. Called from cutsceneUpdate().
*
* @return Any error state that happened.
*/
static errorret_t moduleCutsceneUpdate(void) {
if(CUTSCENE.activeRef == CUTSCENE_SCRIPT_REF_NONE) errorOk();
jerry_value_t key = jerry_string_sz("update");
jerry_value_t fn = jerry_object_get(CUTSCENE.activeRef, key);
jerry_value_free(key);
if(!jerry_value_is_function(fn)) {
jerry_value_free(fn);
errorOk();
}
jerry_value_t result = jerry_call(fn, CUTSCENE.activeRef, NULL, 0);
jerry_value_free(fn);
if(jerry_value_is_exception(result)) {
char_t errMsg[512];
moduleBaseExceptionMessage(result, errMsg, sizeof(errMsg));
jerry_value_free(result);
errorThrow("Cutscene update failed: %s", errMsg);
}
jerry_value_free(result);
errorOk();
}
moduleBaseFunction(moduleCutsceneDefaultConstructor) {
return jerry_undefined();
}
moduleBaseFunction(moduleCutsceneDefaultUpdate) {
return jerry_undefined();
}
moduleBaseFunction(moduleCutsceneDefaultDispose) {
return jerry_undefined();
}
/**
* cutscene.then(callback)
*
* Sets the completion callback fired when Cutscene.finish() is called.
* Only one callback may be set; calling then() again replaces it.
* Returns the instance for optional chaining.
*/
moduleBaseFunction(moduleCutsceneThen) {
if(argc < 1 || !jerry_value_is_function(args[0])) {
return moduleBaseThrow("then() expects a function argument");
}
jerry_value_t key = jerry_string_sz("_onFinished");
jerry_object_set(callInfo->this_value, key, args[0]);
jerry_value_free(key);
return jerry_value_copy(callInfo->this_value);
}
/**
* Cutscene.play(instance)
*
* Registers a cutscene instance with the engine and begins calling its
* update() each tick. Stops any currently active cutscene first.
* Returns the instance to allow chaining .then() directly.
*/
moduleBaseFunction(moduleCutscenePlay) {
if(argc < 1 || !jerry_value_is_object(args[0])) {
return moduleBaseThrow("play() expects a cutscene instance");
}
if(CUTSCENE.activeRef != CUTSCENE_SCRIPT_REF_NONE) {
moduleCutsceneReset();
}
CUTSCENE.activeRef = jerry_value_copy(args[0]);
return jerry_value_copy(args[0]);
}
/**
* Cutscene.finish()
*
* Marks the active cutscene as complete: calls dispose(), clears the
* active ref, then fires the then() callback if one was set.
* Intended to be called from within the cutscene's own update().
*/
moduleBaseFunction(moduleCutsceneFinish) {
if(CUTSCENE.activeRef == CUTSCENE_SCRIPT_REF_NONE) {
return jerry_undefined();
}
jerry_value_t cbKey = jerry_string_sz("_onFinished");
jerry_value_t callback = jerry_object_get(CUTSCENE.activeRef, cbKey);
jerry_value_free(cbKey);
jerry_value_t dispKey = jerry_string_sz("dispose");
jerry_value_t dispFn = jerry_object_get(CUTSCENE.activeRef, dispKey);
jerry_value_free(dispKey);
if(jerry_value_is_function(dispFn)) {
jerry_value_t r = jerry_call(dispFn, CUTSCENE.activeRef, NULL, 0);
jerry_value_free(dispFn);
if(jerry_value_is_exception(r)) {
jerry_value_free(callback);
return r;
}
jerry_value_free(r);
} else {
jerry_value_free(dispFn);
}
jerry_value_free(CUTSCENE.activeRef);
CUTSCENE.activeRef = CUTSCENE_SCRIPT_REF_NONE;
if(jerry_value_is_function(callback)) {
jerry_value_t r = jerry_call(callback, jerry_undefined(), NULL, 0);
jerry_value_free(callback);
if(jerry_value_is_exception(r)) return r;
jerry_value_free(r);
} else {
jerry_value_free(callback);
}
return jerry_undefined();
}
/**
* Cutscene.stop()
*
* Immediately disposes the active cutscene without firing the then
* callback. No-op when no cutscene is active.
*/
moduleBaseFunction(moduleCutsceneStop) {
moduleCutsceneReset();
return jerry_undefined();
}
static void moduleCutscene(void) {
scriptProtoInit(
&MODULE_CUTSCENE_PROTO,
"Cutscene",
sizeof(uint8_t),
moduleCutsceneDefaultConstructor
);
scriptProtoDefineFunc(
&MODULE_CUTSCENE_PROTO, "update", moduleCutsceneDefaultUpdate
);
scriptProtoDefineFunc(
&MODULE_CUTSCENE_PROTO, "dispose", moduleCutsceneDefaultDispose
);
scriptProtoDefineFunc(
&MODULE_CUTSCENE_PROTO, "then", moduleCutsceneThen
);
scriptProtoDefineStaticFunc(
&MODULE_CUTSCENE_PROTO, "play", moduleCutscenePlay
);
scriptProtoDefineStaticFunc(
&MODULE_CUTSCENE_PROTO, "finish", moduleCutsceneFinish
);
scriptProtoDefineStaticFunc(
&MODULE_CUTSCENE_PROTO, "stop", moduleCutsceneStop
);
}
@@ -0,0 +1,18 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
moduleglm.c
modulespritebatch.c
moduleglm.c
modulecolor.c
moduletext.c
modulescreen.c
moduletileset.c
moduletexture.c
moduleshader.c
)
@@ -0,0 +1,186 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "modulecolor.h"
#include "display/color.h"
#include "assert/assert.h"
#include "util/string.h"
#include "time/time.h"
void moduleColor(scriptcontext_t *context) {
assertNotNull(context, "Context cannot be NULL.");
if(luaL_newmetatable(context->luaState, "color_mt")) {
// Metatable is new, set __index and __newindex
lua_pushstring(context->luaState, "__index");
lua_pushcfunction(context->luaState, moduleColorIndex);
lua_settable(context->luaState, -3);
lua_pushstring(context->luaState, "__newindex");
lua_pushcfunction(context->luaState, moduleColorNewIndex);
lua_settable(context->luaState, -3);
lua_pushstring(context->luaState, "__tostring");
lua_pushcfunction(context->luaState, moduleColorToString);
lua_settable(context->luaState, -3);
}
lua_pop(context->luaState, 1);
lua_register(context->luaState, "color", moduleColorFuncColor);
lua_register(context->luaState, "colorRainbow", moduleColorRainbow);
scriptContextExec(context, COLOR_SCRIPT);
}
int moduleColorFuncColor(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL.");
scriptcontext_t *context = *(scriptcontext_t **)lua_getextraspace(L);
assertNotNull(context, "Script context cannot be NULL.");
// Needs 4 channel uint8_t
if(
!lua_isnumber(L, 1) || !lua_isnumber(L, 2) ||
!lua_isnumber(L, 3) || !lua_isnumber(L, 4)
) {
return luaL_error(L, "color(r, g, b, a) requires four number arguments.");
}
colorchannel8_t r = (colorchannel8_t)lua_tonumber(L, 1);
colorchannel8_t g = (colorchannel8_t)lua_tonumber(L, 2);
colorchannel8_t b = (colorchannel8_t)lua_tonumber(L, 3);
colorchannel8_t a = (colorchannel8_t)lua_tonumber(L, 4);
// Create color_t as lua memory, and push metatable
color_t *color = (color_t *)lua_newuserdata(L, sizeof(color_t));
luaL_getmetatable(L, "color_mt");
lua_setmetatable(L, -2);
// Initial values.
color->r = r;
color->g = g;
color->b = b;
color->a = a;
return 1;
}
int moduleColorIndex(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL.");
const char_t *key = lua_tostring(L, 2);
assertStrLenMin(key, 1, "Key cannot be empty.");
const color_t *color = (const color_t*)luaL_checkudata(L, 1, "color_mt");
assertNotNull(color, "Color struct cannot be NULL.");
if(stringCompare(key, "r") == 0) {
lua_pushnumber(L, color->r);
return 1;
}
if(stringCompare(key, "g") == 0) {
lua_pushnumber(L, color->g);
return 1;
}
if(stringCompare(key, "b") == 0) {
lua_pushnumber(L, color->b);
return 1;
}
if(stringCompare(key, "a") == 0) {
lua_pushnumber(L, color->a);
return 1;
}
lua_pushnil(L);
return 1;
}
int moduleColorNewIndex(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL.");
const char_t *key = lua_tostring(L, 2);
assertStrLenMin(key, 1, "Key cannot be empty.");
color_t *color = (color_t*)luaL_checkudata(L, 1, "color_mt");
assertNotNull(color, "Color struct cannot be NULL.");
if(stringCompare(key, "r") == 0) {
if(!lua_isnumber(L, 3)) {
return luaL_error(L, "color channel r must be a number.");
}
color->r = (colorchannel8_t)lua_tonumber(L, 3);
return 0;
} else if(stringCompare(key, "g") == 0) {
if(!lua_isnumber(L, 3)) {
return luaL_error(L, "color channel g must be a number.");
}
color->g = (colorchannel8_t)lua_tonumber(L, 3);
return 0;
} else if(stringCompare(key, "b") == 0) {
if(!lua_isnumber(L, 3)) {
return luaL_error(L, "color channel b must be a number.");
}
color->b = (colorchannel8_t)lua_tonumber(L, 3);
return 0;
} else if(stringCompare(key, "a") == 0) {
if(!lua_isnumber(L, 3)) {
return luaL_error(L, "color channel a must be a number.");
}
color->a = (colorchannel8_t)lua_tonumber(L, 3);
return 0;
}
return 0;
}
int moduleColorToString(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL.");
const color_t *color = (const color_t*)luaL_checkudata(L, 1, "color_mt");
assertNotNull(color, "Color struct cannot be NULL.");
lua_pushfstring(
L, "color(r=%d, g=%d, b=%d, a=%d)",
color->r, color->g, color->b, color->a
);
return 1;
}
int moduleColorRainbow(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL.");
// Allow time offset
float_t t = TIME.time;
if(lua_gettop(L) >= 1) {
if(!lua_isnumber(L, 1)) {
return luaL_error(L, "Rainbow time offset must be a number.");
}
t += (float_t)lua_tonumber(L, 1);
}
// Allow speed multiplier
if(lua_gettop(L) >= 2) {
if(!lua_isnumber(L, 2)) {
return luaL_error(L, "Rainbow speed multiplier must be a number.");
}
t *= (float_t)lua_tonumber(L, 2);
}
// Generate rainbow based on time.
color_t *color = (color_t *)lua_newuserdata(L, sizeof(color_t));
color->r = (colorchannel8_t)((sinf(t) + 1.0f) * 0.5f * 255.0f);
color->g = (colorchannel8_t)((sinf(t + 2.0f) + 1.0f) * 0.5f * 255.0f);
color->b = (colorchannel8_t)((sinf(t + 4.0f) + 1.0f) * 0.5f * 255.0f);
color->a = 255;
// Set metatable
luaL_getmetatable(L, "color_mt");
lua_setmetatable(L, -2);
return 1;
}
+41 -160
View File
@@ -6,169 +6,50 @@
*/
#pragma once
#include "script/module/modulebase.h"
#include "display/color.h"
#include "time/time.h"
#include "script/scriptproto.h"
#include "script/scriptcontext.h"
static scriptproto_t MODULE_COLOR_PROTO;
/**
* Registers the color module with the given script context.
*
* @param context The script context to register the module with.
*/
void moduleColor(scriptcontext_t *context);
static inline color_t * moduleColorGet(
const jerry_call_info_t *callInfo
) {
return (color_t*)scriptProtoGetValue(
&MODULE_COLOR_PROTO, callInfo->this_value
);
}
/**
* Lua function to create a color.
*
* @param L The Lua state.
* @return Number of return values.
*/
int moduleColorFuncColor(lua_State *L);
moduleBaseFunction(moduleColorGetR) {
color_t *c = moduleColorGet(callInfo);
return c ? jerry_number(c->r) : jerry_undefined();
}
/**
* Index function for the color structure.
* @param L The Lua state.
* @return Number of return values.
*/
int moduleColorIndex(lua_State *L);
moduleBaseFunction(moduleColorGetG) {
color_t *c = moduleColorGet(callInfo);
return c ? jerry_number(c->g) : jerry_undefined();
}
/**
* New index function for the color structure.
*
* @param L The Lua state.
* @return Number of return values.
*/
int moduleColorNewIndex(lua_State *L);
moduleBaseFunction(moduleColorGetB) {
color_t *c = moduleColorGet(callInfo);
return c ? jerry_number(c->b) : jerry_undefined();
}
/**
* Color to string method for script
*
* @param L The Lua state.
* @return Number of return values.
*/
int moduleColorToString(lua_State *L);
moduleBaseFunction(moduleColorGetA) {
color_t *c = moduleColorGet(callInfo);
return c ? jerry_number(c->a) : jerry_undefined();
}
moduleBaseFunction(moduleColorSetR) {
if(argc < 1) {
return moduleBaseThrow("Expected at least 1 argument");
}
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) {
if(argc < 1) {
return moduleBaseThrow("Expected at least 1 argument");
}
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) {
if(argc < 1) {
return moduleBaseThrow("Expected at least 1 argument");
}
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) {
if(argc < 1) {
return moduleBaseThrow("Expected at least 1 argument");
}
moduleBaseRequireNumber(0);
color_t *c = moduleColorGet(callInfo);
if(!c) return jerry_undefined();
c->a = (colorchannel8_t)jerry_value_as_number(args[0]);
return args[0];
}
moduleBaseFunction(moduleColorToString) {
color_t *c = moduleColorGet(callInfo);
if(!c) return jerry_undefined();
char_t buf[64];
stringFormat(
buf, sizeof(buf),
"{ \"r\": %d, \"g\": %d, \"b\": %d, \"a\": %d }",
(int32_t)c->r, (int32_t)c->g,
(int32_t)c->b, (int32_t)c->a
);
return jerry_string_sz(buf);
}
static jerry_value_t moduleColorMakeObject(color_t color) {
return scriptProtoCreateValue(&MODULE_COLOR_PROTO, &color);
}
moduleBaseFunction(moduleColorConstructor) {
if(argc > 0 && !jerry_value_is_number(args[0])) {
return moduleBaseThrow("Color: r must be a number");
}
if(argc > 1 && !jerry_value_is_number(args[1])) {
return moduleBaseThrow("Color: g must be a number");
}
if(argc > 2 && !jerry_value_is_number(args[2])) {
return moduleBaseThrow("Color: b must be a number");
}
if(argc > 3 && !jerry_value_is_number(args[3])) {
return moduleBaseThrow("Color: a must be a number");
}
color_t c;
c.r = argc > 0 ? (colorchannel8_t)jerry_value_as_number(args[0]) : 255;
c.g = argc > 1 ? (colorchannel8_t)jerry_value_as_number(args[1]) : 255;
c.b = argc > 2 ? (colorchannel8_t)jerry_value_as_number(args[2]) : 255;
c.a = argc > 3 ? (colorchannel8_t)jerry_value_as_number(args[3]) : 255;
return moduleColorMakeObject(c);
}
moduleBaseFunction(moduleColorRainbow) {
float_t t;
if(argc >= 1 && jerry_value_is_number(args[0])) {
t = (float_t)jerry_value_as_number(args[0]);
} else {
t = TIME.time * 4.0f;
}
if(argc >= 2 && jerry_value_is_number(args[1])) {
t *= (float_t)jerry_value_as_number(args[1]);
}
color_t c;
c.r = (colorchannel8_t)((sinf(t) + 1.0f) * 0.5f * 255.0f);
c.g = (colorchannel8_t)((sinf(t + 2.0f) + 1.0f) * 0.5f * 255.0f);
c.b = (colorchannel8_t)((sinf(t + 4.0f) + 1.0f) * 0.5f * 255.0f);
c.a = 255;
return moduleColorMakeObject(c);
}
static void moduleColor(void) {
scriptProtoInit(
&MODULE_COLOR_PROTO, "Color", sizeof(color_t), moduleColorConstructor
);
scriptProtoDefineProp(
&MODULE_COLOR_PROTO, "r", moduleColorGetR, moduleColorSetR
);
scriptProtoDefineProp(
&MODULE_COLOR_PROTO, "g", moduleColorGetG, moduleColorSetG
);
scriptProtoDefineProp(
&MODULE_COLOR_PROTO, "b", moduleColorGetB, moduleColorSetB
);
scriptProtoDefineProp(
&MODULE_COLOR_PROTO, "a", moduleColorGetA, moduleColorSetA
);
scriptProtoDefineStaticFunc(
&MODULE_COLOR_PROTO, "rainbow", moduleColorRainbow
);
scriptProtoDefineToString(&MODULE_COLOR_PROTO, moduleColorToString);
moduleBaseEval(COLOR_SCRIPT);
}
/**
* Lua function to create a rainbow color based on time.
*
* @param L The Lua state.
* @return Number of return values.
*/
int moduleColorRainbow(lua_State *L);
+283
View File
@@ -0,0 +1,283 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "moduleglm.h"
#include "assert/assert.h"
#include "util/string.h"
#include "util/memory.h"
void moduleGLM(scriptcontext_t *context) {
assertNotNull(context, "Context cannot be NULL.");
// Create metatable for vec3 structure.
if(luaL_newmetatable(context->luaState, "vec3_mt")) {
// Metatable methods
lua_pushcfunction(context->luaState, moduleVec3Index);
lua_setfield(context->luaState, -2, "__index");
lua_pushcfunction(context->luaState, moduleVec3NewIndex);
lua_setfield(context->luaState, -2, "__newindex");
lua_pushcfunction(context->luaState, moduleVec3ToString);
lua_setfield(context->luaState, -2, "__tostring");
}
lua_pop(context->luaState, 1);
if(luaL_newmetatable(context->luaState, "vec4_mt")) {
// Metatable methods
lua_pushcfunction(context->luaState, moduleVec4Index);
lua_setfield(context->luaState, -2, "__index");
lua_pushcfunction(context->luaState, moduleVec4NewIndex);
lua_setfield(context->luaState, -2, "__newindex");
lua_pushcfunction(context->luaState, moduleVec4ToString);
lua_setfield(context->luaState, -2, "__tostring");
}
lua_pop(context->luaState, 1);
lua_register(context->luaState, "vec3", moduleVec3Create);
}
int moduleVec3Create(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
vec3 *v = (vec3 *)lua_newuserdata(l, sizeof(vec3));
memoryZero(v, sizeof(vec3));
// May be expecting between 1 and 3 values.
int top = lua_gettop(l);
if(top >= 1) {
if(!lua_isnumber(l, 1)) {
luaL_error(l, "Vec3 x component must be a number.");
}
(*v)[0] = (float_t)lua_tonumber(l, 1);
}
if(top >= 2) {
if(!lua_isnumber(l, 2)) {
luaL_error(l, "Vec3 y component must be a number.");
}
(*v)[1] = (float_t)lua_tonumber(l, 2);
}
if(top >= 3) {
if(!lua_isnumber(l, 3)) {
luaL_error(l, "Vec3 z component must be a number.");
}
(*v)[2] = (float_t)lua_tonumber(l, 3);
}
// Set metatable
luaL_getmetatable(l, "vec3_mt");
lua_setmetatable(l, -2);
return 1;
}
int moduleVec3Index(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
const char_t *key = lua_tostring(l, 2);
assertStrLenMin(key, 1, "Key cannot be empty.");
vec3 *vec = (vec3 *)luaL_checkudata(l, 1, "vec3_mt");
assertNotNull(vec, "Vec3 pointer cannot be NULL.");
if(stringCompare(key, "x") == 0) {
lua_pushnumber(l, (*vec)[0]);
return 1;
} else if(stringCompare(key, "y") == 0) {
lua_pushnumber(l, (*vec)[1]);
return 1;
} else if(stringCompare(key, "z") == 0) {
lua_pushnumber(l, (*vec)[2]);
return 1;
}
}
int moduleVec3NewIndex(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
const char_t *key = luaL_checkstring(l, 2);
assertStrLenMin(key, 1, "Key cannot be empty.");
vec3 *vec = (vec3 *)luaL_checkudata(l, 1, "vec3_mt");
assertNotNull(vec, "Vec3 pointer cannot be NULL.");
if(stringCompare(key, "x") == 0) {
if(!lua_isnumber(l, 3)) {
luaL_error(l, "Vec3 x component must be a number.");
}
(*vec)[0] = (float_t)lua_tonumber(l, 3);
return 0;
} else if(stringCompare(key, "y") == 0) {
if(!lua_isnumber(l, 3)) {
luaL_error(l, "Vec3 y component must be a number.");
}
(*vec)[1] = (float_t)lua_tonumber(l, 3);
return 0;
} else if(stringCompare(key, "z") == 0) {
if(!lua_isnumber(l, 3)) {
luaL_error(l, "Vec3 z component must be a number.");
}
(*vec)[2] = (float_t)lua_tonumber(l, 3);
return 0;
}
luaL_error(l, "Invalid key for vec3: %s", key);
return 0;
}
int moduleVec3ToString(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
vec3 *vec = (vec3 *)luaL_checkudata(l, 1, "vec3_mt");
assertNotNull(vec, "Vec3 pointer cannot be NULL.");
char buf[128];
snprintf(
buf, sizeof(buf),
"vec3(%.3f, %.3f, %.3f)",
(*vec)[0], (*vec)[1], (*vec)[2]
);
lua_pushstring(l, buf);
return 1;
}
int moduleVec4Create(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
vec4 *v = (vec4 *)lua_newuserdata(l, sizeof(vec4));
memoryZero(v, sizeof(vec4));
// May be expecting between 1 and 4 values.
int top = lua_gettop(l);
if(top >= 1) {
if(!lua_isnumber(l, 1)) {
luaL_error(l, "Vec4 x component must be a number.");
}
(*v)[0] = (float_t)lua_tonumber(l, 1);
}
if(top >= 2) {
if(!lua_isnumber(l, 2)) {
luaL_error(l, "Vec4 y component must be a number.");
}
(*v)[1] = (float_t)lua_tonumber(l, 2);
}
if(top >= 3) {
if(!lua_isnumber(l, 3)) {
luaL_error(l, "Vec4 z component must be a number.");
}
(*v)[2] = (float_t)lua_tonumber(l, 3);
}
if(top >= 4) {
if(!lua_isnumber(l, 4)) {
luaL_error(l, "Vec4 w component must be a number.");
}
(*v)[3] = (float_t)lua_tonumber(l, 4);
}
// Set metatable
luaL_getmetatable(l, "vec4_mt");
lua_setmetatable(l, -2);
return 1;
}
int moduleVec4Index(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
const char_t *key = lua_tostring(l, 2);
assertStrLenMin(key, 1, "Key cannot be empty.");
vec4 *vec = (vec4 *)luaL_checkudata(l, 1, "vec4_mt");
assertNotNull(vec, "Vec4 pointer cannot be NULL.");
if(stringCompare(key, "x") == 0) {
lua_pushnumber(l, (*vec)[0]);
return 1;
} else if(stringCompare(key, "y") == 0) {
lua_pushnumber(l, (*vec)[1]);
return 1;
} else if(stringCompare(key, "z") == 0) {
lua_pushnumber(l, (*vec)[2]);
return 1;
} else if(stringCompare(key, "w") == 0) {
lua_pushnumber(l, (*vec)[3]);
return 1;
} else if(stringCompare(key, "u0") == 0) {
lua_pushnumber(l, (*vec)[0]);
return 1;
} else if(stringCompare(key, "v0") == 0) {
lua_pushnumber(l, (*vec)[1]);
return 1;
} else if(stringCompare(key, "u1") == 0) {
lua_pushnumber(l, (*vec)[2]);
return 1;
} else if(stringCompare(key, "v1") == 0) {
lua_pushnumber(l, (*vec)[3]);
return 1;
}
lua_pushnil(l);
return 1;
}
int moduleVec4NewIndex(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
const char_t *key = luaL_checkstring(l, 2);
assertStrLenMin(key, 1, "Key cannot be empty.");
vec4 *vec = (vec4 *)luaL_checkudata(l, 1, "vec4_mt");
assertNotNull(vec, "Vec4 pointer cannot be NULL.");
if(stringCompare(key, "x") == 0) {
if(!lua_isnumber(l, 3)) {
luaL_error(l, "Vec4 x component must be a number.");
}
(*vec)[0] = (float_t)lua_tonumber(l, 3);
return 0;
} else if(stringCompare(key, "y") == 0) {
if(!lua_isnumber(l, 3)) {
luaL_error(l, "Vec4 y component must be a number.");
}
(*vec)[1] = (float_t)lua_tonumber(l, 3);
return 0;
} else if(stringCompare(key, "z") == 0) {
if(!lua_isnumber(l, 3)) {
luaL_error(l, "Vec4 z component must be a number.");
}
(*vec)[2] = (float_t)lua_tonumber(l, 3);
return 0;
} else if(stringCompare(key, "w") == 0) {
if(!lua_isnumber(l, 3)) {
luaL_error(l, "Vec4 w component must be a number.");
}
(*vec)[3] = (float_t)lua_tonumber(l, 3);
return 0;
}
luaL_error(l, "Invalid key for vec4: %s", key);
return 0;
}
int moduleVec4ToString(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
vec4 *vec = (vec4 *)luaL_checkudata(l, 1, "vec4_mt");
assertNotNull(vec, "Vec4 pointer cannot be NULL.");
char buf[128];
snprintf(
buf, sizeof(buf),
"vec4(%.3f, %.3f, %.3f, %.3f)",
(*vec)[0], (*vec)[1], (*vec)[2], (*vec)[3]
);
lua_pushstring(l, buf);
return 1;
}
@@ -0,0 +1,80 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptcontext.h"
/**
* Registers the GLM module with the given script context.
*
* @param context The script context to register the module with.
*/
void moduleGLM(scriptcontext_t *context);
/**
* Creates a new vec3 structure in Lua.
*
* @param l The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleVec3Create(lua_State *l);
/**
* Lua __index metamethod for vec3 structure.
*
* @param l The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleVec3Index(lua_State *l);
/**
* Lua __newindex metamethod for vec3 structure.
*
* @param l The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleVec3NewIndex(lua_State *l);
/**
* Lua __tostring metamethod for vec3 structure.
*
* @param l The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleVec3ToString(lua_State *l);
/**
* Creates a new vec4 structure in Lua.
*
* @param l The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleVec4Create(lua_State *l);
/**
* Lua __index metamethod for vec4 structure.
*
* @param l The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleVec4Index(lua_State *l);
/**
* Lua __newindex metamethod for vec4 structure.
*
* @param l The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleVec4NewIndex(lua_State *l);
/**
* Lua __tostring metamethod for vec4 structure.
*
* @param l The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleVec4ToString(lua_State *l);
-452
View File
@@ -1,452 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/module/modulebase.h"
#include "script/scriptproto.h"
#include "script/module/math/modulevec3ref.h"
#include "display/mesh/mesh.h"
#include "display/mesh/cube.h"
#include "display/mesh/quad.h"
#include "display/mesh/sphere.h"
#include "display/mesh/plane.h"
#include "display/mesh/capsule.h"
#include "display/mesh/triprism.h"
typedef struct {
mesh_t mesh;
meshvertex_t *vertices;
int32_t vertexCount;
bool_t initialized;
} meshscript_t;
typedef struct {
meshvertex_t *vertex;
} meshvertexscript_t;
static scriptproto_t MODULE_MESH_PROTO;
static scriptproto_t MODULE_MESH_VERTEX_PROTO;
static inline meshscript_t * moduleMeshGet(
const jerry_call_info_t *callInfo
) {
return (meshscript_t*)scriptProtoGetValue(
&MODULE_MESH_PROTO, callInfo->this_value
);
}
static inline meshscript_t * moduleMeshFrom(const jerry_value_t val) {
return (meshscript_t*)scriptProtoGetValue(&MODULE_MESH_PROTO, val);
}
static void moduleMeshFreeData(
void *ptr,
jerry_object_native_info_t *info
) {
(void)info;
meshscript_t *ms = (meshscript_t*)ptr;
if(ms->initialized) (void)meshDispose(&ms->mesh);
if(ms->vertices) memoryFree(ms->vertices);
memoryFree(ptr);
}
// Creates a new JS Mesh object from an already-filled heap-allocated meshscript_t.
static jerry_value_t moduleMeshWrapNew(meshscript_t *ms) {
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &MODULE_MESH_PROTO.info, ms);
jerry_object_set_proto(obj, MODULE_MESH_PROTO.prototype);
jerry_value_t arr = jerry_array((jerry_length_t)ms->vertexCount);
for(int32_t i = 0; i < ms->vertexCount; i++) {
meshvertexscript_t mv = { .vertex = &ms->vertices[i] };
jerry_value_t vobj = scriptProtoCreateValue(&MODULE_MESH_VERTEX_PROTO, &mv);
jerry_value_t res = jerry_object_set_index(arr, (uint32_t)i, vobj);
jerry_value_free(res);
jerry_value_free(vobj);
}
jerry_value_t key = jerry_string_sz("_verts");
jerry_object_set(obj, key, arr);
jerry_value_free(key);
jerry_value_free(arr);
return obj;
}
// Allocates a meshscript_t for the given vertex count and buffers it.
// Caller is responsible for populating ms->vertices before calling meshInit.
static meshscript_t * moduleMeshAlloc(const int32_t vertexCount) {
meshscript_t *ms = (meshscript_t*)memoryAllocate(sizeof(meshscript_t));
memoryZero(ms, sizeof(meshscript_t));
ms->vertices = (meshvertex_t*)memoryAllocate(
(size_t)vertexCount * sizeof(meshvertex_t)
);
memoryZero(ms->vertices, (size_t)vertexCount * sizeof(meshvertex_t));
ms->vertexCount = vertexCount;
return ms;
}
// Finalizes a meshscript_t: uploads to GPU and wraps in a JS object.
static jerry_value_t moduleMeshFinalize(
meshscript_t *ms,
const meshprimitivetype_t type
) {
(void)meshInit(&ms->mesh, type, ms->vertexCount, ms->vertices);
ms->initialized = true;
return moduleMeshWrapNew(ms);
}
// ---- MeshVertex ----
moduleBaseFunction(moduleMeshVertexGetPosition) {
meshvertexscript_t *mv = (meshvertexscript_t*)scriptProtoGetValue(
&MODULE_MESH_VERTEX_PROTO, callInfo->this_value
);
if(!mv) return jerry_undefined();
return moduleVec3RefPush(mv->vertex->pos, NULL, NULL);
}
moduleBaseFunction(moduleMeshVertexSetPosition) {
if(argc < 1) return moduleBaseThrow("Expected position argument");
meshvertexscript_t *mv = (meshvertexscript_t*)scriptProtoGetValue(
&MODULE_MESH_VERTEX_PROTO, callInfo->this_value
);
if(!mv) return jerry_undefined();
vec3 v;
if(!moduleVec3AnyCheck(args[0], v)) return moduleBaseThrow("Expected Vec3");
glm_vec3_copy(v, mv->vertex->pos);
return jerry_undefined();
}
// ---- Mesh instance ----
moduleBaseFunction(moduleMeshConstructor) {
if(argc < 1) return moduleBaseThrow("Expected vertex count");
moduleBaseRequireNumber(0);
int32_t vertexCount = (int32_t)jerry_value_as_number(args[0]);
if(vertexCount <= 0) return moduleBaseThrow("Vertex count must be > 0");
meshscript_t *ms = moduleMeshAlloc(vertexCount);
jerry_object_set_native_ptr(
callInfo->this_value, &MODULE_MESH_PROTO.info, ms
);
jerry_value_t arr = jerry_array((jerry_length_t)vertexCount);
for(int32_t i = 0; i < vertexCount; i++) {
meshvertexscript_t mv = { .vertex = &ms->vertices[i] };
jerry_value_t vobj = scriptProtoCreateValue(&MODULE_MESH_VERTEX_PROTO, &mv);
jerry_value_t res = jerry_object_set_index(arr, (uint32_t)i, vobj);
jerry_value_free(res);
jerry_value_free(vobj);
}
jerry_value_t key = jerry_string_sz("_verts");
jerry_object_set(callInfo->this_value, key, arr);
jerry_value_free(key);
jerry_value_free(arr);
return jerry_undefined();
}
moduleBaseFunction(moduleMeshGetVertices) {
jerry_value_t key = jerry_string_sz("_verts");
jerry_value_t arr = jerry_object_get(callInfo->this_value, key);
jerry_value_free(key);
return arr;
}
moduleBaseFunction(moduleMeshGetVertexCount) {
meshscript_t *ms = moduleMeshGet(callInfo);
if(!ms) return jerry_undefined();
return jerry_number((double)ms->vertexCount);
}
moduleBaseFunction(moduleMeshFlush) {
meshscript_t *ms = moduleMeshGet(callInfo);
if(!ms) return jerry_undefined();
if(!ms->initialized) {
(void)meshInit(
&ms->mesh,
MESH_PRIMITIVE_TYPE_TRIANGLES,
ms->vertexCount,
ms->vertices
);
ms->initialized = true;
} else {
(void)meshFlush(&ms->mesh, 0, -1);
}
return jerry_undefined();
}
moduleBaseFunction(moduleMeshDispose) {
meshscript_t *ms = moduleMeshGet(callInfo);
if(!ms) return jerry_undefined();
if(ms->initialized) {
(void)meshDispose(&ms->mesh);
ms->initialized = false;
}
if(ms->vertices) {
memoryFree(ms->vertices);
ms->vertices = NULL;
}
return jerry_undefined();
}
moduleBaseFunction(moduleMeshToString) {
meshscript_t *ms = moduleMeshGet(callInfo);
if(!ms) return jerry_string_sz("Mesh(?)");
char_t buf[64];
stringFormat(buf, sizeof(buf), "Mesh(%d)", ms->vertexCount);
return jerry_string_sz(buf);
}
// ---- Static defaults (engine-owned, not GC'd) ----
moduleBaseFunction(moduleMeshDefaultCube) {
return moduleBaseWrapPointer(&CUBE_MESH_SIMPLE);
}
moduleBaseFunction(moduleMeshDefaultQuad) {
return moduleBaseWrapPointer(&QUAD_MESH_SIMPLE);
}
moduleBaseFunction(moduleMeshDefaultSphere) {
return moduleBaseWrapPointer(&SPHERE_MESH_SIMPLE);
}
moduleBaseFunction(moduleMeshDefaultPlane) {
return moduleBaseWrapPointer(&PLANE_MESH_SIMPLE);
}
moduleBaseFunction(moduleMeshDefaultCapsule) {
return moduleBaseWrapPointer(&CAPSULE_MESH_SIMPLE);
}
moduleBaseFunction(moduleMeshDefaultTriPrism) {
return moduleBaseWrapPointer(&TRIPRISM_MESH_SIMPLE);
}
// ---- Static factory methods ----
// Mesh.createCube(min?, max?) — defaults to (-0.5,-0.5,-0.5)..(0.5,0.5,0.5)
moduleBaseFunction(moduleMeshCreateCube) {
vec3 min = { -0.5f, -0.5f, -0.5f };
vec3 max = { 0.5f, 0.5f, 0.5f };
if(argc >= 2) {
if(!moduleVec3AnyCheck(args[0], min)) {
return moduleBaseThrow("Mesh.createCube: expected Vec3 min");
}
if(!moduleVec3AnyCheck(args[1], max)) {
return moduleBaseThrow("Mesh.createCube: expected Vec3 max");
}
}
meshscript_t *ms = moduleMeshAlloc(CUBE_VERTEX_COUNT);
cubeBuffer(
ms->vertices, min, max
#if MESH_ENABLE_COLOR
, COLOR_WHITE_4B
#endif
);
return moduleMeshFinalize(ms, CUBE_PRIMITIVE_TYPE);
}
// Mesh.createQuad(minX, minY, maxX, maxY) — 2D XY quad, u0-u1=0-1 v0-v1=0-1
moduleBaseFunction(moduleMeshCreateQuad) {
float_t minX = -0.5f, minY = -0.5f, maxX = 0.5f, maxY = 0.5f;
if(argc >= 4) {
if(!jerry_value_is_number(args[0]) || !jerry_value_is_number(args[1]) ||
!jerry_value_is_number(args[2]) || !jerry_value_is_number(args[3])) {
return moduleBaseThrow("Mesh.createQuad: expected (minX, minY, maxX, maxY)");
}
minX = (float_t)jerry_value_as_number(args[0]);
minY = (float_t)jerry_value_as_number(args[1]);
maxX = (float_t)jerry_value_as_number(args[2]);
maxY = (float_t)jerry_value_as_number(args[3]);
}
meshscript_t *ms = moduleMeshAlloc(QUAD_VERTEX_COUNT);
quadBuffer(
ms->vertices,
minX, minY, maxX, maxY,
0.0f, 0.0f, 1.0f, 1.0f
#if MESH_ENABLE_COLOR
, COLOR_WHITE_4B
#endif
);
return moduleMeshFinalize(ms, QUAD_PRIMITIVE_TYPE);
}
// Mesh.createSphere(radius?, stacks?, sectors?)
moduleBaseFunction(moduleMeshCreateSphere) {
float_t radius = 0.5f;
int32_t stacks = SPHERE_STACKS;
int32_t sectors = SPHERE_SECTORS;
if(argc >= 1 && jerry_value_is_number(args[0])) {
radius = (float_t)jerry_value_as_number(args[0]);
}
if(argc >= 2 && jerry_value_is_number(args[1])) {
stacks = (int32_t)jerry_value_as_number(args[1]);
}
if(argc >= 3 && jerry_value_is_number(args[2])) {
sectors = (int32_t)jerry_value_as_number(args[2]);
}
int32_t vertexCount = stacks * sectors * 6;
vec3 center = { 0.0f, 0.0f, 0.0f };
meshscript_t *ms = moduleMeshAlloc(vertexCount);
sphereBuffer(
ms->vertices, center, radius, stacks, sectors
#if MESH_ENABLE_COLOR
, COLOR_WHITE_4B
#endif
);
return moduleMeshFinalize(ms, SPHERE_PRIMITIVE_TYPE);
}
// Mesh.createPlane(width?, height?) — XZ-aligned, centered at origin
moduleBaseFunction(moduleMeshCreatePlane) {
float_t width = 1.0f, height = 1.0f;
if(argc >= 1 && jerry_value_is_number(args[0])) {
width = (float_t)jerry_value_as_number(args[0]);
}
if(argc >= 2 && jerry_value_is_number(args[1])) {
height = (float_t)jerry_value_as_number(args[1]);
}
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 };
meshscript_t *ms = moduleMeshAlloc(PLANE_VERTEX_COUNT);
planeBuffer(
ms->vertices, PLANE_AXIS_XZ, min, max
#if MESH_ENABLE_COLOR
, COLOR_WHITE_4B
#endif
, uvMin, uvMax
);
return moduleMeshFinalize(ms, PLANE_PRIMITIVE_TYPE);
}
// Mesh.createCapsule(radius?, halfHeight?, capRings?, sectors?)
moduleBaseFunction(moduleMeshCreateCapsule) {
float_t radius = 0.5f;
float_t halfHeight = 0.5f;
int32_t capRings = CAPSULE_CAP_RINGS;
int32_t sectors = CAPSULE_SECTORS;
if(argc >= 1 && jerry_value_is_number(args[0])) {
radius = (float_t)jerry_value_as_number(args[0]);
}
if(argc >= 2 && jerry_value_is_number(args[1])) {
halfHeight = (float_t)jerry_value_as_number(args[1]);
}
if(argc >= 3 && jerry_value_is_number(args[2])) {
capRings = (int32_t)jerry_value_as_number(args[2]);
}
if(argc >= 4 && jerry_value_is_number(args[3])) {
sectors = (int32_t)jerry_value_as_number(args[3]);
}
int32_t vertexCount = (2 * capRings + 1) * sectors * 6;
vec3 center = { 0.0f, 0.0f, 0.0f };
meshscript_t *ms = moduleMeshAlloc(vertexCount);
capsuleBuffer(
ms->vertices, center, radius, halfHeight, capRings, sectors
#if MESH_ENABLE_COLOR
, COLOR_WHITE_4B
#endif
);
return moduleMeshFinalize(ms, CAPSULE_PRIMITIVE_TYPE);
}
// Mesh.createTriPrism(x0, y0, x1, y1, x2, y2, minZ, maxZ)
moduleBaseFunction(moduleMeshCreateTriPrism) {
if(argc < 8) {
return moduleBaseThrow(
"Mesh.createTriPrism: expected (x0,y0, x1,y1, x2,y2, minZ, maxZ)"
);
}
float_t x0 = (float_t)jerry_value_as_number(args[0]);
float_t y0 = (float_t)jerry_value_as_number(args[1]);
float_t x1 = (float_t)jerry_value_as_number(args[2]);
float_t y1 = (float_t)jerry_value_as_number(args[3]);
float_t x2 = (float_t)jerry_value_as_number(args[4]);
float_t y2 = (float_t)jerry_value_as_number(args[5]);
float_t minZ = (float_t)jerry_value_as_number(args[6]);
float_t maxZ = (float_t)jerry_value_as_number(args[7]);
meshscript_t *ms = moduleMeshAlloc(TRIPRISM_VERTEX_COUNT);
triPrismBuffer(
ms->vertices, x0, y0, x1, y1, x2, y2, minZ, maxZ
#if MESH_ENABLE_COLOR
, COLOR_WHITE_4B
#endif
);
return moduleMeshFinalize(ms, TRIPRISM_PRIMITIVE_TYPE);
}
// ---- Registration ----
static void moduleMesh(void) {
// MeshVertex - internal type, no global constructor
scriptProtoInit(
&MODULE_MESH_VERTEX_PROTO, NULL,
sizeof(meshvertexscript_t), NULL
);
scriptProtoDefineProp(
&MODULE_MESH_VERTEX_PROTO, "position",
moduleMeshVertexGetPosition, moduleMeshVertexSetPosition
);
// Mesh - global constructor: new Mesh(vertexCount)
scriptProtoInit(
&MODULE_MESH_PROTO, "Mesh",
sizeof(meshscript_t), moduleMeshConstructor
);
MODULE_MESH_PROTO.info.free_cb = moduleMeshFreeData;
scriptProtoDefineToString(&MODULE_MESH_PROTO, moduleMeshToString);
scriptProtoDefineProp(
&MODULE_MESH_PROTO, "vertices",
moduleMeshGetVertices, NULL
);
scriptProtoDefineProp(
&MODULE_MESH_PROTO, "vertexCount",
moduleMeshGetVertexCount, NULL
);
scriptProtoDefineFunc(&MODULE_MESH_PROTO, "flush", moduleMeshFlush);
scriptProtoDefineFunc(&MODULE_MESH_PROTO, "dispose", moduleMeshDispose);
// Static default mesh references
scriptProtoDefineStaticProp(
&MODULE_MESH_PROTO, "DEFAULT_CUBE", moduleMeshDefaultCube, NULL
);
scriptProtoDefineStaticProp(
&MODULE_MESH_PROTO, "DEFAULT_QUAD", moduleMeshDefaultQuad, NULL
);
scriptProtoDefineStaticProp(
&MODULE_MESH_PROTO, "DEFAULT_SPHERE", moduleMeshDefaultSphere, NULL
);
scriptProtoDefineStaticProp(
&MODULE_MESH_PROTO, "DEFAULT_PLANE", moduleMeshDefaultPlane, NULL
);
scriptProtoDefineStaticProp(
&MODULE_MESH_PROTO, "DEFAULT_CAPSULE", moduleMeshDefaultCapsule, NULL
);
scriptProtoDefineStaticProp(
&MODULE_MESH_PROTO, "DEFAULT_TRIPRISM", moduleMeshDefaultTriPrism, NULL
);
// Static factory methods
scriptProtoDefineStaticFunc(
&MODULE_MESH_PROTO, "createCube", moduleMeshCreateCube
);
scriptProtoDefineStaticFunc(
&MODULE_MESH_PROTO, "createQuad", moduleMeshCreateQuad
);
scriptProtoDefineStaticFunc(
&MODULE_MESH_PROTO, "createSphere", moduleMeshCreateSphere
);
scriptProtoDefineStaticFunc(
&MODULE_MESH_PROTO, "createPlane", moduleMeshCreatePlane
);
scriptProtoDefineStaticFunc(
&MODULE_MESH_PROTO, "createCapsule", moduleMeshCreateCapsule
);
scriptProtoDefineStaticFunc(
&MODULE_MESH_PROTO, "createTriPrism", moduleMeshCreateTriPrism
);
}
@@ -0,0 +1,43 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "modulescreen.h"
#include "assert/assert.h"
#include "display/screen/screen.h"
void moduleScreen(scriptcontext_t *context) {
assertNotNull(context, "Context cannot be NULL.");
lua_register(context->luaState, "screenGetWidth", moduleScreenGetWidth);
lua_register(context->luaState, "screenGetHeight", moduleScreenGetHeight);
lua_register(context->luaState, "screenSetBackground", moduleScreenSetBackground);
}
int moduleScreenGetWidth(lua_State *L) {
assertNotNull(L, "Lua state is null");
lua_pushnumber(L, SCREEN.width);
return 1;
}
int moduleScreenGetHeight(lua_State *L) {
assertNotNull(L, "Lua state is null");
lua_pushnumber(L, SCREEN.height);
return 1;
}
int moduleScreenSetBackground(lua_State *L) {
assertNotNull(L, "Lua state is null");
if(!lua_isuserdata(L, 1)) {
luaL_error(L, "Screen background color must be a color struct");
return 0;
}
color_t *color = (color_t*)luaL_checkudata(L, 1, "color_mt");
SCREEN.background = *color;
return 0;
}
+28 -62
View File
@@ -6,69 +6,35 @@
*/
#pragma once
#include "script/module/display/modulecolor.h"
#include "display/screen/screen.h"
#include "script/scriptcontext.h"
static scriptproto_t MODULE_SCREEN_PROTO;
/**
* Registers the Screen module with the given script context.
*
* @param context The script context to register the module with.
*/
void moduleScreen(scriptcontext_t *context);
moduleBaseFunction(moduleScreenGetWidth) {
return jerry_number(SCREEN.width);
}
/**
* Gets the current screen width.
*
* @param L The Lua state.
* @return Count of return values.
*/
int moduleScreenGetWidth(lua_State *L);
moduleBaseFunction(moduleScreenGetHeight) {
return jerry_number(SCREEN.height);
}
/**
* Gets the current screen height.
*
* @param L The Lua state.
* @return Count of return values.
*/
int moduleScreenGetHeight(lua_State *L);
moduleBaseFunction(moduleScreenGetAspect) {
return jerry_number(SCREEN.aspect);
}
moduleBaseFunction(moduleScreenGetBackground) {
return moduleColorMakeObject(SCREEN.background);
}
moduleBaseFunction(moduleScreenSetBackground) {
if(argc < 1 || !jerry_value_is_object(args[0])) {
return moduleBaseThrow("Screen background color must be a color object");
}
color_t *color = (color_t*)scriptProtoGetValue(
&MODULE_COLOR_PROTO, args[0]
);
if(!color) {
return moduleBaseThrow("Background must be a valid color object");
}
memoryCopy(&SCREEN.background, color, sizeof(color_t));
return jerry_undefined();
}
moduleBaseFunction(moduleScreenToString) {
char_t buf[128];
stringFormat(
buf, sizeof(buf),
"{ \"width\": %d, \"height\": %d, \"aspect\": %.2f }",
SCREEN.width, SCREEN.height, SCREEN.aspect
);
return jerry_string_sz(buf);
}
static void moduleScreen(void) {
scriptProtoInit(
&MODULE_SCREEN_PROTO, "Screen", sizeof(screen_t), NULL
);
scriptProtoDefineProp(
&MODULE_SCREEN_PROTO, "width", moduleScreenGetWidth, NULL
);
scriptProtoDefineProp(
&MODULE_SCREEN_PROTO, "height", moduleScreenGetHeight, NULL
);
scriptProtoDefineProp(
&MODULE_SCREEN_PROTO, "aspect", moduleScreenGetAspect, NULL
);
scriptProtoDefineProp(
&MODULE_SCREEN_PROTO, "background",
moduleScreenGetBackground, moduleScreenSetBackground
);
scriptProtoDefineToString(&MODULE_SCREEN_PROTO, moduleScreenToString);
}
/**
* Sets the screen background color.
*
* @param L The Lua state.
* @return Count of return values.
*/
int moduleScreenSetBackground(lua_State *L);
@@ -0,0 +1,119 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "moduleshader.h"
#include "assert/assert.h"
#include "display/shader/shader.h"
#include "display/shader/shaderunlit.h"
void moduleShader(scriptcontext_t *context) {
assertNotNull(context, "Context cannot be NULL.");
// Shader unlit defs
lua_pushlightuserdata(context->luaState, &SHADER_UNLIT);
lua_setglobal(context->luaState, "SHADER_UNLIT");
lua_pushstring(context->luaState, SHADER_UNLIT_PROJECTION);
lua_setglobal(context->luaState, "SHADER_UNLIT_PROJECTION");
lua_pushstring(context->luaState, SHADER_UNLIT_VIEW);
lua_setglobal(context->luaState, "SHADER_UNLIT_VIEW");
lua_pushstring(context->luaState, SHADER_UNLIT_MODEL);
lua_setglobal(context->luaState, "SHADER_UNLIT_MODEL");
lua_pushstring(context->luaState, SHADER_UNLIT_TEXTURE);
lua_setglobal(context->luaState, "SHADER_UNLIT_TEXTURE");
// Shader methods
lua_register(context->luaState, "shaderBind", moduleShaderBind);
lua_register(context->luaState, "shaderSetMatrix", moduleShaderSetMatrix);
lua_register(context->luaState, "shaderSetTexture", moduleShaderSetTexture);
}
int moduleShaderBind(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
// Should be passed a shader userdata pointer only.
shader_t *shader = (shader_t *)lua_touserdata(l, 1);
assertNotNull(shader, "Shader pointer cannot be NULL.");
errorret_t ret = shaderBind(shader);
if(ret.code != ERROR_OK) {
luaL_error(l, "Failed to bind shader: %s", ret.state->message);
errorCatch(errorPrint(ret));
return 0;
}
return 0;
}
int moduleShaderSetMatrix(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
// Expect shader, string and matrix.
if(!lua_isuserdata(l, 1)) {
luaL_error(l, "First argument must be a shader_mt userdata.");
return 0;
}
if(!lua_isstring(l, 2)) {
luaL_error(l, "Second argument must be a string.");
return 0;
}
if(!lua_isuserdata(l, 3)) {
luaL_error(l, "Third argument must be a mat4_mt userdata.");
return 0;
}
shader_t *shader = (shader_t *)lua_touserdata(l, 1);
assertNotNull(shader, "Shader pointer cannot be NULL.");
const char_t *uniformName = luaL_checkstring(l, 2);
assertStrLenMin(uniformName, 1, "Uniform name cannot be empty.");
mat4 *mat = (mat4 *)lua_touserdata(l, 3);
assertNotNull(mat, "Matrix pointer cannot be NULL.");
errorret_t ret = shaderSetMatrix(shader, uniformName, *mat);
if(ret.code != ERROR_OK) {
luaL_error(l, "Failed to set shader matrix: %s", ret.state->message);
errorCatch(errorPrint(ret));
return 0;
}
return 0;
}
int moduleShaderSetTexture(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
shader_t *shader = (shader_t *)lua_touserdata(l, 1);
assertNotNull(shader, "Shader pointer cannot be NULL.");
const char_t *uniformName = luaL_checkstring(l, 2);
assertStrLenMin(uniformName, 1, "Uniform name cannot be empty.");
texture_t *texture;
// Texture can be Nil or a pointer, if not nil it must be a texture pointer.
if(lua_isnil(l, 3)) {
texture = NULL;
} else if(lua_isuserdata(l, 3)) {
texture = (texture_t *)lua_touserdata(l, 3);
assertNotNull(texture, "Texture pointer cannot be NULL.");
} else {
luaL_error(l, "Third argument must be a texture_mt userdata or nil.");
return 0;
}
errorret_t ret = shaderSetTexture(shader, uniformName, texture);
if(ret.code != ERROR_OK) {
luaL_error(l, "Failed to set shader texture: %s", ret.state->message);
errorCatch(errorPrint(ret));
return 0;
}
return 0;
}
@@ -0,0 +1,42 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptcontext.h"
/**
* Register shader functions to the given script context.
*
* @param context The script context to register shader functions to.
*/
void moduleShader(scriptcontext_t *context);
/**
* Script binding for binding a shader.
*
* @param l The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleShaderBind(lua_State *l);
/**
* Script binding for setting a matrix uniform in a shader.
*
* @param l The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleShaderSetMatrix(lua_State *l);
/**
* Script binding for setting a texture uniform in a shader.
*
* @param l The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleShaderSetTexture(lua_State *l);
errorret_t doThing();
@@ -0,0 +1,105 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "modulespritebatch.h"
#include "display/spritebatch/spritebatch.h"
#include "assert/assert.h"
void moduleSpriteBatch(scriptcontext_t *context) {
lua_register(context->luaState, "spriteBatchFlush", moduleSpriteBatchFlush);
lua_register(context->luaState, "spriteBatchClear", moduleSpriteBatchClear);
lua_register(context->luaState, "spriteBatchPush", moduleSpriteBatchPush);
}
int moduleSpriteBatchFlush(lua_State *L) {
assertNotNull(L, "Lua state is null");
spriteBatchFlush();
return 0;
}
int moduleSpriteBatchClear(lua_State *L) {
assertNotNull(L, "Lua state is null");
spriteBatchClear();
return 0;
}
int moduleSpriteBatchPush(lua_State *L) {
assertNotNull(L, "Lua state is null");
// MinX, MinY, MaxX, MaxY
if(
!lua_isnumber(L, 1) || !lua_isnumber(L, 2) || !lua_isnumber(L, 3) ||
!lua_isnumber(L, 4)
) {
return luaL_error(L, "Sprite coordinates must be numbers");
}
// Color (struct) or nil for white
color_t *color = NULL;
if(lua_gettop(L) < 5 || lua_isnil(L, 5)) {
// Allow NULL
} else if(lua_isuserdata(L, 5)) {
color = (color_t*)luaL_checkudata(L, 5, "color_mt");
} else {
return luaL_error(L, "Sprite color must be a color struct or nil");
}
// Optional UV min and maxes, defaults to 0,0 -> 1,1
float_t u0 = 0.0f;
float_t v0 = 0.0f;
float_t u1 = 1.0f;
float_t v1 = 1.0f;
if(lua_gettop(L) >= 7) {
if(!lua_isnumber(L, 6) || !lua_isnumber(L, 7)) {
return luaL_error(L, "Sprite UV min coordinates must be numbers");
}
u0 = (float_t)lua_tonumber(L, 6);
v0 = (float_t)lua_tonumber(L, 7);
}
if(lua_gettop(L) >= 9) {
if(!lua_isnumber(L, 8) || !lua_isnumber(L, 9)) {
return luaL_error(L, "Sprite UV max coordinates must be numbers");
}
u1 = (float_t)lua_tonumber(L, 8);
v1 = (float_t)lua_tonumber(L, 9);
}
float_t minX = (float_t)lua_tonumber(L, 1);
float_t minY = (float_t)lua_tonumber(L, 2);
float_t maxX = (float_t)lua_tonumber(L, 3);
float_t maxY = (float_t)lua_tonumber(L, 4);
errorret_t ret = spriteBatchPush(
minX,
minY,
maxX,
maxY,
#if MESH_ENABLE_COLOR
color == NULL ? COLOR_WHITE : *color,
#endif
u0,
v0,
u1,
v1
);
if(ret.code != ERROR_OK) {
int err = luaL_error(L,
"Failed to push sprite to batch: %s",
ret.state->message
);
errorCatch(errorPrint(ret));
return err;
}
return 0;
}
@@ -6,164 +6,35 @@
*/
#pragma once
#include "script/module/modulebase.h"
#include "script/scriptproto.h"
#include "script/module/display/modulecolor.h"
#include "script/module/math/modulevec2.h"
#include "script/module/math/modulevec3.h"
#include "display/spritebatch/spritebatch.h"
#include "script/scriptcontext.h"
static scriptproto_t MODULE_SPRITEBATCH_PROTO;
/**
* 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);
moduleBaseFunction(moduleSpriteBatchGetSpriteCount) {
return jerry_number(SPRITEBATCH.spriteCount);
}
/**
* 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);
moduleBaseFunction(moduleSpriteBatchPush) {
#if MESH_ENABLE_COLOR
if(argc < 9) return moduleBaseThrow("expected 9 arguments");
#else
if(argc < 8) return moduleBaseThrow("expected 8 arguments");
#endif
/**
* 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);
moduleBaseRequireNumber(0);
moduleBaseRequireNumber(1);
moduleBaseRequireNumber(2);
moduleBaseRequireNumber(3);
#if MESH_ENABLE_COLOR
if(!jerry_value_is_object(args[4])) {
return moduleBaseThrow("color must be a Color object");
}
color_t *col = (color_t *)scriptProtoGetValue(&MODULE_COLOR_PROTO, args[4]);
if(!col) {
return moduleBaseThrow("color must be a Color object");
}
moduleBaseRequireNumber(5);
moduleBaseRequireNumber(6);
moduleBaseRequireNumber(7);
moduleBaseRequireNumber(8);
spriteBatchPush(
(float_t)jerry_value_as_number(args[0]),
(float_t)jerry_value_as_number(args[1]),
(float_t)jerry_value_as_number(args[2]),
(float_t)jerry_value_as_number(args[3]),
*col,
(float_t)jerry_value_as_number(args[5]),
(float_t)jerry_value_as_number(args[6]),
(float_t)jerry_value_as_number(args[7]),
(float_t)jerry_value_as_number(args[8])
);
#else
moduleBaseRequireNumber(4);
moduleBaseRequireNumber(5);
moduleBaseRequireNumber(6);
moduleBaseRequireNumber(7);
spriteBatchPush(
(float_t)jerry_value_as_number(args[0]),
(float_t)jerry_value_as_number(args[1]),
(float_t)jerry_value_as_number(args[2]),
(float_t)jerry_value_as_number(args[3]),
(float_t)jerry_value_as_number(args[4]),
(float_t)jerry_value_as_number(args[5]),
(float_t)jerry_value_as_number(args[6]),
(float_t)jerry_value_as_number(args[7])
);
#endif
return jerry_undefined();
}
moduleBaseFunction(moduleSpriteBatchPush3D) {
#if MESH_ENABLE_COLOR
if(argc < 5) return moduleBaseThrow("expected 5 arguments");
#else
if(argc < 4) return moduleBaseThrow("expected 4 arguments");
#endif
if(!jerry_value_is_object(args[0])) {
return moduleBaseThrow("min must be a Vec3");
}
float_t *min = moduleVec3From(args[0]);
if(!min) return moduleBaseThrow("min must be a Vec3");
if(!jerry_value_is_object(args[1])) {
return moduleBaseThrow("max must be a Vec3");
}
float_t *max = moduleVec3From(args[1]);
if(!max) return moduleBaseThrow("max must be a Vec3");
#if MESH_ENABLE_COLOR
if(!jerry_value_is_object(args[2])) {
return moduleBaseThrow("color must be a Color object");
}
color_t *col = (color_t *)scriptProtoGetValue(&MODULE_COLOR_PROTO, args[2]);
if(!col) {
return moduleBaseThrow("color must be a Color object");
}
if(!jerry_value_is_object(args[3])) {
return moduleBaseThrow("uvMin must be a Vec2");
}
float_t *uvMin = moduleVec2From(args[3]);
if(!uvMin) return moduleBaseThrow("uvMin must be a Vec2");
if(!jerry_value_is_object(args[4])) {
return moduleBaseThrow("uvMax must be a Vec2");
}
float_t *uvMax = moduleVec2From(args[4]);
if(!uvMax) return moduleBaseThrow("uvMax must be a Vec2");
spriteBatchPush3D(min, max, *col, uvMin, uvMax);
#else
if(!jerry_value_is_object(args[2])) {
return moduleBaseThrow("uvMin must be a Vec2");
}
float_t *uvMin = moduleVec2From(args[2]);
if(!uvMin) return moduleBaseThrow("uvMin must be a Vec2");
if(!jerry_value_is_object(args[3])) {
return moduleBaseThrow("uvMax must be a Vec2");
}
float_t *uvMax = moduleVec2From(args[3]);
if(!uvMax) return moduleBaseThrow("uvMax must be a Vec2");
spriteBatchPush3D(min, max, uvMin, uvMax);
#endif
return jerry_undefined();
}
moduleBaseFunction(moduleSpriteBatchClear) {
spriteBatchClear();
return jerry_undefined();
}
moduleBaseFunction(moduleSpriteBatchFlush) {
spriteBatchFlush();
return jerry_undefined();
}
static void moduleSpriteBatch(void) {
scriptProtoInit(
&MODULE_SPRITEBATCH_PROTO, "SpriteBatch", sizeof(uint8_t), NULL
);
scriptProtoDefineStaticProp(
&MODULE_SPRITEBATCH_PROTO, "spriteCount",
moduleSpriteBatchGetSpriteCount, NULL
);
scriptProtoDefineStaticFunc(
&MODULE_SPRITEBATCH_PROTO, "push", moduleSpriteBatchPush
);
scriptProtoDefineStaticFunc(
&MODULE_SPRITEBATCH_PROTO, "push3D", moduleSpriteBatchPush3D
);
scriptProtoDefineStaticFunc(
&MODULE_SPRITEBATCH_PROTO, "clear", moduleSpriteBatchClear
);
scriptProtoDefineStaticFunc(
&MODULE_SPRITEBATCH_PROTO, "flush", moduleSpriteBatchFlush
);
}
/**
* 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);

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