From 65c4a9c0514fe94ec10f9370014f8f4e8ebe0ea1 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Sat, 7 Nov 2026 17:02:14 -0600 Subject: [PATCH] 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 --- CMakeLists.txt | 177 ++++----------- cmake/determineplatform.cmake | 45 ++++ cmake/dolphin/target.cmake | 27 +++ cmake/dreamcast/target.cmake | 26 +++ cmake/linux/target.cmake | 32 +++ cmake/option.cmake | 40 ++++ cmake/ps2/target.cmake | 26 +++ cmake/psp/target.cmake | 27 +++ cmake/vita/target.cmake | 27 +++ cmake/windows/target.cmake | 21 ++ docker/linux/Dockerfile | 5 + docker/psp/Dockerfile | 5 + scripts/linux/build-docker.sh | 5 + scripts/linux/build.sh | 5 + scripts/linux/install.sh | 42 ++++ scripts/linux/test-docker.sh | 5 + scripts/linux/test.sh | 7 + scripts/psp/build-docker.sh | 5 + scripts/psp/build.sh | 12 + scripts/psp/install.sh | 30 +++ src/dusk/CMakeLists.txt | 20 ++ src/dusk/assert/CMakeLists.txt | 10 + src/dusk/assert/assert.c | 170 ++++++++++++++ src/dusk/assert/assert.h | 290 ++++++++++++++++++++++++ src/dusk/console/CMakeLists.txt | 9 + src/dusk/console/console.c | 49 ++++ src/dusk/console/console.h | 45 ++++ src/dusk/display/CMakeLists.txt | 9 + src/dusk/display/display.c | 37 +++ src/dusk/display/display.h | 36 +++ src/dusk/dusk.h | 28 +++ src/dusk/engine/CMakeLists.txt | 10 + src/dusk/engine/engine.c | 64 ++++++ src/dusk/engine/engine.h | 43 ++++ src/dusk/error/CMakeLists.txt | 10 + src/dusk/error/error.c | 138 +++++++++++ src/dusk/error/error.h | 153 +++++++++++++ src/dusk/log/log.h | 26 +++ src/dusk/perf/CMakeLists.txt | 10 + src/dusk/perf/perf.c | 88 +++++++ src/dusk/perf/perf.h | 89 ++++++++ src/dusk/perf/perfitem.h | 16 ++ src/dusk/thread/CMakeLists.txt | 11 + src/dusk/thread/thread.c | 117 ++++++++++ src/dusk/thread/thread.h | 97 ++++++++ src/dusk/thread/threadlocal.h | 17 ++ src/dusk/thread/threadmutex.c | 52 +++++ src/dusk/thread/threadmutex.h | 73 ++++++ src/dusk/time/CMakeLists.txt | 11 + src/dusk/time/time.c | 61 +++++ src/dusk/time/time.h | 72 ++++++ src/dusk/time/timeepoch.c | 198 ++++++++++++++++ src/dusk/time/timeepoch.h | 145 ++++++++++++ src/dusk/util/CMakeLists.txt | 18 ++ src/dusk/util/array.c | 39 ++++ src/dusk/util/array.h | 22 ++ src/dusk/util/crypt.c | 36 +++ src/dusk/util/crypt.h | 50 ++++ src/dusk/util/endian.c | 100 ++++++++ src/dusk/util/endian.h | 53 +++++ src/dusk/util/math.c | 31 +++ src/dusk/util/math.h | 84 +++++++ src/dusk/util/memory.c | 255 +++++++++++++++++++++ src/dusk/util/memory.h | 191 ++++++++++++++++ src/dusk/util/random.c | 17 ++ src/dusk/util/random.h | 27 +++ src/dusk/util/ref.c | 52 +++++ src/dusk/util/ref.h | 55 +++++ src/dusk/util/sort.c | 71 ++++++ src/dusk/util/sort.h | 65 ++++++ src/dusk/util/string.c | 248 ++++++++++++++++++++ src/dusk/util/string.h | 181 +++++++++++++++ src/duskdolphin/CMakeLists.txt | 8 + src/duskdolphin/duskdolphin.h | 8 + src/duskdolphin/duskplatform.h | 9 + src/duskdreamcast/CMakeLists.txt | 8 + src/duskdreamcast/duskdreamcast.h | 8 + src/duskdreamcast/duskplatform.h | 9 + src/dusklinux/CMakeLists.txt | 20 ++ src/dusklinux/display/displayplatform.h | 15 ++ src/dusklinux/dusklinux.h | 8 + src/dusklinux/duskplatform.h | 11 + src/dusklinux/log/log.c | 24 ++ src/dusklinux/main.c | 35 +++ src/dusklinux/time/timelinux.c | 41 ++++ src/dusklinux/time/timelinux.h | 45 ++++ src/dusklinux/time/timeplatform.h | 14 ++ src/duskps2/CMakeLists.txt | 8 + src/duskps2/duskplatform.h | 9 + src/duskps2/duskps2.h | 8 + src/duskpsp/CMakeLists.txt | 8 + src/duskpsp/duskplatform.h | 9 + src/duskpsp/duskpsp.h | 8 + src/dusksdl3/CMakeLists.txt | 15 ++ src/dusksdl3/display/displaysdl3.c | 65 ++++++ src/dusksdl3/display/displaysdl3.h | 44 ++++ src/dusksdl3/duskplatform.h | 9 + src/dusksdl3/dusksdl3.h | 8 + src/duskvita/CMakeLists.txt | 8 + src/duskvita/duskplatform.h | 9 + src/duskvita/duskvita.h | 8 + src/duskvulkan/CMakeLists.txt | 11 + src/duskvulkan/duskplatform.h | 9 + src/duskvulkan/duskvulkan.h | 8 + src/duskwindows/CMakeLists.txt | 8 + src/duskwindows/duskplatform.h | 9 + src/duskwindows/duskwindows.h | 8 + 107 files changed, 4773 insertions(+), 127 deletions(-) create mode 100644 cmake/determineplatform.cmake create mode 100644 cmake/dolphin/target.cmake create mode 100644 cmake/dreamcast/target.cmake create mode 100644 cmake/linux/target.cmake create mode 100644 cmake/option.cmake create mode 100644 cmake/ps2/target.cmake create mode 100644 cmake/psp/target.cmake create mode 100644 cmake/vita/target.cmake create mode 100644 cmake/windows/target.cmake create mode 100644 docker/linux/Dockerfile create mode 100644 docker/psp/Dockerfile create mode 100755 scripts/linux/build-docker.sh create mode 100755 scripts/linux/build.sh create mode 100755 scripts/linux/install.sh create mode 100755 scripts/linux/test-docker.sh create mode 100755 scripts/linux/test.sh create mode 100755 scripts/psp/build-docker.sh create mode 100755 scripts/psp/build.sh create mode 100755 scripts/psp/install.sh create mode 100644 src/dusk/CMakeLists.txt create mode 100644 src/dusk/assert/CMakeLists.txt create mode 100644 src/dusk/assert/assert.c create mode 100644 src/dusk/assert/assert.h create mode 100644 src/dusk/console/CMakeLists.txt create mode 100644 src/dusk/console/console.c create mode 100644 src/dusk/console/console.h create mode 100644 src/dusk/display/CMakeLists.txt create mode 100644 src/dusk/display/display.c create mode 100644 src/dusk/display/display.h create mode 100644 src/dusk/dusk.h create mode 100644 src/dusk/engine/CMakeLists.txt create mode 100644 src/dusk/engine/engine.c create mode 100644 src/dusk/engine/engine.h create mode 100644 src/dusk/error/CMakeLists.txt create mode 100644 src/dusk/error/error.c create mode 100644 src/dusk/error/error.h create mode 100644 src/dusk/log/log.h create mode 100644 src/dusk/perf/CMakeLists.txt create mode 100644 src/dusk/perf/perf.c create mode 100644 src/dusk/perf/perf.h create mode 100644 src/dusk/perf/perfitem.h create mode 100644 src/dusk/thread/CMakeLists.txt create mode 100644 src/dusk/thread/thread.c create mode 100644 src/dusk/thread/thread.h create mode 100644 src/dusk/thread/threadlocal.h create mode 100644 src/dusk/thread/threadmutex.c create mode 100644 src/dusk/thread/threadmutex.h create mode 100644 src/dusk/time/CMakeLists.txt create mode 100644 src/dusk/time/time.c create mode 100644 src/dusk/time/time.h create mode 100644 src/dusk/time/timeepoch.c create mode 100644 src/dusk/time/timeepoch.h create mode 100644 src/dusk/util/CMakeLists.txt create mode 100644 src/dusk/util/array.c create mode 100644 src/dusk/util/array.h create mode 100644 src/dusk/util/crypt.c create mode 100644 src/dusk/util/crypt.h create mode 100644 src/dusk/util/endian.c create mode 100644 src/dusk/util/endian.h create mode 100644 src/dusk/util/math.c create mode 100644 src/dusk/util/math.h create mode 100644 src/dusk/util/memory.c create mode 100644 src/dusk/util/memory.h create mode 100644 src/dusk/util/random.c create mode 100644 src/dusk/util/random.h create mode 100644 src/dusk/util/ref.c create mode 100644 src/dusk/util/ref.h create mode 100644 src/dusk/util/sort.c create mode 100644 src/dusk/util/sort.h create mode 100644 src/dusk/util/string.c create mode 100644 src/dusk/util/string.h create mode 100644 src/duskdolphin/CMakeLists.txt create mode 100644 src/duskdolphin/duskdolphin.h create mode 100644 src/duskdolphin/duskplatform.h create mode 100644 src/duskdreamcast/CMakeLists.txt create mode 100644 src/duskdreamcast/duskdreamcast.h create mode 100644 src/duskdreamcast/duskplatform.h create mode 100644 src/dusklinux/CMakeLists.txt create mode 100644 src/dusklinux/display/displayplatform.h create mode 100644 src/dusklinux/dusklinux.h create mode 100644 src/dusklinux/duskplatform.h create mode 100644 src/dusklinux/log/log.c create mode 100644 src/dusklinux/main.c create mode 100644 src/dusklinux/time/timelinux.c create mode 100644 src/dusklinux/time/timelinux.h create mode 100644 src/dusklinux/time/timeplatform.h create mode 100644 src/duskps2/CMakeLists.txt create mode 100644 src/duskps2/duskplatform.h create mode 100644 src/duskps2/duskps2.h create mode 100644 src/duskpsp/CMakeLists.txt create mode 100644 src/duskpsp/duskplatform.h create mode 100644 src/duskpsp/duskpsp.h create mode 100644 src/dusksdl3/CMakeLists.txt create mode 100644 src/dusksdl3/display/displaysdl3.c create mode 100644 src/dusksdl3/display/displaysdl3.h create mode 100644 src/dusksdl3/duskplatform.h create mode 100644 src/dusksdl3/dusksdl3.h create mode 100644 src/duskvita/CMakeLists.txt create mode 100644 src/duskvita/duskplatform.h create mode 100644 src/duskvita/duskvita.h create mode 100644 src/duskvulkan/CMakeLists.txt create mode 100644 src/duskvulkan/duskplatform.h create mode 100644 src/duskvulkan/duskvulkan.h create mode 100644 src/duskwindows/CMakeLists.txt create mode 100644 src/duskwindows/duskplatform.h create mode 100644 src/duskwindows/duskwindows.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2aae71f4..b553a978 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 - ${DUSK_LIBRARY_TARGET_NAME} + 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") +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_VERSION="${DUSK_VERSION}" +) + +target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC + ${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() -# Definitions -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}" -) +# Core engine code +add_subdirectory(${DUSK_SOURCES_DIR}/dusk) -if(DUSK_NETWORK) - target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC - DUSK_NETWORK - ) -endif() +# Platform code +foreach(DIR ${DUSK_PLATFORM_DIRS}) + add_subdirectory(${DUSK_SOURCES_DIR}/${DIR}) +endforeach() -# Toolchains -include(cmake/targets/${DUSK_TARGET_SYSTEM}.cmake) - -# Add tools -add_subdirectory(tools) - -# 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) diff --git a/cmake/determineplatform.cmake b/cmake/determineplatform.cmake new file mode 100644 index 00000000..1f695ba4 --- /dev/null +++ b/cmake/determineplatform.cmake @@ -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=. 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=." + ) +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)") diff --git a/cmake/dolphin/target.cmake b/cmake/dolphin/target.cmake new file mode 100644 index 00000000..5650c184 --- /dev/null +++ b/cmake/dolphin/target.cmake @@ -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 +) diff --git a/cmake/dreamcast/target.cmake b/cmake/dreamcast/target.cmake new file mode 100644 index 00000000..6e15419b --- /dev/null +++ b/cmake/dreamcast/target.cmake @@ -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 +) diff --git a/cmake/linux/target.cmake b/cmake/linux/target.cmake new file mode 100644 index 00000000..4a989be8 --- /dev/null +++ b/cmake/linux/target.cmake @@ -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 +) diff --git a/cmake/option.cmake b/cmake/option.cmake new file mode 100644 index 00000000..2d75973e --- /dev/null +++ b/cmake/option.cmake @@ -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() diff --git a/cmake/ps2/target.cmake b/cmake/ps2/target.cmake new file mode 100644 index 00000000..99b4a7da --- /dev/null +++ b/cmake/ps2/target.cmake @@ -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 +) diff --git a/cmake/psp/target.cmake b/cmake/psp/target.cmake new file mode 100644 index 00000000..f54ce33f --- /dev/null +++ b/cmake/psp/target.cmake @@ -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 +) diff --git a/cmake/vita/target.cmake b/cmake/vita/target.cmake new file mode 100644 index 00000000..bf3d0c00 --- /dev/null +++ b/cmake/vita/target.cmake @@ -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 +) diff --git a/cmake/windows/target.cmake b/cmake/windows/target.cmake new file mode 100644 index 00000000..b2b28364 --- /dev/null +++ b/cmake/windows/target.cmake @@ -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 +) diff --git a/docker/linux/Dockerfile b/docker/linux/Dockerfile new file mode 100644 index 00000000..53f0e128 --- /dev/null +++ b/docker/linux/Dockerfile @@ -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 diff --git a/docker/psp/Dockerfile b/docker/psp/Dockerfile new file mode 100644 index 00000000..1d6d4085 --- /dev/null +++ b/docker/psp/Dockerfile @@ -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"] diff --git a/scripts/linux/build-docker.sh b/scripts/linux/build-docker.sh new file mode 100755 index 00000000..2af5761a --- /dev/null +++ b/scripts/linux/build-docker.sh @@ -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" diff --git a/scripts/linux/build.sh b/scripts/linux/build.sh new file mode 100755 index 00000000..5df14265 --- /dev/null +++ b/scripts/linux/build.sh @@ -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)" diff --git a/scripts/linux/install.sh b/scripts/linux/install.sh new file mode 100755 index 00000000..5a5b66db --- /dev/null +++ b/scripts/linux/install.sh @@ -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 diff --git a/scripts/linux/test-docker.sh b/scripts/linux/test-docker.sh new file mode 100755 index 00000000..21d40f4e --- /dev/null +++ b/scripts/linux/test-docker.sh @@ -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" diff --git a/scripts/linux/test.sh b/scripts/linux/test.sh new file mode 100755 index 00000000..5a3699fe --- /dev/null +++ b/scripts/linux/test.sh @@ -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 diff --git a/scripts/psp/build-docker.sh b/scripts/psp/build-docker.sh new file mode 100755 index 00000000..5ebd962d --- /dev/null +++ b/scripts/psp/build-docker.sh @@ -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" diff --git a/scripts/psp/build.sh b/scripts/psp/build.sh new file mode 100755 index 00000000..f577c697 --- /dev/null +++ b/scripts/psp/build.sh @@ -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)" diff --git a/scripts/psp/install.sh b/scripts/psp/install.sh new file mode 100755 index 00000000..e55917eb --- /dev/null +++ b/scripts/psp/install.sh @@ -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 diff --git a/src/dusk/CMakeLists.txt b/src/dusk/CMakeLists.txt new file mode 100644 index 00000000..b8beba4b --- /dev/null +++ b/src/dusk/CMakeLists.txt @@ -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) diff --git a/src/dusk/assert/CMakeLists.txt b/src/dusk/assert/CMakeLists.txt new file mode 100644 index 00000000..af43017d --- /dev/null +++ b/src/dusk/assert/CMakeLists.txt @@ -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 +) \ No newline at end of file diff --git a/src/dusk/assert/assert.c b/src/dusk/assert/assert.c new file mode 100644 index 00000000..f84992c1 --- /dev/null +++ b/src/dusk/assert/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 + #include + + 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 + diff --git a/src/dusk/assert/assert.h b/src/dusk/assert/assert.h new file mode 100644 index 00000000..0040065d --- /dev/null +++ b/src/dusk/assert/assert.h @@ -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 + + #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 diff --git a/src/dusk/console/CMakeLists.txt b/src/dusk/console/CMakeLists.txt new file mode 100644 index 00000000..2668e0b3 --- /dev/null +++ b/src/dusk/console/CMakeLists.txt @@ -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 +) diff --git a/src/dusk/console/console.c b/src/dusk/console/console.c new file mode 100644 index 00000000..b885dd82 --- /dev/null +++ b/src/dusk/console/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); +} \ No newline at end of file diff --git a/src/dusk/console/console.h b/src/dusk/console/console.h new file mode 100644 index 00000000..8f0a8fc6 --- /dev/null +++ b/src/dusk/console/console.h @@ -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); \ No newline at end of file diff --git a/src/dusk/display/CMakeLists.txt b/src/dusk/display/CMakeLists.txt new file mode 100644 index 00000000..466356d5 --- /dev/null +++ b/src/dusk/display/CMakeLists.txt @@ -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 +) diff --git a/src/dusk/display/display.c b/src/dusk/display/display.c new file mode 100644 index 00000000..f0336f16 --- /dev/null +++ b/src/dusk/display/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(); +} diff --git a/src/dusk/display/display.h b/src/dusk/display/display.h new file mode 100644 index 00000000..e7e8054f --- /dev/null +++ b/src/dusk/display/display.h @@ -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); diff --git a/src/dusk/dusk.h b/src/dusk/dusk.h new file mode 100644 index 00000000..c1212f04 --- /dev/null +++ b/src/dusk/dusk.h @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "duskplatform.h" + +typedef bool bool_t; +typedef int int_t; +typedef float float_t; +typedef char char_t; diff --git a/src/dusk/engine/CMakeLists.txt b/src/dusk/engine/CMakeLists.txt new file mode 100644 index 00000000..84851ec0 --- /dev/null +++ b/src/dusk/engine/CMakeLists.txt @@ -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 +) \ No newline at end of file diff --git a/src/dusk/engine/engine.c b/src/dusk/engine/engine.c new file mode 100644 index 00000000..806c5086 --- /dev/null +++ b/src/dusk/engine/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(); +} diff --git a/src/dusk/engine/engine.h b/src/dusk/engine/engine.h new file mode 100644 index 00000000..c0915879 --- /dev/null +++ b/src/dusk/engine/engine.h @@ -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); diff --git a/src/dusk/error/CMakeLists.txt b/src/dusk/error/CMakeLists.txt new file mode 100644 index 00000000..e6839225 --- /dev/null +++ b/src/dusk/error/CMakeLists.txt @@ -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 +) \ No newline at end of file diff --git a/src/dusk/error/error.c b/src/dusk/error/error.c new file mode 100644 index 00000000..804b8013 --- /dev/null +++ b/src/dusk/error/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; +} \ No newline at end of file diff --git a/src/dusk/error/error.h b/src/dusk/error/error.h new file mode 100644 index 00000000..68a8a387 --- /dev/null +++ b/src/dusk/error/error.h @@ -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 \ No newline at end of file diff --git a/src/dusk/log/log.h b/src/dusk/log/log.h new file mode 100644 index 00000000..60180752 --- /dev/null +++ b/src/dusk/log/log.h @@ -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, ...); \ No newline at end of file diff --git a/src/dusk/perf/CMakeLists.txt b/src/dusk/perf/CMakeLists.txt new file mode 100644 index 00000000..bc198e9e --- /dev/null +++ b/src/dusk/perf/CMakeLists.txt @@ -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 +) diff --git a/src/dusk/perf/perf.c b/src/dusk/perf/perf.c new file mode 100644 index 00000000..1b83095f --- /dev/null +++ b/src/dusk/perf/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 +#elif defined(DUSK_PSP) + #include +#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 + ); +} diff --git a/src/dusk/perf/perf.h b/src/dusk/perf/perf.h new file mode 100644 index 00000000..82619a2f --- /dev/null +++ b/src/dusk/perf/perf.h @@ -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() diff --git a/src/dusk/perf/perfitem.h b/src/dusk/perf/perfitem.h new file mode 100644 index 00000000..c9975758 --- /dev/null +++ b/src/dusk/perf/perfitem.h @@ -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; \ No newline at end of file diff --git a/src/dusk/thread/CMakeLists.txt b/src/dusk/thread/CMakeLists.txt new file mode 100644 index 00000000..17267ba9 --- /dev/null +++ b/src/dusk/thread/CMakeLists.txt @@ -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 +) \ No newline at end of file diff --git a/src/dusk/thread/thread.c b/src/dusk/thread/thread.c new file mode 100644 index 00000000..f0d09195 --- /dev/null +++ b/src/dusk/thread/thread.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 \ No newline at end of file diff --git a/src/dusk/thread/thread.h b/src/dusk/thread/thread.h new file mode 100644 index 00000000..046747f5 --- /dev/null +++ b/src/dusk/thread/thread.h @@ -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 \ No newline at end of file diff --git a/src/dusk/thread/threadlocal.h b/src/dusk/thread/threadlocal.h new file mode 100644 index 00000000..0e2cad82 --- /dev/null +++ b/src/dusk/thread/threadlocal.h @@ -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 diff --git a/src/dusk/thread/threadmutex.c b/src/dusk/thread/threadmutex.c new file mode 100644 index 00000000..366b4bf5 --- /dev/null +++ b/src/dusk/thread/threadmutex.c @@ -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 +} \ No newline at end of file diff --git a/src/dusk/thread/threadmutex.h b/src/dusk/thread/threadmutex.h new file mode 100644 index 00000000..29ef7ba7 --- /dev/null +++ b/src/dusk/thread/threadmutex.h @@ -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 +#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); \ No newline at end of file diff --git a/src/dusk/time/CMakeLists.txt b/src/dusk/time/CMakeLists.txt new file mode 100644 index 00000000..a202dc37 --- /dev/null +++ b/src/dusk/time/CMakeLists.txt @@ -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 +) \ No newline at end of file diff --git a/src/dusk/time/time.c b/src/dusk/time/time.c new file mode 100644 index 00000000..ab4f20a5 --- /dev/null +++ b/src/dusk/time/time.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()); +} \ No newline at end of file diff --git a/src/dusk/time/time.h b/src/dusk/time/time.h new file mode 100644 index 00000000..68f53537 --- /dev/null +++ b/src/dusk/time/time.h @@ -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); \ No newline at end of file diff --git a/src/dusk/time/timeepoch.c b/src/dusk/time/timeepoch.c new file mode 100644 index 00000000..9acbd22a --- /dev/null +++ b/src/dusk/time/timeepoch.c @@ -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; +} \ No newline at end of file diff --git a/src/dusk/time/timeepoch.h b/src/dusk/time/timeepoch.h new file mode 100644 index 00000000..d7da2b9e --- /dev/null +++ b/src/dusk/time/timeepoch.h @@ -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 +); \ No newline at end of file diff --git a/src/dusk/util/CMakeLists.txt b/src/dusk/util/CMakeLists.txt new file mode 100644 index 00000000..62fb7902 --- /dev/null +++ b/src/dusk/util/CMakeLists.txt @@ -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 +) \ No newline at end of file diff --git a/src/dusk/util/array.c b/src/dusk/util/array.c new file mode 100644 index 00000000..214f8410 --- /dev/null +++ b/src/dusk/util/array.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); +} \ No newline at end of file diff --git a/src/dusk/util/array.h b/src/dusk/util/array.h new file mode 100644 index 00000000..ff2d5390 --- /dev/null +++ b/src/dusk/util/array.h @@ -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 +); \ No newline at end of file diff --git a/src/dusk/util/crypt.c b/src/dusk/util/crypt.c new file mode 100644 index 00000000..32734e31 --- /dev/null +++ b/src/dusk/util/crypt.c @@ -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); +} diff --git a/src/dusk/util/crypt.h b/src/dusk/util/crypt.h new file mode 100644 index 00000000..971bfa7d --- /dev/null +++ b/src/dusk/util/crypt.h @@ -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); diff --git a/src/dusk/util/endian.c b/src/dusk/util/endian.c new file mode 100644 index 00000000..4f225b01 --- /dev/null +++ b/src/dusk/util/endian.c @@ -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; +} + diff --git a/src/dusk/util/endian.h b/src/dusk/util/endian.h new file mode 100644 index 00000000..a5a397f1 --- /dev/null +++ b/src/dusk/util/endian.h @@ -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); \ No newline at end of file diff --git a/src/dusk/util/math.c b/src/dusk/util/math.c new file mode 100644 index 00000000..01bb0168 --- /dev/null +++ b/src/dusk/util/math.c @@ -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); +} \ No newline at end of file diff --git a/src/dusk/util/math.h b/src/dusk/util/math.h new file mode 100644 index 00000000..80239eca --- /dev/null +++ b/src/dusk/util/math.h @@ -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); \ No newline at end of file diff --git a/src/dusk/util/memory.c b/src/dusk/util/memory.c new file mode 100644 index 00000000..0f5edb3d --- /dev/null +++ b/src/dusk/util/memory.c @@ -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; +} \ No newline at end of file diff --git a/src/dusk/util/memory.h b/src/dusk/util/memory.h new file mode 100644 index 00000000..94171a32 --- /dev/null +++ b/src/dusk/util/memory.h @@ -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 +); \ No newline at end of file diff --git a/src/dusk/util/random.c b/src/dusk/util/random.c new file mode 100644 index 00000000..c7974029 --- /dev/null +++ b/src/dusk/util/random.c @@ -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 + +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)); +} diff --git a/src/dusk/util/random.h b/src/dusk/util/random.h new file mode 100644 index 00000000..1fba6946 --- /dev/null +++ b/src/dusk/util/random.h @@ -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); diff --git a/src/dusk/util/ref.c b/src/dusk/util/ref.c new file mode 100644 index 00000000..faf84a9c --- /dev/null +++ b/src/dusk/util/ref.c @@ -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; +} diff --git a/src/dusk/util/ref.h b/src/dusk/util/ref.h new file mode 100644 index 00000000..e2c41af5 --- /dev/null +++ b/src/dusk/util/ref.h @@ -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); diff --git a/src/dusk/util/sort.c b/src/dusk/util/sort.c new file mode 100644 index 00000000..3e184bbb --- /dev/null +++ b/src/dusk/util/sort.c @@ -0,0 +1,71 @@ +#include +/** + * 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); +} \ No newline at end of file diff --git a/src/dusk/util/sort.h b/src/dusk/util/sort.h new file mode 100644 index 00000000..203bcc0b --- /dev/null +++ b/src/dusk/util/sort.h @@ -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); \ No newline at end of file diff --git a/src/dusk/util/string.c b/src/dusk/util/string.c new file mode 100644 index 00000000..783a23cd --- /dev/null +++ b/src/dusk/util/string.c @@ -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; +} \ No newline at end of file diff --git a/src/dusk/util/string.h b/src/dusk/util/string.h new file mode 100644 index 00000000..77a6c5ba --- /dev/null +++ b/src/dusk/util/string.h @@ -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); \ No newline at end of file diff --git a/src/duskdolphin/CMakeLists.txt b/src/duskdolphin/CMakeLists.txt new file mode 100644 index 00000000..ab9a2f91 --- /dev/null +++ b/src/duskdolphin/CMakeLists.txt @@ -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} +) diff --git a/src/duskdolphin/duskdolphin.h b/src/duskdolphin/duskdolphin.h new file mode 100644 index 00000000..127e1eb3 --- /dev/null +++ b/src/duskdolphin/duskdolphin.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 \ No newline at end of file diff --git a/src/duskdolphin/duskplatform.h b/src/duskdolphin/duskplatform.h new file mode 100644 index 00000000..a08aa102 --- /dev/null +++ b/src/duskdolphin/duskplatform.h @@ -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" \ No newline at end of file diff --git a/src/duskdreamcast/CMakeLists.txt b/src/duskdreamcast/CMakeLists.txt new file mode 100644 index 00000000..ab9a2f91 --- /dev/null +++ b/src/duskdreamcast/CMakeLists.txt @@ -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} +) diff --git a/src/duskdreamcast/duskdreamcast.h b/src/duskdreamcast/duskdreamcast.h new file mode 100644 index 00000000..127e1eb3 --- /dev/null +++ b/src/duskdreamcast/duskdreamcast.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 \ No newline at end of file diff --git a/src/duskdreamcast/duskplatform.h b/src/duskdreamcast/duskplatform.h new file mode 100644 index 00000000..90fdc429 --- /dev/null +++ b/src/duskdreamcast/duskplatform.h @@ -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" \ No newline at end of file diff --git a/src/dusklinux/CMakeLists.txt b/src/dusklinux/CMakeLists.txt new file mode 100644 index 00000000..1c9f0a51 --- /dev/null +++ b/src/dusklinux/CMakeLists.txt @@ -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) diff --git a/src/dusklinux/display/displayplatform.h b/src/dusklinux/display/displayplatform.h new file mode 100644 index 00000000..dc601e88 --- /dev/null +++ b/src/dusklinux/display/displayplatform.h @@ -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 diff --git a/src/dusklinux/dusklinux.h b/src/dusklinux/dusklinux.h new file mode 100644 index 00000000..127e1eb3 --- /dev/null +++ b/src/dusklinux/dusklinux.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 \ No newline at end of file diff --git a/src/dusklinux/duskplatform.h b/src/dusklinux/duskplatform.h new file mode 100644 index 00000000..b340e8ab --- /dev/null +++ b/src/dusklinux/duskplatform.h @@ -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" \ No newline at end of file diff --git a/src/dusklinux/log/log.c b/src/dusklinux/log/log.c new file mode 100644 index 00000000..951d93bb --- /dev/null +++ b/src/dusklinux/log/log.c @@ -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); +} \ No newline at end of file diff --git a/src/dusklinux/main.c b/src/dusklinux/main.c new file mode 100644 index 00000000..4f9819ae --- /dev/null +++ b/src/dusklinux/main.c @@ -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; +} diff --git a/src/dusklinux/time/timelinux.c b/src/dusklinux/time/timelinux.c new file mode 100644 index 00000000..bf2b2d11 --- /dev/null +++ b/src/dusklinux/time/timelinux.c @@ -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 + +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); +} diff --git a/src/dusklinux/time/timelinux.h b/src/dusklinux/time/timelinux.h new file mode 100644 index 00000000..5aad7057 --- /dev/null +++ b/src/dusklinux/time/timelinux.h @@ -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); diff --git a/src/dusklinux/time/timeplatform.h b/src/dusklinux/time/timeplatform.h new file mode 100644 index 00000000..cb66270a --- /dev/null +++ b/src/dusklinux/time/timeplatform.h @@ -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 diff --git a/src/duskps2/CMakeLists.txt b/src/duskps2/CMakeLists.txt new file mode 100644 index 00000000..ab9a2f91 --- /dev/null +++ b/src/duskps2/CMakeLists.txt @@ -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} +) diff --git a/src/duskps2/duskplatform.h b/src/duskps2/duskplatform.h new file mode 100644 index 00000000..5103423d --- /dev/null +++ b/src/duskps2/duskplatform.h @@ -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" \ No newline at end of file diff --git a/src/duskps2/duskps2.h b/src/duskps2/duskps2.h new file mode 100644 index 00000000..127e1eb3 --- /dev/null +++ b/src/duskps2/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 \ No newline at end of file diff --git a/src/duskpsp/CMakeLists.txt b/src/duskpsp/CMakeLists.txt new file mode 100644 index 00000000..ab9a2f91 --- /dev/null +++ b/src/duskpsp/CMakeLists.txt @@ -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} +) diff --git a/src/duskpsp/duskplatform.h b/src/duskpsp/duskplatform.h new file mode 100644 index 00000000..1e778d28 --- /dev/null +++ b/src/duskpsp/duskplatform.h @@ -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" \ No newline at end of file diff --git a/src/duskpsp/duskpsp.h b/src/duskpsp/duskpsp.h new file mode 100644 index 00000000..127e1eb3 --- /dev/null +++ b/src/duskpsp/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 \ No newline at end of file diff --git a/src/dusksdl3/CMakeLists.txt b/src/dusksdl3/CMakeLists.txt new file mode 100644 index 00000000..fe76ffc9 --- /dev/null +++ b/src/dusksdl3/CMakeLists.txt @@ -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 +) diff --git a/src/dusksdl3/display/displaysdl3.c b/src/dusksdl3/display/displaysdl3.c new file mode 100644 index 00000000..2b6df8fa --- /dev/null +++ b/src/dusksdl3/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(); +} diff --git a/src/dusksdl3/display/displaysdl3.h b/src/dusksdl3/display/displaysdl3.h new file mode 100644 index 00000000..db358a80 --- /dev/null +++ b/src/dusksdl3/display/displaysdl3.h @@ -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 + +#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); diff --git a/src/dusksdl3/duskplatform.h b/src/dusksdl3/duskplatform.h new file mode 100644 index 00000000..ab39993a --- /dev/null +++ b/src/dusksdl3/duskplatform.h @@ -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" \ No newline at end of file diff --git a/src/dusksdl3/dusksdl3.h b/src/dusksdl3/dusksdl3.h new file mode 100644 index 00000000..127e1eb3 --- /dev/null +++ b/src/dusksdl3/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 \ No newline at end of file diff --git a/src/duskvita/CMakeLists.txt b/src/duskvita/CMakeLists.txt new file mode 100644 index 00000000..ab9a2f91 --- /dev/null +++ b/src/duskvita/CMakeLists.txt @@ -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} +) diff --git a/src/duskvita/duskplatform.h b/src/duskvita/duskplatform.h new file mode 100644 index 00000000..67bfb719 --- /dev/null +++ b/src/duskvita/duskplatform.h @@ -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" \ No newline at end of file diff --git a/src/duskvita/duskvita.h b/src/duskvita/duskvita.h new file mode 100644 index 00000000..127e1eb3 --- /dev/null +++ b/src/duskvita/duskvita.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 \ No newline at end of file diff --git a/src/duskvulkan/CMakeLists.txt b/src/duskvulkan/CMakeLists.txt new file mode 100644 index 00000000..3f4408d4 --- /dev/null +++ b/src/duskvulkan/CMakeLists.txt @@ -0,0 +1,11 @@ +# 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(Vulkan REQUIRED) +target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC Vulkan::Vulkan) diff --git a/src/duskvulkan/duskplatform.h b/src/duskvulkan/duskplatform.h new file mode 100644 index 00000000..00df4cd6 --- /dev/null +++ b/src/duskvulkan/duskplatform.h @@ -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 "duskvulkan.h" \ No newline at end of file diff --git a/src/duskvulkan/duskvulkan.h b/src/duskvulkan/duskvulkan.h new file mode 100644 index 00000000..127e1eb3 --- /dev/null +++ b/src/duskvulkan/duskvulkan.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 \ No newline at end of file diff --git a/src/duskwindows/CMakeLists.txt b/src/duskwindows/CMakeLists.txt new file mode 100644 index 00000000..ab9a2f91 --- /dev/null +++ b/src/duskwindows/CMakeLists.txt @@ -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} +) diff --git a/src/duskwindows/duskplatform.h b/src/duskwindows/duskplatform.h new file mode 100644 index 00000000..cc72d788 --- /dev/null +++ b/src/duskwindows/duskplatform.h @@ -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 "duskwindows.h" diff --git a/src/duskwindows/duskwindows.h b/src/duskwindows/duskwindows.h new file mode 100644 index 00000000..127e1eb3 --- /dev/null +++ b/src/duskwindows/duskwindows.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 \ No newline at end of file