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