Add fresh CMake/platform scaffolding, core engine, and SDL3 window

Platform folders, cmake target/option/platform detection, Linux and PSP
scripts and Docker setup, assert/util/thread/error/log/perf/time/console/
engine/display modules, and an SDL3 window cleared to red.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
YourWishesandClaude Sonnet 5.5 committed 2026-11-07 17:02:14 -06:00
1 parent 3710924503
commit 65c4a9c051
107 files changed
+4765 -119

No files matched your search

+42 -119
View File
@@ -1,155 +1,78 @@
# Copyright (c) 2025 Dominic Masters # Copyright (c) 2026 Dominic Masters
# #
# This software is released under the MIT License. # This software is released under the MIT License.
# https://opensource.org/licenses/MIT # https://opensource.org/licenses/MIT
# Setup
cmake_minimum_required(VERSION 3.13) cmake_minimum_required(VERSION 3.13)
set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_MODULE_PATH
${CMAKE_MODULE_PATH}
${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules
)
cmake_policy(SET CMP0079 NEW)
# set(FETCHCONTENT_UPDATES_DISCONNECTED ON)
option(DUSK_BUILD_TESTS "Enable tests" OFF) # Directories
option(DUSK_NETWORK "Enable network support" ON)
set(DUSK_GAME_NAME "Dusk" CACHE STRING "Game display name")
set(DUSK_GAME_AUTHOR "YourWishes" CACHE STRING "Game author / coder")
set(DUSK_GAME_SHORT_DESCRIPTION "Dusk game" CACHE STRING "One-line description")
set(DUSK_GAME_LONG_DESCRIPTION
"No description yet." CACHE STRING "Full description"
)
# Prep cache
set(DUSK_CACHE_TARGET "dusk-target")
# Build variables
set(DUSK_ROOT_DIR "${CMAKE_SOURCE_DIR}") set(DUSK_ROOT_DIR "${CMAKE_SOURCE_DIR}")
set(DUSK_BUILD_DIR "${CMAKE_BINARY_DIR}")
set(DUSK_SOURCES_DIR "${DUSK_ROOT_DIR}/src") set(DUSK_SOURCES_DIR "${DUSK_ROOT_DIR}/src")
set(DUSK_TEST_DIR "${DUSK_ROOT_DIR}/test")
set(DUSK_TEMP_DIR "${DUSK_BUILD_DIR}/temp")
set(DUSK_TOOLS_DIR "${DUSK_ROOT_DIR}/tools")
set(DUSK_DATA_DIR "${DUSK_ROOT_DIR}/data")
set(DUSK_ASSETS_DIR "${DUSK_ROOT_DIR}/assets")
set(DUSK_BUILT_ASSETS_DIR "${DUSK_BUILD_DIR}/built_assets"
CACHE INTERNAL ${DUSK_CACHE_TARGET}
)
set(DUSK_GENERATED_HEADERS_DIR "${DUSK_BUILD_DIR}/generated")
set(DUSK_BUILD_BINARY ${DUSK_BUILD_DIR}/Dusk
CACHE INTERNAL ${DUSK_CACHE_TARGET}
)
set(DUSK_ASSETS "" CACHE INTERNAL ${DUSK_CACHE_TARGET})
set(DUSK_LIBRARY_TARGET_NAME "DuskCore" CACHE INTERNAL ${DUSK_CACHE_TARGET})
set(DUSK_BINARY_TARGET_NAME "Dusk" CACHE INTERNAL ${DUSK_CACHE_TARGET})
set(DUSK_ASSETS_ZIP "${DUSK_BUILD_DIR}/dusk.dsk"
CACHE INTERNAL ${DUSK_CACHE_TARGET}
)
if(NOT DEFINED DUSK_TARGET_SYSTEM) project(DuskCore VERSION 1.0.0 LANGUAGES C)
set(DUSK_TARGET_SYSTEM "linux")
# Work out the target platform unless one was given explicitly.
include(${DUSK_ROOT_DIR}/cmake/determineplatform.cmake)
# Platform defaults and definitions (sets DUSK_DEFAULT_* and
# DUSK_PLATFORM_DIRS / DUSK_PLATFORM_DEFINITIONS).
set(DUSK_TARGET_FILE
"${DUSK_ROOT_DIR}/cmake/${DUSK_TARGET_SYSTEM}/target.cmake"
)
if(NOT EXISTS "${DUSK_TARGET_FILE}")
message(FATAL_ERROR "Unknown DUSK_TARGET_SYSTEM: ${DUSK_TARGET_SYSTEM}")
endif() endif()
include("${DUSK_TARGET_FILE}")
# Create directories # All options and their defaults.
file(MAKE_DIRECTORY ${DUSK_GENERATED_HEADERS_DIR}) include(${DUSK_ROOT_DIR}/cmake/option.cmake)
file(MAKE_DIRECTORY ${DUSK_TEMP_DIR})
file(MAKE_DIRECTORY ${DUSK_BUILT_ASSETS_DIR})
# Required build packages # Targets. Tests split the code into a library plus the binary, otherwise all
find_package(Python3 COMPONENTS Interpreter REQUIRED) # code is compiled straight into the binary.
set(DUSK_BINARY_TARGET_NAME "Dusk")
# Init Project. add_executable(${DUSK_BINARY_TARGET_NAME})
project(${DUSK_LIBRARY_TARGET_NAME}
VERSION 1.0.0
LANGUAGES C
)
# Either, create library and binary separately (used for tests), or make them
# one in the same so all code is in the binary only.
# Binary Executable
add_executable(${DUSK_BINARY_TARGET_NAME} ${DUSK_SOURCES_DIR}/dusk/null.c)
if(DUSK_BUILD_TESTS) if(DUSK_BUILD_TESTS)
# MainLibrary set(DUSK_LIBRARY_TARGET_NAME "DuskCore")
add_library(${DUSK_LIBRARY_TARGET_NAME} STATIC) add_library(${DUSK_LIBRARY_TARGET_NAME} STATIC)
target_link_libraries(${DUSK_BINARY_TARGET_NAME} PUBLIC
# Link library to binary
target_link_libraries(${DUSK_BINARY_TARGET_NAME}
PUBLIC
${DUSK_LIBRARY_TARGET_NAME} ${DUSK_LIBRARY_TARGET_NAME}
) )
else() else()
set(DUSK_LIBRARY_TARGET_NAME "${DUSK_BINARY_TARGET_NAME}" set(DUSK_LIBRARY_TARGET_NAME "${DUSK_BINARY_TARGET_NAME}")
CACHE INTERNAL ${DUSK_CACHE_TARGET}
)
endif() endif()
if(NOT DEFINED DUSK_VERSION) target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
string(TIMESTAMP DUSK_VERSION "debug-%y%m%d%H%M%S")
endif()
# Definitions
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
DUSK_TARGET_SYSTEM="${DUSK_TARGET_SYSTEM}" DUSK_TARGET_SYSTEM="${DUSK_TARGET_SYSTEM}"
DUSK_GAME_NAME="${DUSK_GAME_NAME}" DUSK_GAME_NAME="${DUSK_GAME_NAME}"
DUSK_GAME_AUTHOR="${DUSK_GAME_AUTHOR}" DUSK_GAME_AUTHOR="${DUSK_GAME_AUTHOR}"
DUSK_GAME_SHORT_DESCRIPTION="${DUSK_GAME_SHORT_DESCRIPTION}"
DUSK_GAME_LONG_DESCRIPTION="${DUSK_GAME_LONG_DESCRIPTION}"
DUSK_VERSION="${DUSK_VERSION}" DUSK_VERSION="${DUSK_VERSION}"
) )
if(DUSK_NETWORK) target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC ${DUSK_PLATFORM_DEFINITIONS}
DUSK_NETWORK )
) foreach(OPT ASSERTIONS_FAKED BACKTRACE TRACK_MEMORY NETWORK)
if(DUSK_${OPT})
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC DUSK_${OPT})
endif()
endforeach()
if(DUSK_BACKTRACE)
target_link_options(${DUSK_LIBRARY_TARGET_NAME} PUBLIC -rdynamic)
endif() endif()
# Toolchains # Core engine code
include(cmake/targets/${DUSK_TARGET_SYSTEM}.cmake) add_subdirectory(${DUSK_SOURCES_DIR}/dusk)
# Add tools # Platform code
add_subdirectory(tools) foreach(DIR ${DUSK_PLATFORM_DIRS})
add_subdirectory(${DUSK_SOURCES_DIR}/${DIR})
endforeach()
# Include generated headers from tools.
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
${DUSK_GENERATED_HEADERS_DIR}
)
# Add main code
add_subdirectory(${DUSK_SOURCES_DIR})
# Include generated headers
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
${DUSK_GENERATED_HEADERS_DIR}
)
# Handle tests
if(DUSK_BUILD_TESTS) if(DUSK_BUILD_TESTS)
enable_testing() enable_testing()
add_subdirectory(test)
endif() endif()
# Build assets. Runs unconditionally on every build (rather than being
# gated behind a custom_command's file-level DEPENDS list) because
# CONFIGURE_DEPENDS globs only pick up added/removed files on the next
# CMake configure, so that approach could miss/lag a rebuild behind an
# asset edit. tools.asset.pack itself decides whether anything actually
# changed (see its manifest check) and no-ops quickly when nothing did,
# so this stays cheap on a build where assets are untouched.
add_custom_target(DUSK_ASSETS_BUILT ALL
COMMAND ${CMAKE_COMMAND} -E make_directory "${DUSK_ASSETS_DIR}"
COMMAND ${Python3_EXECUTABLE} -m tools.asset.pack
--input "${DUSK_ASSETS_DIR}"
--output "${DUSK_ASSETS_ZIP}"
WORKING_DIRECTORY "${DUSK_ROOT_DIR}"
BYPRODUCTS "${DUSK_ASSETS_ZIP}"
COMMENT "Packing assets into dusk.dsk"
VERBATIM
)
add_dependencies(${DUSK_LIBRARY_TARGET_NAME} DUSK_ASSETS_BUILT)
+45
View File
@@ -0,0 +1,45 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Determines DUSK_TARGET_SYSTEM when it was not explicitly provided with
# -DDUSK_TARGET_SYSTEM=<target>. Must be included after project() so that the
# compiler and CMAKE_SYSTEM_NAME are known.
#
# Console toolchains all cross compile as "Generic", so they are recognised by
# the compiler they configure.
if(DUSK_TARGET_SYSTEM)
message(STATUS "Dusk target: ${DUSK_TARGET_SYSTEM} (explicit)")
return()
endif()
if(CMAKE_C_COMPILER MATCHES "psp-")
set(DUSK_DETECTED_SYSTEM "psp")
elseif(CMAKE_C_COMPILER MATCHES "arm-vita-eabi")
set(DUSK_DETECTED_SYSTEM "vita")
elseif(CMAKE_C_COMPILER MATCHES "powerpc-eabi")
set(DUSK_DETECTED_SYSTEM "dolphin")
elseif(CMAKE_C_COMPILER MATCHES "ps2")
set(DUSK_DETECTED_SYSTEM "ps2")
elseif(CMAKE_C_COMPILER MATCHES "sh-elf")
set(DUSK_DETECTED_SYSTEM "dreamcast")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
set(DUSK_DETECTED_SYSTEM "windows")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(DUSK_DETECTED_SYSTEM "linux")
else()
message(FATAL_ERROR
"Could not determine the target platform (system: ${CMAKE_SYSTEM_NAME}, "
"compiler: ${CMAKE_C_COMPILER}). Pass -DDUSK_TARGET_SYSTEM=<target>."
)
endif()
set(DUSK_TARGET_SYSTEM "${DUSK_DETECTED_SYSTEM}" CACHE STRING
"Target platform" FORCE
)
set_property(CACHE DUSK_TARGET_SYSTEM PROPERTY STRINGS
linux windows psp dolphin vita ps2 dreamcast
)
message(STATUS "Dusk target: ${DUSK_TARGET_SYSTEM} (detected)")
+27
View File
@@ -0,0 +1,27 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Platform setup for the dolphin target, included by the root CMakeLists.txt.
# Only sets defaults and definitions; the root applies them.
# Source directories to build for this platform.
set(DUSK_PLATFORM_DIRS duskdolphin)
# Default values for the shared options declared in the root CMakeLists.txt.
# Assertions are faked (compiled out) unless this is a Debug build.
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(DUSK_DEFAULT_ASSERTIONS_FAKED OFF)
else()
set(DUSK_DEFAULT_ASSERTIONS_FAKED ON)
endif()
set(DUSK_DEFAULT_BACKTRACE OFF)
set(DUSK_DEFAULT_TRACK_MEMORY OFF)
# Platform definitions.
set(DUSK_PLATFORM_DEFINITIONS
DUSK_PLATFORM_ENDIAN_BIG
DUSK_DOLPHIN
DUSK_THREAD_PTHREAD
)
+26
View File
@@ -0,0 +1,26 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Platform setup for the dreamcast target, included by the root CMakeLists.txt.
# Only sets defaults and definitions; the root applies them.
# Source directories to build for this platform.
set(DUSK_PLATFORM_DIRS duskdreamcast)
# Default values for the shared options declared in the root CMakeLists.txt.
# Assertions are faked (compiled out) unless this is a Debug build.
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(DUSK_DEFAULT_ASSERTIONS_FAKED OFF)
else()
set(DUSK_DEFAULT_ASSERTIONS_FAKED ON)
endif()
set(DUSK_DEFAULT_BACKTRACE OFF)
set(DUSK_DEFAULT_TRACK_MEMORY OFF)
# Platform definitions.
set(DUSK_PLATFORM_DEFINITIONS
DUSK_PLATFORM_ENDIAN_LITTLE
DUSK_DREAMCAST
)
+32
View File
@@ -0,0 +1,32 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Platform setup for the linux target, included by the root CMakeLists.txt.
# Only sets defaults and definitions; the root applies them.
# Source directories to build for this platform.
set(DUSK_PLATFORM_DIRS dusklinux dusksdl3 duskvulkan)
# Default values for the shared options declared in the root CMakeLists.txt.
set(DUSK_DEFAULT_ASSERTIONS_FAKED OFF)
set(DUSK_DEFAULT_BACKTRACE ON)
set(DUSK_DEFAULT_TRACK_MEMORY ON)
# Linux runs on both endiannesses, so detect it from the target compiler.
include(TestBigEndian)
test_big_endian(DUSK_IS_BIG_ENDIAN)
if(DUSK_IS_BIG_ENDIAN)
set(DUSK_ENDIAN_DEFINITION DUSK_PLATFORM_ENDIAN_BIG)
else()
set(DUSK_ENDIAN_DEFINITION DUSK_PLATFORM_ENDIAN_LITTLE)
endif()
# Platform definitions.
set(DUSK_PLATFORM_DEFINITIONS
${DUSK_ENDIAN_DEFINITION}
DUSK_LINUX
DUSK_THREAD_PTHREAD
DUSK_TIME_DYNAMIC
)
+40
View File
@@ -0,0 +1,40 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# All user facing CMake options and their defaults. Included by the root
# CMakeLists.txt after the platform's target.cmake, which supplies the
# DUSK_DEFAULT_* values used below. DUSK_TARGET_SYSTEM itself is declared by
# determineplatform.cmake.
# General
option(DUSK_BUILD_TESTS "Enable tests." OFF)
option(DUSK_NETWORK "Enable network support." ON)
# Game metadata
set(DUSK_GAME_NAME "Dusk" CACHE STRING "Game display name.")
set(DUSK_GAME_AUTHOR "YourWishes" CACHE STRING "Game author / coder.")
if(NOT DEFINED DUSK_VERSION)
string(TIMESTAMP DUSK_VERSION "debug-%y%m%d%H%M%S")
endif()
# Platform dependent defaults
option(DUSK_ASSERTIONS_FAKED
"Compile assertions out (they do nothing)."
${DUSK_DEFAULT_ASSERTIONS_FAKED}
)
option(DUSK_BACKTRACE
"Log a stack trace on assertion failure."
${DUSK_DEFAULT_BACKTRACE}
)
option(DUSK_TRACK_MEMORY
"Track memoryAllocate() call sites to help find memory leaks."
${DUSK_DEFAULT_TRACK_MEMORY}
)
# Tests mock assertions, so they can never be faked.
if(DUSK_BUILD_TESTS AND DUSK_ASSERTIONS_FAKED)
message(STATUS "DUSK_BUILD_TESTS: forcing DUSK_ASSERTIONS_FAKED off")
set(DUSK_ASSERTIONS_FAKED OFF CACHE BOOL "" FORCE)
endif()
+26
View File
@@ -0,0 +1,26 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Platform setup for the ps2 target, included by the root CMakeLists.txt.
# Only sets defaults and definitions; the root applies them.
# Source directories to build for this platform.
set(DUSK_PLATFORM_DIRS duskps2)
# Default values for the shared options declared in the root CMakeLists.txt.
# Assertions are faked (compiled out) unless this is a Debug build.
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(DUSK_DEFAULT_ASSERTIONS_FAKED OFF)
else()
set(DUSK_DEFAULT_ASSERTIONS_FAKED ON)
endif()
set(DUSK_DEFAULT_BACKTRACE OFF)
set(DUSK_DEFAULT_TRACK_MEMORY OFF)
# Platform definitions.
set(DUSK_PLATFORM_DEFINITIONS
DUSK_PLATFORM_ENDIAN_LITTLE
DUSK_PS2
)
+27
View File
@@ -0,0 +1,27 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Platform setup for the psp target, included by the root CMakeLists.txt.
# Only sets defaults and definitions; the root applies them.
# Source directories to build for this platform.
set(DUSK_PLATFORM_DIRS duskpsp)
# Default values for the shared options declared in the root CMakeLists.txt.
# Assertions are faked (compiled out) unless this is a Debug build.
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(DUSK_DEFAULT_ASSERTIONS_FAKED OFF)
else()
set(DUSK_DEFAULT_ASSERTIONS_FAKED ON)
endif()
set(DUSK_DEFAULT_BACKTRACE OFF)
set(DUSK_DEFAULT_TRACK_MEMORY OFF)
# Platform definitions.
set(DUSK_PLATFORM_DEFINITIONS
DUSK_PLATFORM_ENDIAN_LITTLE
DUSK_PSP
DUSK_THREAD_PTHREAD
)
+27
View File
@@ -0,0 +1,27 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Platform setup for the vita target, included by the root CMakeLists.txt.
# Only sets defaults and definitions; the root applies them.
# Source directories to build for this platform.
set(DUSK_PLATFORM_DIRS duskvita)
# Default values for the shared options declared in the root CMakeLists.txt.
# Assertions are faked (compiled out) unless this is a Debug build.
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(DUSK_DEFAULT_ASSERTIONS_FAKED OFF)
else()
set(DUSK_DEFAULT_ASSERTIONS_FAKED ON)
endif()
set(DUSK_DEFAULT_BACKTRACE OFF)
set(DUSK_DEFAULT_TRACK_MEMORY OFF)
# Platform definitions.
set(DUSK_PLATFORM_DEFINITIONS
DUSK_PLATFORM_ENDIAN_LITTLE
DUSK_VITA
DUSK_THREAD_PTHREAD
)
+21
View File
@@ -0,0 +1,21 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Platform setup for the windows target, included by the root CMakeLists.txt.
# Only sets defaults and definitions; the root applies them.
# Source directories to build for this platform.
set(DUSK_PLATFORM_DIRS duskwindows dusksdl3 duskvulkan)
# Default values for the shared options declared in the root CMakeLists.txt.
set(DUSK_DEFAULT_ASSERTIONS_FAKED OFF)
set(DUSK_DEFAULT_BACKTRACE OFF)
set(DUSK_DEFAULT_TRACK_MEMORY ON)
# Platform definitions.
set(DUSK_PLATFORM_DEFINITIONS
DUSK_PLATFORM_ENDIAN_LITTLE
DUSK_WINDOWS
)
+5
View File
@@ -0,0 +1,5 @@
FROM ubuntu:latest
ENV DEBIAN_FRONTEND=noninteractive
WORKDIR /workdir
COPY scripts/linux/install.sh /tmp/install.sh
RUN chmod +x /tmp/install.sh && /tmp/install.sh && rm /tmp/install.sh
+5
View File
@@ -0,0 +1,5 @@
FROM pspdev/pspdev:latest
WORKDIR /workdir
COPY scripts/psp/install.sh /tmp/install.sh
RUN chmod +x /tmp/install.sh && /tmp/install.sh && rm /tmp/install.sh
VOLUME ["/workdir"]
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
set -e
cd "$(dirname "$0")/../.."
docker build -t dusk-linux -f docker/linux/Dockerfile .
docker run --rm -v "$(pwd):/workdir" dusk-linux /bin/bash -c "./scripts/linux/build.sh"
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
set -e
cd "$(dirname "$0")/../.."
cmake -S . -B build-linux -DDUSK_TARGET_SYSTEM=linux
cmake --build build-linux -- -j"$(nproc)"
+42
View File
@@ -0,0 +1,42 @@
#!/bin/bash
# Installs the packages required to build and test Dusk on Linux.
set -e
SUDO=""
if [ "$(id -u)" -ne 0 ]; then
SUDO="sudo"
fi
if command -v apt-get >/dev/null 2>&1; then
$SUDO apt-get update
$SUDO apt-get install -y \
build-essential \
cmake \
git \
python3 \
python3-pip \
python3-polib \
python3-pil \
python3-dotenv \
python3-numpy \
libsdl3-dev \
libvulkan-dev \
libzip-dev \
libbz2-dev \
liblzma-dev \
zlib1g-dev \
libssl-dev \
xz-utils
elif command -v dnf >/dev/null 2>&1; then
$SUDO dnf install -y \
git make gcc cmake python python-polib python3-pillow python3-dotenv \
python3-numpy SDL2-devel mesa-libGL-devel zlib-devel libzip-devel \
bzip2-devel openssl-devel xz xz-devel
elif command -v pacman >/dev/null 2>&1; then
$SUDO pacman -Sy --noconfirm --needed \
base-devel git cmake python python-polib python-pillow python-dotenv \
python-numpy sdl2 mesa libzip bzip2 openssl xz zlib
else
echo "Unsupported package manager; install dependencies manually." >&2
exit 1
fi
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
set -e
cd "$(dirname "$0")/../.."
docker build -t dusk-linux -f docker/linux/Dockerfile .
docker run --rm -v "$(pwd):/workdir" dusk-linux /bin/bash -c "./scripts/linux/test.sh"
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
set -e
cd "$(dirname "$0")/../.."
rm -rf build-tests
cmake -S . -B build-tests -DDUSK_BUILD_TESTS=ON -DDUSK_TARGET_SYSTEM=linux
cmake --build build-tests -- -j"$(nproc)"
ctest --output-on-failure --test-dir build-tests
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
set -e
cd "$(dirname "$0")/../.."
docker build -t dusk-psp -f docker/psp/Dockerfile .
docker run --rm -v "$(pwd):/workdir" dusk-psp /bin/bash -c "./scripts/psp/build.sh"
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
set -e
cd "$(dirname "$0")/../.."
if [ -z "$PSPDEV" ]; then
echo "PSPDEV environment variable is not set." >&2
exit 1
fi
psp-cmake -S . -B build-psp \
-DDUSK_TARGET_SYSTEM=psp \
-DBUILD_PRX=1 \
-DCMAKE_BUILD_TYPE=Release
cmake --build build-psp -- -j"$(nproc)"
+30
View File
@@ -0,0 +1,30 @@
#!/bin/bash
# Installs the packages required to build Dusk for the PSP. The PSP toolchain
# itself (pspdev) must already be installed; the pspdev/pspdev docker image
# provides it.
set -e
SUDO=""
if [ "$(id -u)" -ne 0 ]; then
SUDO="sudo"
fi
if command -v apk >/dev/null 2>&1; then
$SUDO apk add --no-cache git cmake make python3 py3-pip py3-dotenv
elif command -v apt-get >/dev/null 2>&1; then
$SUDO apt-get update
$SUDO apt-get install -y git cmake make python3 python3-pip python3-dotenv
elif command -v dnf >/dev/null 2>&1; then
$SUDO dnf install -y git cmake make python python3-dotenv
elif command -v pacman >/dev/null 2>&1; then
$SUDO pacman -Sy --noconfirm --needed git cmake make python python-dotenv
else
echo "Unsupported package manager; install dependencies manually." >&2
exit 1
fi
if [ -z "$PSPDEV" ] || ! command -v psp-cmake >/dev/null 2>&1; then
echo "pspdev toolchain not found. Install it from https://pspdev.github.io/" >&2
echo "and set PSPDEV, or use scripts/psp/build-docker.sh." >&2
exit 1
fi
+20
View File
@@ -0,0 +1,20 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Headers are included relative to this directory (e.g. "util/string.h").
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
# Modules
add_subdirectory(assert)
add_subdirectory(console)
add_subdirectory(display)
add_subdirectory(engine)
add_subdirectory(error)
add_subdirectory(perf)
add_subdirectory(time)
add_subdirectory(thread)
add_subdirectory(util)
+10
View File
@@ -0,0 +1,10 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
assert.c
)
+170
View File
@@ -0,0 +1,170 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "assert.h"
#include "log/log.h"
#include "util/string.h"
#include "util/memory.h"
#ifdef DUSK_THREAD_PTHREAD
pthread_t ASSERT_MAIN_THREAD_ID = 0;
#endif
#ifndef DUSK_ASSERTIONS_FAKED
void assertInit(void) {
#ifdef DUSK_THREAD_PTHREAD
ASSERT_MAIN_THREAD_ID = pthread_self();
#endif
}
#ifdef DUSK_TEST_ASSERT
void assertTrueImpl(
const char *file,
const int32_t line,
const bool x,
const char *message
) {
mock_assert(
x == true,
message,
file,
line
);
}
#else
#ifdef DUSK_BACKTRACE
#include <execinfo.h>
#include <stdlib.h>
void assertLogBacktrace(void) {
void *frames[64];
int count = backtrace(frames, 64);
char **symbols = backtrace_symbols(frames, count);
memoryTrack(symbols);
logError("Stack trace:\n");
if(symbols) {
for(int i = 0; i < count; i++) {
logError(" %s\n", symbols[i]);
}
memoryFree(symbols);
}
}
#endif
void assertTrueImpl(
const char *file,
const int32_t line,
const bool x,
const char *message
) {
if(x != true) {
logError(
"Assertion Failed in %s:%i\n\n%s\n\n",
file,
line,
message
);
#ifdef DUSK_BACKTRACE
assertLogBacktrace();
#endif
abort();
}
}
#endif
void assertFalseImpl(
const char *file,
const int32_t line,
bool x,
const char *message
) {
assertTrueImpl(file, line, !x, message);
}
void assertUnreachableImpl(
const char *file,
const int32_t line,
const char *message
) {
assertTrueImpl(file, line, false, message);
}
void assertNotNullImpl(
const char *file,
const int32_t line,
const void *pointer,
const char *message
) {
assertTrueImpl(
file,
line,
pointer != NULL,
message
);
// Ensure we can touch it
volatile char temp;
temp = *((char*)pointer);
}
void assertNullImpl(
const char *file,
const int32_t line,
const void *pointer,
const char *message
) {
assertTrueImpl(
file,
line,
pointer == NULL,
message
);
}
void assertDeprecatedImpl(
const char *file,
const int32_t line,
const char *message
) {
assertUnreachableImpl(file, line, message);
}
void assertStringEqualImpl(
const char *file,
const int32_t line,
const char *a,
const char *b,
const char *message
) {
assertTrueImpl(file, line, stringCompare(a, b) == 0, message);
}
void assertIsMainThreadImpl(
const char *file,
const int32_t line,
const char *message
) {
#ifdef DUSK_THREAD_PTHREAD
assertTrueImpl(
file, line, pthread_self() == ASSERT_MAIN_THREAD_ID, message
);
#endif
}
void assertNotMainThreadImpl(
const char *file,
const int32_t line,
const char *message
) {
#ifdef DUSK_THREAD_PTHREAD
assertTrueImpl(
file, line, pthread_self() != ASSERT_MAIN_THREAD_ID, message
);
#endif
}
#endif
+290
View File
@@ -0,0 +1,290 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#ifdef DUSK_TEST_ASSERT
#include <cmocka.h>
#ifdef DUSK_ASSERTIONS_FAKED
#error "Cannot fake assertions when DUSK_TEST_ASSERT is set."
#endif
#endif
#ifdef DUSK_THREAD_PTHREAD
#include "thread/thread.h"
extern pthread_t ASSERT_MAIN_THREAD_ID;
#endif
#ifndef DUSK_ASSERTIONS_FAKED
#define DUSK_ASSERTIONS 1
/**
* Initializes the assert system. Must be the very first call in engine
* startup.
*/
void assertInit(void);
#ifdef DUSK_BACKTRACE
/**
* Logs a stack trace of the current call stack to the error log.
*/
void assertLogBacktrace(void);
#endif
/**
* Assert a given value to be true.
*
* @param file File that the assertion is being made from.
* @param line Line that the assertion is being made from.
* @param x Value to assert as true.
* @param message Message to throw against assertion failure.
*/
void assertTrueImpl(
const char *file,
const int32_t line,
const bool_t x,
const char *message
);
/**
* Asserts a given statement to be false.
*
* @param file File that the assertion is being made from.
* @param line Line that the assertion is being made from.
* @param x Value to assert as false.
* @param message Message to throw against assertion failure.
*/
void assertFalseImpl(
const char *file,
const int32_t line,
const bool_t x,
const char *message
);
/**
* Asserts that a given line of code is unreachable. Essentially a forced
* assertion failure, good for "edge cases"
*
* @param file File that the assertion is being made from.
* @param line Line that the assertion is being made from.
* @param message Message to throw against assertion failure.
*/
void assertUnreachableImpl(
const char *file,
const int32_t line,
const char *message
);
/**
* Assert a given pointer to not point to a null pointer.
*
* @param file File that the assertion is being made from.
* @param line Line that the assertion is being made from.
* @param pointer Pointer to assert is not a null pointer.
* @param message Message to throw against assertion failure.
*/
void assertNotNullImpl(
const char *file,
const int32_t line,
const void *pointer,
const char *message
);
/**
* Asserts a given pointer to be a nullptr.
*
* @param file File that the assertion is being made from.
* @param line Line that the assertion is being made from.
* @param pointer Pointer to assert is nullptr.
* @param message Message to throw against assertion failure.
*/
void assertNullImpl(
const char *file,
const int32_t line,
const void *pointer,
const char *message
);
/**
* Asserts a function as being deprecated.
*
* @param file File that the assertion is being made from.
* @param line Line that the assertion is being made from.
* @param message Message to throw against assertion failure.
*/
void assertDeprecatedImpl(
const char *file,
const int32_t line,
const char *message
);
/**
* Asserts two strings to be equal.
*
* @param file File that the assertion is being made from.
* @param line Line that the assertion is being made from.
* @param a First string to compare.
* @param b Second string to compare.
* @param message Message to throw against assertion failure.
*/
void assertStringEqualImpl(
const char *file,
const int32_t line,
const char *a,
const char *b,
const char *message
);
/**
* Asserts that the current thread is the main thread.
*
* @param message Message to throw against assertion failure.
*/
void assertIsMainThreadImpl(
const char *file,
const int32_t line,
const char *message
);
/**
* Asserts that the current thread is NOT the main thread.
*
* @param message Message to throw against assertion failure.
*/
void assertNotMainThreadImpl(
const char *file,
const int32_t line,
const char *message
);
/**
* Asserts a given value to be true.
*
* @param x Value to assert as true.
* @param message Message to throw against assertion failure.
*/
#define assertTrue(x, message) \
assertTrueImpl(__FILE__, __LINE__, x, message)
/**
* Asserts a given statement to be false.
*
* @param x Value to assert as false.
* @param message Message to throw against assertion failure.
*/
#define assertFalse(x, message) \
assertFalseImpl(__FILE__, __LINE__, x, message)
/**
* Asserts that a given line of code is unreachable. Essentially a forced
* assertion failure, good for "edge cases"
*
* @param message Message to throw against assertion failure.
*/
#define assertUnreachable(message) \
assertUnreachableImpl(__FILE__, __LINE__, message)
/**
* Asserts a pointer is not null.
*
* @param pointer Pointer to check.
* @param message Message to throw against assertion failure.
*/
#define assertNotNull(pointer, message) \
assertNotNullImpl(__FILE__, __LINE__, pointer, message)
/**
* Asserts a pointer is null.
*
* @param pointer Pointer to check.
* @param message Message to throw against assertion failure.
*/
#define assertNull(pointer, message) \
assertNullImpl(__FILE__, __LINE__, pointer, message)
/**
* Asserts a function or code path is deprecated.
*
* @param message Message to throw against assertion failure.
*/
#define assertDeprecated(message) \
assertDeprecatedImpl(__FILE__, __LINE__, message)
/**
* Asserts a string's length is less than a maximum.
*
* @param str String to check.
* @param len Maximum length.
* @param message Message to throw against assertion failure.
*/
#define assertStrLenMax(str, len, message) \
assertTrue(strlen(str) < len, message)
/**
* Asserts a string's length is at least a minimum.
*
* @param str String to check.
* @param len Minimum length.
* @param message Message to throw against assertion failure.
*/
#define assertStrLenMin(str, len, message) \
assertTrue(strlen(str) >= len, message)
/**
* Asserts two strings to be equal.
*
* @param a First string to compare.
* @param b Second string to compare.
* @param message Message to throw against assertion failure.
*/
#define assertStringEqual(a, b, message) \
assertStringEqualImpl(__FILE__, __LINE__, a, b, message)
/**
* Asserts that the current thread is the main thread.
*
* @param message Message to throw against assertion failure.
*/
#define assertIsMainThread(message) \
assertIsMainThreadImpl(__FILE__, __LINE__, message)
/**
* Asserts that the current thread is NOT the main thread.
*
* @param message Message to throw against assertion failure.
*/
#define assertNotMainThread(message) \
assertNotMainThreadImpl(__FILE__, __LINE__, message)
#else
// If assertions are faked, we define the macros to do nothing.
#define assertInit() ((void)0)
#define assertMainThreadInit() ((void)0)
#define assertIsMainThread(message) ((void)0)
#define assertNotMainThread(message) ((void)0)
#define assertTrue(x, message) ((void)0)
#define assertFalse(x, message) ((void)0)
#define assertUnreachable(message) ((void)0)
#define assertNotNull(pointer, message) ((void)0)
#define assertNull(pointer, message) ((void)0)
#define assertDeprecated(message) ((void)0)
#define assertStrLenMax(str, len, message) ((void)0)
#define assertStrLenMin(str, len, message) ((void)0)
#define assertStringEqual(a, b, message) ((void)0)
#define assertIsMainThread(message) ((void)0)
#define assertNotMainThread(message) ((void)0)
#endif
// Static Assertions
#define assertStructSize(struct, size) \
_Static_assert(sizeof(struct) == size, "Size of " #struct " must be " #size)
// EOF
+9
View File
@@ -0,0 +1,9 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
console.c
)
+49
View File
@@ -0,0 +1,49 @@
/**
* 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 "log/log.h"
console_t CONSOLE;
void consoleInit(void) {
memoryZero(&CONSOLE, sizeof(console_t));
CONSOLE.visible = false;
// CONSOLE.visible = true;
threadMutexInit(&CONSOLE.printMutex);
}
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);
threadMutexLock(&CONSOLE.printMutex);
memoryMove(
CONSOLE.line[0],
CONSOLE.line[1],
(CONSOLE_HISTORY_MAX - 1) * CONSOLE_LINE_MAX
);
memoryCopy(CONSOLE.line[CONSOLE_HISTORY_MAX - 1], buffer, len + 1);
threadMutexUnlock(&CONSOLE.printMutex);
logDebug("%s\n", buffer);
}
void consoleUpdate(void) {
// TODO: Toggle console visibility once the input system exists.
}
void consoleDispose(void) {
threadMutexDispose(&CONSOLE.printMutex);
}
+45
View File
@@ -0,0 +1,45 @@
/**
* 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 "dusk.h"
#include "thread/thread.h"
#define CONSOLE_LINE_MAX 128
#define CONSOLE_HISTORY_MAX 16
#define CONSOLE_EXEC_BUFFER_MAX 32
typedef struct {
char_t line[CONSOLE_HISTORY_MAX][CONSOLE_LINE_MAX];
bool_t visible;
threadmutex_t printMutex;
} 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, ...);
/**
* Processes pending queued script lines. Call once per frame from main thread.
*/
void consoleUpdate(void);
/**
* Disposes of the console.
*/
void consoleDispose(void);
+9
View File
@@ -0,0 +1,9 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
display.c
)
+37
View File
@@ -0,0 +1,37 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "display/display.h"
#include "util/memory.h"
display_t DISPLAY = { 0 };
errorret_t displayInit(void) {
memoryZero(&DISPLAY, sizeof(DISPLAY));
#ifdef displayPlatformInit
errorChain(displayPlatformInit());
#endif
errorOk();
}
errorret_t displayUpdate(void) {
#ifdef displayPlatformUpdate
errorChain(displayPlatformUpdate());
#endif
errorOk();
}
errorret_t displayDispose(void) {
#ifdef displayPlatformDispose
errorChain(displayPlatformDispose());
#endif
errorOk();
}
+36
View File
@@ -0,0 +1,36 @@
/**
* 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 "display/displayplatform.h"
// Main display struct, platform-specific.
typedef displayplatform_t display_t;
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);
/**
* Disposes of the display system.
*
* @return An errorret_t indicating success or failure.
*/
errorret_t displayDispose(void);
+28
View File
@@ -0,0 +1,28 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <errno.h>
#include <ctype.h>
#include <stdarg.h>
#include <float.h>
#include <memory.h>
#include <malloc.h>
#include "duskplatform.h"
typedef bool bool_t;
typedef int int_t;
typedef float float_t;
typedef char char_t;
+10
View File
@@ -0,0 +1,10 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
engine.c
)
+64
View File
@@ -0,0 +1,64 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "engine.h"
#include "util/memory.h"
#include "perf/perf.h"
#include "time/time.h"
#include "display/display.h"
#include "assert/assert.h"
#include "console/console.h"
engine_t ENGINE;
errorret_t engineInit(const int32_t argc, const char_t **argv) {
assertInit();
memoryZero(&ENGINE, sizeof(engine_t));
ENGINE.running = true;
ENGINE.argc = argc;
ENGINE.argv = argv;
ENGINE.version = DUSK_VERSION;
// Init systems. Order is important.
perfInit();
timeInit();
consoleInit();
errorChain(displayInit());
consolePrint("Engine initialized");
#ifdef DUSK_ASSERTIONS_FAKED
consolePrint("Assertions faked");
#else
consolePrint("Assertions real");
#endif
errorOk();
}
errorret_t engineUpdate(void) {
// Order here is important.
timeUpdate();
consoleUpdate();
errorChain(displayUpdate());
errorOk();
}
void engineExit(void) {
ENGINE.running = false;
}
errorret_t engineDispose(void) {
errorChain(displayDispose());
consoleDispose();
perfDispose();
memoryCheckUnfreed();
errorOk();
}
+43
View File
@@ -0,0 +1,43 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#include "error/error.h"
typedef struct {
bool_t running;
int32_t argc;
const char_t **argv;
const char_t *version;
} engine_t;
extern engine_t ENGINE;
/**
* Initializes the engine.
*
* @param argc The argument count from main().
* @param argv The argument vector from main().
*/
errorret_t engineInit(const int32_t argc, const char_t **argv);
/**
* Updates the engine.
*/
errorret_t engineUpdate(void);
/**
* Shuts down the engine.
*/
errorret_t engineDispose(void);
/**
* Requests that the engine stops running.
*/
void engineExit(void);
+10
View File
@@ -0,0 +1,10 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
error.c
)
+138
View File
@@ -0,0 +1,138 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "assert/assert.h"
#include "error.h"
#include "util/memory.h"
#include "util/string.h"
#include "log/log.h"
THREAD_LOCAL errorstate_t ERROR_STATE = { 0 };
errorret_t errorThrowImpl(
errorstate_t *state,
errorcode_t code,
const char_t *file,
const char_t *function,
const int32_t line,
const char_t *message,
...
) {
assertNotNull(state, "Error state cannot be NULL");
assertTrue(code != ERROR_OK, "Error code must not be OK");
assertNotNull(file, "File cannot be NULL");
assertNotNull(function, "Function cannot be NULL");
assertTrue(line >= 0, "File pointer must be valid");
assertNotNull(message, "Message cannot be NULL");
memoryZero(state, sizeof(errorstate_t));
state->code = code;
// Format args.
va_list args;
va_start(args, message);
// Get length of formatted message
va_list argsCopy;
va_copy(argsCopy, args);
int32_t len = stringFormatVA(NULL, 0, message, argsCopy);
va_end(argsCopy);
// Create string to hold the formatted message
state->message = (char_t *)memoryAllocate(len + 1);
stringFormatVA(state->message, len + 1, message, args);
va_end(args);
// Format lines
len = stringFormat(NULL, 0, ERROR_LINE_FORMAT, file, line, function);
assertTrue(len >= 0, "Line formatting failed");
state->lines = (char_t *)memoryAllocate(len + 1);
stringFormat(state->lines, len + 1, ERROR_LINE_FORMAT, file, line, function);
return (errorret_t) {
.code = code,
.state = state
};
}
errorret_t errorOkImpl() {
assertTrue(
ERROR_STATE.code == ERROR_OK,
"Global error state is not OK (Likely missing errorCatch)"
);
return (errorret_t) {
.code = ERROR_OK,
.state = NULL
};
}
errorret_t errorChainImpl(
const errorret_t retval,
const char_t *file,
const char_t *function,
const int32_t line
) {
if(errorIsOk(retval)) return retval;
assertNotNull(retval.state, "Error state NULL (Likely missing errorOk)");
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
);
assertTrue(newLineLen >= 0, "Line formatting failed");
char_t *newLine = (char_t *)memoryAllocate(newLineLen + 1);
snprintf(newLine, newLineLen + 1, ERROR_LINE_FORMAT, file, line, function);
// Resize the existing lines to accommodate the new line
size_t existingLen = strlen(retval.state->lines);
memoryResize(
(void**)&retval.state->lines,
existingLen,
existingLen + newLineLen + 1
);
// Now append the new line to the existing lines
memoryCopy(
retval.state->lines + existingLen,
newLine,
newLineLen + 1
);
// Cleanup the temporary new line
memoryFree(newLine);
return retval;
}
void errorCatch(errorret_t retval) {
if(errorIsOk(retval)) return;
assertNotNull(retval.state, "Error state cannot be NULL");
assertNotNull(retval.state->message, "Message cannot be NULL");
// Clear the error state
memoryFree((void*)retval.state->message);
memoryFree((void*)retval.state->lines);
retval.state->code = ERROR_OK;
}
errorret_t errorPrint(const errorret_t retval) {
if(errorIsOk(retval)) return retval;
assertNotNull(retval.state, "Error state cannot be NULL");
assertNotNull(retval.state->message, "Message cannot be NULL");
logError(
ERROR_PRINT_FORMAT,
retval.state->code,
retval.state->message,
retval.state->lines
);
return retval;
}
+153
View File
@@ -0,0 +1,153 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#include "thread/threadlocal.h"
typedef uint8_t errorcode_t;
typedef struct {
errorcode_t code;
char_t *message;
char_t *lines;
} errorstate_t;
typedef struct {
errorcode_t code;
errorstate_t *state;
} errorret_t;
static const errorcode_t ERROR_OK = 0;
static const errorcode_t ERROR_NOT_OK = 1;
static const char_t *ERROR_PRINT_FORMAT = "Error (%d): %s\n%s";
static const char_t *ERROR_LINE_FORMAT = " at %s:%d in function %s\n";
extern THREAD_LOCAL errorstate_t ERROR_STATE;
/**
* Sets the error state with the provided code and message.
*
* @param state The error state to initialize.
* @param code The error code to set.
* @param file The file where the error occurred.
* @param function The function where the error occurred.
* @param line The line number where the error occurred.
* @param message The error message.
* @param args The arguments for the error message.
* @return The error code.
*/
errorret_t errorThrowImpl(
errorstate_t *state,
errorcode_t code,
const char_t *file,
const char_t *function,
const int32_t line,
const char_t *message,
...
);
/**
* Returns an error state with no error.
*
* @return An error state with code ERROR_OK.
*/
errorret_t errorOkImpl();
/**
* Chains an error state, allowing for error propagation.
*
* @param retval The return value containing the error state.
* @param file The file where the error occurred.
* @param function The function where the error occurred.
* @param line The line number where the error occurred.
* @return The error code if an error occurred, otherwise continues execution.
*/
errorret_t errorChainImpl(
const errorret_t retval,
const char_t *file,
const char_t *function,
const int32_t line
);
/**
* Catches an error and handles it.
*
* @param retval The return value containing the error state.
*/
void errorCatch(errorret_t retval);
/**
* Prints the error state to the console.
*
* @param retval The return value containing the error state.
* @return Passed retval for chaining.
*/
errorret_t errorPrint(const errorret_t retval);
/**
* Throws an error with a formatted message.
*
* @param code The error code to throw.
* @param message The format string for the error message.
* @param ... Additional arguments for the format string.
* @return The error code.
*/
#define errorThrowWithCode(code, message, ... ) \
return errorThrowImpl(\
&ERROR_STATE, (code), __FILE__, __func__, __LINE__, (message), \
__VA_ARGS__ \
)
/**
* Throws an error with a default error code of ERROR_NOT_OK.
*
* @param message The format string for the error message.
* @param ... Additional arguments for the format string.
* @return The error code.
*/
#define errorThrow(message, ...) \
return errorThrowImpl(\
&ERROR_STATE, ERROR_NOT_OK, __FILE__, __func__, __LINE__, (message), \
##__VA_ARGS__ \
)
/**
* Checks if a child method errored, and if it did, then send it up the chain.
* @param retval The return value containing the error state.
* @return The error code if an error occurred, otherwise continues execution.
*/
#define errorChain(retval) { \
errorret_t errorChainRetval = (retval); \
if(errorChainRetval.code != ERROR_OK) { \
return errorChainImpl(errorChainRetval, __FILE__, __func__, __LINE__); \
} \
}
/**
* Returns without an error.
*
* @return An error state with code ERROR_OK.
*/
#define errorOk() \
return errorOkImpl()
/**
* Returns non-zero if retval indicates success.
*
* @param retval errorret_t to test.
*/
#define errorIsOk(retval) ((retval).code == ERROR_OK)
/**
* Returns non-zero if retval indicates failure.
*
* @param retval errorret_t to test.
*/
#define errorIsNotOk(retval) ((retval).code != ERROR_OK)
// EOF
+26
View File
@@ -0,0 +1,26 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
/**
* Prints a debug message to the debug console.
*
* @param message The message format string.
* @param ... Additional arguments for the format string.
*/
void logDebug(const char_t *message, ...);
/**
* Prints a message to the error console. For some platforms this will pause
* the program.
*
* @param message The message format string.
* @param ... Additional arguments for the format string.
*/
void logError(const char_t *message, ...);
+10
View File
@@ -0,0 +1,10 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
perf.c
)
+88
View File
@@ -0,0 +1,88 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "perf.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "console/console.h"
#if defined(DUSK_LINUX)
#include <time.h>
#elif defined(DUSK_PSP)
#include <pspthreadman.h>
#endif
perf_t PERF;
void perfInit(void) {
assertIsMainThread("Must be called from the main thread.");
memoryZero(&PERF, sizeof(perf_t));
}
void perfDispose(void) {
assertIsMainThread("Must be called from the main thread.");
}
uint64_t perfGetTick(void) {
assertIsMainThread("Must be called from the main thread.");
#if defined(DUSK_LINUX)
// TEST - CLOCK_MONOTONIC via clock_gettime(), Linux desktop only for
// now. Not a cross-platform answer yet (see perfGetTick's doc
// comment in perf.h) - just something real to look at while that
// gets settled. Returns nanoseconds since some unspecified starting
// point, so only differences between two calls are meaningful.
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec;
#elif defined(DUSK_PSP)
// TEST - sceKernelGetSystemTimeWide(), microseconds since boot at
// ~1us resolution (the standard PSP homebrew idiom for elapsed-time
// measurement - psprtc.h's sceRtcGetCurrentTick is for wall-clock
// date/time instead, already used by timepsp.c for that purpose).
// Scaled to nanoseconds to match every other platform's tick unit.
return (uint64_t)sceKernelGetSystemTimeWide() * 1000ull;
#else
return 0;
#endif
}
void perfPushItem(
const char_t *label, const char_t *file, const int32_t line
) {
assertIsMainThread("Must be called from the main thread.");
assertNotNull(label, "Perf item label cannot be NULL.");
assertNotNull(file, "Perf item file cannot be NULL.");
assertTrue(PERF.stackSize < PERF_STACK_SIZE, "Perf stack overflow.");
PERF.stack[PERF.stackSize++] = (perfitem_t){
.label = label,
.file = file,
.line = line,
.tick = perfGetTick()
};
}
void perfPopItem(void) {
assertIsMainThread("Must be called from the main thread.");
assertTrue(PERF.stackSize > 0, "Perf stack underflow.");
PERF.stackSize--;
}
void perfReport(void) {
assertIsMainThread("Must be called from the main thread.");
if(PERF.stackSize == 0) return;
const perfitem_t *item = &PERF.stack[PERF.stackSize - 1];
uint64_t elapsedNs = perfGetTick() - item->tick;
double_t elapsedMs = (double_t)elapsedNs / 1000000.0;
consolePrint(
"perf: %s (%s:%d, %.4fms)",
item->label, item->file, item->line, elapsedMs
);
}
+89
View File
@@ -0,0 +1,89 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "perfitem.h"
#define PERF_STACK_SIZE 256
typedef struct {
perfitem_t stack[PERF_STACK_SIZE];
uint32_t stackSize;
} perf_t;
extern perf_t PERF;
/**
* Initializes the perf system.
*/
void perfInit(void);
/**
* Disposes/cleans up the perf system. Currently a no-op - here so
* engine.c has a symmetric call to make once this needs real cleanup.
*/
void perfDispose(void);
/**
* Returns a tick value representing "now", for measuring the duration
* between a perfPush/perfPushItem and its matching perfPopItem. Always
* nanoseconds since some unspecified starting point, on every platform
* that implements it, so perfReport's ms conversion stays correct
* everywhere without per-platform special-casing.
*
* PLACEHOLDER - currently only reads a real value on DUSK_LINUX desktop
* builds (via clock_gettime(CLOCK_MONOTONIC)) and DUSK_PSP (via
* sceKernelGetSystemTimeWide(), scaled from microseconds), as a test;
* every other platform still gets a meaningless 0. A common,
* cross-platform way of reading CPU cycles/time hasn't been settled on
* yet - don't treat results as comparable across platforms until this is
* replaced with a real per-platform implementation. Only the difference
* between two calls is meaningful, never the raw value itself.
*
* @return A tick value, in nanoseconds. 0 on unimplemented platforms.
*/
uint64_t perfGetTick(void);
/**
* Pushes a new item onto the perf stack, capturing the current tick (see
* perfGetTick) as its start time. Prefer the perfPush() macro, which
* supplies file/line automatically.
*
* @param label Short caller-supplied memo of what this item represents
* (e.g. "frame start") - stored by pointer, not copied, so pass a
* string literal or something that otherwise outlives the push.
* @param file Source file the push originated from (e.g. __FILE__).
* @param line Source line the push originated from (e.g. __LINE__).
*/
void perfPushItem(
const char_t *label, const char_t *file, const int32_t line
);
/**
* Pops the most recently pushed perf item off the stack.
*/
void perfPopItem(void);
/**
* Reports on the item currently at the top of the perf stack (the most
* recently pushed one still awaiting its perfPopItem), if any.
*/
void perfReport(void);
/**
* Pushes a new perf item onto the stack, tagged with the call site this
* macro is expanded at.
*
* @param label Short memo of what this item represents (e.g. "frame
* start") - see perfPushItem.
*/
#define perfPush(label) perfPushItem(label, __FILE__, __LINE__)
/**
* Pops the most recently pushed perf item off the stack.
*/
#define perfPop() perfPopItem()
+16
View File
@@ -0,0 +1,16 @@
/**
* 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 {
const char_t *label;
int32_t line;
const char_t *file;
uint64_t tick;
} perfitem_t;
+11
View File
@@ -0,0 +1,11 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
thread.c
threadmutex.c
)
+117
View File
@@ -0,0 +1,117 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "thread.h"
#include "util/memory.h"
#include "assert/assert.h"
void threadInit(thread_t *thread, const threadcallback_t callback) {
assertNotNull(thread, "Thread cannot be NULL.");
assertNotNull(callback, "Thread callback cannot be NULL.");
memoryZero(thread, sizeof(thread_t));
threadMutexInit(&thread->stateMutex);
thread->state = THREAD_STATE_STOPPED;
thread->callback = callback;
#ifdef DUSK_THREAD_PTHREAD
thread->threadId = 0;
#endif
}
void threadStartRequest(thread_t *thread) {
assertNotNull(thread, "Thread cannot be NULL.");
threadMutexLock(&thread->stateMutex);
thread->state = THREAD_STATE_STARTING;
threadMutexUnlock(&thread->stateMutex);
#ifdef DUSK_THREAD_PTHREAD
assertTrue(thread->threadId == 0, "Thread id not 0.");
pthread_create(
&thread->threadId,
NULL,
(void * (*)(void *))threadHandler,
thread
);
pthread_detach(thread->threadId);
#endif
}
void threadStopRequest(thread_t *thread) {
assertNotNull(thread, "Thread cannot be NULL.");
threadMutexLock(&thread->stateMutex);
if(thread->state != THREAD_STATE_STOPPED) {
thread->state = THREAD_STATE_STOP_REQUESTED;
}
threadMutexUnlock(&thread->stateMutex);
}
void threadStart(thread_t *thread) {
assertNotNull(thread, "Thread cannot be NULL.");
threadStartRequest(thread);
threadMutexLock(&thread->stateMutex);
while(thread->state == THREAD_STATE_STARTING) {
threadMutexWaitLock(&thread->stateMutex);
}
threadMutexUnlock(&thread->stateMutex);
}
void threadStop(thread_t *thread) {
assertNotNull(thread, "Thread cannot be NULL.");
threadStopRequest(thread);
threadMutexLock(&thread->stateMutex);
while(thread->state != THREAD_STATE_STOPPED) {
threadMutexWaitLock(&thread->stateMutex);
}
threadMutexUnlock(&thread->stateMutex);
}
bool_t threadShouldStop(thread_t *thread) {
bool_t state;
assertNotNull(thread, "Thread cannot be NULL.");
threadMutexLock(&thread->stateMutex);
switch(thread->state) {
case THREAD_STATE_STOPPED:
case THREAD_STATE_STOP_REQUESTED:
state = true;
break;
default:
state = false;
break;
}
threadMutexUnlock(&thread->stateMutex);
return state;
}
#ifdef DUSK_THREAD_PTHREAD
void * threadHandler(thread_t *thread) {
assertNotNull(thread, "Thread cannot be NULL.");
threadMutexLock(&thread->stateMutex);
thread->state = THREAD_STATE_RUNNING;
threadMutexSignal(&thread->stateMutex);
threadMutexUnlock(&thread->stateMutex);
// Send to callback.
thread->callback(thread);
threadMutexLock(&thread->stateMutex);
thread->state = THREAD_STATE_STOPPED;
thread->threadId = 0;
threadMutexSignal(&thread->stateMutex);
threadMutexUnlock(&thread->stateMutex);
return NULL;
}
#endif
+97
View File
@@ -0,0 +1,97 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "thread/threadlocal.h"
#include "thread/threadmutex.h"
typedef struct thread_s thread_t;
typedef void (*threadcallback_t)(thread_t *thread);
typedef enum {
// Thread is definitely not running.
THREAD_STATE_STOPPED,
// Start has been requested, it may even be started by the time you
// see this value.
THREAD_STATE_STARTING,
// Thread has definitely started and is running.
THREAD_STATE_RUNNING,
// Something has requested the thread to stop, but it may not have
// stopped yet.
THREAD_STATE_STOP_REQUESTED,
} threadstate_t;
typedef struct thread_s {
threadstate_t state;
threadmutex_t stateMutex;
threadcallback_t callback;
void *data;
#ifdef DUSK_THREAD_PTHREAD
pthread_t threadId;
#endif
} thread_t;
/**
* Initializes a thread structure.
*
* @param thread Pointer to the thread structure to initialize.
* @param callback The callback function to be called when the thread runs.
*/
void threadInit(thread_t *thread, const threadcallback_t callback);
/**
* Starts the thread, does not wait for it to finish starting.
*
* @param thread Pointer to the thread structure to start.
*/
void threadStartRequest(thread_t *thread);
/**
* Requests the thread to stop, does not block the calling thread.
*
* @param thread Pointer to the thread structure to stop.
*/
void threadStopRequest(thread_t *thread);
/**
* Starts the thread, blocking until it has started. Does this as efficiently as
* possible depending on the threading implementation. Note that it is possible
* for the thread to fully COMPLETE before this function returns.
*
* @param thread Pointer to the thread structure to start.
*/
void threadStart(thread_t *thread);
/**
* Stops the thread, blocking until it has stopped. Does this as efficiently as
* possible depending on the threading implementation. Note that it is possible
* for the thread to fully COMPLETE well before this function returns.
*
* @param thread Pointer to the thread structure to stop.
*/
void threadStop(thread_t *thread);
/**
* Checks if the thread should stop, based on its state.
*
* @param thread Pointer to the thread structure to check.
* @return true if the thread should stop, false otherwise.
*/
bool_t threadShouldStop(thread_t *thread);
#ifdef DUSK_THREAD_PTHREAD
/**
* Handles the thread's lifecycle for pthreads.
* @param thread Pointer to the thread structure to handle.
*/
void * threadHandler(thread_t *thread);
#endif
+17
View File
@@ -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"
#ifdef DUSK_THREAD_PTHREAD
#define THREAD_LOCAL __thread
#endif
#ifndef THREAD_LOCAL
#error "No threading implementation found, cannot define THREAD_LOCAL."
#endif
+52
View File
@@ -0,0 +1,52 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "threadmutex.h"
void threadMutexInit(threadmutex_t *lock) {
#ifdef DUSK_THREAD_PTHREAD
pthread_mutex_init(&lock->mutex, NULL);
pthread_cond_init(&lock->cond, NULL);
#endif
}
void threadMutexLock(threadmutex_t *lock) {
#ifdef DUSK_THREAD_PTHREAD
pthread_mutex_lock(&lock->mutex);
#endif
}
bool_t threadMutexTryLock(threadmutex_t *lock) {
#ifdef DUSK_THREAD_PTHREAD
return pthread_mutex_trylock(&lock->mutex) == 0;
#endif
}
void threadMutexUnlock(threadmutex_t *lock) {
#ifdef DUSK_THREAD_PTHREAD
pthread_mutex_unlock(&lock->mutex);
#endif
}
void threadMutexWaitLock(threadmutex_t *lock) {
#ifdef DUSK_THREAD_PTHREAD
pthread_cond_wait(&lock->cond, &lock->mutex);
#endif
}
void threadMutexSignal(threadmutex_t *lock) {
#ifdef DUSK_THREAD_PTHREAD
pthread_cond_signal(&lock->cond);
#endif
}
void threadMutexDispose(threadmutex_t *lock) {
#ifdef DUSK_THREAD_PTHREAD
pthread_mutex_destroy(&lock->mutex);
pthread_cond_destroy(&lock->cond);
#endif
}
+73
View File
@@ -0,0 +1,73 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#ifdef DUSK_THREAD_PTHREAD
#include <pthread.h>
#else
#error "At least one threading implementation must be defined."
#endif
typedef struct threadlock_t {
#ifdef DUSK_THREAD_PTHREAD
pthread_mutex_t mutex;
pthread_cond_t cond;
#endif
} threadmutex_t;
/**
* Initializes a thread mutex.
*
* @param lock Pointer to the thread mutex structure to initialize.
*/
void threadMutexInit(threadmutex_t *lock);
/**
* Locks the thread mutex.
*
* @param lock Pointer to the thread mutex structure to lock.
*/
void threadMutexLock(threadmutex_t *lock);
/**
* Attempts to lock the thread mutex without blocking.
*
* @param lock Pointer to the thread mutex structure to try locking.
* @return true if the lock was acquired, false otherwise.
*/
bool_t threadMutexTryLock(threadmutex_t *lock);
/**
* Unlocks the thread mutex.
*
* @param lock Pointer to the thread mutex structure to unlock.
*/
void threadMutexUnlock(threadmutex_t *lock);
/**
* Releases the thread mutex and waits for it to be locked again.
*
* @param lock Pointer to the thread mutex structure to wait on.
*/
void threadMutexWaitLock(threadmutex_t *lock);
/**
* Signals the thread mutex, allowing a waiting thread to proceed as soon as
* this mutex is unlocked.
*
* @param lock Pointer to the thread mutex structure to signal.
*/
void threadMutexSignal(threadmutex_t *lock);
/**
* Disposes of the thread mutex.
*
* @param lock Pointer to the thread mutex structure to dispose.
*/
void threadMutexDispose(threadmutex_t *lock);
+11
View File
@@ -0,0 +1,11 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
time.c
timeepoch.c
)
+61
View File
@@ -0,0 +1,61 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "time.h"
#include "util/memory.h"
#include "assert/assert.h"
#include "console/console.h"
dusktime_t TIME;
void timeInit(void) {
memoryZero(&TIME, sizeof(TIME));
// Set these to something non-zero.
TIME.time = DUSK_TIME_STEP;
TIME.delta = DUSK_TIME_STEP;
#ifdef DUSK_TIME_DYNAMIC
TIME.dynamicTime = DUSK_TIME_STEP;
TIME.dynamicDelta = DUSK_TIME_STEP;
TIME.dynamicUpdate = false;
TIME.lastNonDynamic = TIME.dynamicTime;
#endif
}
void timeUpdate(void) {
#ifdef DUSK_TIME_DYNAMIC
timeTickPlatform();
TIME.dynamicDelta = timeGetDeltaPlatform();
TIME.dynamicTime += TIME.dynamicDelta;
TIME.dynamicUpdate = true;
assertTrue(TIME.dynamicDelta >= 0.0f, "Time delta is negative");
// Is within 1ms of a full step?
if(TIME.dynamicTime - TIME.lastNonDynamic >= DUSK_TIME_STEP * 0.999f) {
TIME.dynamicUpdate = false;
TIME.lastNonDynamic = TIME.dynamicTime;
TIME.delta = DUSK_TIME_STEP;
TIME.time += DUSK_TIME_STEP;
}
#else
TIME.delta = DUSK_TIME_STEP;
TIME.time += DUSK_TIME_STEP;
#endif
// Print time in UTC style string
dusktimeepoch_t epoch = timeGetEpoch();
char_t buffer[256];
timeEpochFormat(epoch, "%Y-%m-%d %H:%M:%S", buffer, sizeof(buffer));
// consolePrint("Real Time: %s", buffer);
}
dusktimeepoch_t timeGetEpoch(void) {
return timeEpochInit(timeGetRealPlatform(), timeGetRealTimeZonePlatform());
}
+72
View File
@@ -0,0 +1,72 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "timeepoch.h"
#include "time/timeplatform.h"
#ifndef DUSK_TIME_STEP
#define DUSK_TIME_STEP (16.0f / 1000.0f)
#endif
/**
* Converts a fixed-step tick count to a duration in seconds.
*
* @param ticks The number of ticks to convert.
* @return The duration in seconds.
*/
#define TIME_TICKS_TO_TIME(ticks) ((float_t)(ticks) * DUSK_TIME_STEP)
/**
* Converts a duration in seconds to a fixed-step tick count.
*
* @param seconds The duration in seconds to convert.
* @return The number of ticks.
*/
#define TIME_TIME_TO_TICKS(seconds) ((int32_t)((seconds) / DUSK_TIME_STEP))
#ifdef DUSK_TIME_DYNAMIC
#ifndef timeTickPlatform
#error "DUSK_TIME_DYNAMIC needs tick method defined"
#endif
#ifndef timeGetDeltaPlatform
#error "DUSK_TIME_DYNAMIC needs delta method defined"
#endif
#endif
typedef struct {
float_t delta;
float_t time;
#ifdef DUSK_TIME_DYNAMIC
float_t lastNonDynamic;
bool_t dynamicUpdate;
float_t dynamicDelta;
float_t dynamicTime;
#endif
} dusktime_t;
extern dusktime_t TIME;
/**
* Initializes the time system.
*/
void timeInit(void);
/**
* Updates the time system
*/
void timeUpdate(void);
/**
* Returns the current epoch time, in seconds since January 1, 1970, with the
* current time zone offset applied.
*
* @return The current epoch time.
*/
dusktimeepoch_t timeGetEpoch(void);
+198
View File
@@ -0,0 +1,198 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "time.h"
#include "assert/assert.h"
dusktimeepoch_t timeEpochInit(const double_t time, const double_t timeZone) {
dusktimeepoch_t epoch;
epoch.time = time;
epoch.timeZone = timeZone;
epoch.offsetTime = time + timeZone;
return epoch;
}
dusktimeepoch_t timeEpochSwitchTimeZone(
const dusktimeepoch_t epoch,
const double_t offset
) {
return timeEpochInit(
epoch.time + offset - epoch.timeZone,
offset
);
}
int32_t timeEpochCompare(const dusktimeepoch_t a, const dusktimeepoch_t b) {
if(a.time < b.time) return -1;
if(a.time > b.time) return 1;
return 0;
}
int32_t timeEpochGetHours(const dusktimeepoch_t epoch) {
return ((int32_t)(epoch.offsetTime / 3600.0)) % 24;
}
int32_t timeEpochGetMinutes(const dusktimeepoch_t epoch) {
return ((int32_t)(epoch.offsetTime / 60.0)) % 60;
}
int32_t timeEpochGetSeconds(const dusktimeepoch_t epoch) {
return ((int32_t)(epoch.offsetTime)) % 60;
}
int32_t timeEpochGetDayOfMonth(const dusktimeepoch_t epoch) {
int64_t days = (int64_t)(epoch.offsetTime / 86400.0);
int32_t year = TIME_EPOCH_START_YEAR;
while(1) {
int32_t daysInYear = TIME_EPOCH_DAYS_IN_YEAR(year);
if(days < daysInYear) break;
days -= daysInYear;
year++;
}
int32_t month = timeEpochGetMonth(epoch);
for(int32_t i = 0; i < month - 1; i++) {
int32_t daysInMonth = TIME_EPOCH_DAYS_IN_MONTH[i];
if(i == 1 && timeEpochIsLeapYear(year)) {
daysInMonth = 29;
}
days -= daysInMonth;
}
return (int32_t)days + 1;
}
int32_t timeEpochGetMonth(const dusktimeepoch_t epoch) {
int64_t days = (int64_t)(epoch.offsetTime / 86400.0);
int32_t year = TIME_EPOCH_START_YEAR;
while(1) {
int32_t daysInYear = TIME_EPOCH_DAYS_IN_YEAR(year);
if(days < daysInYear) break;
days -= daysInYear;
year++;
}
for(int32_t month = 0; month < 12; month++) {
int32_t daysInMonth = TIME_EPOCH_DAYS_IN_MONTH[month];
if(month == 1 && timeEpochIsLeapYear(year)) daysInMonth = 29;
if(days < daysInMonth) return month + 1;
days -= daysInMonth;
}
return -1;
}
int32_t timeEpochGetYear(const dusktimeepoch_t epoch) {
int64_t days = (int64_t)(epoch.offsetTime / 86400.0);
int32_t year = TIME_EPOCH_START_YEAR;
while(1) {
int32_t daysInYear = TIME_EPOCH_DAYS_IN_YEAR(year);
if(days < daysInYear) return year;
days -= daysInYear;
year++;
}
}
bool_t timeEpochIsLeapYear(const int32_t year) {
return (
((year % 4 == 0) && (year % 100 != 0)) ||
(year % 400 == 0)
);
}
size_t timeEpochFormat(
const dusktimeepoch_t epoch,
const char_t* format,
char_t* buffer,
const size_t bufferSize
) {
assertNotNull(format, "Format string cannot be null");
assertNotNull(buffer, "Buffer cannot be null");
assertTrue(bufferSize > 0, "Buffer size must be greater than 0");
size_t bufferIndex = 0;
for(size_t i = 0; format[i] != '\0'; i++) {
if(format[i] == '%') {
i++;
char_t formatSpecifier = format[i];
char_t tempBuffer[16];
size_t tempBufferIndex = 0;
switch(formatSpecifier) {
case 'Y':
tempBufferIndex = snprintf(
tempBuffer, sizeof(tempBuffer), "%04d",
timeEpochGetYear(epoch)
);
break;
case 'm':
tempBufferIndex = snprintf(
tempBuffer, sizeof(tempBuffer), "%02d",
timeEpochGetMonth(epoch)
);
break;
case 'd':
tempBufferIndex = snprintf(
tempBuffer, sizeof(tempBuffer), "%02d",
timeEpochGetDayOfMonth(epoch)
);
break;
case 'H':
tempBufferIndex = snprintf(
tempBuffer, sizeof(tempBuffer), "%02d",
timeEpochGetHours(epoch)
);
break;
case 'M':
tempBufferIndex = snprintf(
tempBuffer, sizeof(tempBuffer), "%02d",
timeEpochGetMinutes(epoch)
);
break;
case 'S':
tempBufferIndex = snprintf(
tempBuffer, sizeof(tempBuffer), "%02d",
timeEpochGetSeconds(epoch)
);
break;
default:
continue;
}
for(size_t j = 0; j < tempBufferIndex; j++) {
if(bufferIndex < bufferSize - 1) {
buffer[bufferIndex++] = tempBuffer[j];
} else {
break;
}
}
} else {
if(bufferIndex < bufferSize - 1) {
buffer[bufferIndex++] = format[i];
} else {
break;
}
}
}
buffer[bufferIndex] = '\0';
return bufferIndex;
}
+145
View File
@@ -0,0 +1,145 @@
/**
* 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 {
double_t time;
double_t timeZone;
double_t offsetTime;
} dusktimeepoch_t;
static const int32_t TIME_EPOCH_DAYS_IN_MONTH[] = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
#define TIME_EPOCH_START_YEAR 1970
#define TIME_EPOCH_DAYS_IN_YEAR_NON_LEAP 365
#define TIME_EPOCH_DAYS_IN_YEAR_LEAP 366
#define TIME_EPOCH_DAYS_IN_YEAR(year) (\
timeEpochIsLeapYear(year) ? \
TIME_EPOCH_DAYS_IN_YEAR_LEAP : \
TIME_EPOCH_DAYS_IN_YEAR_NON_LEAP\
)
/**
* Initializes an epoch time with the given time and time zone.
*
* @param time The time in seconds since January 1, 1970.
* @param timeZone The time zone offset in seconds from UTC.
* @return The initialized epoch time.
*/
dusktimeepoch_t timeEpochInit(const double_t time, const double_t timeZone);
/**
* Changes the time from one timezone to another.
*
* @param epoch The epoch time to change the timezone of.
* @param offset The offset in seconds to change the timezone by.
* @return The epoch time with the timezone changed.
*/
dusktimeepoch_t timeEpochSwitchTimeZone(
const dusktimeepoch_t epoch,
const double_t offset
);
/**
* Compares two epoch times.
*
* @param a The first epoch time.
* @param b The second epoch time.
* @return A negative value if a < b, zero if a == b, a positive value if a > b.
*/
int32_t timeEpochCompare(const dusktimeepoch_t a, const dusktimeepoch_t b);
/**
* Returns the hours component of the epoch time, in the range [0, 23]. This is
* in the timezone of the epoch.
*
* @param epoch The epoch time to get the hours component of.
* @return The hours component of the epoch time.
*/
int32_t timeEpochGetHours(const dusktimeepoch_t epoch);
/**
* Returns the minutes component of the epoch time, in the range [0, 59]. This
* is in the timezone of the epoch.
*
* @param epoch The epoch time to get the minutes component of.
* @return The minutes component of the epoch time.
*/
int32_t timeEpochGetMinutes(const dusktimeepoch_t epoch);
/**
* Returns the seconds component of the epoch time, in the range [0, 59]. This
* is in the timezone of the epoch.
*
* @param epoch The epoch time to get the seconds component of.
* @return The seconds component of the epoch time.
*/
int32_t timeEpochGetSeconds(const dusktimeepoch_t epoch);
/**
* Returns the day of the month component of the epoch time, in the range of [
* 0, 30]. This is in the timezone of the epoch. e.g. Jan 1st is 0, Jan 31st is
* 30.
*
* @param epoch The epoch time to get the day of the month component of.
* @return The day of the month component of the epoch time.
*/
int32_t timeEpochGetDayOfMonth(const dusktimeepoch_t epoch);
/**
* Returns the month component of the epoch time, in the range of [0, 11]. This
* is in the timezone of the epoch. e.g. Jan is 0, Dec is 11.
*
* @param epoch The epoch time to get the month component of.
* @return The month component of the epoch time.
*/
int32_t timeEpochGetMonth(const dusktimeepoch_t epoch);
/**
* Returns the year component of the epoch time. This is in the timezone of the
* epoch.
*
* @param epoch The epoch time to get the year component of.
* @return The year component of the epoch time.
*/
int32_t timeEpochGetYear(const dusktimeepoch_t epoch);
/**
* Returns whether the given year is a leap year.
*
* @param year The year to check if it is a leap year.
* @return Whether the given year is a leap year.
*/
bool_t timeEpochIsLeapYear(const int32_t year);
/**
* Buffers the epoch time into a string according to the given format.
*
* Format parameters:
* %Y - Year with century as a decimal number.
* %m - Month as a decimal number (01-12).
* %d - Day of the month as a decimal number (01-31).
* %H - Hour (24-hour clock) as a decimal number (00-23).
* %M - Minute as a decimal number (00-59).
* %S - Second as a decimal number (00-59).
*
* @param epoch The epoch time to format.
* @param format The format string to format the epoch time with.
* @param buffer The buffer to write the formatted string to.
* @param bufferSize The size of the buffer to write the formatted string to.
* @return The number of characters written to the buffer, excluding the null
*/
size_t timeEpochFormat(
const dusktimeepoch_t epoch,
const char_t* format,
char_t* buffer,
const size_t bufferSize
);
+18
View File
@@ -0,0 +1,18 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
array.c
crypt.c
endian.c
memory.c
random.c
string.c
math.c
sort.c
ref.c
)
+39
View File
@@ -0,0 +1,39 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "array.h"
#include "assert/assert.h"
#include "util/memory.h"
void arrayReverse(
void *array,
const size_t count,
const size_t size
) {
assertNotNull(array, "Cannot reverse NULL memory.");
assertTrue(size > 0, "Element size must be greater than zero.");
if(count == 0) return;
// For half the array length, swap elements
uint8_t *start = (uint8_t*)array;
uint8_t *end = start + (count - 1) * size;
uint8_t *temp = (uint8_t*)memoryAllocate(size);
// Floorf
size_t i;
for(i = 0; i < count / 2; i++) {
// Swap start and end
memoryCopy(temp, start, size);
memoryCopy(start, end, size);
memoryCopy(end, temp, size);
start += size;
end -= size;
}
memoryFree(temp);
}
+22
View File
@@ -0,0 +1,22 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
/**
* Reverses the order of elements in an array.
*
* @param array The array to reverse.
* @param count The number of elements in the array.
* @param size The size of each element (the stride).
*/
void arrayReverse(
void *array,
const size_t count,
const size_t size
);
+36
View File
@@ -0,0 +1,36 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "util/crypt.h"
void cryptCRC32Step(uint32_t *crc, const uint8_t byte) {
*crc ^= byte;
for(int j = 0; j < 8; j++) {
*crc = (*crc >> 1) ^ (0xEDB88320u & -(*crc & 1u));
}
}
uint32_t cryptCRC32Begin(void) {
return 0xFFFFFFFF;
}
void cryptCRC32Update(uint32_t *crc, const void *data, const size_t size) {
const uint8_t *bytes = (const uint8_t *)data;
for(size_t i = 0; i < size; i++) {
cryptCRC32Step(crc, bytes[i]);
}
}
uint32_t cryptCRC32End(const uint32_t crc) {
return ~crc;
}
uint32_t cryptCRC32(const void *data, const size_t size) {
uint32_t crc = cryptCRC32Begin();
cryptCRC32Update(&crc, data, size);
return cryptCRC32End(crc);
}
+50
View File
@@ -0,0 +1,50 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
/**
* Computes a CRC32 checksum over a block of data.
*
* @param data Pointer to the data to checksum.
* @param size Number of bytes to checksum.
* @return The CRC32 checksum.
*/
uint32_t cryptCRC32(const void *data, const size_t size);
/**
* Returns the initial CRC32 accumulator value.
*
* @return The initial accumulator.
*/
uint32_t cryptCRC32Begin(void);
/**
* Feeds a single byte into a running CRC32 accumulator.
*
* @param crc Pointer to the current accumulator (updated in place).
* @param byte The byte to feed in.
*/
void cryptCRC32Step(uint32_t *crc, const uint8_t byte);
/**
* Feeds bytes into a running CRC32 accumulator.
*
* @param crc Pointer to the current accumulator (updated in place).
* @param data Pointer to the data to feed in.
* @param size Number of bytes to process.
*/
void cryptCRC32Update(uint32_t *crc, const void *data, const size_t size);
/**
* Finalizes a running CRC32 accumulator and returns the checksum.
*
* @param crc The current accumulator value.
* @return The final CRC32 checksum.
*/
uint32_t cryptCRC32End(const uint32_t crc);
+100
View File
@@ -0,0 +1,100 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "util/endian.h"
#include "util/memory.h"
bool_t isHostLittleEndian(void) {
#if defined(DUSK_PLATFORM_ENDIAN_LITTLE)
return true;
#elif defined(DUSK_PLATFORM_ENDIAN_BIG)
return false;
#else
uint32_t value = ENDIAN_MAGIC;
uint8_t *bytePtr = (uint8_t *)&value;
return bytePtr[0] == 0x04;
#endif
}
uint32_t endianLittleToHost32(uint32_t value) {
#if defined(DUSK_PLATFORM_ENDIAN_LITTLE)
return value;
#elif defined(DUSK_PLATFORM_ENDIAN_BIG)
// Perform conversion below
#else
if(isHostLittleEndian()) {
return value;
}
#endif
return (
((value & 0x000000FF) << 24) |
((value & 0x0000FF00) << 8) |
((value & 0x00FF0000) >> 8) |
((value & 0xFF000000) >> 24)
);
}
uint16_t endianLittleToHost16(uint16_t value) {
#if defined(DUSK_PLATFORM_ENDIAN_LITTLE)
return value;
#elif defined(DUSK_PLATFORM_ENDIAN_BIG)
// Perform conversion below
#else
if(isHostLittleEndian()) {
return value;
}
#endif
return (uint16_t)(((value & 0x00FF) << 8) |
((value & 0xFF00) >> 8));
}
uint64_t endianLittleToHost64(uint64_t value) {
#if defined(DUSK_PLATFORM_ENDIAN_LITTLE)
return value;
#elif defined(DUSK_PLATFORM_ENDIAN_BIG)
// Perform conversion below
#else
if(isHostLittleEndian()) {
return value;
}
#endif
return (
((value & 0x00000000000000FFULL) << 56) |
((value & 0x000000000000FF00ULL) << 40) |
((value & 0x0000000000FF0000ULL) << 24) |
((value & 0x00000000FF000000ULL) << 8) |
((value & 0x000000FF00000000ULL) >> 8) |
((value & 0x0000FF0000000000ULL) >> 24) |
((value & 0x00FF000000000000ULL) >> 40) |
((value & 0xFF00000000000000ULL) >> 56)
);
}
float_t endianLittleToHostFloat(float_t value) {
#if defined(DUSK_PLATFORM_ENDIAN_LITTLE)
return value;
#elif defined(DUSK_PLATFORM_ENDIAN_BIG)
// Perform conversion below
#else
if(isHostLittleEndian()) {
return value;
}
#endif
uint32_t temp;
float_t result;
memoryCopy(&temp, &value, sizeof(uint32_t));
temp = endianLittleToHost32(temp);
memoryCopy(&result, &temp, sizeof(uint32_t));
return result;
}
+53
View File
@@ -0,0 +1,53 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#if !defined(DUSK_PLATFORM_ENDIAN_LITTLE) && !defined(DUSK_PLATFORM_ENDIAN_BIG)
#warning "Platform does not define endianness, requiring runtime check."
#endif
static const uint32_t ENDIAN_MAGIC = 0x01020304;
/**
* Checks if the host system is little-endian.
*
* @return true if the host is little-endian, false otherwise.
*/
bool_t isHostLittleEndian(void);
/**
* Converts a 32-bit integer from little-endian to host byte order.
*
* @param value The little-endian value to convert.
* @return The value in host byte order.
*/
uint32_t endianLittleToHost32(uint32_t value);
/**
* Converts a 16-bit integer from little-endian to host byte order.
* @param value The little-endian value to convert.
* @return The value in host byte order.
*/
uint16_t endianLittleToHost16(uint16_t value);
/**
* Converts a 64-bit integer from little-endian to host byte order.
* @param value The little-endian value to convert.
* @return The value in host byte order.
*/
uint64_t endianLittleToHost64(uint64_t value);
/**
* Converts a float from little-endian to host byte order.
*
* @param value The little-endian value to convert.
* @return The value in host byte order.
*/
float_t endianLittleToHostFloat(float_t value);
+31
View File
@@ -0,0 +1,31 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "math.h"
uint32_t mathNextPowTwo(uint32_t value) {
if(value == 0) return 1; // Handle zero case
if(value >= 0x80000000) return 1; // Handle overflow case
value--;
value |= value >> 1;
value |= value >> 2;
value |= value >> 4;
value |= value >> 8;
value |= value >> 16;
return value + 1;
}
float_t mathModFloat(float_t x, float_t y) {
float_t result = fmodf(x, y);
if(result < 0) result += y;
return result;
}
float_t mathLerp(float_t a, float_t b, float_t t) {
return a + t * (b - a);
}
+84
View File
@@ -0,0 +1,84 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#define MATH_PI M_PI
/**
* Finds the next power of two greater than or equal to the given value.
*
* @param value The value to find the next power of two for.
* @return The next power of two greater than or equal to the value.
*/
uint32_t mathNextPowTwo(uint32_t value);
/**
* Returns the maximum of two values.
*
* @param a The first value.
* @param b The second value.
* @return The maximum of the two values.
*/
#define mathMax(a, b) ((a) > (b) ? (a) : (b))
/**
* Returns the minimum of two values.
*
* @param a The first value.
* @param b The second value.
* @return The minimum of the two values.
*/
#define mathMin(a, b) ((a) < (b) ? (a) : (b))
/**
* Clamps a value between a lower and upper bound.
*
* @param x The value to clamp.
* @param lower The lower bound.
* @param upper The upper bound.
* @return The clamped value.
*/
#define mathClamp(x, lower, upper) (mathMin(upper, mathMax(lower, x)))
/**
* Integer ceiling division: ceil(n / d) using only integer arithmetic.
*
* @param n The dividend.
* @param d The divisor.
* @return The ceiling of n divided by d.
*/
#define mathCeilDiv(n, d) (((n) + (d) - 1) / (d))
/**
* Returns the absolute value of a number.
*
* @param amt The number to get the absolute value of.
* @return The absolute value of the number.
*/
#define mathAbs(amt) ((amt) < 0 ? -(amt) : (amt))
/**
* Performs a modulus operation that always returns a non-negative result, even
* if the dividend is negative.
*
* @param x The dividend.
* @param y The divisor.
* @return The result of the modulus operation.
*/
float_t mathModFloat(float_t x, float_t y);
/**
* Linearly interpolates between two values.
*
* @param a The start value.
* @param b The end value.
* @param t The interpolation factor, between 0 and 1.
* @return The interpolated value.
*/
float_t mathLerp(float_t a, float_t b, float_t t);
+255
View File
@@ -0,0 +1,255 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "memory.h"
#include "assert/assert.h"
#include "util/math.h"
#include "log/log.h"
#ifdef DUSK_TRACK_MEMORY
// Fixed-size, pre-allocated table of live memoryAllocate() calls, keyed by
// pointer - deliberately never grows (backed by static storage, not
// malloc) so the tracker itself can never allocate, recurse or leak. Raise
// this if it's ever exhausted (see the assert in memoryAllocateTracked).
#define MEMORY_TRACK_MAX 16384
typedef struct {
void *ptr;
const char_t *file;
int_t line;
size_t size;
} memorytrackentry_t;
static memorytrackentry_t MEMORY_TRACK_ENTRIES[MEMORY_TRACK_MAX];
static size_t MEMORY_TRACK_COUNT = 0;
#endif
size_t memoryGetAllocatedCount(void) {
return MEMORY_POINTERS_IN_USE;
}
void memoryTrack(void *ptr) {
assertNotNull(ptr, "Cannot track NULL pointer.");
MEMORY_POINTERS_IN_USE++;
}
#ifdef DUSK_TRACK_MEMORY
void * memoryAllocateTracked(
const size_t size, const char_t *file, const int_t line
) {
assertTrue(size > 0, "Cannot allocate 0 bytes of memory.");
void *ptr = malloc(size);
assertNotNull(ptr, "Memory allocation failed.");
memoryTrack(ptr);
assertTrue(
MEMORY_TRACK_COUNT < MEMORY_TRACK_MAX,
"Memory tracking table is full - raise MEMORY_TRACK_MAX."
);
MEMORY_TRACK_ENTRIES[MEMORY_TRACK_COUNT++] = (memorytrackentry_t){
.ptr = ptr,
.file = file,
.line = line,
.size = size
};
return ptr;
}
#else
void * memoryAllocate(const size_t size) {
assertTrue(size > 0, "Cannot allocate 0 bytes of memory.");
void *ptr = malloc(size);
assertNotNull(ptr, "Memory allocation failed.");
memoryTrack(ptr);
return ptr;
}
#endif
void * memoryAlign(size_t alignment, size_t size) {
assertTrue(alignment > 0, "Alignment must be greater than 0.");
assertTrue(size > 0, "Cannot allocate 0 bytes of memory.");
void *ptr = memalign(alignment, size);
assertNotNull(ptr, "Aligned memory allocation failed.");
memoryTrack(ptr);
return ptr;
}
void memoryCheckUnfreed(void) {
size_t count = memoryGetAllocatedCount();
if(count == 0) {
logDebug("memoryCheckUnfreed: no unfreed allocations.\n");
return;
}
#ifdef DUSK_TRACK_MEMORY
logDebug("memoryCheckUnfreed: %zu unfreed allocation(s):\n", count);
size_t trackedBytes = 0;
for(size_t i = 0; i < MEMORY_TRACK_COUNT; i++) {
memorytrackentry_t *entry = &MEMORY_TRACK_ENTRIES[i];
logDebug(
" - %zu bytes allocated at %s:%d\n",
entry->size, entry->file, entry->line
);
trackedBytes += entry->size;
}
logDebug(" (%zu bytes across %zu tracked allocations)\n",
trackedBytes, MEMORY_TRACK_COUNT
);
// memoryAlign()/memoryTrack() callers aren't recorded into
// MEMORY_TRACK_ENTRIES yet, so the tracked count can fall short of the
// real total - call that out rather than silently under-reporting.
size_t untracked = count - MEMORY_TRACK_COUNT;
if(untracked > 0) {
logDebug(
" + %zu more unfreed allocation(s) with no call site recorded "
"(from memoryAlign()/memoryTrack())\n",
untracked
);
}
#else
logDebug(
"memoryCheckUnfreed: %zu unfreed allocation(s) - rebuild with "
"DUSK_TRACK_MEMORY=ON for call site details.\n",
count
);
#endif
}
void memoryFree(void *ptr) {
assertNotNull(ptr, "Cannot free NULL memory.");
#ifdef DUSK_TRACK_MEMORY
// Only pointers from memoryAllocate() are in here (memoryAlign/
// memoryTrack callers aren't tracked yet), so a miss here is expected,
// not an error - just leave the table alone.
for(size_t i = 0; i < MEMORY_TRACK_COUNT; i++) {
if(MEMORY_TRACK_ENTRIES[i].ptr == ptr) {
// Swap-remove - order doesn't matter for leak tracking.
MEMORY_TRACK_ENTRIES[i] = MEMORY_TRACK_ENTRIES[--MEMORY_TRACK_COUNT];
break;
}
}
#endif
free(ptr);
MEMORY_POINTERS_IN_USE--;
}
void memoryCopy(void *dest, const void *src, const size_t size) {
assertNotNull(dest, "Cannot copy to NULL memory.");
assertNotNull(src, "Cannot copy from NULL memory.");
assertTrue(size > 0, "Cannot copy 0 bytes of memory.");
assertTrue(dest != src, "Cannot copy memory to itself.");
// Check for overlapping regions
assertTrue(
((uint8_t*)dest + size <= (uint8_t*)src) ||
((uint8_t*)src + size <= (uint8_t*)dest),
"Source and destination memory regions overlap."
);
memcpy(dest, src, size);
}
void memorySet(void *dest, const uint8_t value, const size_t size) {
assertNotNull(dest, "Cannot set NULL memory.");
assertTrue(size > 0, "Cannot set 0 bytes of memory.");
memset(dest, value, size);
}
void memoryZero(void *dest, const size_t size) {
memorySet(dest, 0, size);
}
void memoryCopyRangeSafe(
void *dest,
const void *start,
const void *end,
const size_t sizeMax
) {
assertFalse(start == end, "Start and end pointers are the same.");
assertTrue(end > start, "End pointer is not after start pointer.");
size_t copy = (size_t)end - (size_t)start;
assertTrue(copy <= sizeMax, "Size of memory to copy is too large.");
memoryCopy(dest, start, copy);
}
void memoryMove(void *dest, const void *src, const size_t size) {
assertNotNull(dest, "Cannot move to NULL memory.");
assertNotNull(src, "Cannot move from NULL memory.");
assertTrue(size > 0, "Cannot move 0 bytes of memory.");
assertTrue(dest != src, "Cannot move memory to itself.");
memmove(dest, src, size);
}
int_t memoryCompare(
const void *a,
const void *b,
const size_t size
) {
assertNotNull(a, "Cannot compare NULL memory.");
assertNotNull(b, "Cannot compare NULL memory.");
assertTrue(size >= 0, "Cannot compare negative bytes of memory.");
if(size == 0) return 0; // Comparing 0 bytes is always equal
return memcmp(a, b, size);
}
void memoryReallocate(void **ptr, const size_t size) {
assertNotNull(ptr, "Cannot reallocate NULL pointer.");
assertTrue(size > 0, "Cannot reallocate to 0 bytes of memory.");
void *newPointer = memoryAllocate(size);
assertNotNull(newPointer, "Memory reallocation failed.");
memoryFree(*ptr);
*ptr = newPointer;
}
void memoryCopyInterleaved(
void *dest,
const size_t destStride,
const void *src,
const size_t srcStride,
const size_t elementSize,
const size_t count
) {
assertNotNull(dest, "Cannot copy to NULL memory.");
assertNotNull(src, "Cannot copy from NULL memory.");
assertTrue(elementSize > 0, "Element size must be > 0.");
assertTrue(destStride >= elementSize, "Dest stride must be >= element size.");
assertTrue(srcStride >= elementSize, "Src stride must be >= element size.");
uint8_t *d = (uint8_t *)dest;
const uint8_t *s = (const uint8_t *)src;
for(size_t i = 0; i < count; i++) {
memoryCopy(d, s, elementSize);
d += destStride;
s += srcStride;
}
}
void memoryResize(void **ptr, const size_t oldSize, const size_t newSize) {
assertNotNull(ptr, "Cannot resize NULL pointer.");
assertTrue(newSize > 0, "Cannot resize to 0 bytes of memory.");
assertTrue(oldSize > 0, "Old size cannot be 0 bytes.");
if(newSize == oldSize) return;
void *newPointer = memoryAllocate(newSize);
assertNotNull(newPointer, "Memory resizing failed.");
memoryCopy(newPointer, *ptr, mathMin(oldSize, newSize));
memoryFree(*ptr);
*ptr = newPointer;
}
+191
View File
@@ -0,0 +1,191 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
static size_t MEMORY_POINTERS_IN_USE = 0;
/**
* Gets the number of currently allocated memory blocks.
*
* @return The number of allocated memory blocks.
*/
size_t memoryGetAllocatedCount(void);
/**
* Track a pointer that was malloc'd outside of the dusk engine.
*
* @param ptr The pointer to track.
*/
void memoryTrack(void *ptr);
/**
* Reports (via logDebug) whether any memoryAllocate()-family allocation is
* still outstanding - intended to be called once, near shutdown, after
* every subsystem has had a chance to dispose (see engineDispose). With
* DUSK_TRACK_MEMORY defined, prints exactly where (file:line) and how many
* bytes each still-unfreed allocation came from; without it, there's
* nowhere to report a call site from, so it only prints how many blocks
* remain outstanding.
*/
void memoryCheckUnfreed(void);
#ifdef DUSK_TRACK_MEMORY
/**
* Allocates memory, recording the call site (file/line) into a small
* fixed-size tracking table so a block still allocated at shutdown (see
* engineDispose's memoryGetAllocatedCount() check) can be traced back to
* where it came from. memoryFree removes the matching entry again once the
* pointer is freed. Only called via the memoryAllocate(size) macro below
* when DUSK_TRACK_MEMORY is defined - callers keep writing
* memoryAllocate(size) exactly as without it.
*
* @param size The size of the memory to allocate.
* @param file Call site file name (__FILE__).
* @param line Call site line number (__LINE__).
* @return The allocated memory.
*/
void * memoryAllocateTracked(
const size_t size, const char_t *file, const int_t line
);
#define memoryAllocate(size) \
memoryAllocateTracked((size), __FILE__, __LINE__)
#else
/**
* Allocates memory.
*
* @param size The size of the memory to allocate.
* @return The allocated memory.
*/
void * memoryAllocate(const size_t size);
#endif
/**
* Allocates aligned memory.
*
* @param alignment The alignment of the memory to allocate.
* @param size The size of the memory to allocate.
* @return The allocated memory.
*/
void * memoryAlign(size_t alignment, size_t size);
/**
* Frees memory.
*
* @param ptr The memory to free.
*/
void memoryFree(void *ptr);
/**
* Copies memory.
*
* @param dest The destination to copy to.
* @param src The source to copy from.
* @param size The size of the memory to copy.
*/
void memoryCopy(void *dest, const void *src, const size_t size);
/**
* Sets memory.
*
* @param dest The destination to set.
* @param value The value to set.
* @param size The size of the memory to set.
*/
void memorySet(void *dest, const uint8_t value, const size_t size);
/**
* Zeroes memory.
*
* @param dest The destination to zero.
* @param size The size of the memory to zero.
*/
void memoryZero(void *dest, const size_t size);
/**
* Copies memory, ensuring that the memory range is as expected, typically this
* is done if you're trying to reshape data in a buffer. Extremely useful in
* copying data to a shader buffer.
*
* @param dest The destination to copy to.
* @param start The start of the source to copy from.
* @param end The end of the source to copy from.
* @param sizeMax The maximum size of the memory to copy.
*/
void memoryCopyRangeSafe(
void *dest,
const void *start,
const void *end,
const size_t sizeMax
);
/**
* Moves memory.
*
* @param dest The destination to move to.
* @param src The source to move from.
* @param size The size of the memory to move.
*/
void memoryMove(void *dest, const void *src, const size_t size);
/**
* Compares memory.
*
* @param a The first memory to compare.
* @param b The second memory to compare.
* @param size The size of the memory to compare.
* @return 0 if the memory is equal, < 0 if a < b, > 0 if a > b.
*/
int_t memoryCompare(
const void *a,
const void *b,
const size_t size
);
/**
* Reallocates memory.
*
* @param ptr The pointer to the memory to reallocate.
* @param size The new size of the memory.
*/
void memoryReallocate(void **ptr, const size_t size);
/**
* Reallocates memory, but copies existing data to the new memory.
*
* @param ptr The pointer to the memory to resize.
* @param oldSize The old size of the memory.
* @param newSize The new size of the memory.
*/
void memoryResize(void **ptr, const size_t oldSize, const size_t newSize);
/**
* Scatter-copies elements from a contiguous source array into a strided
* destination. For each of count elements, copies elementSize bytes from
* src[i * srcStride] into dest[i * destStride].
*
* @param dest Base destination pointer.
* @param destStride Byte stride between destination slots.
* @param src Base source pointer.
* @param srcStride Byte stride between source elements.
* @param elementSize Bytes to copy per element.
* @param count Number of elements to copy.
*/
void memoryCopyInterleaved(
void *dest,
const size_t destStride,
const void *src,
const size_t srcStride,
const size_t elementSize,
const size_t count
);
+17
View File
@@ -0,0 +1,17 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "random.h"
#include <stdlib.h>
float_t randomFloat(const float_t min, const float_t max) {
return min + ((float_t)rand() / (float_t)RAND_MAX) * (max - min);
}
int_t randomInt(const int_t min, const int_t max) {
return min + (rand() % (max - min));
}
+27
View File
@@ -0,0 +1,27 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
/**
* Returns a random float between min (inclusive) and max (exclusive).
*
* @param min Lower bound.
* @param max Upper bound.
* @returns A random float in [min, max).
*/
float_t randomFloat(const float_t min, const float_t max);
/**
* Returns a random integer between min (inclusive) and max (exclusive).
*
* @param min Lower bound.
* @param max Upper bound.
* @returns A random integer in [min, max).
*/
int_t randomInt(const int_t min, const int_t max);
+52
View File
@@ -0,0 +1,52 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "ref.h"
#include "memory.h"
#include "assert/assert.h"
void refInit(
ref_t *ref,
void *data,
refcallback_t onLock,
refcallback_t onUnlock,
refcallback_t onAllUnlocked
) {
assertNotNull(ref, "Ref cannot be NULL.");
memoryZero(ref, sizeof(ref_t));
ref->count = 0;
ref->data = data;
ref->onLock = onLock;
ref->onUnlock = onUnlock;
ref->onAllUnlocked = onAllUnlocked;
}
void refLock(ref_t *ref) {
assertNotNull(ref, "Ref cannot be NULL.");
assertTrue(ref->count >= 0, "Cannot lock a ref with negative count.");
ref->count++;
if(ref->onLock != NULL) ref->onLock(ref);
}
bool_t refUnlock(ref_t *ref) {
assertNotNull(ref, "Ref cannot be NULL.");
assertTrue(ref->count >= 0, "Cannot unlock a ref with negative count.");
ref->count--;
if(ref->count > 0) {
if(ref->onUnlock != NULL) ref->onUnlock(ref);
return false;
}
void *data = ref->data;
refcallback_t onAllUnlocked = ref->onAllUnlocked;
memoryZero(ref, sizeof(ref_t));
ref->data = data;
if(onAllUnlocked != NULL) onAllUnlocked(ref);
return true;
}
+55
View File
@@ -0,0 +1,55 @@
/**
* 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 ref_s ref_t;
typedef void (*refcallback_t)(ref_t *ref);
typedef struct ref_s {
uint32_t count;
void *data;
refcallback_t onLock;
refcallback_t onUnlock;
refcallback_t onAllUnlocked;
} ref_t;
/**
* Initializes a ref with a count of 1.
*
* @param ref The ref to initialize.
* @param data Opaque context pointer accessible to all callbacks.
* @param onLock Called each time the ref is locked. May be NULL.
* @param onUnlock Called each time the ref is unlocked (count still > 0).
* May be NULL.
* @param onAllUnlocked Called when the count reaches zero. Responsible for
* any cleanup. May be NULL.
*/
void refInit(
ref_t *ref,
void *data,
refcallback_t onLock,
refcallback_t onUnlock,
refcallback_t onAllUnlocked
);
/**
* Increments the lock count.
*
* @param ref The ref to lock.
*/
void refLock(ref_t *ref);
/**
* Decrements the lock count. When it reaches zero onAllUnlocked is called
* (if set) and the ref struct is zeroed.
*
* @param ref The ref to unlock.
* @return true if the count reached zero.
*/
bool_t refUnlock(ref_t *ref);
+71
View File
@@ -0,0 +1,71 @@
#include <stdlib.h>
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "sort.h"
#include "assert/assert.h"
#include "util/memory.h"
void sortBubble(
void *array,
const size_t count,
const size_t size,
const sortcompare_t compare
) {
assertNotNull(array, "Cannot sort NULL memory.");
assertTrue(count > 0, "Cannot sort 0 elements.");
assertTrue(size > 0, "Element size must be greater than zero.");
assertNotNull(compare, "Compare function cannot be NULL.");
// Create variables
uint8_t *temp = (uint8_t*)memoryAllocate(size);
bool_t swapped;
uint8_t *a, *b;
size_t i;
do {
swapped = false;
for(i = 1; i < count; i++) {
a = array + (i - 1) * size;
b = array + i * size;
if(compare(a, b) > 0) {
// Swap
memoryCopy(temp, a, size);
memoryCopy(a, b, size);
memoryCopy(b, temp, size);
swapped = true;
}
}
} while(swapped);
memoryFree(temp);
}
void sortQuick(
void *array,
const size_t count,
const size_t size,
const sortcompare_t compare
) {
assertNotNull(array, "Cannot sort NULL memory.");
assertTrue(count > 0, "Cannot sort 0 elements.");
assertTrue(size > 0, "Element size must be greater than zero.");
assertNotNull(compare, "Compare function cannot be NULL.");
// qsort expects a comparator returning int, which matches sortcompare_t
qsort(array, count, size, compare);
}
int sortArrayU8Compare(const void *a, const void *b) {
uint8_t valA = *(const uint8_t*)a;
uint8_t valB = *(const uint8_t*)b;
return (valA > valB) - (valA < valB);
}
void sortArrayU8(uint8_t *array, const size_t count) {
return sort((void*)array, count, sizeof(uint8_t), sortArrayU8Compare);
}
+65
View File
@@ -0,0 +1,65 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef int_t (*sortcompare_t)(const void*, const void*);
/**
* Sorts an array using the bubble sort algorithm.
*
* @param array The array to sort.
* @param count The number of elements in the array.
* @param size The size of each element.
* @param compare The comparison function.
*/
void sortBubble(
void *array,
const size_t count,
const size_t size,
const sortcompare_t compare
);
/**
* Sorts an array using the quicksort algorithm.
*
* @param array The array to sort.
* @param count The number of elements in the array.
* @param size The size of each element.
* @param compare The comparison function.
*/
void sortQuick(
void *array,
const size_t count,
const size_t size,
const sortcompare_t compare
);
/**
* Preferred sort function.
*
* @see sortQuick
*/
#define sort sortQuick
/**
* Comparison function for uint8_t arrays.
*
* @param a First element.
* @param b Second element.
* @return Negative if a < b, zero if a == b, positive if a > b.
*/
int sortArrayU8Compare(const void *a, const void *b);
/**
* Sorts an array of uint8_t in ascending order.
*
* @param array The array to sort.
* @param count The number of elements in the array.
*/
void sortArrayU8(uint8_t *array, const size_t count);
+248
View File
@@ -0,0 +1,248 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "string.h"
#include "assert/assert.h"
#include "util/memory.h"
bool_t stringIsWhitespace(const char_t c) {
return isspace(c);
}
void stringCopy(char_t *dest, const char_t *src, const size_t destSize) {
assertNotNull(dest, "dest must not be NULL");
assertNotNull(src, "src must not be NULL");
assertTrue(destSize > 0, "destSize must be greater than 0");
assertStrLenMax(src, destSize, "src is too long");
assertTrue(dest != src, "Cannot copy string to itself.");
memoryCopy(dest, src, strlen(src) + 1);
}
int stringCompare(const char_t *str1, const char_t *str2) {
assertNotNull(str1, "str1 must not be NULL");
assertNotNull(str2, "str2 must not be NULL");
return strcmp(str1, str2);
}
bool_t stringEquals(const char_t *str1, const char_t *str2) {
return stringCompare(str1, str2) == 0;
}
int stringCompareInsensitive(const char_t *str1, const char_t *str2) {
assertNotNull(str1, "str1 must not be NULL");
assertNotNull(str2, "str2 must not be NULL");
return strcasecmp(str1, str2);
}
void stringTrim(char_t *str) {
assertNotNull(str, "str must not be NULL");
// Trim leading whitespace
char_t *start = str;
while(stringIsWhitespace(*start)) start++;
// Trim trailing whitespace
char_t *end = start + strlen(start) - 1;
while(end >= start && stringIsWhitespace(*end)) end--;
// Null-terminate the string
*(end + 1) = '\0';
// Move trimmed string to the original buffer
if(start != str) memmove(str, start, end - start + 2);
}
char_t * stringFindLastChar(const char_t *str, const char_t c) {
assertNotNull(str, "str must not be NULL");
char_t *last = NULL;
for(const char_t *p = str; *p != '\0'; p++) {
if(*p == c) {
last = (char_t *)p;
}
}
return last;
}
int32_t stringFormat(
char_t *dest,
const size_t destSize,
const char_t *format,
...
) {
va_list args;
va_start(args, format);
int32_t len = stringFormatVA(dest, destSize, format, args);
va_end(args);
return len;
}
int32_t stringFormatVA(
char_t *dest,
const size_t destSize,
const char_t *format,
va_list args
) {
assertNotNull(format, "format must not be NULL");
if(dest == NULL) {
int32_t ret = vsnprintf(NULL, 0, format, args);
assertTrue(ret >= 0, "Failed to format string.");
return ret;
}
assertTrue(destSize > 0, "destSize must be greater than 0");
int32_t ret = vsnprintf(dest, destSize, format, args);
assertTrue(ret >= 0, "Failed to format string.");
assertTrue(ret < destSize, "Formatted string is too long.");
return ret;
}
bool_t stringToI32(const char_t *str, int32_t *out) {
assertNotNull(str, "str must not be NULL");
assertNotNull(out, "out must not be NULL");
// Empty string check
if(str[0] == '\0') return false;
// Leading or trailing whitespace check
if(stringIsWhitespace(str[0])) return false;
if(stringIsWhitespace(str[strlen(str) - 1])) return false;
char_t *endptr;
errno = 0;
long int result = strtol(str, &endptr, 10);
if(errno != 0 || *endptr != '\0') {
return false;
}
// Within INT32 range
if(result < INT32_MIN || result > INT32_MAX) {
return false;
}
*out = (int32_t)result;
return true;
}
bool_t stringToI64(const char_t *str, int64_t *out) {
assertNotNull(str, "str must not be NULL");
assertNotNull(out, "out must not be NULL");
// Empty string check
if(str[0] == '\0') return false;
// Leading or trailing whitespace check
if(stringIsWhitespace(str[0])) return false;
if(stringIsWhitespace(str[strlen(str) - 1])) return false;
char_t *endptr;
errno = 0;
long long int result = strtoll(str, &endptr, 10);
if(errno != 0 || *endptr != '\0') {
return false;
}
*out = (int64_t)result;
return true;
}
bool_t stringToI16(const char_t *str, int16_t *out) {
assertNotNull(str, "str must not be NULL");
assertNotNull(out, "out must not be NULL");
// Empty string check
if(str[0] == '\0') return false;
// Leading or trailing whitespace check
if(stringIsWhitespace(str[0])) return false;
if(stringIsWhitespace(str[strlen(str) - 1])) return false;
char_t *endptr;
errno = 0;
long int result = strtol(str, &endptr, 10);
if(
errno != 0 ||
*endptr != '\0' ||
result < INT16_MIN ||
result > INT16_MAX
) {
return false;
}
*out = (int16_t)result;
return true;
}
bool_t stringToU16(const char_t *str, uint16_t *out) {
assertNotNull(str, "str must not be NULL");
assertNotNull(out, "out must not be NULL");
// Empty string check
if(str[0] == '\0') return false;
// Leading or trailing whitespace check
if(stringIsWhitespace(str[0])) return false;
if(stringIsWhitespace(str[strlen(str) - 1])) return false;
char_t *endptr;
errno = 0;
unsigned long int result = strtoul(str, &endptr, 10);
if(errno != 0 || *endptr != '\0' || result > UINT16_MAX) {
return false;
}
*out = (uint16_t)result;
return true;
}
bool_t stringToF32(const char_t *str, float_t *out) {
assertNotNull(str, "str must not be NULL");
assertNotNull(out, "out must not be NULL");
// Empty string check
if(str[0] == '\0') return false;
// Leading or trailing whitespace check
if(stringIsWhitespace(str[0])) return false;
if(stringIsWhitespace(str[strlen(str) - 1])) return false;
char_t *endptr;
errno = 0;
float_t result = strtof(str, &endptr);
if(errno != 0 || *endptr != '\0') {
return false;
}
*out = result;
return true;
}
bool_t stringEndsWith(const char_t *str, const char_t *suffix) {
assertNotNull(str, "str must not be NULL");
assertNotNull(suffix, "suffix must not be NULL");
size_t strLen = strlen(str);
size_t suffixLen = strlen(suffix);
if(suffixLen > strLen) return false;
return strcmp(str + strLen - suffixLen, suffix) == 0;
}
bool_t stringEndsWithCaseInsensitive(
const char_t *str,
const char_t *suffix
) {
assertNotNull(str, "str must not be NULL");
assertNotNull(suffix, "suffix must not be NULL");
size_t strLen = strlen(str);
size_t suffixLen = strlen(suffix);
if(suffixLen > strLen) return false;
return strcasecmp(str + strLen - suffixLen, suffix) == 0;
}
bool_t stringIncludesString(const char_t *haystack, const char_t *needle) {
assertNotNull(haystack, "haystack must not be NULL");
assertNotNull(needle, "needle must not be NULL");
return strstr(haystack, needle) != NULL;
}
+181
View File
@@ -0,0 +1,181 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
/**
* Determines if a character is whitespace.
*
* @param c The character to check.
* @return TRUE if the character is whitespace, FALSE otherwise.
*/
bool_t stringIsWhitespace(const char_t c);
/**
* Copies a string from src to dest, ensuring the dest string is null-terminated
* and does not exceed the specified size.
*
* @param dest The destination string.
* @param src The source string.
* @param destSize The size of the destination string exc. null terminator.
*/
void stringCopy(char_t *dest, const char_t *src, const size_t destSize);
/**
* Compares two strings.
*
* @param str1 The first string.
* @param str2 The second string.
* @return 0 if the strings are equal, -1 if str1 is less than str2, 1 if str1
* is greater than str2.
*/
int stringCompare(const char_t *str1, const char_t *str2);
/**
* Compares two strings for equality.
*
* @param str1 The first string.
* @param str2 The second string.
* @return TRUE if the strings are equal, FALSE otherwise.
*/
bool_t stringEquals(const char_t *str1, const char_t *str2);
/**
* Compares two strings, ignoring case.
*
* @param str1 The first string.
* @param str2 The second string.
* @return 0 if the strings are equal, -1 if str1 is less than str2, 1 if str1
* is greater than str2.
*/
int stringCompareInsensitive(const char_t *str1, const char_t *str2);
/**
* Trims whitespace from the beginning and end of a string.
*
* @param str The string to trim.
*/
void stringTrim(char_t *str);
/**
* Finds the last occurrence of a character in a string.
*
* @param str The string to search.
* @param c The character to find.
* @return A pointer to the last occurrence of the character in the string, or
* NULL if not found.
*/
char_t * stringFindLastChar(const char_t *str, const char_t c);
/**
* Formats a string.
*
* @param dest The destination string, or NULL to get the length of the
* formatted string.
* @param destSize The size of the destination string exc. null terminator, can
* be anything if dest is NULL.
* @param format The format string.
* @param ... The arguments to format.
* @return The number of characters written.
*/
int32_t stringFormat(
char_t *dest,
const size_t destSize,
const char_t *format,
...
);
/**
* Formats a string using a va_list.
*
* @param dest The destination string, or NULL to get the length of the
* formatted string.
* @param destSize The size of the destination string exc. null terminator, can
* be anything if dest is NULL.
* @param format The format string.
* @param args The va_list of arguments.
* @return The number of characters written.
*/
int32_t stringFormatVA(
char_t *dest,
const size_t destSize,
const char_t *format,
va_list args
);
/**
* Converts a string to an integer.
*
* @param str The string to convert.
* @param out The output integer.
* @return TRUE if the conversion was successful, FALSE otherwise.
*/
bool_t stringToI32(const char_t *str, int32_t *out);
/**
* Converts a string to a signed 64-bit integer.
*
* @param str The string to convert.
* @param out The output signed integer.
* @return TRUE if the conversion was successful, FALSE otherwise.
*/
bool_t stringToI64(const char_t *str, int64_t *out);
/**
* Converts a string to a signed 16-bit integer.
*
* @param str The string to convert.
* @param out The output signed integer.
* @return TRUE if the conversion was successful, FALSE otherwise.
*/
bool_t stringToI16(const char_t *str, int16_t *out);
/**
* Converts a string to an unsigned 16-bit integer.
*
* @param str The string to convert.
* @param out The output unsigned integer.
* @return TRUE if the conversion was successful, FALSE otherwise.
*/
bool_t stringToU16(const char_t *str, uint16_t *out);
/**
* Converts a string to a float.
*
* @param str The string to convert.
* @param out The output float.
* @return TRUE if the conversion was successful, FALSE otherwise.
*/
bool_t stringToF32(const char_t *str, float_t *out);
/**
* Determines if a string ends with a specified suffix.
*
* @param str The string to check.
* @param suffix The suffix to check for.
* @return TRUE if the string ends with the suffix, FALSE otherwise.
*/
bool_t stringEndsWith(const char_t *str, const char_t *suffix);
/**
* Determines if a string ends with a specified suffix, ignoring case.
*
* @param str The string to check.
* @param suffix The suffix to check for.
* @return TRUE if the string ends with the suffix, FALSE otherwise.
*/
bool_t stringEndsWithCaseInsensitive(const char_t *str, const char_t *suffix);
/**
* Determines if a string includes a specified substring.
*
* @param haystack The string to check.
* @param needle The substring to check for.
* @return TRUE if the string includes the substring, FALSE otherwise.
*/
bool_t stringIncludesString(const char_t *haystack, const char_t *needle);
+8
View File
@@ -0,0 +1,8 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
+8
View File
@@ -0,0 +1,8 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
+9
View File
@@ -0,0 +1,9 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "duskdolphin.h"
+8
View File
@@ -0,0 +1,8 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
+8
View File
@@ -0,0 +1,8 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
+9
View File
@@ -0,0 +1,9 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "duskdreamcast.h"
+20
View File
@@ -0,0 +1,20 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
target_sources(${DUSK_BINARY_TARGET_NAME} PRIVATE
main.c
)
target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
log/log.c
time/timelinux.c
)
find_package(Threads REQUIRED)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC Threads::Threads m)
+15
View File
@@ -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 "display/displaysdl3.h"
typedef displaysdl3_t displayplatform_t;
#define displayPlatformInit displaySDL3Init
#define displayPlatformUpdate displaySDL3Update
#define displayPlatformDispose displaySDL3Dispose
+8
View File
@@ -0,0 +1,8 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
+11
View File
@@ -0,0 +1,11 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusklinux.h"
#include "dusksdl3.h"
#include "duskvulkan.h"
+24
View File
@@ -0,0 +1,24 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "log/log.h"
void logDebug(const char_t *message, ...) {
va_list args;
va_start(args, message);
vprintf(message, args);
va_end(args);
fflush(stdout);
}
void logError(const char_t *message, ...) {
va_list args;
va_start(args, message);
vprintf(message, args);
va_end(args);
fflush(stdout);
}
+35
View File
@@ -0,0 +1,35 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "engine/engine.h"
int main(int argc, char **argv) {
errorret_t ret;
ret = engineInit(argc, (const char_t **)argv);
if(errorIsNotOk(ret)) {
errorCatch(errorPrint(ret));
return ret.code;
}
do {
ret = engineUpdate();
if(errorIsNotOk(ret)) {
errorCatch(errorPrint(ret));
return ret.code;
}
} while(ENGINE.running);
ret = engineDispose();
if(errorIsNotOk(ret)) {
errorCatch(errorPrint(ret));
return ret.code;
}
printf("hello world\n");
return 0;
}
+41
View File
@@ -0,0 +1,41 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "time/timelinux.h"
#include <time.h>
uint64_t TIME_TICKS_LINUX = 0;
uint64_t TIME_TICKS_LAST_LINUX = 0;
uint64_t timeGetMonotonicLinux(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec;
}
void timeTickLinux(void) {
TIME_TICKS_LAST_LINUX = TIME_TICKS_LINUX;
TIME_TICKS_LINUX = timeGetMonotonicLinux();
if(TIME_TICKS_LAST_LINUX == 0) TIME_TICKS_LAST_LINUX = TIME_TICKS_LINUX;
}
float_t timeGetDeltaLinux(void) {
return (float_t)(TIME_TICKS_LINUX - TIME_TICKS_LAST_LINUX) / 1e9f;
}
double_t timeGetRealLinux(void) {
struct timespec ts;
if(clock_gettime(CLOCK_REALTIME, &ts) != 0) return 0.0;
return ((double_t)ts.tv_sec) + ((double_t)ts.tv_nsec / 1000000000.0);
}
double_t timeGetRealTimeZoneLinux(void) {
time_t now = time(NULL);
struct tm local_tm;
localtime_r(&now, &local_tm);
return (double_t)(local_tm.tm_gmtoff);
}
+45
View File
@@ -0,0 +1,45 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
extern uint64_t TIME_TICKS_LINUX;
extern uint64_t TIME_TICKS_LAST_LINUX;
/**
* Returns the monotonic clock in nanoseconds.
*
* @return Nanoseconds since an unspecified starting point.
*/
uint64_t timeGetMonotonicLinux(void);
/**
* Advances the monotonic tick used for delta time.
*/
void timeTickLinux(void);
/**
* Returns the time in seconds between the last two ticks.
*
* @return The delta in seconds.
*/
float_t timeGetDeltaLinux(void);
/**
* Returns the real current time, in seconds since January 1, 1970.
*
* @return The real current time, in seconds since January 1, 1970.
*/
double_t timeGetRealLinux(void);
/**
* Returns the real time zone offset in seconds from UTC.
*
* @return The real time zone offset in seconds from UTC.
*/
double_t timeGetRealTimeZoneLinux(void);
+14
View File
@@ -0,0 +1,14 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "time/timelinux.h"
#define timeTickPlatform timeTickLinux
#define timeGetDeltaPlatform timeGetDeltaLinux
#define timeGetRealPlatform timeGetRealLinux
#define timeGetRealTimeZonePlatform timeGetRealTimeZoneLinux
+8
View File
@@ -0,0 +1,8 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
+9
View File
@@ -0,0 +1,9 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "duskps2.h"
+8
View File
@@ -0,0 +1,8 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
+8
View File
@@ -0,0 +1,8 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
+9
View File
@@ -0,0 +1,9 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "duskpsp.h"
+8
View File
@@ -0,0 +1,8 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
+15
View File
@@ -0,0 +1,15 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
find_package(SDL3 REQUIRED)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC SDL3::SDL3)
target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
display/displaysdl3.c
)
+65
View File
@@ -0,0 +1,65 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "display/displaysdl3.h"
#include "display/display.h"
#include "engine/engine.h"
errorret_t displaySDL3Init(void) {
if(!SDL_Init(SDL_INIT_VIDEO)) {
errorThrow("SDL_Init failed: %s", SDL_GetError());
}
DISPLAY.window = SDL_CreateWindow(
DUSK_GAME_NAME,
DUSK_DISPLAY_WIDTH_DEFAULT,
DUSK_DISPLAY_HEIGHT_DEFAULT,
SDL_WINDOW_RESIZABLE
);
if(DISPLAY.window == NULL) {
errorThrow("SDL_CreateWindow failed: %s", SDL_GetError());
}
DISPLAY.renderer = SDL_CreateRenderer(DISPLAY.window, NULL);
if(DISPLAY.renderer == NULL) {
errorThrow("SDL_CreateRenderer failed: %s", SDL_GetError());
}
SDL_SetRenderVSync(DISPLAY.renderer, 1);
errorOk();
}
errorret_t displaySDL3Update(void) {
SDL_Event event;
while(SDL_PollEvent(&event)) {
if(event.type == SDL_EVENT_QUIT) engineExit();
}
SDL_SetRenderDrawColor(DISPLAY.renderer, 255, 0, 0, 255);
if(!SDL_RenderClear(DISPLAY.renderer)) {
errorThrow("SDL_RenderClear failed: %s", SDL_GetError());
}
if(!SDL_RenderPresent(DISPLAY.renderer)) {
errorThrow("SDL_RenderPresent failed: %s", SDL_GetError());
}
errorOk();
}
errorret_t displaySDL3Dispose(void) {
if(DISPLAY.renderer != NULL) {
SDL_DestroyRenderer(DISPLAY.renderer);
DISPLAY.renderer = NULL;
}
if(DISPLAY.window != NULL) {
SDL_DestroyWindow(DISPLAY.window);
DISPLAY.window = NULL;
}
SDL_Quit();
errorOk();
}
+44
View File
@@ -0,0 +1,44 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#include <SDL3/SDL.h>
#ifndef DUSK_DISPLAY_WIDTH_DEFAULT
#define DUSK_DISPLAY_WIDTH_DEFAULT 854
#endif
#ifndef DUSK_DISPLAY_HEIGHT_DEFAULT
#define DUSK_DISPLAY_HEIGHT_DEFAULT 480
#endif
typedef struct {
SDL_Window *window;
SDL_Renderer *renderer;
} displaysdl3_t;
/**
* Initializes SDL3 and creates the window and renderer.
*
* @return An errorret_t indicating success or failure.
*/
errorret_t displaySDL3Init(void);
/**
* Handles window events, clears the window to red and presents it. Called once
* per frame.
*
* @return An errorret_t indicating success or failure.
*/
errorret_t displaySDL3Update(void);
/**
* Destroys the window and renderer and shuts SDL3 down.
*
* @return An errorret_t indicating success or failure.
*/
errorret_t displaySDL3Dispose(void);
+9
View File
@@ -0,0 +1,9 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusksdl3.h"
+8
View File
@@ -0,0 +1,8 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
+8
View File
@@ -0,0 +1,8 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
+9
View File
@@ -0,0 +1,9 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "duskvita.h"
Loaded 100 of 107 files, more files were not shown because too many files have changed in this diff. Show more