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:
1 parent
3710924503
commit
65c4a9c051
107 files changed
+4763
-117
No files matched your search
+40
-117
@@ -1,155 +1,78 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Setup
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_MODULE_PATH
|
||||
${CMAKE_MODULE_PATH}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules
|
||||
)
|
||||
cmake_policy(SET CMP0079 NEW)
|
||||
# set(FETCHCONTENT_UPDATES_DISCONNECTED ON)
|
||||
|
||||
option(DUSK_BUILD_TESTS "Enable tests" OFF)
|
||||
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
|
||||
# Directories
|
||||
set(DUSK_ROOT_DIR "${CMAKE_SOURCE_DIR}")
|
||||
set(DUSK_BUILD_DIR "${CMAKE_BINARY_DIR}")
|
||||
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)
|
||||
set(DUSK_TARGET_SYSTEM "linux")
|
||||
project(DuskCore VERSION 1.0.0 LANGUAGES C)
|
||||
|
||||
# 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()
|
||||
include("${DUSK_TARGET_FILE}")
|
||||
|
||||
# Create directories
|
||||
file(MAKE_DIRECTORY ${DUSK_GENERATED_HEADERS_DIR})
|
||||
file(MAKE_DIRECTORY ${DUSK_TEMP_DIR})
|
||||
file(MAKE_DIRECTORY ${DUSK_BUILT_ASSETS_DIR})
|
||||
# All options and their defaults.
|
||||
include(${DUSK_ROOT_DIR}/cmake/option.cmake)
|
||||
|
||||
# Required build packages
|
||||
find_package(Python3 COMPONENTS Interpreter REQUIRED)
|
||||
|
||||
# Init Project.
|
||||
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)
|
||||
# Targets. Tests split the code into a library plus the binary, otherwise all
|
||||
# code is compiled straight into the binary.
|
||||
set(DUSK_BINARY_TARGET_NAME "Dusk")
|
||||
add_executable(${DUSK_BINARY_TARGET_NAME})
|
||||
|
||||
if(DUSK_BUILD_TESTS)
|
||||
# MainLibrary
|
||||
set(DUSK_LIBRARY_TARGET_NAME "DuskCore")
|
||||
add_library(${DUSK_LIBRARY_TARGET_NAME} STATIC)
|
||||
|
||||
# Link library to binary
|
||||
target_link_libraries(${DUSK_BINARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
target_link_libraries(${DUSK_BINARY_TARGET_NAME} PUBLIC
|
||||
${DUSK_LIBRARY_TARGET_NAME}
|
||||
)
|
||||
else()
|
||||
set(DUSK_LIBRARY_TARGET_NAME "${DUSK_BINARY_TARGET_NAME}"
|
||||
CACHE INTERNAL ${DUSK_CACHE_TARGET}
|
||||
)
|
||||
set(DUSK_LIBRARY_TARGET_NAME "${DUSK_BINARY_TARGET_NAME}")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED DUSK_VERSION)
|
||||
string(TIMESTAMP DUSK_VERSION "debug-%y%m%d%H%M%S")
|
||||
endif()
|
||||
|
||||
# Definitions
|
||||
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||
DUSK_TARGET_SYSTEM="${DUSK_TARGET_SYSTEM}"
|
||||
DUSK_GAME_NAME="${DUSK_GAME_NAME}"
|
||||
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}"
|
||||
)
|
||||
|
||||
if(DUSK_NETWORK)
|
||||
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||
DUSK_NETWORK
|
||||
${DUSK_PLATFORM_DEFINITIONS}
|
||||
)
|
||||
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()
|
||||
|
||||
# Toolchains
|
||||
include(cmake/targets/${DUSK_TARGET_SYSTEM}.cmake)
|
||||
# Core engine code
|
||||
add_subdirectory(${DUSK_SOURCES_DIR}/dusk)
|
||||
|
||||
# Add tools
|
||||
add_subdirectory(tools)
|
||||
# Platform code
|
||||
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)
|
||||
enable_testing()
|
||||
add_subdirectory(test)
|
||||
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)
|
||||
@@ -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)")
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
@@ -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()
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
@@ -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"]
|
||||
Executable
+5
@@ -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"
|
||||
Executable
+5
@@ -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)"
|
||||
Executable
+42
@@ -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
|
||||
Executable
+5
@@ -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"
|
||||
Executable
+7
@@ -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
|
||||
Executable
+5
@@ -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"
|
||||
Executable
+12
@@ -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)"
|
||||
Executable
+30
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
)
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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
|
||||
)
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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
|
||||
)
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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
|
||||
)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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, ...);
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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;
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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
|
||||
)
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
@@ -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
|
||||
)
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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}
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
@@ -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"
|
||||
@@ -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}
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
@@ -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"
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
@@ -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"
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -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}
|
||||
)
|
||||
@@ -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"
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
@@ -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}
|
||||
)
|
||||
@@ -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"
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
@@ -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
|
||||
)
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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"
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
@@ -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}
|
||||
)
|
||||
@@ -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
Reference in new issue
Block a user