Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d8fe0f6923 | |||
| 581dbc2b3c | |||
| 7301d2ad76 | |||
| 3232a14d1d | |||
| 84c1f88d42 | |||
| 3695b10e4b | |||
| 6da02b25fa | |||
| 3bc544fba1 | |||
| 368d370f49 | |||
| bb29c0edef | |||
| 6edcf75a0c | |||
| 31cc186424 | |||
| 0e94c1fa6d | |||
| 6d9e2dd3e1 | |||
| 4a4adeb3c8 | |||
| ff77f8cfa0 | |||
| 36db89c36e | |||
| a9948142ad | |||
| 8d05510584 | |||
| d373de7a29 | |||
| 1efa9a9f7b | |||
| 0fb3ba2f91 | |||
| 3b4c5b5153 | |||
| 9293aeeec8 | |||
| 03ae83b119 | |||
| abd63cc6cf | |||
| 2e43aa2c44 | |||
| 3d984e13c2 | |||
| 010900fe21 | |||
| ffed626447 | |||
| 61f69af35a | |||
| bd248ee91c | |||
| 194255bffe | |||
| 52ee627079 | |||
| bd4200e707 | |||
| 73e7d6c7f3 | |||
| a41b0e916b | |||
| 19f2a2c616 | |||
| 998601f722 | |||
| 7c3386cf3e | |||
| d161182997 | |||
| 1646dc2dbd | |||
| b640295be2 | |||
| b89ae2391b | |||
| a0fad441d0 | |||
| 340084dac3 | |||
| c78135aa09 | |||
| d19f8bbd30 |
+15
-15
@@ -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
|
||||
|
||||
+3
-1
@@ -4,11 +4,13 @@
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Setup
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
@@ -0,0 +1,35 @@
|
||||
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;
|
||||
@@ -0,0 +1,25 @@
|
||||
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;
|
||||
@@ -0,0 +1,21 @@
|
||||
function OverworldEntity() {
|
||||
Entity.call(this);
|
||||
|
||||
this.add(POSITION);
|
||||
}
|
||||
|
||||
OverworldEntity.prototype = Object.create(Entity.prototype);
|
||||
OverworldEntity.prototype.constructor = OverworldEntity;
|
||||
|
||||
OverworldEntity.prototype.update = function() {
|
||||
// var speed = 3.0;
|
||||
// var move = Input.axis2D(INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT, INPUT_ACTION_UP, INPUT_ACTION_DOWN);
|
||||
// this.position.position.x += move.x * speed * TIME.delta;
|
||||
// this.position.position.z += move.y * speed * TIME.delta;
|
||||
}
|
||||
|
||||
OverworldEntity.prototype.dispose = function() {
|
||||
// Nothing to dispose
|
||||
}
|
||||
|
||||
module = OverworldEntity;
|
||||
@@ -0,0 +1,77 @@
|
||||
Console.visible = true;
|
||||
|
||||
// Default input bindings.
|
||||
if (typeof PSP !== 'undefined') {
|
||||
Input.bind("up", INPUT_ACTION_UP);
|
||||
Input.bind("down", INPUT_ACTION_DOWN);
|
||||
Input.bind("left", INPUT_ACTION_LEFT);
|
||||
Input.bind("right", INPUT_ACTION_RIGHT);
|
||||
Input.bind("accept", INPUT_ACTION_ACCEPT);
|
||||
Input.bind("cancel", INPUT_ACTION_CANCEL);
|
||||
Input.bind("select", INPUT_ACTION_RAGEQUIT);
|
||||
Input.bind("lstick_up", INPUT_ACTION_UP);
|
||||
Input.bind("lstick_down", INPUT_ACTION_DOWN);
|
||||
Input.bind("lstick_left", INPUT_ACTION_LEFT);
|
||||
Input.bind("lstick_right", INPUT_ACTION_RIGHT);
|
||||
Input.bind("triangle", INPUT_ACTION_CONSOLE);
|
||||
|
||||
} else if (typeof DOLPHIN !== 'undefined') {
|
||||
Input.bind("up", INPUT_ACTION_UP);
|
||||
Input.bind("down", INPUT_ACTION_DOWN);
|
||||
Input.bind("left", INPUT_ACTION_LEFT);
|
||||
Input.bind("right", INPUT_ACTION_RIGHT);
|
||||
Input.bind("b", INPUT_ACTION_CANCEL);
|
||||
Input.bind("a", INPUT_ACTION_ACCEPT);
|
||||
Input.bind("z", INPUT_ACTION_CONSOLE);
|
||||
Input.bind("lstick_up", INPUT_ACTION_UP);
|
||||
Input.bind("lstick_down", INPUT_ACTION_DOWN);
|
||||
Input.bind("lstick_left", INPUT_ACTION_LEFT);
|
||||
Input.bind("lstick_right", INPUT_ACTION_RIGHT);
|
||||
|
||||
} else if (typeof LINUX !== 'undefined') {
|
||||
if (typeof INPUT_KEYBOARD !== 'undefined') {
|
||||
Input.bind("w", INPUT_ACTION_UP);
|
||||
Input.bind("s", INPUT_ACTION_DOWN);
|
||||
Input.bind("a", INPUT_ACTION_LEFT);
|
||||
Input.bind("d", INPUT_ACTION_RIGHT);
|
||||
|
||||
Input.bind("left", INPUT_ACTION_LEFT);
|
||||
Input.bind("right", INPUT_ACTION_RIGHT);
|
||||
Input.bind("up", INPUT_ACTION_UP);
|
||||
Input.bind("down", INPUT_ACTION_DOWN);
|
||||
|
||||
Input.bind("enter", INPUT_ACTION_ACCEPT);
|
||||
Input.bind("e", INPUT_ACTION_ACCEPT);
|
||||
|
||||
Input.bind("q", INPUT_ACTION_CANCEL);
|
||||
|
||||
Input.bind("escape", INPUT_ACTION_RAGEQUIT);
|
||||
Input.bind("`", INPUT_ACTION_CONSOLE);
|
||||
}
|
||||
|
||||
if (typeof INPUT_GAMEPAD !== 'undefined') {
|
||||
Input.bind("gamepad_up", INPUT_ACTION_UP);
|
||||
Input.bind("gamepad_down", INPUT_ACTION_DOWN);
|
||||
Input.bind("gamepad_left", INPUT_ACTION_LEFT);
|
||||
Input.bind("gamepad_right", INPUT_ACTION_RIGHT);
|
||||
|
||||
Input.bind("gamepad_a", INPUT_ACTION_ACCEPT);
|
||||
Input.bind("gamepad_b", INPUT_ACTION_CANCEL);
|
||||
Input.bind("gamepad_back", INPUT_ACTION_RAGEQUIT);
|
||||
|
||||
Input.bind("gamepad_lstick_up", INPUT_ACTION_UP);
|
||||
Input.bind("gamepad_lstick_down", INPUT_ACTION_DOWN);
|
||||
Input.bind("gamepad_lstick_left", INPUT_ACTION_LEFT);
|
||||
Input.bind("gamepad_lstick_right", INPUT_ACTION_RIGHT);
|
||||
}
|
||||
|
||||
if (typeof INPUT_POINTER !== 'undefined') {
|
||||
Input.bind("mouse_x", INPUT_ACTION_POINTERX);
|
||||
Input.bind("mouse_y", INPUT_ACTION_POINTERY);
|
||||
}
|
||||
|
||||
} else {
|
||||
print("Unknown platform, no default input bindings set.");
|
||||
}
|
||||
|
||||
Scene.set('scenes/cube.js');
|
||||
@@ -1,76 +0,0 @@
|
||||
module('input')
|
||||
module('platform')
|
||||
module('scene')
|
||||
module('locale')
|
||||
|
||||
-- Default Input bindings.
|
||||
if PSP then
|
||||
inputBind("up", INPUT_ACTION_UP)
|
||||
inputBind("down", INPUT_ACTION_DOWN)
|
||||
inputBind("left", INPUT_ACTION_LEFT)
|
||||
inputBind("right", INPUT_ACTION_RIGHT)
|
||||
inputBind("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
|
||||
@@ -0,0 +1,50 @@
|
||||
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;
|
||||
@@ -0,0 +1,6 @@
|
||||
module = {
|
||||
render() {
|
||||
Text.draw(0, 0, "Hello World");
|
||||
SpriteBatch.flush();
|
||||
}
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* Wii/Dolphin overrides for mbedtls 4.x.
|
||||
* Used as both MBEDTLS_USER_CONFIG_FILE and TF_PSA_CRYPTO_USER_CONFIG_FILE,
|
||||
* so intentionally has no include guard (operations are idempotent).
|
||||
*/
|
||||
|
||||
/* Disable the mbedtls TCP/socket layer — Wii has no Unix/Windows sockets.
|
||||
* We provide our own socket implementation via networksocket.c. */
|
||||
#undef MBEDTLS_NET_C
|
||||
|
||||
/* Disable built-in Unix/Windows entropy; use driver-provided entropy instead.
|
||||
* We implement mbedtls_platform_get_entropy() in networktls.c. */
|
||||
#undef MBEDTLS_PSA_BUILTIN_GET_ENTROPY
|
||||
#define MBEDTLS_PSA_DRIVER_GET_ENTROPY
|
||||
|
||||
/* Disable the built-in ms_time; we implement mbedtls_ms_time() via OGC
|
||||
* timer (gettime / ticks_to_millisecs) in networktls.c. */
|
||||
#define MBEDTLS_PLATFORM_MS_TIME_ALT
|
||||
@@ -0,0 +1,96 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Turn things off we don't need
|
||||
set(JERRY_CMDLINE OFF CACHE BOOL "" FORCE)
|
||||
set(JERRY_EXT ON CACHE BOOL "" FORCE)
|
||||
set(JERRY_DEBUGGER OFF CACHE BOOL "" FORCE)
|
||||
set(JERRY_BUILTIN_DATE OFF CACHE BOOL "" FORCE)
|
||||
set(ENABLE_LTO OFF CACHE BOOL "" FORCE)
|
||||
|
||||
# Fetch Jerry
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
jerryscript
|
||||
GIT_REPOSITORY https://git.wish.moe/YourWishes/jerryscript
|
||||
GIT_TAG float32-fix
|
||||
)
|
||||
FetchContent_MakeAvailable(jerryscript)
|
||||
|
||||
# Mark found
|
||||
set(jerryscript_FOUND ON)
|
||||
|
||||
# Define targets
|
||||
if(TARGET jerryscript-core)
|
||||
set(JERRY_CORE_TARGET jerryscript-core)
|
||||
elseif(TARGET jerry-core)
|
||||
set(JERRY_CORE_TARGET jerry-core)
|
||||
endif()
|
||||
|
||||
if(TARGET jerryscript-ext)
|
||||
set(JERRY_EXT_TARGET jerryscript-ext)
|
||||
elseif(TARGET jerry-ext)
|
||||
set(JERRY_EXT_TARGET jerry-ext)
|
||||
endif()
|
||||
|
||||
if(TARGET jerryscript-port-default)
|
||||
set(JERRY_PORT_TARGET jerryscript-port-default)
|
||||
elseif(TARGET jerry-port-default)
|
||||
set(JERRY_PORT_TARGET jerry-port-default)
|
||||
elseif(TARGET jerryscript-port)
|
||||
set(JERRY_PORT_TARGET jerryscript-port)
|
||||
elseif(TARGET jerry-port)
|
||||
set(JERRY_PORT_TARGET jerry-port)
|
||||
endif()
|
||||
|
||||
if(NOT JERRY_CORE_TARGET)
|
||||
message(FATAL_ERROR "JerryScript core target not found")
|
||||
endif()
|
||||
|
||||
if(NOT JERRY_EXT_TARGET)
|
||||
message(FATAL_ERROR "JerryScript ext target not found")
|
||||
endif()
|
||||
|
||||
if(NOT JERRY_PORT_TARGET)
|
||||
message(FATAL_ERROR "JerryScript port target not found")
|
||||
endif()
|
||||
|
||||
foreach(tgt IN ITEMS
|
||||
${JERRY_CORE_TARGET}
|
||||
${JERRY_EXT_TARGET}
|
||||
${JERRY_PORT_TARGET}
|
||||
)
|
||||
if(TARGET ${tgt})
|
||||
set_property(TARGET ${tgt} PROPERTY INTERPROCEDURAL_OPTIMIZATION OFF)
|
||||
target_compile_definitions(${JERRY_CORE_TARGET} PRIVATE
|
||||
JERRY_NUMBER_TYPE_FLOAT64=0
|
||||
JERRY_BUILTIN_DATE=0
|
||||
)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# Export include dirs through the targets
|
||||
target_include_directories(${JERRY_CORE_TARGET} INTERFACE
|
||||
${jerryscript_SOURCE_DIR}/jerry-core/include
|
||||
)
|
||||
|
||||
target_include_directories(${JERRY_EXT_TARGET} INTERFACE
|
||||
${jerryscript_SOURCE_DIR}/jerry-ext/include
|
||||
)
|
||||
|
||||
target_include_directories(${JERRY_PORT_TARGET} INTERFACE
|
||||
${jerryscript_SOURCE_DIR}/jerry-port/default/include
|
||||
)
|
||||
|
||||
# Suppress JerryScript-only warning
|
||||
if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
|
||||
target_compile_options(${JERRY_CORE_TARGET} PRIVATE
|
||||
-Wno-error
|
||||
)
|
||||
endif()
|
||||
|
||||
add_library(jerryscript::core ALIAS ${JERRY_CORE_TARGET})
|
||||
add_library(jerryscript::ext ALIAS ${JERRY_EXT_TARGET})
|
||||
add_library(jerryscript::port ALIAS ${JERRY_PORT_TARGET})
|
||||
@@ -1,201 +0,0 @@
|
||||
# Findmbedtls.cmake
|
||||
#
|
||||
# Usage:
|
||||
# find_package(mbedtls REQUIRED)
|
||||
#
|
||||
# Optional cache variables the parent project may set before calling:
|
||||
# MBEDTLS_FETCHCONTENT_VERSION e.g. "v3.6.4" or "mbedtls-4.1.0"
|
||||
# MBEDTLS_FETCHCONTENT_GIT_REPOSITORY
|
||||
# MBEDTLS_FETCHCONTENT_GIT_TAG
|
||||
# MBEDTLS_FETCHCONTENT_BASE_DIR
|
||||
# MBEDTLS_BUILD_SHARED ON/OFF
|
||||
#
|
||||
# Provided variables:
|
||||
# mbedtls_FOUND
|
||||
# MBEDTLS_FOUND
|
||||
# MBEDTLS_INCLUDE_DIRS
|
||||
# MBEDTLS_LIBRARIES
|
||||
#
|
||||
# Provided imported targets:
|
||||
# MbedTLS::mbedtls
|
||||
# MbedTLS::mbedx509
|
||||
# MbedTLS::mbedcrypto
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
include(FetchContent)
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
set(_MBEDTLS_DEFAULT_REPOSITORY "https://github.com/Mbed-TLS/mbedtls.git")
|
||||
set(_MBEDTLS_DEFAULT_TAG "v4.1.0")
|
||||
|
||||
set(MBEDTLS_FETCHCONTENT_GIT_REPOSITORY
|
||||
"${_MBEDTLS_DEFAULT_REPOSITORY}"
|
||||
CACHE STRING "Git repository for fetching Mbed TLS")
|
||||
|
||||
if(DEFINED MBEDTLS_FETCHCONTENT_VERSION AND NOT DEFINED MBEDTLS_FETCHCONTENT_GIT_TAG)
|
||||
set(MBEDTLS_FETCHCONTENT_GIT_TAG
|
||||
"${MBEDTLS_FETCHCONTENT_VERSION}"
|
||||
CACHE STRING "Git tag/branch/commit for fetching Mbed TLS")
|
||||
endif()
|
||||
|
||||
set(MBEDTLS_FETCHCONTENT_GIT_TAG
|
||||
"${MBEDTLS_FETCHCONTENT_GIT_TAG}"
|
||||
CACHE STRING "Git tag/branch/commit for fetching Mbed TLS")
|
||||
|
||||
if(NOT MBEDTLS_FETCHCONTENT_GIT_TAG)
|
||||
set(MBEDTLS_FETCHCONTENT_GIT_TAG "${_MBEDTLS_DEFAULT_TAG}" CACHE STRING "" FORCE)
|
||||
endif()
|
||||
|
||||
option(MBEDTLS_BUILD_SHARED "Build Mbed TLS shared libraries" OFF)
|
||||
|
||||
# 1) Prefer an installed package config if available.
|
||||
find_package(MbedTLS CONFIG QUIET)
|
||||
|
||||
if(TARGET MbedTLS::mbedtls AND TARGET MbedTLS::mbedx509 AND TARGET MbedTLS::mbedcrypto)
|
||||
set(mbedtls_FOUND TRUE)
|
||||
set(MBEDTLS_FOUND TRUE)
|
||||
set(MBEDTLS_LIBRARIES
|
||||
MbedTLS::mbedtls
|
||||
MbedTLS::mbedx509
|
||||
MbedTLS::mbedcrypto)
|
||||
set(MBEDTLS_INCLUDE_DIRS "")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# 2) If upstream exported plain targets instead of namespaced ones, alias them.
|
||||
if(TARGET mbedtls AND TARGET mbedx509 AND TARGET mbedcrypto)
|
||||
if(NOT TARGET MbedTLS::mbedtls)
|
||||
add_library(MbedTLS::mbedtls INTERFACE IMPORTED)
|
||||
target_link_libraries(MbedTLS::mbedtls INTERFACE mbedtls)
|
||||
endif()
|
||||
if(NOT TARGET MbedTLS::mbedx509)
|
||||
add_library(MbedTLS::mbedx509 INTERFACE IMPORTED)
|
||||
target_link_libraries(MbedTLS::mbedx509 INTERFACE mbedx509)
|
||||
endif()
|
||||
if(NOT TARGET MbedTLS::mbedcrypto)
|
||||
add_library(MbedTLS::mbedcrypto INTERFACE IMPORTED)
|
||||
target_link_libraries(MbedTLS::mbedcrypto INTERFACE mbedcrypto)
|
||||
endif()
|
||||
|
||||
set(mbedtls_FOUND TRUE)
|
||||
set(MBEDTLS_FOUND TRUE)
|
||||
set(MBEDTLS_LIBRARIES
|
||||
MbedTLS::mbedtls
|
||||
MbedTLS::mbedx509
|
||||
MbedTLS::mbedcrypto)
|
||||
set(MBEDTLS_INCLUDE_DIRS "")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# 3) Fetch and build Mbed TLS.
|
||||
# Upstream options:
|
||||
# - USE_STATIC_MBEDTLS_LIBRARY / USE_SHARED_MBEDTLS_LIBRARY
|
||||
# - ENABLE_PROGRAMS / ENABLE_TESTING
|
||||
# - MBEDTLS_AS_SUBPROJECT / DISABLE_PACKAGE_CONFIG_AND_INSTALL
|
||||
# - MBEDTLS_TARGET_PREFIX
|
||||
#
|
||||
# These are supported by the upstream CMake build. :contentReference[oaicite:1]{index=1}
|
||||
|
||||
set(FETCHCONTENT_QUIET FALSE)
|
||||
|
||||
if(MBEDTLS_FETCHCONTENT_BASE_DIR)
|
||||
set(FETCHCONTENT_BASE_DIR "${MBEDTLS_FETCHCONTENT_BASE_DIR}")
|
||||
endif()
|
||||
|
||||
# Avoid polluting the parent build and skip extras we usually do not want.
|
||||
set(ENABLE_PROGRAMS OFF CACHE BOOL "" FORCE)
|
||||
set(ENABLE_TESTING OFF CACHE BOOL "" FORCE)
|
||||
set(DISABLE_PACKAGE_CONFIG_AND_INSTALL ON CACHE BOOL "" FORCE)
|
||||
set(MBEDTLS_AS_SUBPROJECT ON CACHE BOOL "" FORCE)
|
||||
set(MBEDTLS_TARGET_PREFIX "" CACHE STRING "" FORCE)
|
||||
|
||||
if(MBEDTLS_BUILD_SHARED)
|
||||
set(USE_SHARED_MBEDTLS_LIBRARY ON CACHE BOOL "" FORCE)
|
||||
set(USE_STATIC_MBEDTLS_LIBRARY OFF CACHE BOOL "" FORCE)
|
||||
else()
|
||||
set(USE_SHARED_MBEDTLS_LIBRARY OFF CACHE BOOL "" FORCE)
|
||||
set(USE_STATIC_MBEDTLS_LIBRARY ON CACHE BOOL "" FORCE)
|
||||
endif()
|
||||
|
||||
FetchContent_Declare(
|
||||
mbedtls_fc
|
||||
GIT_REPOSITORY "${MBEDTLS_FETCHCONTENT_GIT_REPOSITORY}"
|
||||
GIT_TAG "${MBEDTLS_FETCHCONTENT_GIT_TAG}"
|
||||
GIT_SHALLOW TRUE
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(mbedtls_fc)
|
||||
|
||||
# 4) Normalize targets across upstream versions.
|
||||
#
|
||||
# Mbed TLS 3.x:
|
||||
# mbedtls, mbedx509, mbedcrypto
|
||||
#
|
||||
# Mbed TLS 4.x:
|
||||
# mbedtls, mbedx509, tfpsacrypto
|
||||
#
|
||||
# Map everything to stable namespaced targets for the consumer. :contentReference[oaicite:2]{index=2}
|
||||
|
||||
set(_mbedtls_tls_target "")
|
||||
set(_mbedtls_x509_target "")
|
||||
set(_mbedtls_crypto_target "")
|
||||
|
||||
if(TARGET mbedtls)
|
||||
set(_mbedtls_tls_target mbedtls)
|
||||
elseif(TARGET MbedTLS::mbedtls)
|
||||
set(_mbedtls_tls_target MbedTLS::mbedtls)
|
||||
endif()
|
||||
|
||||
if(TARGET mbedx509)
|
||||
set(_mbedtls_x509_target mbedx509)
|
||||
elseif(TARGET MbedTLS::mbedx509)
|
||||
set(_mbedtls_x509_target MbedTLS::mbedx509)
|
||||
endif()
|
||||
|
||||
if(TARGET mbedcrypto)
|
||||
set(_mbedtls_crypto_target mbedcrypto)
|
||||
elseif(TARGET tfpsacrypto)
|
||||
set(_mbedtls_crypto_target tfpsacrypto)
|
||||
elseif(TARGET MbedTLS::mbedcrypto)
|
||||
set(_mbedtls_crypto_target MbedTLS::mbedcrypto)
|
||||
endif()
|
||||
|
||||
if(_mbedtls_tls_target AND NOT TARGET MbedTLS::mbedtls)
|
||||
add_library(MbedTLS::mbedtls INTERFACE IMPORTED)
|
||||
target_link_libraries(MbedTLS::mbedtls INTERFACE "${_mbedtls_tls_target}")
|
||||
endif()
|
||||
|
||||
if(_mbedtls_x509_target AND NOT TARGET MbedTLS::mbedx509)
|
||||
add_library(MbedTLS::mbedx509 INTERFACE IMPORTED)
|
||||
target_link_libraries(MbedTLS::mbedx509 INTERFACE "${_mbedtls_x509_target}")
|
||||
endif()
|
||||
|
||||
if(_mbedtls_crypto_target AND NOT TARGET MbedTLS::mbedcrypto)
|
||||
add_library(MbedTLS::mbedcrypto INTERFACE IMPORTED)
|
||||
target_link_libraries(MbedTLS::mbedcrypto INTERFACE "${_mbedtls_crypto_target}")
|
||||
endif()
|
||||
|
||||
find_package_handle_standard_args(
|
||||
mbedtls
|
||||
REQUIRED_VARS
|
||||
_mbedtls_tls_target
|
||||
_mbedtls_x509_target
|
||||
_mbedtls_crypto_target
|
||||
)
|
||||
|
||||
if(mbedtls_FOUND)
|
||||
set(MBEDTLS_FOUND TRUE)
|
||||
set(MBEDTLS_LIBRARIES
|
||||
MbedTLS::mbedtls
|
||||
MbedTLS::mbedx509
|
||||
MbedTLS::mbedcrypto)
|
||||
|
||||
# Best-effort include directory discovery for legacy consumers.
|
||||
get_target_property(_mbedtls_inc "${_mbedtls_tls_target}" INTERFACE_INCLUDE_DIRECTORIES)
|
||||
if(_mbedtls_inc)
|
||||
set(MBEDTLS_INCLUDE_DIRS "${_mbedtls_inc}")
|
||||
else()
|
||||
set(MBEDTLS_INCLUDE_DIRS "")
|
||||
endif()
|
||||
endif()
|
||||
@@ -1,23 +1,10 @@
|
||||
# mbedtls/tf-psa-crypto user config overrides for Wii/Dolphin.
|
||||
# Both variables point to the same file; it is included after each library's
|
||||
# default config header, so #undef/#define are applied on top of the defaults.
|
||||
# Must be set before find_package(mbedtls) so FetchContent picks them up.
|
||||
set(TF_PSA_CRYPTO_USER_CONFIG_FILE
|
||||
"${CMAKE_SOURCE_DIR}/cmake/mbedtls_dolphin_config.h"
|
||||
CACHE FILEPATH "tf-psa-crypto user config for Wii/Dolphin" FORCE)
|
||||
set(MBEDTLS_USER_CONFIG_FILE
|
||||
"${CMAKE_SOURCE_DIR}/cmake/mbedtls_dolphin_config.h"
|
||||
CACHE FILEPATH "mbedtls user config for Wii/Dolphin" FORCE)
|
||||
|
||||
# Target definitions
|
||||
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||
DUSK_DOLPHIN
|
||||
DUSK_INPUT_GAMEPAD
|
||||
DUSK_DISPLAY_WIDTH=640
|
||||
DUSK_DISPLAY_HEIGHT=480
|
||||
MBEDTLS_PSA_DRIVER_GET_ENTROPY
|
||||
MBEDTLS_PLATFORM_MS_TIME_ALT
|
||||
THREAD_PTHREAD=1
|
||||
DUSK_THREAD_PTHREAD
|
||||
)
|
||||
|
||||
# Custom compiler flags
|
||||
@@ -35,31 +22,9 @@ 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
|
||||
|
||||
@@ -26,14 +26,11 @@ target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||
# CURL::libcurl
|
||||
)
|
||||
|
||||
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||
${MBEDTLS_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
# Define platform-specific macros.
|
||||
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||
DUSK_SDL2
|
||||
DUSK_OPENGL
|
||||
DUSK_CONSOLE_POSIX
|
||||
# DUSK_OPENGL_LEGACY
|
||||
DUSK_LINUX
|
||||
DUSK_DISPLAY_SIZE_DYNAMIC
|
||||
@@ -45,5 +42,5 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||
DUSK_INPUT_GAMEPAD
|
||||
DUSK_TIME_DYNAMIC
|
||||
DUSK_NETWORK_IPV6
|
||||
THREAD_PTHREAD=1
|
||||
DUSK_THREAD_PTHREAD
|
||||
)
|
||||
+21
-4
@@ -1,6 +1,19 @@
|
||||
# Fixes some problems building JerryScript
|
||||
set(CMAKE_AR "$ENV{PSPDEV}/bin/psp-ar" CACHE FILEPATH "" FORCE)
|
||||
set(CMAKE_RANLIB "$ENV{PSPDEV}/bin/psp-ranlib" CACHE FILEPATH "" FORCE)
|
||||
set(CMAKE_C_COMPILER_AR "$ENV{PSPDEV}/bin/psp-ar" CACHE FILEPATH "" FORCE)
|
||||
set(CMAKE_C_COMPILER_RANLIB "$ENV{PSPDEV}/bin/psp-ranlib" CACHE FILEPATH "" FORCE)
|
||||
set(CMAKE_C_ARCHIVE_CREATE "$ENV{PSPDEV}/bin/psp-ar qc <TARGET> <LINK_FLAGS> <OBJECTS>")
|
||||
set(CMAKE_C_ARCHIVE_APPEND "$ENV{PSPDEV}/bin/psp-ar q <TARGET> <LINK_FLAGS> <OBJECTS>")
|
||||
set(CMAKE_C_ARCHIVE_FINISH "$ENV{PSPDEV}/bin/psp-ranlib <TARGET>")
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION OFF CACHE BOOL "" FORCE)
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_C OFF CACHE BOOL "" FORCE)
|
||||
set(JERRY_LTO OFF CACHE BOOL "" FORCE)
|
||||
|
||||
find_package(jerryscript REQUIRED)
|
||||
find_package(SDL2 REQUIRED)
|
||||
find_package(OpenGL REQUIRED)
|
||||
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||
target_link_libraries(${DUSK_BINARY_TARGET_NAME} PUBLIC
|
||||
${SDL2_LIBRARIES}
|
||||
SDL2
|
||||
pthread
|
||||
@@ -29,13 +42,17 @@ target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||
pspnet_apctl
|
||||
psphttp
|
||||
pspssl
|
||||
|
||||
jerryscript::core
|
||||
jerryscript::ext
|
||||
jerryscript::port
|
||||
)
|
||||
|
||||
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
|
||||
target_include_directories(${DUSK_BINARY_TARGET_NAME} PRIVATE
|
||||
${SDL2_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||
target_compile_definitions(${DUSK_BINARY_TARGET_NAME} PUBLIC
|
||||
DUSK_SDL2
|
||||
DUSK_OPENGL
|
||||
DUSK_PSP
|
||||
@@ -44,7 +61,7 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||
DUSK_OPENGL_LEGACY
|
||||
DUSK_DISPLAY_WIDTH=480
|
||||
DUSK_DISPLAY_HEIGHT=272
|
||||
THREAD_PTHREAD=1
|
||||
DUSK_THREAD_PTHREAD
|
||||
)
|
||||
|
||||
# Postbuild, create .pbp file for PSP.
|
||||
|
||||
@@ -20,31 +20,9 @@ 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,6 +1,6 @@
|
||||
FROM devkitpro/devkitppc
|
||||
WORKDIR /workdir
|
||||
RUN apt update && \
|
||||
apt install -y python3 python3-pip python3-polib python3-pil python3-dotenv python3-pyqt5 python3-opengl python3-jsonschema python3-jinja2 python3-jsonschema && \
|
||||
apt install -y python3 python3-pip python3-polib python3-pil python3-dotenv python3-pyqt5 python3-opengl && \
|
||||
dkp-pacman -S --needed --noconfirm gamecube-sdl2 ppc-liblzma ppc-libzip
|
||||
VOLUME ["/workdir"]
|
||||
@@ -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 \
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
# 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)
|
||||
|
||||
+12
-23
@@ -32,28 +32,13 @@ if(NOT yyjson_FOUND)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT Lua_FOUND)
|
||||
find_package(Lua REQUIRED)
|
||||
if(Lua_FOUND AND NOT TARGET Lua::Lua)
|
||||
add_library(Lua::Lua INTERFACE IMPORTED)
|
||||
set_target_properties(
|
||||
Lua::Lua
|
||||
PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${LUA_INCLUDE_DIR}"
|
||||
INTERFACE_LINK_LIBRARIES "${LUA_LIBRARIES}"
|
||||
if(NOT jerryscript_FOUND)
|
||||
find_package(jerryscript REQUIRED)
|
||||
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||
jerryscript::core
|
||||
jerryscript::ext
|
||||
jerryscript::port
|
||||
)
|
||||
endif()
|
||||
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC Lua::Lua)
|
||||
endif()
|
||||
|
||||
if(NOT mbedtls_FOUND)
|
||||
find_package(mbedtls REQUIRED)
|
||||
if(mbedtls_FOUND)
|
||||
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC ${MBEDTLS_LIBRARIES})
|
||||
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PUBLIC ${MBEDTLS_INCLUDE_DIRS})
|
||||
else()
|
||||
message(FATAL_ERROR "mbedtls not found. Please ensure mbedtls is correctly installed.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Includes
|
||||
@@ -69,14 +54,18 @@ target_sources(${DUSK_BINARY_TARGET_NAME}
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(animation)
|
||||
add_subdirectory(assert)
|
||||
add_subdirectory(asset)
|
||||
add_subdirectory(log)
|
||||
add_subdirectory(cutscene)
|
||||
add_subdirectory(item)
|
||||
add_subdirectory(story)
|
||||
add_subdirectory(console)
|
||||
add_subdirectory(display)
|
||||
add_subdirectory(log)
|
||||
add_subdirectory(engine)
|
||||
add_subdirectory(entity)
|
||||
add_subdirectory(error)
|
||||
add_subdirectory(event)
|
||||
add_subdirectory(input)
|
||||
add_subdirectory(locale)
|
||||
add_subdirectory(physics)
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
modulescene.c
|
||||
easing.c
|
||||
animation.c
|
||||
)
|
||||
@@ -0,0 +1,75 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// 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);
|
||||
@@ -0,0 +1,106 @@
|
||||
// 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);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// 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);
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) 2026 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#pragma once
|
||||
#include "easing.h"
|
||||
|
||||
#define ANIMATION_KEYFRAME_COUNT_MAX 16
|
||||
|
||||
typedef struct {
|
||||
float_t time;
|
||||
float_t value;
|
||||
easingtype_t easing;
|
||||
} keyframe_t;
|
||||
@@ -25,7 +25,7 @@ errorret_t assetInit(void) {
|
||||
}
|
||||
|
||||
bool_t assetFileExists(const char_t *filename) {
|
||||
assertStrLenMax(filename, FILENAME_MAX, "Filename too long.");
|
||||
assertStrLenMax(filename, ASSET_FILE_PATH_MAX, "Filename too long.");
|
||||
|
||||
zip_int64_t idx = zip_name_locate(ASSET.zip, filename, 0);
|
||||
if(idx < 0) return false;
|
||||
@@ -38,7 +38,7 @@ errorret_t assetLoad(
|
||||
void *params,
|
||||
void *output
|
||||
) {
|
||||
assertStrLenMax(filename, FILENAME_MAX, "Filename too long.");
|
||||
assertStrLenMax(filename, ASSET_FILE_PATH_MAX, "Filename too long.");
|
||||
assertNotNull(output, "Output pointer cannot be NULL.");
|
||||
assertNotNull(loader, "Asset file loader cannot be NULL.");
|
||||
|
||||
|
||||
@@ -146,7 +146,9 @@ 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;
|
||||
@@ -177,11 +179,16 @@ 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.");
|
||||
@@ -284,7 +291,10 @@ 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--;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#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 conditional clauses for nplurals=%d.",
|
||||
"Too many plural conditions. Expected %d clauses for nplurals=%d.",
|
||||
localeFile->pluralStateCount - 1,
|
||||
localeFile->pluralStateCount
|
||||
);
|
||||
|
||||
@@ -7,76 +7,86 @@
|
||||
|
||||
#include "assetscriptloader.h"
|
||||
#include "assert/assert.h"
|
||||
#include <stdlib.h>
|
||||
#include <zip.h>
|
||||
|
||||
errorret_t assetScriptLoader(assetfile_t *file) {
|
||||
assertNotNull(file, "Asset file cannot be NULL");
|
||||
assertNull(file->zipFile, "Asset file zip handle must be NULL");
|
||||
assertNull(file->zipFile, "Asset file zip handle must be NULL before open");
|
||||
assertNotNull(file->output, "Asset file output cannot be NULL");
|
||||
|
||||
assetscript_t *script = (assetscript_t *)file->output;
|
||||
|
||||
// Open the asset for buffering
|
||||
errorChain(assetFileOpen(file));
|
||||
|
||||
// Request loading
|
||||
if(!lua_load(
|
||||
script->ctx->luaState,
|
||||
assetScriptReader,
|
||||
file,
|
||||
file->filename,
|
||||
NULL
|
||||
) == LUA_OK) {
|
||||
const char_t *strErr = lua_tostring(script->ctx->luaState, -1);
|
||||
lua_pop(script->ctx->luaState, 1);
|
||||
errorThrow("Failed to load Lua script: %s", strErr);
|
||||
// Accumulate full source into a dynamically grown buffer.
|
||||
size_t srcLen = 0;
|
||||
size_t capacity = ASSET_SCRIPT_CHUNK_SIZE;
|
||||
char_t *src = (char_t *)malloc(capacity + 1);
|
||||
if(!src) {
|
||||
assetFileClose(file);
|
||||
errorThrow("Out of memory reading script: %s", file->filename);
|
||||
}
|
||||
|
||||
// Now loaded, exec
|
||||
if(lua_pcall(script->ctx->luaState, 0, LUA_MULTRET, 0) != LUA_OK) {
|
||||
const char_t *strErr = lua_tostring(script->ctx->luaState, -1);
|
||||
lua_pop(script->ctx->luaState, 1);
|
||||
errorThrow("Failed to execute Lua script: %s", strErr);
|
||||
while(1) {
|
||||
if(srcLen + ASSET_SCRIPT_CHUNK_SIZE > capacity) {
|
||||
capacity = srcLen + ASSET_SCRIPT_CHUNK_SIZE;
|
||||
char_t *tmp = (char_t *)realloc(src, capacity + 1);
|
||||
if(!tmp) {
|
||||
free(src);
|
||||
assetFileClose(file);
|
||||
errorThrow("Out of memory reading script: %s", file->filename);
|
||||
}
|
||||
src = tmp;
|
||||
}
|
||||
zip_int64_t n = zip_fread(
|
||||
file->zipFile, src + srcLen, ASSET_SCRIPT_CHUNK_SIZE
|
||||
);
|
||||
if(n <= 0) break;
|
||||
srcLen += (size_t)n;
|
||||
}
|
||||
src[srcLen] = '\0';
|
||||
|
||||
errorret_t closeRet = assetFileClose(file);
|
||||
|
||||
jerry_value_t result = jerry_eval(
|
||||
(const jerry_char_t *)src,
|
||||
srcLen,
|
||||
JERRY_PARSE_NO_OPTS
|
||||
);
|
||||
free(src);
|
||||
|
||||
if(jerry_value_is_exception(result)) {
|
||||
jerry_value_t errVal = jerry_exception_value(result, false);
|
||||
jerry_value_t errStr = jerry_value_to_string(errVal);
|
||||
char_t buf[256];
|
||||
jerry_size_t len = jerry_string_to_buffer(
|
||||
errStr, JERRY_ENCODING_UTF8, (jerry_char_t *)buf, sizeof(buf) - 1
|
||||
);
|
||||
buf[len] = '\0';
|
||||
jerry_value_free(errStr);
|
||||
jerry_value_free(errVal);
|
||||
jerry_value_free(result);
|
||||
errorThrow("Script error in '%s': %s", file->filename, buf);
|
||||
}
|
||||
|
||||
// Close the file
|
||||
return assetFileClose(file);
|
||||
if(script->resultOut != NULL) {
|
||||
*(script->resultOut) = result;
|
||||
} else {
|
||||
jerry_value_free(result);
|
||||
}
|
||||
|
||||
return closeRet;
|
||||
}
|
||||
|
||||
errorret_t assetScriptLoad(const char_t *path, scriptcontext_t *ctx) {
|
||||
errorret_t assetScriptLoad(
|
||||
const char_t *path,
|
||||
jerry_value_t *resultOut
|
||||
) {
|
||||
assertNotNull(path, "Script path cannot be NULL");
|
||||
assertNotNull(ctx, "Script context cannot be NULL");
|
||||
|
||||
assetscript_t script;
|
||||
script.ctx = ctx;
|
||||
assetscript_t scriptData;
|
||||
scriptData.resultOut = resultOut;
|
||||
|
||||
return assetLoad(
|
||||
path,
|
||||
assetScriptLoader,
|
||||
NULL,
|
||||
&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;
|
||||
return assetLoad(path, assetScriptLoader, NULL, &scriptData);
|
||||
}
|
||||
@@ -7,21 +7,16 @@
|
||||
|
||||
#pragma once
|
||||
#include "asset/asset.h"
|
||||
#include "script/scriptcontext.h"
|
||||
|
||||
#define ASSET_SCRIPT_BUFFER_SIZE 1024
|
||||
#define ASSET_SCRIPT_CHUNK_SIZE 1024
|
||||
|
||||
typedef struct {
|
||||
void *nothing;
|
||||
} assetscriptloaderparams_t;
|
||||
|
||||
typedef struct {
|
||||
scriptcontext_t *ctx;
|
||||
char_t buffer[ASSET_SCRIPT_BUFFER_SIZE];
|
||||
jerry_value_t *resultOut;
|
||||
} assetscript_t;
|
||||
|
||||
/**
|
||||
* Handler for script assets.
|
||||
* Handler for script assets. Reads the full source, evaluates it with
|
||||
* JerryScript, and optionally stores the result.
|
||||
*
|
||||
* @param file Asset file to load the script from.
|
||||
* @return Any error that occurs during loading.
|
||||
@@ -32,17 +27,12 @@ errorret_t assetScriptLoader(assetfile_t *file);
|
||||
* Loads a script from the specified path.
|
||||
*
|
||||
* @param path Path to the script asset.
|
||||
* @param ctx Script context to load the script into.
|
||||
* @param resultOut Optional out-parameter for the script return value.
|
||||
* Caller must call jerry_value_free() if non-NULL.
|
||||
* Pass NULL to discard the return value.
|
||||
* @return Any error that occurs during loading.
|
||||
*/
|
||||
errorret_t assetScriptLoad(const char_t *path, scriptcontext_t *ctx);
|
||||
|
||||
/**
|
||||
* Reader function for Lua to read script data from the asset.
|
||||
*
|
||||
* @param L Lua state.
|
||||
* @param data Pointer to the scriptcontext_t structure.
|
||||
* @param size Pointer to store the size of the read data.
|
||||
* @return Pointer to the read data buffer.
|
||||
*/
|
||||
const char_t * assetScriptReader(lua_State* L, void* data, size_t* size);
|
||||
errorret_t assetScriptLoad(
|
||||
const char_t *path,
|
||||
jerry_value_t *resultOut
|
||||
);
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
event.c
|
||||
console.c
|
||||
)
|
||||
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* 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
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "consoledefs.h"
|
||||
#include "error/error.h"
|
||||
#include "dusk.h"
|
||||
|
||||
#ifdef DUSK_CONSOLE_POSIX
|
||||
#include "thread/thread.h"
|
||||
#include <poll.h>
|
||||
#include <unistd.h>
|
||||
#define CONSOLE_POSIX_POLL_RATE 75
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
char_t line[CONSOLE_HISTORY_MAX][CONSOLE_LINE_MAX];
|
||||
bool_t visible;
|
||||
|
||||
char_t execBuffer[CONSOLE_EXEC_BUFFER_MAX][CONSOLE_LINE_MAX];
|
||||
uint32_t execBufferCount;
|
||||
|
||||
#ifdef DUSK_CONSOLE_POSIX
|
||||
thread_t thread;
|
||||
threadmutex_t execMutex;
|
||||
threadmutex_t printMutex;
|
||||
#endif
|
||||
} console_t;
|
||||
|
||||
extern console_t CONSOLE;
|
||||
|
||||
/**
|
||||
* Initializes the console.
|
||||
*/
|
||||
void consoleInit(void);
|
||||
|
||||
/**
|
||||
* Prints a message to the console history.
|
||||
*
|
||||
* @param message The message to print (printf-style).
|
||||
*/
|
||||
void consolePrint(const char_t *message, ...);
|
||||
|
||||
/**
|
||||
* Queues a JS string for execution on the main thread. Thread-safe.
|
||||
*
|
||||
* @param line The JS source line to execute.
|
||||
*/
|
||||
void consoleExec(const char_t *line);
|
||||
|
||||
/**
|
||||
* Processes pending queued script lines. Call once per frame from main thread.
|
||||
*/
|
||||
void consoleUpdate(void);
|
||||
|
||||
/**
|
||||
* Renders the console history to the screen (UI space).
|
||||
*
|
||||
* @return The error return value.
|
||||
*/
|
||||
errorret_t consoleDraw(void);
|
||||
|
||||
/**
|
||||
* Disposes of the console.
|
||||
*/
|
||||
void consoleDispose(void);
|
||||
|
||||
#ifdef DUSK_CONSOLE_POSIX
|
||||
/**
|
||||
* Input thread handler for POSIX stdin.
|
||||
*
|
||||
* @param thread The thread that is running.
|
||||
*/
|
||||
void consoleInputThread(thread_t *thread);
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#define CONSOLE_LINE_MAX 512
|
||||
#define CONSOLE_HISTORY_MAX 16
|
||||
#define CONSOLE_EXEC_BUFFER_MAX 32
|
||||
@@ -3,8 +3,7 @@
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
moduleui.c
|
||||
cutscene.c
|
||||
)
|
||||
@@ -0,0 +1,187 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// 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);
|
||||
@@ -25,8 +25,6 @@
|
||||
#include "display/shader/shaderunlit.h"
|
||||
#include "time/time.h"
|
||||
|
||||
#include "script/module/display/moduleshader.h"
|
||||
|
||||
display_t DISPLAY = { 0 };
|
||||
|
||||
errorret_t displayInit(void) {
|
||||
@@ -35,6 +33,7 @@ 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 }
|
||||
@@ -112,6 +111,13 @@ 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());
|
||||
|
||||
@@ -40,15 +40,26 @@ 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);
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 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;
|
||||
@@ -119,8 +119,12 @@ 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;
|
||||
@@ -152,8 +156,12 @@ 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;
|
||||
|
||||
@@ -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 (must hold CUBE_VERTEX_COUNT).
|
||||
* @param vertices The vertex array to buffer into.
|
||||
* @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.
|
||||
|
||||
@@ -34,10 +34,14 @@ 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) {
|
||||
|
||||
@@ -9,12 +9,6 @@
|
||||
#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
|
||||
|
||||
@@ -35,7 +29,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 (must hold TRIPRISM_VERTEX_COUNT).
|
||||
* @param vertices Vertex array to write into.
|
||||
* @param x0,y0 First triangle vertex (XY).
|
||||
* @param x1,y1 Second triangle vertex (XY).
|
||||
* @param x2,y2 Third triangle vertex (XY).
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
#pragma once
|
||||
#include "display/mesh/quad.h"
|
||||
|
||||
#define SPRITEBATCH_SPRITES_MAX 32
|
||||
#define SPRITEBATCH_SPRITES_MAX 256
|
||||
#define SPRITEBATCH_VERTEX_COUNT (SPRITEBATCH_SPRITES_MAX * QUAD_VERTEX_COUNT)
|
||||
#define SPRITEBATCH_FLUSH_COUNT 4
|
||||
#define SPRITEBATCH_FLUSH_COUNT 8
|
||||
#define SPRITEBATCH_SPRITES_MAX_PER_FLUSH (\
|
||||
SPRITEBATCH_SPRITES_MAX / SPRITEBATCH_FLUSH_COUNT \
|
||||
)
|
||||
|
||||
@@ -13,19 +13,19 @@
|
||||
#include "asset/loader/display/assettilesetloader.h"
|
||||
#include "display/shader/shaderunlit.h"
|
||||
|
||||
texture_t DEFAULT_FONT_TEXTURE;
|
||||
tileset_t DEFAULT_FONT_TILESET;
|
||||
texture_t FONT_TEXTURE_DEFAULT;
|
||||
tileset_t FONT_TILESET_DEFAULT;
|
||||
|
||||
errorret_t textInit(void) {
|
||||
errorChain(assetTextureLoad(
|
||||
"ui/minogram.png", &DEFAULT_FONT_TEXTURE, TEXTURE_FORMAT_RGBA
|
||||
"ui/minogram.png", &FONT_TEXTURE_DEFAULT, TEXTURE_FORMAT_RGBA
|
||||
));
|
||||
errorChain(assetTilesetLoad("ui/minogram.dtf", &DEFAULT_FONT_TILESET));
|
||||
errorChain(assetTilesetLoad("ui/minogram.dtf", &FONT_TILESET_DEFAULT));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t textDispose(void) {
|
||||
errorChain(textureDispose(&DEFAULT_FONT_TEXTURE));
|
||||
errorChain(textureDispose(&FONT_TEXTURE_DEFAULT));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
|
||||
#define TEXT_CHAR_START '!'
|
||||
|
||||
extern texture_t DEFAULT_FONT_TEXTURE;
|
||||
extern tileset_t DEFAULT_FONT_TILESET;
|
||||
extern texture_t FONT_TEXTURE_DEFAULT;
|
||||
extern tileset_t FONT_TILESET_DEFAULT;
|
||||
|
||||
/**
|
||||
* Initializes the text system.
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <cglm/cglm.h>
|
||||
#include <cglm/types.h>
|
||||
#include <cglm/vec2.h>
|
||||
#include <jerryscript.h>
|
||||
|
||||
#include "duskplatform.h"
|
||||
|
||||
|
||||
+33
-186
@@ -12,127 +12,31 @@
|
||||
#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 "system/system.h"
|
||||
#include "console/console.h"
|
||||
#include "item/backpack.h"
|
||||
|
||||
#include "network/httpclient.h"
|
||||
double jerry_port_current_time(void) {
|
||||
dusktimeepoch_t epoch = timeGetEpoch();
|
||||
return epoch.time * 1000.0;
|
||||
}
|
||||
|
||||
#include "display/mesh/cube.h"
|
||||
#include "display/mesh/plane.h"
|
||||
int32_t jerry_port_local_tza(double unix_ms) {
|
||||
(void) unix_ms;
|
||||
return 0;
|
||||
}
|
||||
|
||||
engine_t ENGINE;
|
||||
entityid_t phBoxEnt;
|
||||
componentid_t phBoxPhys;
|
||||
|
||||
float_t onlineSwapTime = FLT_MAX;
|
||||
|
||||
void goOnline();
|
||||
void goOffline();
|
||||
|
||||
void onGETComplete(httpclient_t *client, void *user) {
|
||||
sceneLog("GET request complete!\n");
|
||||
sceneLog("Response status: %u\n", client->statusCode);
|
||||
for(size_t i = 0; i < client->responseHeaderCount; i++) {
|
||||
sceneLog("Header: %s: %s\n", client->responseHeaders[i].name, client->responseHeaders[i].value);
|
||||
}
|
||||
}
|
||||
|
||||
void onGETError(httpclient_t *client, errorret_t err, void *user) {
|
||||
errorCatch(errorPrint(err));
|
||||
}
|
||||
|
||||
void onNetworkConnected(void *user) {
|
||||
onlineSwapTime = TIME.time + 3.0f;
|
||||
|
||||
networkinfo_t info = networkGetInfo();
|
||||
if(info.type == NETWORK_TYPE_IPV4) {
|
||||
sceneLog(
|
||||
"Connected to network with IPv4 address: " NETWORK_INFO_FORMAT_IPV4 "\n",
|
||||
info.ipv4.ip[0], info.ipv4.ip[1], info.ipv4.ip[2], info.ipv4.ip[3]
|
||||
);
|
||||
#ifdef DUSK_NETWORK_IPV6
|
||||
} else if(info.type == NETWORK_TYPE_IPV6) {
|
||||
sceneLog(
|
||||
"Connected to network with IPv6 address: " NETWORK_INFO_FORMAT_IPV6 "\n",
|
||||
info.ipv6.ip[0], info.ipv6.ip[1], info.ipv6.ip[2], info.ipv6.ip[3],
|
||||
info.ipv6.ip[4], info.ipv6.ip[5], info.ipv6.ip[6], info.ipv6.ip[7],
|
||||
info.ipv6.ip[8], info.ipv6.ip[9], info.ipv6.ip[10], info.ipv6.ip[11],
|
||||
info.ipv6.ip[12], info.ipv6.ip[13], info.ipv6.ip[14], info.ipv6.ip[15]
|
||||
);
|
||||
#endif
|
||||
}
|
||||
|
||||
char_t *domain = "https://google.com";
|
||||
sceneLog("Online, sending GET to %s...\n", domain);
|
||||
httpclient_t client;
|
||||
errorret_t ret;
|
||||
ret = httpclientInit(&client);
|
||||
if(ret.code != ERROR_OK) {
|
||||
errorCatch(errorPrint(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
httpclientRequest(
|
||||
&client,
|
||||
"GET",
|
||||
domain,
|
||||
false,
|
||||
NULL,
|
||||
NULL,
|
||||
onGETComplete,
|
||||
onGETError,
|
||||
NULL
|
||||
);
|
||||
|
||||
ret = httpclientDispose(&client);
|
||||
if(ret.code != ERROR_OK) {
|
||||
errorCatch(errorPrint(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
// sceneLog("Network connected, I will disconnect at: %.2f1.\n", onlineSwapTime);
|
||||
}
|
||||
|
||||
void onNetworkFailed(errorret_t error, void *user) {
|
||||
onlineSwapTime = TIME.time + 3.0f;
|
||||
sceneLog("Failed to connect to network, will try again at %.2f1.\n", onlineSwapTime);
|
||||
}
|
||||
|
||||
void onNetworkDisconnected(errorret_t error, void *user) {
|
||||
onlineSwapTime = TIME.time + 3.0f;
|
||||
sceneLog("Network disconnected, will go online at %.2f1.\n", onlineSwapTime);
|
||||
errorCatch(errorPrint(error));
|
||||
}
|
||||
|
||||
void onNetworkDisconnectFinished(void *user) {
|
||||
onlineSwapTime = TIME.time + 3.0f;
|
||||
sceneLog("Finished disconnecting from network, will go online at %.2f1.\n", onlineSwapTime);
|
||||
}
|
||||
|
||||
void goOnline() {
|
||||
sceneLog("Going online...\n");
|
||||
networkRequestConnection(
|
||||
onNetworkConnected,
|
||||
onNetworkFailed,
|
||||
onNetworkDisconnected,
|
||||
NULL
|
||||
);
|
||||
}
|
||||
|
||||
void goOffline() {
|
||||
sceneLog("Going offline...\n");
|
||||
networkRequestDisconnection(onNetworkDisconnectFinished, NULL);
|
||||
}
|
||||
|
||||
|
||||
errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
||||
memoryZero(&ENGINE, sizeof(engine_t));
|
||||
@@ -143,106 +47,46 @@ 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(gameInit());
|
||||
|
||||
sceneLog("Init done, going to queue online in 1 seconds...\n");
|
||||
onlineSwapTime = TIME.time + 1.0f;
|
||||
|
||||
// Camera
|
||||
entityid_t cam = entityManagerAdd();
|
||||
componentid_t camPos = entityAddComponent(cam, COMPONENT_TYPE_POSITION);
|
||||
float_t distance = 6.0f;
|
||||
entityPositionLookAt(
|
||||
cam, camPos,
|
||||
(vec3){ 0.0f, 1.0f, 0.0f },
|
||||
(vec3){ 0.0f, 1.0f, 0.0f },
|
||||
(vec3){ distance, distance, distance }
|
||||
);
|
||||
componentid_t camCam = entityAddComponent(cam, COMPONENT_TYPE_CAMERA);
|
||||
entityCameraSetZFar(cam, camCam, 100.0f);
|
||||
|
||||
// Floor
|
||||
entityid_t floorEnt = entityManagerAdd();
|
||||
componentid_t floorPos = entityAddComponent(floorEnt, COMPONENT_TYPE_POSITION);
|
||||
componentid_t floorMesh = entityAddComponent(floorEnt, COMPONENT_TYPE_MESH);
|
||||
componentid_t floorMat = entityAddComponent(floorEnt, COMPONENT_TYPE_MATERIAL);
|
||||
componentid_t floorPhys = entityAddComponent(floorEnt, COMPONENT_TYPE_PHYSICS);
|
||||
|
||||
entityPositionSetPosition(floorEnt, floorPos, (vec3){ -5.0f, 0.0f, -5.0f });
|
||||
entityPositionSetScale(floorEnt, floorPos, (vec3){ 10.0f, 1.0f, 10.0f });
|
||||
entityMeshSetMesh(floorEnt, floorMesh, &PLANE_MESH_SIMPLE);
|
||||
entityMaterialGetShaderMaterial(floorEnt, floorMat)->unlit.color = COLOR_GREEN;
|
||||
|
||||
entityphysics_t *floorPhysData = entityPhysicsGet(floorEnt, floorPhys);
|
||||
floorPhysData->type = PHYSICS_BODY_STATIC;
|
||||
floorPhysData->shape.type = PHYSICS_SHAPE_PLANE;
|
||||
floorPhysData->shape.data.plane.normal[0] = 0.0f;
|
||||
floorPhysData->shape.data.plane.normal[1] = 1.0f;
|
||||
floorPhysData->shape.data.plane.normal[2] = 0.0f;
|
||||
floorPhysData->shape.data.plane.distance = 0.0f;
|
||||
|
||||
// Box
|
||||
phBoxEnt = entityManagerAdd();
|
||||
componentid_t boxPos = entityAddComponent(phBoxEnt, COMPONENT_TYPE_POSITION);
|
||||
componentid_t boxMesh = entityAddComponent(phBoxEnt, COMPONENT_TYPE_MESH);
|
||||
componentid_t boxMat = entityAddComponent(phBoxEnt, COMPONENT_TYPE_MATERIAL);
|
||||
phBoxPhys = entityAddComponent(phBoxEnt, COMPONENT_TYPE_PHYSICS);
|
||||
|
||||
entityMeshSetMesh(phBoxEnt, boxMesh, &CUBE_MESH_SIMPLE);
|
||||
entityMaterialGetShaderMaterial(phBoxEnt, boxMat)->unlit.color = COLOR_RED;
|
||||
entityPositionSetPosition(phBoxEnt, boxPos, (vec3){ 0.0f, 4.0f, 0.0f });
|
||||
errorChain(networkInit());
|
||||
|
||||
/* Run the init script. */
|
||||
scriptcontext_t ctx;
|
||||
errorChain(scriptContextInit(&ctx));
|
||||
errorChain(scriptContextExecFile(&ctx, "init.lua"));
|
||||
scriptContextDispose(&ctx);
|
||||
consolePrint("Engine initialized");
|
||||
errorChain(scriptManagerExecFile("init.js", NULL));
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t engineUpdate(void) {
|
||||
// errorChain(networkUpdate());
|
||||
|
||||
// Order here is important.
|
||||
errorChain(networkUpdate());
|
||||
timeUpdate();
|
||||
inputUpdate();
|
||||
|
||||
consoleUpdate();
|
||||
uiUpdate();
|
||||
errorChain(sceneUpdate());
|
||||
|
||||
/* Reset the box to its start position on demand. */
|
||||
if(inputIsDown(INPUT_ACTION_ACCEPT)) {
|
||||
componentid_t posComp = entityGetComponent(phBoxEnt, COMPONENT_TYPE_POSITION);
|
||||
entityPositionSetPosition(phBoxEnt, posComp, (vec3){ 0.0f, 4.0f, 0.0f });
|
||||
entityPhysicsSetVelocity(phBoxEnt, phBoxPhys, (vec3){ 0.0f, 0.0f, 0.0f });
|
||||
}
|
||||
|
||||
/* Step physics: positions are updated directly on POSITION components. */
|
||||
errorChain(uiTextboxUpdate());
|
||||
physicsManagerUpdate();
|
||||
|
||||
errorChain(gameUpdate());
|
||||
errorChain(displayUpdate());
|
||||
|
||||
if(inputPressed(INPUT_ACTION_RAGEQUIT)) ENGINE.running = false;
|
||||
|
||||
if(TIME.time >= onlineSwapTime) {
|
||||
onlineSwapTime = FLT_MAX;
|
||||
if(NETWORK.state == NETWORK_STATE_CONNECTED) {
|
||||
goOffline();
|
||||
} else {
|
||||
goOnline();
|
||||
}
|
||||
}
|
||||
// Scene update occurs last because only after rendering would we want to do
|
||||
// scene switching, refer to sceneSet() for information.
|
||||
errorChain(cutsceneUpdate());
|
||||
errorChain(sceneUpdate());
|
||||
|
||||
errorOk();
|
||||
}
|
||||
@@ -252,13 +96,16 @@ void engineExit(void) {
|
||||
}
|
||||
|
||||
errorret_t engineDispose(void) {
|
||||
// errorChain(networkDispose());
|
||||
uiTextboxDispose();
|
||||
cutsceneDispose();
|
||||
sceneDispose();
|
||||
errorChain(gameDispose());
|
||||
errorChain(networkDispose());
|
||||
entityManagerDispose();
|
||||
localeManagerDispose();
|
||||
uiDispose();
|
||||
consoleDispose();
|
||||
errorChain(displayDispose());
|
||||
errorChain(assetDispose());
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -36,3 +36,4 @@ errorret_t engineUpdate(void);
|
||||
* Shuts down the engine.
|
||||
*/
|
||||
errorret_t engineDispose(void);
|
||||
|
||||
|
||||
@@ -12,8 +12,14 @@
|
||||
componentdefinition_t COMPONENT_DEFINITIONS[] = {
|
||||
[COMPONENT_TYPE_NULL] = { 0 },
|
||||
|
||||
#define X(enumName, type, field, iMethod, dMethod) \
|
||||
[COMPONENT_TYPE_##enumName] = { .init = iMethod, .dispose = dMethod },
|
||||
#define X(enm, type, field, iMethod, dMethod) \
|
||||
[COMPONENT_TYPE_##enm] = { \
|
||||
.enumName = #enm, \
|
||||
.name = #field, \
|
||||
.init = iMethod, \
|
||||
.dispose = dMethod \
|
||||
},
|
||||
|
||||
#include "componentlist.h"
|
||||
#undef X
|
||||
|
||||
@@ -95,7 +101,7 @@ entityid_t componentGetEntitiesWithComponent(
|
||||
"Component ID OOB in entitiesWithComponent lookup"
|
||||
);
|
||||
assertTrue(
|
||||
componentGetIndex(i, used) < ENTITY_COUNT_MAX * ENTITY_COMPONENT_COUNT_MAX,
|
||||
componentGetIndex(i,used) < ENTITY_COUNT_MAX*ENTITY_COMPONENT_COUNT_MAX,
|
||||
"Component index OOB in entitiesWithComponent lookup"
|
||||
);
|
||||
assertTrue(
|
||||
|
||||
@@ -20,6 +20,8 @@ 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;
|
||||
|
||||
@@ -5,3 +5,4 @@
|
||||
|
||||
add_subdirectory(display)
|
||||
add_subdirectory(physics)
|
||||
add_subdirectory(script)
|
||||
@@ -6,6 +6,8 @@
|
||||
*/
|
||||
|
||||
#include "entity/entitymanager.h"
|
||||
#include "entity/entity.h"
|
||||
#include "entity/component/display/entityposition.h"
|
||||
#include "display/framebuffer/framebuffer.h"
|
||||
#include "display/screen/screen.h"
|
||||
|
||||
@@ -19,42 +21,6 @@ void entityCameraInit(const entityid_t ent, const componentid_t comp) {
|
||||
cam->perspective.fov = glm_rad(45.0f);
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -93,3 +59,38 @@ 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,45 +55,25 @@ void entityCameraGetProjection(
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the near clip distance of a camera.
|
||||
*
|
||||
* @param ent The entity ID.
|
||||
* @param comp The component ID.
|
||||
* @return The near clip distance.
|
||||
* Returns the entity ID of the first active camera, or ENTITY_COUNT_MAX if
|
||||
* none are active.
|
||||
*/
|
||||
float_t entityCameraGetZNear(const entityid_t ent, const componentid_t comp);
|
||||
entityid_t entityCameraGetCurrent(void);
|
||||
|
||||
/**
|
||||
* Sets the near clip distance of a camera.
|
||||
* Gets the camera's horizontal forward direction (XZ plane) from its position
|
||||
* component. Automatically finds the position component on the entity.
|
||||
*
|
||||
* @param ent The entity ID.
|
||||
* @param comp The component ID.
|
||||
* @param zNear The near clip distance.
|
||||
* @param entityId The camera entity ID.
|
||||
* @param out Output vec2: {forwardX, forwardZ} normalized.
|
||||
*/
|
||||
void entityCameraSetZNear(
|
||||
const entityid_t ent,
|
||||
const componentid_t comp,
|
||||
const float_t zNear
|
||||
);
|
||||
void entityCameraGetForward(const entityid_t entityId, vec2 out);
|
||||
|
||||
/**
|
||||
* Gets the far clip distance of a camera.
|
||||
* Gets the camera's horizontal right direction (XZ plane) from its position
|
||||
* component. Automatically finds the position component on the entity.
|
||||
*
|
||||
* @param ent The entity ID.
|
||||
* @param comp The component ID.
|
||||
* @return The far clip distance.
|
||||
* @param entityId The camera entity ID.
|
||||
* @param out Output vec2: {rightX, rightZ} normalized.
|
||||
*/
|
||||
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
|
||||
);
|
||||
void entityCameraGetRight(const entityid_t entityId, vec2 out);
|
||||
@@ -49,3 +49,14 @@ 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,3 +61,16 @@ 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,6 +5,7 @@
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entitymesh.h"
|
||||
#include "entity/entitymanager.h"
|
||||
|
||||
void entityMeshInit(
|
||||
@@ -37,3 +38,9 @@ void entityMeshSetMesh(
|
||||
);
|
||||
comp->mesh = mesh;
|
||||
}
|
||||
|
||||
void entityMeshDispose(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -41,10 +41,21 @@ mesh_t * entityMeshGetMesh(
|
||||
*
|
||||
* @param entityId The entity ID.
|
||||
* @param componentId The component ID.
|
||||
* @param mesh A pointer to the mesh to associate with the entity mesh component.
|
||||
* @param mesh A pointer to the mesh to associate.
|
||||
*/
|
||||
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,6 +100,25 @@ 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,6 +122,31 @@ 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.
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(module)
|
||||
|
||||
|
||||
@@ -11,8 +11,14 @@
|
||||
#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, NULL)
|
||||
X(MESH, entitymesh_t, mesh, entityMeshInit, entityMeshDispose)
|
||||
X(MATERIAL, entitymaterial_t, material, entityMaterialInit, NULL)
|
||||
X(PHYSICS, entityphysics_t, physics, entityPhysicsInit, entityPhysicsDispose)
|
||||
@@ -8,8 +8,8 @@
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
#define ENTITY_COUNT_MAX 6
|
||||
#define ENTITY_COMPONENT_COUNT_MAX 6
|
||||
#define ENTITY_COUNT_MAX 20
|
||||
#define ENTITY_COMPONENT_COUNT_MAX 8
|
||||
|
||||
typedef uint8_t entityid_t;
|
||||
typedef uint8_t componentid_t;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "entitymanager.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "scene/scene.h"
|
||||
#include "console/console.h"
|
||||
|
||||
entitymanager_t ENTITY_MANAGER;
|
||||
|
||||
@@ -19,8 +19,8 @@ void entityManagerInit(void) {
|
||||
sizeof(entityid_t) * COMPONENT_TYPE_COUNT * ENTITY_COUNT_MAX
|
||||
);
|
||||
|
||||
sceneLog(
|
||||
"Entity Manager size: %zu bytes (%.2f KB)\n",
|
||||
consolePrint(
|
||||
"Entity Manager size: %zu bytes (%.2f KB)",
|
||||
sizeof(entitymanager_t),
|
||||
sizeof(entitymanager_t) / 1024.0f
|
||||
);
|
||||
|
||||
@@ -83,7 +83,9 @@ 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);
|
||||
|
||||
@@ -1,252 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
/**
|
||||
* 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
|
||||
);
|
||||
@@ -1,14 +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"
|
||||
|
||||
typedef struct eventdata_s eventdata_t;
|
||||
typedef struct eventc_s eventc_t;
|
||||
|
||||
typedef void (*eventcallback_t)(eventdata_t *data, eventc_t user);
|
||||
@@ -1,30 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
+14
-37
@@ -27,13 +27,6 @@ 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();
|
||||
}
|
||||
|
||||
@@ -94,28 +87,6 @@ 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) {
|
||||
@@ -171,6 +142,16 @@ 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,
|
||||
@@ -179,19 +160,15 @@ 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 = 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");
|
||||
inputbuttondata_t *data = inputButtonGetData(button);
|
||||
assertNotNull(data, "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);
|
||||
|
||||
@@ -5,6 +5,7 @@ LEFT,
|
||||
RIGHT,
|
||||
ACCEPT,
|
||||
CANCEL,
|
||||
RAGEQUIT
|
||||
RAGEQUIT,
|
||||
CONSOLE,
|
||||
POINTERX,
|
||||
POINTERY,
|
||||
|
+16
-10
@@ -9,23 +9,13 @@
|
||||
#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;
|
||||
|
||||
@@ -120,6 +110,22 @@ 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,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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "input.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/string.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
inputbutton_t inputButtonGetByName(const char_t *name) {
|
||||
assertNotNull(name, "name must not be NULL");
|
||||
@@ -27,3 +28,15 @@ 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;
|
||||
}
|
||||
@@ -97,3 +97,11 @@ 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);
|
||||
@@ -7,7 +7,4 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
network.c
|
||||
networkinfo.c
|
||||
networksocket.c
|
||||
networktls.c
|
||||
httpclient.c
|
||||
)
|
||||
|
||||
@@ -1,545 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "httpclient.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
/* ---- helpers ----------------------------------------------------------- */
|
||||
|
||||
static bool_t httpclientStrEqualCI(const char_t *a, const char_t *b) {
|
||||
while(*a && *b) {
|
||||
if(tolower((unsigned char)*a) != tolower((unsigned char)*b)) return false;
|
||||
a++; b++;
|
||||
}
|
||||
return *a == '\0' && *b == '\0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads exactly len bytes from the TLS stream, retrying on timeout.
|
||||
* Returns an error on I/O failure or unexpected connection close.
|
||||
*/
|
||||
static errorret_t httpclientReadExact(
|
||||
networktls_t *tls,
|
||||
errorstate_t *es,
|
||||
uint8_t *buf,
|
||||
size_t len
|
||||
) {
|
||||
size_t total = 0;
|
||||
while(total < len) {
|
||||
size_t got = 0;
|
||||
errorret_t err = networktlsRead(tls, buf + total, len - total, &got);
|
||||
if(err.code != ERROR_OK) return err;
|
||||
if(got == NETWORKSOCKET_RECV_CLOSED) {
|
||||
errorThrowState(es, "Connection closed before all expected bytes arrived");
|
||||
}
|
||||
total += got;
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads bytes one at a time until \r\n is found. Writes the line (without
|
||||
* the terminator) into buf, null-terminates, and sets *outLen. Returns an
|
||||
* error on I/O failure or if the line exceeds maxLen-1 characters.
|
||||
*/
|
||||
static errorret_t httpclientReadLine(
|
||||
networktls_t *tls,
|
||||
errorstate_t *es,
|
||||
char_t *buf,
|
||||
size_t maxLen,
|
||||
size_t *outLen
|
||||
) {
|
||||
size_t len = 0;
|
||||
uint8_t ch;
|
||||
size_t got;
|
||||
errorret_t err;
|
||||
|
||||
while(len < maxLen - 1) {
|
||||
do {
|
||||
got = 0;
|
||||
err = networktlsRead(tls, &ch, 1, &got);
|
||||
if(err.code != ERROR_OK) return err;
|
||||
if(got == NETWORKSOCKET_RECV_CLOSED) {
|
||||
errorThrowState(es, "Connection closed during header read");
|
||||
}
|
||||
} while(got == 0);
|
||||
|
||||
if(ch == '\r') {
|
||||
/* consume the \n */
|
||||
do {
|
||||
got = 0;
|
||||
err = networktlsRead(tls, &ch, 1, &got);
|
||||
if(err.code != ERROR_OK) return err;
|
||||
if(got == NETWORKSOCKET_RECV_CLOSED) break;
|
||||
} while(got == 0);
|
||||
break;
|
||||
}
|
||||
buf[len++] = (char_t)ch;
|
||||
}
|
||||
|
||||
buf[len] = '\0';
|
||||
*outLen = len;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
/* ---- response parsing -------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Parses status code from "HTTP/x.y NNN ..." — returns 0 on failure.
|
||||
*/
|
||||
static uint16_t httpclientParseStatus(const char_t *line) {
|
||||
const char_t *p = strchr(line, ' ');
|
||||
if(!p) return 0;
|
||||
return (uint16_t)atoi(p + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses "Name: Value" into the client's responseHeaders array. Trims
|
||||
* leading whitespace from the value.
|
||||
*/
|
||||
static void httpclientParseHeader(
|
||||
httpclient_t *client,
|
||||
const char_t *line
|
||||
) {
|
||||
const char_t *colon;
|
||||
const char_t *valStart;
|
||||
size_t nameLen;
|
||||
size_t valLen;
|
||||
httpheader_t *h;
|
||||
|
||||
if(client->responseHeaderCount >= HTTPCLIENT_HEADER_MAX) return;
|
||||
|
||||
colon = strchr(line, ':');
|
||||
if(!colon) return;
|
||||
|
||||
nameLen = (size_t)(colon - line);
|
||||
if(nameLen == 0 || nameLen >= HTTPCLIENT_HEADER_NAME_MAX) return;
|
||||
|
||||
valStart = colon + 1;
|
||||
while(*valStart == ' ' || *valStart == '\t') valStart++;
|
||||
valLen = strlen(valStart);
|
||||
if(valLen >= HTTPCLIENT_HEADER_VALUE_MAX) valLen = HTTPCLIENT_HEADER_VALUE_MAX - 1;
|
||||
|
||||
h = &client->responseHeaders[client->responseHeaderCount++];
|
||||
memoryCopy(h->name, line, nameLen);
|
||||
h->name[nameLen] = '\0';
|
||||
memoryCopy(h->value, valStart, valLen);
|
||||
h->value[valLen] = '\0';
|
||||
}
|
||||
|
||||
static const char_t *httpclientGetResponseHeader(
|
||||
const httpclient_t *client,
|
||||
const char_t *name
|
||||
) {
|
||||
size_t i;
|
||||
for(i = 0; i < client->responseHeaderCount; i++) {
|
||||
if(httpclientStrEqualCI(client->responseHeaders[i].name, name)) {
|
||||
return client->responseHeaders[i].value;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ---- body delivery ----------------------------------------------------- */
|
||||
|
||||
static errorret_t httpclientReadBodyFixed(
|
||||
httpclient_t *client,
|
||||
size_t contentLength
|
||||
) {
|
||||
uint8_t buf[HTTPCLIENT_BODY_BUF_SIZE];
|
||||
size_t remaining = contentLength;
|
||||
size_t got;
|
||||
errorret_t err;
|
||||
|
||||
while(remaining > 0) {
|
||||
size_t want = remaining < sizeof(buf) ? remaining : sizeof(buf);
|
||||
err = httpclientReadExact(&client->tls, &client->errorState, buf, want);
|
||||
if(err.code != ERROR_OK) return err;
|
||||
if(client->onData) client->onData(client, buf, want, client->user);
|
||||
remaining -= want;
|
||||
got = want;
|
||||
(void)got;
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
static errorret_t httpclientReadBodyChunked(httpclient_t *client) {
|
||||
char_t sizeLine[32];
|
||||
size_t lineLen;
|
||||
errorret_t err;
|
||||
uint8_t buf[HTTPCLIENT_BODY_BUF_SIZE];
|
||||
|
||||
for(;;) {
|
||||
err = httpclientReadLine(
|
||||
&client->tls, &client->errorState, sizeLine, sizeof(sizeLine), &lineLen
|
||||
);
|
||||
if(err.code != ERROR_OK) return err;
|
||||
|
||||
/* strtol parses hex chunk size; stop on terminating "0" chunk */
|
||||
size_t chunkSize = (size_t)strtol(sizeLine, NULL, 16);
|
||||
if(chunkSize == 0) {
|
||||
/* consume trailing CRLF after the zero chunk */
|
||||
httpclientReadLine(
|
||||
&client->tls, &client->errorState, sizeLine, sizeof(sizeLine), &lineLen
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
size_t remaining = chunkSize;
|
||||
while(remaining > 0) {
|
||||
size_t want = remaining < sizeof(buf) ? remaining : sizeof(buf);
|
||||
err = httpclientReadExact(
|
||||
&client->tls, &client->errorState, buf, want
|
||||
);
|
||||
if(err.code != ERROR_OK) return err;
|
||||
if(client->onData) client->onData(client, buf, want, client->user);
|
||||
remaining -= want;
|
||||
}
|
||||
|
||||
/* consume the CRLF after chunk data */
|
||||
err = httpclientReadLine(
|
||||
&client->tls, &client->errorState, sizeLine, sizeof(sizeLine), &lineLen
|
||||
);
|
||||
if(err.code != ERROR_OK) return err;
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
static errorret_t httpclientReadBodyUntilClose(httpclient_t *client) {
|
||||
uint8_t buf[HTTPCLIENT_BODY_BUF_SIZE];
|
||||
size_t got;
|
||||
errorret_t err;
|
||||
|
||||
for(;;) {
|
||||
err = networktlsRead(&client->tls, buf, sizeof(buf), &got);
|
||||
if(err.code != ERROR_OK) return err;
|
||||
if(got == NETWORKSOCKET_RECV_CLOSED) break;
|
||||
if(got > 0 && client->onData) {
|
||||
client->onData(client, buf, got, client->user);
|
||||
}
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
/* ---- TLS onConnect — runs the full HTTP exchange ----------------------- */
|
||||
|
||||
static void httpclientTlsOnConnect(networktls_t *tls, void *user) {
|
||||
httpclient_t *client = (httpclient_t *)user;
|
||||
char_t reqBuf[HTTPCLIENT_REQ_BUF_SIZE];
|
||||
char_t lineBuf[HTTPCLIENT_HEADER_NAME_MAX + HTTPCLIENT_HEADER_VALUE_MAX + 4];
|
||||
int reqLen;
|
||||
size_t lineLen;
|
||||
size_t i;
|
||||
errorret_t err;
|
||||
|
||||
/* ---- build request headers ----------------------------------------- */
|
||||
reqLen = snprintf(
|
||||
reqBuf, sizeof(reqBuf),
|
||||
"%s %s HTTP/1.1\r\n"
|
||||
"Host: %s\r\n"
|
||||
"Connection: close\r\n"
|
||||
"User-Agent: DuskEngine/1.0\r\n",
|
||||
client->method,
|
||||
client->url.path[0] != '\0' ? client->url.path : "/",
|
||||
client->url.host
|
||||
);
|
||||
|
||||
for(i = 0; i < client->requestHeaderCount; i++) {
|
||||
int n = snprintf(
|
||||
reqBuf + reqLen, sizeof(reqBuf) - (size_t)reqLen,
|
||||
"%s: %s\r\n",
|
||||
client->requestHeaders[i].name,
|
||||
client->requestHeaders[i].value
|
||||
);
|
||||
if(n > 0) reqLen += n;
|
||||
}
|
||||
|
||||
if(client->requestBodyLen > 0) {
|
||||
int n = snprintf(
|
||||
reqBuf + reqLen, sizeof(reqBuf) - (size_t)reqLen,
|
||||
"Content-Length: %zu\r\n",
|
||||
client->requestBodyLen
|
||||
);
|
||||
if(n > 0) reqLen += n;
|
||||
}
|
||||
|
||||
/* blank line terminates headers */
|
||||
if((size_t)reqLen + 2 < sizeof(reqBuf)) {
|
||||
reqBuf[reqLen++] = '\r';
|
||||
reqBuf[reqLen++] = '\n';
|
||||
}
|
||||
|
||||
err = networktlsWrite(tls, (const uint8_t *)reqBuf, (size_t)reqLen);
|
||||
if(err.code != ERROR_OK) {
|
||||
if(client->onError) client->onError(client, err, client->user);
|
||||
return;
|
||||
}
|
||||
|
||||
if(client->requestBody != NULL && client->requestBodyLen > 0) {
|
||||
err = networktlsWrite(tls, client->requestBody, client->requestBodyLen);
|
||||
if(err.code != ERROR_OK) {
|
||||
if(client->onError) client->onError(client, err, client->user);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- read response status line ------------------------------------- */
|
||||
err = httpclientReadLine(
|
||||
tls, &client->errorState, lineBuf, sizeof(lineBuf), &lineLen
|
||||
);
|
||||
if(err.code != ERROR_OK) {
|
||||
if(client->onError) client->onError(client, err, client->user);
|
||||
return;
|
||||
}
|
||||
|
||||
client->statusCode = httpclientParseStatus(lineBuf);
|
||||
|
||||
/* ---- read response headers until blank line ------------------------ */
|
||||
client->responseHeaderCount = 0;
|
||||
for(;;) {
|
||||
err = httpclientReadLine(
|
||||
tls, &client->errorState, lineBuf, sizeof(lineBuf), &lineLen
|
||||
);
|
||||
if(err.code != ERROR_OK) {
|
||||
if(client->onError) client->onError(client, err, client->user);
|
||||
return;
|
||||
}
|
||||
if(lineLen == 0) break; /* blank line = end of headers */
|
||||
httpclientParseHeader(client, lineBuf);
|
||||
}
|
||||
|
||||
if(client->onHeaders) {
|
||||
client->onHeaders(
|
||||
client,
|
||||
client->statusCode,
|
||||
client->responseHeaders,
|
||||
client->responseHeaderCount,
|
||||
client->user
|
||||
);
|
||||
}
|
||||
|
||||
/* ---- read response body -------------------------------------------- */
|
||||
|
||||
/* 1xx, 204 No Content and 304 Not Modified carry no body */
|
||||
bool_t noBody = (
|
||||
client->statusCode < 200 ||
|
||||
client->statusCode == 204 ||
|
||||
client->statusCode == 304
|
||||
);
|
||||
|
||||
if(!noBody) {
|
||||
const char_t *transferEncoding =
|
||||
httpclientGetResponseHeader(client, "Transfer-Encoding");
|
||||
const char_t *contentLengthStr =
|
||||
httpclientGetResponseHeader(client, "Content-Length");
|
||||
|
||||
if(transferEncoding != NULL &&
|
||||
strstr(transferEncoding, "chunked") != NULL) {
|
||||
err = httpclientReadBodyChunked(client);
|
||||
} else if(contentLengthStr != NULL) {
|
||||
size_t contentLength = (size_t)atoi(contentLengthStr);
|
||||
err = httpclientReadBodyFixed(client, contentLength);
|
||||
} else {
|
||||
err = httpclientReadBodyUntilClose(client);
|
||||
}
|
||||
|
||||
if(err.code != ERROR_OK) {
|
||||
if(client->onError) client->onError(client, err, client->user);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(client->onComplete) client->onComplete(client, client->user);
|
||||
}
|
||||
|
||||
static void httpclientTlsOnError(
|
||||
networktls_t *tls,
|
||||
errorret_t err,
|
||||
void *user
|
||||
) {
|
||||
httpclient_t *client = (httpclient_t *)user;
|
||||
if(client->onError) client->onError(client, err, client->user);
|
||||
}
|
||||
|
||||
static void httpclientTlsOnDisconnect(networktls_t *tls, void *user) {
|
||||
/* nothing — httpclientTlsOnConnect drives the full lifecycle */
|
||||
}
|
||||
|
||||
/* ---- public API -------------------------------------------------------- */
|
||||
|
||||
errorret_t httpclientInit(httpclient_t *client) {
|
||||
memoryZero(client, sizeof(httpclient_t));
|
||||
errorChain(networktlsInit(&client->tls));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t httpclientParseUrl(
|
||||
httpurl_t *url,
|
||||
errorstate_t *errorState,
|
||||
const char_t *urlStr
|
||||
) {
|
||||
const char_t *schemeEnd;
|
||||
const char_t *hostStart;
|
||||
const char_t *portStart;
|
||||
const char_t *pathStart;
|
||||
size_t hostLen;
|
||||
|
||||
memoryZero(url, sizeof(httpurl_t));
|
||||
|
||||
schemeEnd = strstr(urlStr, "://");
|
||||
if(!schemeEnd) {
|
||||
errorThrowState(errorState, "URL missing scheme: %s", urlStr);
|
||||
}
|
||||
|
||||
size_t schemeLen = (size_t)(schemeEnd - urlStr);
|
||||
if(schemeLen >= HTTPCLIENT_SCHEME_MAX) {
|
||||
errorThrowState(errorState, "URL scheme too long");
|
||||
}
|
||||
memoryCopy(url->scheme, urlStr, schemeLen);
|
||||
url->scheme[schemeLen] = '\0';
|
||||
|
||||
/* default port by scheme */
|
||||
if(httpclientStrEqualCI(url->scheme, "https")) {
|
||||
url->port = 443;
|
||||
} else if(httpclientStrEqualCI(url->scheme, "http")) {
|
||||
url->port = 80;
|
||||
} else {
|
||||
errorThrowState(errorState, "Unsupported URL scheme: %s", url->scheme);
|
||||
}
|
||||
|
||||
hostStart = schemeEnd + 3;
|
||||
pathStart = strchr(hostStart, '/');
|
||||
portStart = strchr(hostStart, ':');
|
||||
|
||||
/* port present and appears before the path */
|
||||
if(portStart != NULL && (pathStart == NULL || portStart < pathStart)) {
|
||||
hostLen = (size_t)(portStart - hostStart);
|
||||
url->port = (uint16_t)atoi(portStart + 1);
|
||||
} else {
|
||||
hostLen = pathStart != NULL
|
||||
? (size_t)(pathStart - hostStart)
|
||||
: strlen(hostStart);
|
||||
}
|
||||
|
||||
if(hostLen == 0 || hostLen >= HTTPCLIENT_HOST_MAX) {
|
||||
errorThrowState(errorState, "URL host missing or too long");
|
||||
}
|
||||
memoryCopy(url->host, hostStart, hostLen);
|
||||
url->host[hostLen] = '\0';
|
||||
|
||||
if(pathStart != NULL) {
|
||||
size_t pathLen = strlen(pathStart);
|
||||
if(pathLen >= HTTPCLIENT_PATH_MAX) pathLen = HTTPCLIENT_PATH_MAX - 1;
|
||||
memoryCopy(url->path, pathStart, pathLen);
|
||||
url->path[pathLen] = '\0';
|
||||
} else {
|
||||
url->path[0] = '/';
|
||||
url->path[1] = '\0';
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void httpclientSetHeader(
|
||||
httpclient_t *client,
|
||||
const char_t *name,
|
||||
const char_t *value
|
||||
) {
|
||||
size_t nameLen;
|
||||
size_t valLen;
|
||||
size_t i;
|
||||
httpheader_t *h;
|
||||
|
||||
assertNotNull(name, "header name must not be null");
|
||||
assertNotNull(value, "header value must not be null");
|
||||
|
||||
/* replace existing header with same name */
|
||||
for(i = 0; i < client->requestHeaderCount; i++) {
|
||||
if(httpclientStrEqualCI(client->requestHeaders[i].name, name)) {
|
||||
h = &client->requestHeaders[i];
|
||||
valLen = strlen(value);
|
||||
if(valLen >= HTTPCLIENT_HEADER_VALUE_MAX) valLen = HTTPCLIENT_HEADER_VALUE_MAX - 1;
|
||||
memoryCopy(h->value, value, valLen);
|
||||
h->value[valLen] = '\0';
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
assertTrue(
|
||||
client->requestHeaderCount < HTTPCLIENT_HEADER_MAX,
|
||||
"httpclientSetHeader: request header limit exceeded"
|
||||
);
|
||||
|
||||
h = &client->requestHeaders[client->requestHeaderCount++];
|
||||
|
||||
nameLen = strlen(name);
|
||||
if(nameLen >= HTTPCLIENT_HEADER_NAME_MAX) nameLen = HTTPCLIENT_HEADER_NAME_MAX - 1;
|
||||
memoryCopy(h->name, name, nameLen);
|
||||
h->name[nameLen] = '\0';
|
||||
|
||||
valLen = strlen(value);
|
||||
if(valLen >= HTTPCLIENT_HEADER_VALUE_MAX) valLen = HTTPCLIENT_HEADER_VALUE_MAX - 1;
|
||||
memoryCopy(h->value, value, valLen);
|
||||
h->value[valLen] = '\0';
|
||||
}
|
||||
|
||||
void httpclientRequest(
|
||||
httpclient_t *client,
|
||||
const char_t *method,
|
||||
const char_t *url,
|
||||
bool_t verifyPeer,
|
||||
httpclientheadercallback_t onHeaders,
|
||||
httpclientdatacallback_t onData,
|
||||
httpclientcompletecallback_t onComplete,
|
||||
httpclienterrorcallback_t onError,
|
||||
void *user
|
||||
) {
|
||||
size_t methodLen;
|
||||
errorret_t err;
|
||||
|
||||
assertNotNull(method, "method must not be null");
|
||||
assertNotNull(url, "url must not be null");
|
||||
|
||||
methodLen = strlen(method);
|
||||
if(methodLen >= HTTPCLIENT_METHOD_MAX) methodLen = HTTPCLIENT_METHOD_MAX - 1;
|
||||
memoryCopy(client->method, method, methodLen);
|
||||
client->method[methodLen] = '\0';
|
||||
|
||||
err = httpclientParseUrl(&client->url, &client->errorState, url);
|
||||
if(err.code != ERROR_OK) {
|
||||
if(onError) onError(client, err, user);
|
||||
return;
|
||||
}
|
||||
|
||||
client->onHeaders = onHeaders;
|
||||
client->onData = onData;
|
||||
client->onComplete = onComplete;
|
||||
client->onError = onError;
|
||||
client->user = user;
|
||||
client->statusCode = 0;
|
||||
client->responseHeaderCount = 0;
|
||||
|
||||
networktlsConnect(
|
||||
&client->tls,
|
||||
client->url.host,
|
||||
client->url.port,
|
||||
verifyPeer,
|
||||
httpclientTlsOnConnect,
|
||||
httpclientTlsOnError,
|
||||
httpclientTlsOnDisconnect,
|
||||
client
|
||||
);
|
||||
}
|
||||
|
||||
void httpclientDisconnect(httpclient_t *client) {
|
||||
networktlsDisconnect(&client->tls);
|
||||
}
|
||||
|
||||
errorret_t httpclientDispose(httpclient_t *client) {
|
||||
return networktlsDispose(&client->tls);
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "network/networktls.h"
|
||||
#include "util/endian.h"
|
||||
|
||||
#define HTTPCLIENT_METHOD_MAX 8
|
||||
#define HTTPCLIENT_URL_MAX 1024
|
||||
#define HTTPCLIENT_HOST_MAX 256
|
||||
#define HTTPCLIENT_PATH_MAX 768
|
||||
#define HTTPCLIENT_SCHEME_MAX 8
|
||||
#define HTTPCLIENT_HEADER_NAME_MAX 64
|
||||
#define HTTPCLIENT_HEADER_VALUE_MAX 512
|
||||
#define HTTPCLIENT_HEADER_MAX 32
|
||||
#define HTTPCLIENT_HEADER_BUF_SIZE 8192
|
||||
#define HTTPCLIENT_BODY_BUF_SIZE 4096
|
||||
#define HTTPCLIENT_REQ_BUF_SIZE 4096
|
||||
|
||||
/** Parsed components of a URL. */
|
||||
typedef struct {
|
||||
char_t scheme[HTTPCLIENT_SCHEME_MAX];
|
||||
char_t host[HTTPCLIENT_HOST_MAX];
|
||||
uint16_t port;
|
||||
char_t path[HTTPCLIENT_PATH_MAX];
|
||||
} httpurl_t;
|
||||
|
||||
/** A single HTTP header (name + value pair). */
|
||||
typedef struct {
|
||||
char_t name[HTTPCLIENT_HEADER_NAME_MAX];
|
||||
char_t value[HTTPCLIENT_HEADER_VALUE_MAX];
|
||||
} httpheader_t;
|
||||
|
||||
typedef struct httpclient_s httpclient_t;
|
||||
|
||||
/**
|
||||
* Called once the response status line and all headers have been parsed.
|
||||
* Fired from the socket thread before any body data arrives.
|
||||
*/
|
||||
typedef void (*httpclientheadercallback_t)(
|
||||
httpclient_t *client,
|
||||
uint16_t statusCode,
|
||||
const httpheader_t *headers,
|
||||
size_t headerCount,
|
||||
void *user
|
||||
);
|
||||
|
||||
/**
|
||||
* Called for each chunk of response body data.
|
||||
* May be called multiple times per request. Fired from the socket thread.
|
||||
* For binary payloads use endianReadBE32 / endianReadLE32 from util/endian.h
|
||||
* to read multi-byte values with correct byte order.
|
||||
*/
|
||||
typedef void (*httpclientdatacallback_t)(
|
||||
httpclient_t *client,
|
||||
const uint8_t *data,
|
||||
size_t len,
|
||||
void *user
|
||||
);
|
||||
|
||||
/** Called once when the response body has been fully received. */
|
||||
typedef void (*httpclientcompletecallback_t)(
|
||||
httpclient_t *client,
|
||||
void *user
|
||||
);
|
||||
|
||||
/** Called on any transport or protocol error. */
|
||||
typedef void (*httpclienterrorcallback_t)(
|
||||
httpclient_t *client,
|
||||
errorret_t err,
|
||||
void *user
|
||||
);
|
||||
|
||||
typedef struct httpclient_s {
|
||||
networktls_t tls;
|
||||
errorstate_t errorState;
|
||||
|
||||
/* --- request config (set before httpclientRequest) --- */
|
||||
char_t method[HTTPCLIENT_METHOD_MAX];
|
||||
httpurl_t url;
|
||||
httpheader_t requestHeaders[HTTPCLIENT_HEADER_MAX];
|
||||
size_t requestHeaderCount;
|
||||
|
||||
/**
|
||||
* Optional request body. The pointer must remain valid until onComplete or
|
||||
* onError fires. The HTTP layer does not copy the body.
|
||||
*/
|
||||
const uint8_t *requestBody;
|
||||
size_t requestBodyLen;
|
||||
|
||||
/* --- response state (populated during request) --- */
|
||||
uint16_t statusCode;
|
||||
httpheader_t responseHeaders[HTTPCLIENT_HEADER_MAX];
|
||||
size_t responseHeaderCount;
|
||||
|
||||
/* --- callbacks (all fired from the socket thread) --- */
|
||||
httpclientheadercallback_t onHeaders;
|
||||
httpclientdatacallback_t onData;
|
||||
httpclientcompletecallback_t onComplete;
|
||||
httpclienterrorcallback_t onError;
|
||||
void *user;
|
||||
} httpclient_t;
|
||||
|
||||
/**
|
||||
* Initializes an HTTP client. Must be called before any other httpclient
|
||||
* function.
|
||||
*/
|
||||
errorret_t httpclientInit(httpclient_t *client);
|
||||
|
||||
/**
|
||||
* Parses a URL string into its components. Supports http:// and https://.
|
||||
* Port defaults to 80 (HTTP) or 443 (HTTPS) when not specified.
|
||||
*
|
||||
* @return ERROR_NOT_OK if the URL is malformed.
|
||||
*/
|
||||
errorret_t httpclientParseUrl(
|
||||
httpurl_t *url,
|
||||
errorstate_t *errorState,
|
||||
const char_t *urlStr
|
||||
);
|
||||
|
||||
/**
|
||||
* Adds or replaces a request header. Must be called before httpclientRequest.
|
||||
* Asserts if HTTPCLIENT_HEADER_MAX is exceeded.
|
||||
*/
|
||||
void httpclientSetHeader(
|
||||
httpclient_t *client,
|
||||
const char_t *name,
|
||||
const char_t *value
|
||||
);
|
||||
|
||||
/**
|
||||
* Starts an asynchronous HTTPS (or plain HTTP) request. All callbacks fire
|
||||
* from the socket thread, not the main thread.
|
||||
*
|
||||
* @param method HTTP verb ("GET", "POST", etc.).
|
||||
* @param url Full URL string — parsed internally.
|
||||
* @param verifyPeer Pass true to verify the server TLS certificate. Pass
|
||||
* false for self-signed / development servers.
|
||||
* @param onHeaders Called when response headers arrive (may be NULL).
|
||||
* @param onData Called for each body chunk (may be NULL).
|
||||
* @param onComplete Called when the full response is received (may be NULL).
|
||||
* @param onError Called on any error (may be NULL).
|
||||
* @param user Passed to all callbacks.
|
||||
*/
|
||||
void httpclientRequest(
|
||||
httpclient_t *client,
|
||||
const char_t *method,
|
||||
const char_t *url,
|
||||
bool_t verifyPeer,
|
||||
httpclientheadercallback_t onHeaders,
|
||||
httpclientdatacallback_t onData,
|
||||
httpclientcompletecallback_t onComplete,
|
||||
httpclienterrorcallback_t onError,
|
||||
void *user
|
||||
);
|
||||
|
||||
/**
|
||||
* Requests cancellation of an in-flight request. Non-blocking.
|
||||
*/
|
||||
void httpclientDisconnect(httpclient_t *client);
|
||||
|
||||
/**
|
||||
* Blocks until the request thread exits, then frees all resources.
|
||||
*/
|
||||
errorret_t httpclientDispose(httpclient_t *client);
|
||||
@@ -25,17 +25,11 @@ 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
|
||||
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "networksocket.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
static void networksocketThreadCallback(thread_t *thread) {
|
||||
networksocket_t *socket = (networksocket_t *)thread->data;
|
||||
errorret_t err;
|
||||
bool_t hadError;
|
||||
|
||||
hadError = false;
|
||||
|
||||
err = networksocketPlatformConnect(socket);
|
||||
if (err.code != ERROR_OK) {
|
||||
socket->state = NETWORKSOCKET_STATE_DISCONNECTED;
|
||||
if (socket->onError) socket->onError(socket, err, socket->user);
|
||||
return;
|
||||
}
|
||||
|
||||
socket->state = NETWORKSOCKET_STATE_CONNECTED;
|
||||
if (socket->onConnect) socket->onConnect(socket, socket->user);
|
||||
|
||||
if (socket->onReceive != NULL) {
|
||||
uint8_t recvBuf[NETWORKSOCKET_RECV_BUFFER_SIZE];
|
||||
uint8_t localSendBuf[NETWORKSOCKET_SEND_BUFFER_SIZE];
|
||||
size_t sendLen;
|
||||
size_t recvLen;
|
||||
|
||||
while (!threadShouldStop(thread)) {
|
||||
threadMutexLock(&socket->sendMutex);
|
||||
sendLen = socket->sendLen;
|
||||
if (sendLen > 0) {
|
||||
memoryCopy(localSendBuf, socket->sendBuffer, sendLen);
|
||||
socket->sendLen = 0;
|
||||
}
|
||||
threadMutexUnlock(&socket->sendMutex);
|
||||
|
||||
if (sendLen > 0) {
|
||||
err = networksocketPlatformSend(socket, localSendBuf, sendLen);
|
||||
if (err.code != ERROR_OK) {
|
||||
hadError = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
recvLen = 0;
|
||||
err = networksocketPlatformRecv(
|
||||
socket, recvBuf, NETWORKSOCKET_RECV_BUFFER_SIZE, &recvLen
|
||||
);
|
||||
if (err.code != ERROR_OK) {
|
||||
hadError = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (recvLen == NETWORKSOCKET_RECV_CLOSED) break;
|
||||
|
||||
if (recvLen > 0) {
|
||||
socket->onReceive(socket, recvBuf, recvLen, socket->user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
socket->state = NETWORKSOCKET_STATE_DISCONNECTING;
|
||||
networksocketPlatformDisconnect(socket);
|
||||
socket->state = NETWORKSOCKET_STATE_DISCONNECTED;
|
||||
|
||||
if (hadError) {
|
||||
if (socket->onError) socket->onError(socket, err, socket->user);
|
||||
} else {
|
||||
if (socket->onDisconnect) socket->onDisconnect(socket, socket->user);
|
||||
}
|
||||
}
|
||||
|
||||
errorret_t networksocketInit(networksocket_t *socket) {
|
||||
memoryZero(socket, sizeof(networksocket_t));
|
||||
socket->state = NETWORKSOCKET_STATE_DISCONNECTED;
|
||||
threadInit(&socket->thread, networksocketThreadCallback);
|
||||
socket->thread.data = socket;
|
||||
threadMutexInit(&socket->sendMutex);
|
||||
errorChain(networksocketPlatformInit(socket));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void networksocketConnect(
|
||||
networksocket_t *socket,
|
||||
const char_t *host,
|
||||
uint16_t port,
|
||||
networksocketcallback_t onConnect,
|
||||
networksocketerrorcallback_t onError,
|
||||
networksocketcallback_t onDisconnect,
|
||||
networksocketrecvcallback_t onReceive,
|
||||
void *user
|
||||
) {
|
||||
size_t hostLen;
|
||||
|
||||
assertNotNull(host, "host must not be null");
|
||||
hostLen = strlen(host);
|
||||
assertTrue(hostLen < NETWORKSOCKET_HOST_MAX, "host exceeds NETWORKSOCKET_HOST_MAX");
|
||||
|
||||
memoryCopy(socket->host, host, hostLen + 1);
|
||||
socket->port = port;
|
||||
socket->onConnect = onConnect;
|
||||
socket->onError = onError;
|
||||
socket->onDisconnect = onDisconnect;
|
||||
socket->onReceive = onReceive;
|
||||
socket->user = user;
|
||||
socket->sendLen = 0;
|
||||
socket->state = NETWORKSOCKET_STATE_CONNECTING;
|
||||
|
||||
threadStart(&socket->thread);
|
||||
}
|
||||
|
||||
errorret_t networksocketSend(
|
||||
networksocket_t *socket,
|
||||
const uint8_t *data,
|
||||
size_t len
|
||||
) {
|
||||
assertNotNull(data, "data must not be null");
|
||||
assertTrue(len > 0, "len must be greater than 0");
|
||||
|
||||
threadMutexLock(&socket->sendMutex);
|
||||
assertTrue(
|
||||
socket->sendLen + len <= NETWORKSOCKET_SEND_BUFFER_SIZE,
|
||||
"networksocketSend would overflow send buffer"
|
||||
);
|
||||
memoryCopy(socket->sendBuffer + socket->sendLen, data, len);
|
||||
socket->sendLen += len;
|
||||
threadMutexUnlock(&socket->sendMutex);
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t networksocketWrite(
|
||||
networksocket_t *socket,
|
||||
const uint8_t *data,
|
||||
size_t len
|
||||
) {
|
||||
return networksocketPlatformSend(socket, data, len);
|
||||
}
|
||||
|
||||
errorret_t networksocketRead(
|
||||
networksocket_t *socket,
|
||||
uint8_t *buf,
|
||||
size_t maxLen,
|
||||
size_t *outLen
|
||||
) {
|
||||
return networksocketPlatformRecv(socket, buf, maxLen, outLen);
|
||||
}
|
||||
|
||||
void networksocketDisconnect(networksocket_t *socket) {
|
||||
threadStopRequest(&socket->thread);
|
||||
}
|
||||
|
||||
errorret_t networksocketDispose(networksocket_t *socket) {
|
||||
threadStop(&socket->thread);
|
||||
errorChain(networksocketPlatformDispose(socket));
|
||||
threadMutexDispose(&socket->sendMutex);
|
||||
errorOk();
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "thread/thread.h"
|
||||
#include "network/networksocketplatform.h"
|
||||
#ifndef networksocketPlatformInit
|
||||
#error "networksocketPlatformInit must be defined"
|
||||
#endif
|
||||
#ifndef networksocketPlatformConnect
|
||||
#error "networksocketPlatformConnect must be defined"
|
||||
#endif
|
||||
#ifndef networksocketPlatformSend
|
||||
#error "networksocketPlatformSend must be defined"
|
||||
#endif
|
||||
#ifndef networksocketPlatformRecv
|
||||
#error "networksocketPlatformRecv must be defined"
|
||||
#endif
|
||||
#ifndef networksocketPlatformDisconnect
|
||||
#error "networksocketPlatformDisconnect must be defined"
|
||||
#endif
|
||||
#ifndef networksocketPlatformDispose
|
||||
#error "networksocketPlatformDispose must be defined"
|
||||
#endif
|
||||
|
||||
#define NETWORKSOCKET_HOST_MAX 256
|
||||
#define NETWORKSOCKET_SEND_BUFFER_SIZE 4096
|
||||
#define NETWORKSOCKET_RECV_BUFFER_SIZE 4096
|
||||
|
||||
/** Sentinel returned by networksocketPlatformRecv when the peer closed the connection. */
|
||||
#define NETWORKSOCKET_RECV_CLOSED ((size_t)(-1))
|
||||
|
||||
typedef enum {
|
||||
NETWORKSOCKET_STATE_DISCONNECTED,
|
||||
NETWORKSOCKET_STATE_CONNECTING,
|
||||
NETWORKSOCKET_STATE_CONNECTED,
|
||||
NETWORKSOCKET_STATE_DISCONNECTING,
|
||||
} networksocketstate_t;
|
||||
|
||||
typedef struct networksocket_s networksocket_t;
|
||||
|
||||
typedef void (*networksocketcallback_t)(
|
||||
networksocket_t *socket, void *user
|
||||
);
|
||||
typedef void (*networksocketerrorcallback_t)(
|
||||
networksocket_t *socket, errorret_t err, void *user
|
||||
);
|
||||
typedef void (*networksocketrecvcallback_t)(
|
||||
networksocket_t *socket, const uint8_t *data, size_t len, void *user
|
||||
);
|
||||
|
||||
typedef struct networksocket_s {
|
||||
networksocketstate_t state;
|
||||
errorstate_t errorState;
|
||||
|
||||
thread_t thread;
|
||||
threadmutex_t sendMutex;
|
||||
|
||||
char_t host[NETWORKSOCKET_HOST_MAX];
|
||||
uint16_t port;
|
||||
|
||||
networksocketplatform_t platform;
|
||||
|
||||
uint8_t sendBuffer[NETWORKSOCKET_SEND_BUFFER_SIZE];
|
||||
size_t sendLen;
|
||||
|
||||
/**
|
||||
* Called from the socket thread once TCP is established. If onReceive is
|
||||
* NULL, the socket thread will exit after this returns and disconnect.
|
||||
* If onReceive is set, a recv loop runs after this returns.
|
||||
* Suitable entry point for TLS/HTTP layers that drive their own I/O.
|
||||
*/
|
||||
networksocketcallback_t onConnect;
|
||||
|
||||
/**
|
||||
* When non-NULL, the socket thread runs a receive loop after connection
|
||||
* and calls this for each incoming data chunk. When NULL, the thread only
|
||||
* calls onConnect and then disconnects.
|
||||
*/
|
||||
networksocketrecvcallback_t onReceive;
|
||||
|
||||
networksocketerrorcallback_t onError;
|
||||
networksocketcallback_t onDisconnect;
|
||||
void *user;
|
||||
} networksocket_t;
|
||||
|
||||
/**
|
||||
* Initializes a socket structure. Must be called before any other socket
|
||||
* function.
|
||||
*/
|
||||
errorret_t networksocketInit(networksocket_t *socket);
|
||||
|
||||
/**
|
||||
* Starts an asynchronous connection in a dedicated thread. All callbacks are
|
||||
* invoked from the socket thread, not the main thread.
|
||||
*
|
||||
* If onReceive is non-NULL, the thread runs a receive loop after connection
|
||||
* and delivers incoming data via onReceive. If onReceive is NULL, the thread
|
||||
* calls onConnect and exits (useful for TLS or request-driven I/O that
|
||||
* manages its own reads via networksocketRead/networksocketWrite).
|
||||
*
|
||||
* @param socket Socket to connect.
|
||||
* @param host Hostname or IP address to connect to.
|
||||
* @param port Port number.
|
||||
* @param onConnect Called once TCP is established (may be NULL).
|
||||
* @param onError Called on connect or I/O error (may be NULL).
|
||||
* @param onDisconnect Called on graceful disconnect (may be NULL).
|
||||
* @param onReceive Called for each received chunk; NULL disables recv loop.
|
||||
* @param user User data passed to all callbacks.
|
||||
*/
|
||||
void networksocketConnect(
|
||||
networksocket_t *socket,
|
||||
const char_t *host,
|
||||
uint16_t port,
|
||||
networksocketcallback_t onConnect,
|
||||
networksocketerrorcallback_t onError,
|
||||
networksocketcallback_t onDisconnect,
|
||||
networksocketrecvcallback_t onReceive,
|
||||
void *user
|
||||
);
|
||||
|
||||
/**
|
||||
* Queues data for sending. Thread-safe; may be called from any thread.
|
||||
* The socket thread drains the queue on each recv-loop iteration.
|
||||
* Asserts if the send buffer would overflow.
|
||||
*/
|
||||
errorret_t networksocketSend(
|
||||
networksocket_t *socket,
|
||||
const uint8_t *data,
|
||||
size_t len
|
||||
);
|
||||
|
||||
/**
|
||||
* Writes data directly to the socket without buffering. Must be called from
|
||||
* the socket's own thread (e.g., from within onConnect or a TLS bio callback).
|
||||
*/
|
||||
errorret_t networksocketWrite(
|
||||
networksocket_t *socket,
|
||||
const uint8_t *data,
|
||||
size_t len
|
||||
);
|
||||
|
||||
/**
|
||||
* Reads data directly from the socket. Must be called from the socket's own
|
||||
* thread. Returns 0 in *outLen on timeout (no data yet); returns
|
||||
* NETWORKSOCKET_RECV_CLOSED in *outLen when the peer has closed the connection.
|
||||
*/
|
||||
errorret_t networksocketRead(
|
||||
networksocket_t *socket,
|
||||
uint8_t *buf,
|
||||
size_t maxLen,
|
||||
size_t *outLen
|
||||
);
|
||||
|
||||
/**
|
||||
* Requests the socket thread to stop. Non-blocking; onDisconnect fires once
|
||||
* the thread has finished.
|
||||
*/
|
||||
void networksocketDisconnect(networksocket_t *socket);
|
||||
|
||||
/**
|
||||
* Blocks until the socket thread has stopped, then releases platform
|
||||
* resources. Safe to call on an already-disconnected socket.
|
||||
*/
|
||||
errorret_t networksocketDispose(networksocket_t *socket);
|
||||
@@ -1,288 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "networktls.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
// Define missing mbedtls net error codes if not present
|
||||
#ifndef MBEDTLS_ERR_NET_SEND_FAILED
|
||||
#define MBEDTLS_ERR_NET_SEND_FAILED -0x004E
|
||||
#endif
|
||||
#ifndef MBEDTLS_ERR_NET_RECV_FAILED
|
||||
#define MBEDTLS_ERR_NET_RECV_FAILED -0x004C
|
||||
#endif
|
||||
|
||||
/* ---- mbedTLS bio callbacks -------------------------------------------- */
|
||||
|
||||
#ifdef DUSK_DOLPHIN
|
||||
#include <ogc/lwp_watchdog.h>
|
||||
#include <mbedtls/platform_time.h>
|
||||
mbedtls_ms_time_t mbedtls_ms_time(void) {
|
||||
return (mbedtls_ms_time_t)ticks_to_millisecs(gettime());
|
||||
}
|
||||
#endif
|
||||
|
||||
int mbedtls_platform_get_entropy(uint8_t *output, size_t len) {
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
output[i] = rand() & 0xFF;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int networktlsBioSend(
|
||||
void *ctx,
|
||||
const unsigned char *buf,
|
||||
size_t len
|
||||
) {
|
||||
networktls_t *tls = (networktls_t *)ctx;
|
||||
errorret_t err = networksocketWrite(&tls->socket, (const uint8_t *)buf, len);
|
||||
if(err.code != ERROR_OK) return MBEDTLS_ERR_NET_SEND_FAILED;
|
||||
return (int)len;
|
||||
}
|
||||
|
||||
static int networktlsBioRecv(void *ctx, unsigned char *buf, size_t len) {
|
||||
networktls_t *tls = (networktls_t *)ctx;
|
||||
size_t outLen = 0;
|
||||
errorret_t err = networksocketRead(
|
||||
&tls->socket, (uint8_t *)buf, len, &outLen
|
||||
);
|
||||
if(err.code != ERROR_OK) return MBEDTLS_ERR_NET_RECV_FAILED;
|
||||
if(outLen == NETWORKSOCKET_RECV_CLOSED) return 0;
|
||||
if(outLen == 0) return MBEDTLS_ERR_SSL_WANT_READ;
|
||||
return (int)outLen;
|
||||
}
|
||||
|
||||
/* ---- socket callbacks (run in socket thread) --------------------------- */
|
||||
|
||||
static void networktlsSocketOnConnect(networksocket_t *socket, void *user) {
|
||||
networktls_t *tls = (networktls_t *)user;
|
||||
errorret_t errret;
|
||||
int ret;
|
||||
|
||||
if(psa_crypto_init() != PSA_SUCCESS) {
|
||||
errret = errorThrowImpl(
|
||||
&tls->errorState, ERROR_NOT_OK,
|
||||
__FILE__, __func__, __LINE__,
|
||||
"psa_crypto_init failed"
|
||||
);
|
||||
if(tls->onError) tls->onError(tls, errret, tls->user);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = mbedtls_ssl_config_defaults(
|
||||
&tls->conf,
|
||||
MBEDTLS_SSL_IS_CLIENT,
|
||||
MBEDTLS_SSL_TRANSPORT_STREAM,
|
||||
MBEDTLS_SSL_PRESET_DEFAULT
|
||||
);
|
||||
if(ret != 0) {
|
||||
errret = errorThrowImpl(
|
||||
&tls->errorState, ERROR_NOT_OK,
|
||||
__FILE__, __func__, __LINE__,
|
||||
"mbedtls_ssl_config_defaults failed: -0x%04x", -ret
|
||||
);
|
||||
if(tls->onError) tls->onError(tls, errret, tls->user);
|
||||
return;
|
||||
}
|
||||
|
||||
mbedtls_ssl_conf_authmode(
|
||||
&tls->conf,
|
||||
tls->verifyPeer ? MBEDTLS_SSL_VERIFY_REQUIRED : MBEDTLS_SSL_VERIFY_NONE
|
||||
);
|
||||
mbedtls_ssl_conf_ca_chain(&tls->conf, &tls->cacert, NULL);
|
||||
|
||||
ret = mbedtls_ssl_setup(&tls->ssl, &tls->conf);
|
||||
if(ret != 0) {
|
||||
errret = errorThrowImpl(
|
||||
&tls->errorState, ERROR_NOT_OK,
|
||||
__FILE__, __func__, __LINE__,
|
||||
"mbedtls_ssl_setup failed: -0x%04x", -ret
|
||||
);
|
||||
if(tls->onError) tls->onError(tls, errret, tls->user);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = mbedtls_ssl_set_hostname(&tls->ssl, tls->socket.host);
|
||||
if(ret != 0) {
|
||||
errret = errorThrowImpl(
|
||||
&tls->errorState, ERROR_NOT_OK,
|
||||
__FILE__, __func__, __LINE__,
|
||||
"mbedtls_ssl_set_hostname failed: -0x%04x", -ret
|
||||
);
|
||||
if(tls->onError) tls->onError(tls, errret, tls->user);
|
||||
return;
|
||||
}
|
||||
|
||||
mbedtls_ssl_set_bio(
|
||||
&tls->ssl, tls,
|
||||
networktlsBioSend, networktlsBioRecv, NULL
|
||||
);
|
||||
|
||||
/* TLS handshake loop — WANT_READ/WANT_WRITE are non-fatal retry signals */
|
||||
do {
|
||||
ret = mbedtls_ssl_handshake(&tls->ssl);
|
||||
} while(
|
||||
ret == MBEDTLS_ERR_SSL_WANT_READ ||
|
||||
ret == MBEDTLS_ERR_SSL_WANT_WRITE
|
||||
);
|
||||
|
||||
if(ret != 0) {
|
||||
errret = errorThrowImpl(
|
||||
&tls->errorState, ERROR_NOT_OK,
|
||||
__FILE__, __func__, __LINE__,
|
||||
"TLS handshake failed: -0x%04x", -ret
|
||||
);
|
||||
if(tls->onError) tls->onError(tls, errret, tls->user);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Handshake succeeded — let the user drive I/O from this thread */
|
||||
if(tls->onConnect) tls->onConnect(tls, tls->user);
|
||||
|
||||
mbedtls_ssl_close_notify(&tls->ssl);
|
||||
}
|
||||
|
||||
static void networktlsSocketOnError(
|
||||
networksocket_t *socket,
|
||||
errorret_t err,
|
||||
void *user
|
||||
) {
|
||||
networktls_t *tls = (networktls_t *)user;
|
||||
if(tls->onError) tls->onError(tls, err, tls->user);
|
||||
}
|
||||
|
||||
static void networktlsSocketOnDisconnect(
|
||||
networksocket_t *socket,
|
||||
void *user
|
||||
) {
|
||||
networktls_t *tls = (networktls_t *)user;
|
||||
if(tls->onDisconnect) tls->onDisconnect(tls, tls->user);
|
||||
}
|
||||
|
||||
/* ---- public API -------------------------------------------------------- */
|
||||
|
||||
errorret_t networktlsInit(networktls_t *tls) {
|
||||
memoryZero(tls, sizeof(networktls_t));
|
||||
errorChain(networksocketInit(&tls->socket));
|
||||
mbedtls_ssl_init(&tls->ssl);
|
||||
mbedtls_ssl_config_init(&tls->conf);
|
||||
mbedtls_x509_crt_init(&tls->cacert);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t networktlsAddCACert(
|
||||
networktls_t *tls,
|
||||
const uint8_t *certPem,
|
||||
size_t len
|
||||
) {
|
||||
int ret = mbedtls_x509_crt_parse(&tls->cacert, certPem, len);
|
||||
if(ret != 0) {
|
||||
errorThrowState(
|
||||
&tls->errorState,
|
||||
"Failed to parse CA certificate: -0x%04x", -ret
|
||||
);
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void networktlsConnect(
|
||||
networktls_t *tls,
|
||||
const char_t *host,
|
||||
uint16_t port,
|
||||
bool_t verifyPeer,
|
||||
networktlscallback_t onConnect,
|
||||
networktlserrorcallback_t onError,
|
||||
networktlscallback_t onDisconnect,
|
||||
void *user
|
||||
) {
|
||||
tls->verifyPeer = verifyPeer;
|
||||
tls->onConnect = onConnect;
|
||||
tls->onError = onError;
|
||||
tls->onDisconnect = onDisconnect;
|
||||
tls->user = user;
|
||||
|
||||
networksocketConnect(
|
||||
&tls->socket,
|
||||
host, port,
|
||||
networktlsSocketOnConnect,
|
||||
networktlsSocketOnError,
|
||||
networktlsSocketOnDisconnect,
|
||||
NULL, /* no recv loop — TLS drives all I/O from onConnect */
|
||||
tls
|
||||
);
|
||||
}
|
||||
|
||||
errorret_t networktlsWrite(
|
||||
networktls_t *tls,
|
||||
const uint8_t *data,
|
||||
size_t len
|
||||
) {
|
||||
size_t sent = 0;
|
||||
int ret;
|
||||
while(sent < len) {
|
||||
ret = mbedtls_ssl_write(
|
||||
&tls->ssl,
|
||||
(const unsigned char *)(data + sent),
|
||||
len - sent
|
||||
);
|
||||
if(ret == MBEDTLS_ERR_SSL_WANT_WRITE || ret == MBEDTLS_ERR_SSL_WANT_READ) {
|
||||
continue;
|
||||
}
|
||||
if(ret < 0) {
|
||||
errorThrowState(
|
||||
&tls->errorState,
|
||||
"TLS write failed: -0x%04x", -ret
|
||||
);
|
||||
}
|
||||
sent += (size_t)ret;
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t networktlsRead(
|
||||
networktls_t *tls,
|
||||
uint8_t *buf,
|
||||
size_t maxLen,
|
||||
size_t *outLen
|
||||
) {
|
||||
int ret;
|
||||
do {
|
||||
ret = mbedtls_ssl_read(&tls->ssl, (unsigned char *)buf, maxLen);
|
||||
} while(
|
||||
ret == MBEDTLS_ERR_SSL_WANT_READ ||
|
||||
ret == MBEDTLS_ERR_SSL_WANT_WRITE
|
||||
);
|
||||
|
||||
if(ret == 0 || ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
|
||||
*outLen = NETWORKSOCKET_RECV_CLOSED;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
if(ret < 0) {
|
||||
errorThrowState(
|
||||
&tls->errorState,
|
||||
"TLS read failed: -0x%04x", -ret
|
||||
);
|
||||
}
|
||||
|
||||
*outLen = (size_t)ret;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void networktlsDisconnect(networktls_t *tls) {
|
||||
networksocketDisconnect(&tls->socket);
|
||||
}
|
||||
|
||||
errorret_t networktlsDispose(networktls_t *tls) {
|
||||
errorChain(networksocketDispose(&tls->socket));
|
||||
mbedtls_ssl_free(&tls->ssl);
|
||||
mbedtls_ssl_config_free(&tls->conf);
|
||||
mbedtls_x509_crt_free(&tls->cacert);
|
||||
errorOk();
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "network/networksocket.h"
|
||||
|
||||
#include <mbedtls/ssl.h>
|
||||
#include <mbedtls/x509_crt.h>
|
||||
#include <psa/crypto.h>
|
||||
|
||||
typedef struct networktls_s networktls_t;
|
||||
|
||||
typedef void (*networktlscallback_t)(networktls_t *tls, void *user);
|
||||
typedef void (*networktlserrorcallback_t)(
|
||||
networktls_t *tls, errorret_t err, void *user
|
||||
);
|
||||
|
||||
typedef struct networktls_s {
|
||||
networksocket_t socket;
|
||||
errorstate_t errorState;
|
||||
bool_t verifyPeer;
|
||||
|
||||
mbedtls_ssl_context ssl;
|
||||
mbedtls_ssl_config conf;
|
||||
mbedtls_x509_crt cacert;
|
||||
|
||||
networktlscallback_t onConnect;
|
||||
networktlserrorcallback_t onError;
|
||||
networktlscallback_t onDisconnect;
|
||||
void *user;
|
||||
} networktls_t;
|
||||
|
||||
/**
|
||||
* Initializes the TLS context and the underlying socket.
|
||||
*/
|
||||
errorret_t networktlsInit(networktls_t *tls);
|
||||
|
||||
/**
|
||||
* Adds a PEM-encoded CA certificate used to verify the server. Must be called
|
||||
* before networktlsConnect when verifyPeer is true.
|
||||
*/
|
||||
errorret_t networktlsAddCACert(
|
||||
networktls_t *tls,
|
||||
const uint8_t *certPem,
|
||||
size_t len
|
||||
);
|
||||
|
||||
/**
|
||||
* Starts an async TCP+TLS connection in the socket's own thread. All callbacks
|
||||
* fire from that thread, not the main thread.
|
||||
*
|
||||
* When onConnect fires, the TLS handshake is complete and the connection is
|
||||
* ready for networktlsWrite / networktlsRead. The connection is kept alive
|
||||
* until onConnect returns, so all I/O should be performed synchronously within
|
||||
* that callback.
|
||||
*
|
||||
* @param verifyPeer When true, the server certificate is verified against the
|
||||
* CA certificates added via networktlsAddCACert. When false,
|
||||
* certificate verification is skipped (useful for development
|
||||
* or self-signed certificates).
|
||||
*/
|
||||
void networktlsConnect(
|
||||
networktls_t *tls,
|
||||
const char_t *host,
|
||||
uint16_t port,
|
||||
bool_t verifyPeer,
|
||||
networktlscallback_t onConnect,
|
||||
networktlserrorcallback_t onError,
|
||||
networktlscallback_t onDisconnect,
|
||||
void *user
|
||||
);
|
||||
|
||||
/**
|
||||
* Writes data through the TLS layer. Must be called from within the
|
||||
* onConnect callback (i.e., from the socket thread).
|
||||
*/
|
||||
errorret_t networktlsWrite(
|
||||
networktls_t *tls,
|
||||
const uint8_t *data,
|
||||
size_t len
|
||||
);
|
||||
|
||||
/**
|
||||
* Reads up to maxLen bytes from the TLS layer. Blocks until data arrives or
|
||||
* the connection closes. Sets *outLen to bytes received, or
|
||||
* NETWORKSOCKET_RECV_CLOSED when the peer has closed the connection.
|
||||
* Must be called from within the onConnect callback.
|
||||
*/
|
||||
errorret_t networktlsRead(
|
||||
networktls_t *tls,
|
||||
uint8_t *buf,
|
||||
size_t maxLen,
|
||||
size_t *outLen
|
||||
);
|
||||
|
||||
/**
|
||||
* Requests disconnect. Non-blocking; onDisconnect fires once complete.
|
||||
*/
|
||||
void networktlsDisconnect(networktls_t *tls);
|
||||
|
||||
/**
|
||||
* Blocks until the connection thread stops, then releases all resources.
|
||||
*/
|
||||
errorret_t networktlsDispose(networktls_t *tls);
|
||||
@@ -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];
|
||||
|
||||
+130
-105
@@ -11,60 +11,19 @@
|
||||
#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));
|
||||
|
||||
memoryZero(SCENE_LOG, sizeof(SCENE_LOG));
|
||||
sceneLog("Init\n");
|
||||
|
||||
|
||||
SCENE.scriptRef = SCENE_SCRIPT_REF_NONE;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -75,31 +34,35 @@ 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();
|
||||
|
||||
// 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
|
||||
shader_t *shaderCurrent = NULL;
|
||||
mat4 view, proj, model;
|
||||
errorChain(shaderBind(&SHADER_UNLIT));
|
||||
|
||||
// For each camera.
|
||||
errorChain(displaySetState((displaystate_t){
|
||||
// .flags = DISPLAY_STATE_FLAG_CULL | DISPLAY_STATE_FLAG_DEPTH_TEST
|
||||
.flags = 0
|
||||
}));
|
||||
|
||||
// For each camera
|
||||
for(entityid_t camIndex = 0; camIndex < camCount; camIndex++) {
|
||||
entityid_t camEnt = camEnts[camIndex];
|
||||
componentid_t camComp = camComps[camIndex];
|
||||
@@ -112,53 +75,67 @@ errorret_t sceneRender(void) {
|
||||
entityCameraGetProjection(camEnt, camComp, proj);
|
||||
entityPositionGetTransform(camEnt, camPos, view);
|
||||
|
||||
// For each mesh.
|
||||
for(entityid_t meshIndex = 0; meshIndex < meshCount; meshIndex++) {
|
||||
entityid_t meshEnt = meshEnts[meshIndex];
|
||||
|
||||
componentid_t meshComp = meshComps[meshIndex];
|
||||
mesh_t *mesh = entityMeshGetMesh(meshEnt, meshComp);
|
||||
// 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);
|
||||
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(
|
||||
meshEnt, COMPONENT_TYPE_POSITION
|
||||
entityId, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
if(meshPos == 0xFF) {
|
||||
logError("Mesh entity without entity position found\n");
|
||||
continue;
|
||||
glm_mat4_identity(model);
|
||||
} else {
|
||||
entityPositionGetTransform(entityId, meshPos, model);
|
||||
}
|
||||
|
||||
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));
|
||||
// Render the mesh.
|
||||
if(shaderCurrent != shader) {
|
||||
shaderCurrent = shader;
|
||||
errorChain(shaderBind(shaderCurrent));
|
||||
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.
|
||||
}
|
||||
}
|
||||
|
||||
// Here is where UI will go
|
||||
// UI Rendering
|
||||
glm_ortho(
|
||||
0.0f, SCREEN.width,
|
||||
SCREEN.height, 0.0f,
|
||||
@@ -177,25 +154,73 @@ 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(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());
|
||||
errorChain(displaySetState((displaystate_t){
|
||||
.flags = DISPLAY_STATE_FLAG_BLEND
|
||||
}));
|
||||
|
||||
errorChain(uiRender());
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t sceneSet(const char_t *script) {
|
||||
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;
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void sceneDispose(void) {
|
||||
|
||||
void sceneSet(const char_t *scene) {
|
||||
stringCopy(
|
||||
SCENE.sceneNext,
|
||||
scene == NULL ? "" : scene,
|
||||
ASSET_FILE_PATH_MAX
|
||||
);
|
||||
}
|
||||
|
||||
errorret_t sceneDispose(void) {
|
||||
errorChain(moduleSceneCall("dispose"));
|
||||
errorOk();
|
||||
}
|
||||
+31
-16
@@ -6,47 +6,62 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "script/scriptmanager.h"
|
||||
#include "asset/assetfile.h"
|
||||
|
||||
#define SCENE_EVENT_UPDATE_MAX 16
|
||||
|
||||
typedef struct {
|
||||
void *nothing;
|
||||
bool_t sceneActive;
|
||||
jerry_value_t scriptRef;
|
||||
char_t sceneCurrent[ASSET_FILE_PATH_MAX];
|
||||
char_t sceneNext[ASSET_FILE_PATH_MAX];
|
||||
} scene_t;
|
||||
|
||||
extern scene_t SCENE;
|
||||
|
||||
#define SCENE_LOG_SIZE 1024
|
||||
extern char_t SCENE_LOG[SCENE_LOG_SIZE];
|
||||
void sceneLog(const char *fmt, ...);
|
||||
/** Sentinel value meaning no scene script is loaded. */
|
||||
#define SCENE_SCRIPT_REF_NONE ((jerry_value_t)0)
|
||||
|
||||
/**
|
||||
* Initialize the scene subsystem.
|
||||
* Initializes the scene manager.
|
||||
*
|
||||
* @return The error return value.
|
||||
* @return Any error state that happened.
|
||||
*/
|
||||
errorret_t sceneInit(void);
|
||||
|
||||
/**
|
||||
* Update the current scene.
|
||||
* Ticks the scene manager; may call the scene's update method.
|
||||
*
|
||||
* @return The error return value.
|
||||
* @return Any error state that happened.
|
||||
*/
|
||||
errorret_t sceneUpdate(void);
|
||||
|
||||
/**
|
||||
* Render the current scene.
|
||||
* Renders the scene.
|
||||
*
|
||||
* @return The error return value.
|
||||
* @return Any error state that happened.
|
||||
*/
|
||||
errorret_t sceneRender(void);
|
||||
|
||||
/**
|
||||
* Set the current scene by script name.
|
||||
* Immediately switches scenes, disposing the current one first.
|
||||
*
|
||||
* @param script The script name of the scene to set.
|
||||
* @param scene Scene to switch to (asset file path).
|
||||
* @return Any error state that happened.
|
||||
*/
|
||||
errorret_t sceneSet(const char_t *script);
|
||||
errorret_t sceneSetImmediate(const char_t *scene);
|
||||
|
||||
/**
|
||||
* Dispose of the scene subsystem.
|
||||
* Requests a scene change on the next safe opportunity.
|
||||
*
|
||||
* @param scene Which scene to set.
|
||||
*/
|
||||
void sceneDispose(void);
|
||||
void sceneSet(const char_t *scene);
|
||||
|
||||
/**
|
||||
* Disposes of the current scene.
|
||||
*
|
||||
* @return Any error state that happened.
|
||||
*/
|
||||
errorret_t sceneDispose(void);
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
scriptmanager.c
|
||||
scriptcontext.c
|
||||
scriptmodule.c
|
||||
scriptproto.c
|
||||
)
|
||||
|
||||
# Subdirectories
|
||||
add_subdirectory(module)
|
||||
@@ -1,14 +0,0 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Subdirectories
|
||||
add_subdirectory(display)
|
||||
add_subdirectory(event)
|
||||
add_subdirectory(input)
|
||||
add_subdirectory(locale)
|
||||
add_subdirectory(system)
|
||||
add_subdirectory(scene)
|
||||
add_subdirectory(time)
|
||||
add_subdirectory(ui)
|
||||
@@ -0,0 +1,293 @@
|
||||
// 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
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user