dqn: Organise into files and use an amalgamated build

This commit is contained in:
doyle 2023-07-05 00:04:53 +10:00
parent 807c65a253
commit 8ae369db0d
29 changed files with 12027 additions and 12206 deletions

View File

@ -59,7 +59,7 @@
#if defined(NDEBUG)
#define DQN_KECCAK_ASSERT(expr)
#else
#define DQN_KECCAK_ASSERT(expr) \
#define DQN_KECCAK_ASSERT(expr) \
do \
{ \
if (!(expr)) \

View File

@ -26,7 +26,7 @@ pushd Build
echo [ERROR] cl is not found, please put MSVC's cl on the path
exit /b 1
)
cl %compile_flags% %msvc_flags% %code_dir%dqn_unit_tests.cpp /Fe:dqn_unit_tests_msvc %link_flags%
cl %compile_flags% %msvc_flags% %code_dir%\Misc\dqn_unit_tests.cpp -I %code_dir% /Fe:dqn_unit_tests_msvc %link_flags%
REM Compiler: clang-cl
REM ------------------------------------------------------------------------
@ -34,5 +34,5 @@ pushd Build
echo [WARN] Optional clang compile via clang-cl if it's in the path, please put clang-cl on the path for this feature
exit /b 1
)
clang-cl -D DQN_TEST_WITH_MAIN %code_dir%dqn_unit_tests.cpp /Fe:dqn_unit_tests_clang -link
clang-cl -D DQN_TEST_WITH_MAIN %code_dir%\Misc\dqn_unit_tests.cpp -I %code_dir% /Fe:dqn_unit_tests_clang -link
popd

12076
dqn.h

File diff suppressed because it is too large Load Diff

BIN
dqn.rdbg

Binary file not shown.

36
dqn_containers.cpp Normal file
View File

@ -0,0 +1,36 @@
#if !defined(DQN_NO_DSMAP)
// =================================================================================================
// [$DMAP] Dqn_DSMap | DQN_NO_DSMAP | Hashtable, 70% max load, PoT size, linear probe, chain repair
// =================================================================================================
DQN_API Dqn_DSMapKey Dqn_DSMap_KeyU64NoHash(uint64_t u64)
{
Dqn_DSMapKey result = {};
result.type = Dqn_DSMapKeyType_U64NoHash;
result.payload.u64 = u64;
result.hash = DQN_CAST(uint32_t)u64;
return result;
}
DQN_API bool Dqn_DSMap_KeyEquals(Dqn_DSMapKey lhs, Dqn_DSMapKey rhs)
{
bool result = false;
if (lhs.type == rhs.type && lhs.hash == rhs.hash) {
switch (lhs.type) {
case Dqn_DSMapKeyType_Invalid: result = true; break;
case Dqn_DSMapKeyType_U64NoHash: result = true; break;
case Dqn_DSMapKeyType_U64: result = lhs.payload.u64 == rhs.payload.u64; break;
case Dqn_DSMapKeyType_Buffer: result = lhs.payload.buffer.size == rhs.payload.buffer.size &&
memcmp(lhs.payload.buffer.data, rhs.payload.buffer.data, lhs.payload.buffer.size) == 0; break;
}
}
return result;
}
DQN_API bool operator==(Dqn_DSMapKey lhs, Dqn_DSMapKey rhs)
{
bool result = Dqn_DSMap_KeyEquals(lhs, rhs);
return result;
}
#endif // !defined(DQN_NO_DSMAP)

1073
dqn_containers.h Normal file

File diff suppressed because it is too large Load Diff

66
dqn_core.cpp Normal file
View File

@ -0,0 +1,66 @@
// =================================================================================================
// [$INTR] Intrinsics | | Atomics, cpuid, ticket mutex
// =================================================================================================
#if !defined(DQN_OS_ARM64)
#if defined(DQN_COMPILER_GCC) || defined(DQN_COMPILER_CLANG)
#include <cpuid.h>
#endif
Dqn_CPUIDRegisters Dqn_CPUID(int function_id)
{
Dqn_CPUIDRegisters result = {};
#if defined(DQN_COMPILER_W32_MSVC) || defined(DQN_COMPILER_W32_CLANG)
__cpuid(DQN_CAST(int *)result.array, function_id);
#elif defined(DQN_COMPILER_GCC) || defined(DQN_COMPILER_CLANG)
__get_cpuid(function_id, &result.array[0] /*eax*/, &result.array[1] /*ebx*/, &result.array[2] /*ecx*/ , &result.array[3] /*edx*/);
#else
#error "Compiler not supported"
#endif
return result;
}
#endif // !defined(DQN_OS_ARM64)
// =================================================================================================
// [$TMUT] Dqn_TicketMutex | | Userland mutex via spinlocking atomics
// =================================================================================================
DQN_API void Dqn_TicketMutex_Begin(Dqn_TicketMutex *mutex)
{
unsigned int ticket = Dqn_Atomic_AddU32(&mutex->ticket, 1);
Dqn_TicketMutex_BeginTicket(mutex, ticket);
}
DQN_API void Dqn_TicketMutex_End(Dqn_TicketMutex *mutex)
{
Dqn_Atomic_AddU32(&mutex->serving, 1);
}
DQN_API Dqn_uint Dqn_TicketMutex_MakeTicket(Dqn_TicketMutex *mutex)
{
Dqn_uint result = Dqn_Atomic_AddU32(&mutex->ticket, 1);
return result;
}
DQN_API void Dqn_TicketMutex_BeginTicket(Dqn_TicketMutex const *mutex, Dqn_uint ticket)
{
DQN_ASSERTF(mutex->serving <= ticket,
"Mutex skipped ticket? Was ticket generated by the correct mutex via MakeTicket? ticket = %u, "
"mutex->serving = %u",
ticket,
mutex->serving);
while (ticket != mutex->serving) {
// NOTE: Use spinlock intrinsic
_mm_pause();
}
}
DQN_API bool Dqn_TicketMutex_CanLock(Dqn_TicketMutex const *mutex, Dqn_uint ticket)
{
bool result = (ticket == mutex->serving);
return result;
}
#if defined(DQN_COMPILER_W32_MSVC) || defined(DQN_COMPILER_W32_CLANG)
#if !defined(DQN_CRT_SECURE_NO_WARNINGS_PREVIOUSLY_DEFINED)
#undef _CRT_SECURE_NO_WARNINGS
#endif
#endif

488
dqn_core.h Normal file
View File

@ -0,0 +1,488 @@
// =================================================================================================
// [$CFGM] Config macros | | Compile time customisation of library
// =================================================================================================
// #define DQN_IMPLEMENTATION
// Define this in one and only one C++ file to enable the implementation
// code of the header file
//
// #define DQN_NO_ASSERT
// Turn all assertion macros to no-ops
//
// #define DQN_NO_CHECK_BREAK
// Disable debug break when a check macro's expression fails. Instead only
// the error will be logged.
//
// #define DQN_NO_WIN32_MINIMAL_HEADER
// Define this to stop this library from defining a minimal subset of Win32
// prototypes and definitions in this file. Useful for stopping redefinition
// of symbols if another library includes "Windows.h"
//
// #define DQN_STATIC_API
// Apply static to all function definitions and disable external linkage to
// other translation units.
//
// #define DQN_STB_SPRINTF_HEADER_ONLY
// Define this to stop this library from defining
// STB_SPRINTF_IMPLEMENTATION. Useful if another library uses and includes
// "stb_sprintf.h"
//
// #define DQN_MEMSET_BYTE 0
// Change the byte that DQN_MEMSET will clear memory with. By default this
// is set to 0. Some of this library API accepts are clear memory parameter
// to scrub memory after certain operations.
//
// #define DQN_LEAK_TRACING
// When defined to some allocating calls in the library will automatically
// get passed in the file name, function name, line number and an optional
// leak_msg.
#if defined(DQN_LEAK_TRACING)
#error Leak tracing not supported because we enter an infinite leak tracing loop tracing our own allocations made to tracks leaks in the internal leak table.
#endif
//
// #define DQN_DEBUG_THREAD_CONTEXT
// Define this macro to record allocation stats for arenas used in the
// thread context. The thread context arena stats can be printed by using
// Dqn_Library_DumpThreadContextArenaStat.
//
// =================================================================================================
// [$CMAC] Compiler macros | | Macros for the compiler
// =================================================================================================
// NOTE: Warning! Order is important here, clang-cl on Windows defines _MSC_VER
#if defined(_MSC_VER)
#if defined(__clang__)
#define DQN_COMPILER_W32_CLANG
#else
#define DQN_COMPILER_W32_MSVC
#endif
#elif defined(__clang__)
#define DQN_COMPILER_CLANG
#elif defined(__GNUC__)
#define DQN_COMPILER_GCC
#endif
#if defined(_WIN32)
#define DQN_OS_WIN32
#elif defined(__aarch64__) || defined(_M_ARM64)
#define DQN_OS_ARM64
#else
#define DQN_OS_UNIX
#endif
#if defined(DQN_COMPILER_W32_MSVC) || defined(DQN_COMPILER_W32_CLANG)
#if defined(_CRT_SECURE_NO_WARNINGS)
#define DQN_CRT_SECURE_NO_WARNINGS_PREVIOUSLY_DEFINED
#else
#define _CRT_SECURE_NO_WARNINGS
#endif
#endif
// =================================================================================================
// [$MACR] Macros | | Define macros used in the library
// =================================================================================================
#define DQN_FOR_UINDEX(index, size) for (Dqn_usize index = 0; index < size; index++)
#define DQN_FOR_IINDEX(index, size) for (Dqn_isize index = 0; index < size; index++)
#define Dqn_PowerOfTwoAlign(value, power_of_two) (((value) + ((power_of_two) - 1)) & ~((power_of_two) - 1))
#define Dqn_IsPowerOfTwo(value) (((value) & (value - 1)) == 0)
#define Dqn_IsPowerOfTwoAligned(value, power_of_two) (((value) & (power_of_two - 1)) == 0)
// NOTE: Alloc Macros ==============================================================================
#if !defined(DQN_ALLOC)
#define DQN_ALLOC(size) Dqn_VMem_Reserve(size, Dqn_VMemCommit_Yes, Dqn_VMemPage_ReadWrite)
#endif
#if !defined(DQN_DEALLOC)
#define DQN_DEALLOC(ptr, size) Dqn_VMem_Release(ptr, size)
#endif
// NOTE: String.h Dependnecies =====================================================================
#if !defined(DQN_MEMCPY) || !defined(DQN_MEMSET) || !defined(DQN_MEMCMP) || !defined(DQN_MEMMOVE)
#include <string.h>
#if !defined(DQN_MEMCPY)
#define DQN_MEMCPY(dest, src, count) memcpy(dest, src, count)
#endif
#if !defined(DQN_MEMSET)
#define DQN_MEMSET(dest, value, count) memset(dest, value, count)
#endif
#if !defined(DQN_MEMCMP)
#define DQN_MEMCMP(ptr1, ptr2, num) memcmp(ptr1, ptr2, num)
#endif
#if !defined(DQN_MEMMOVE)
#define DQN_MEMMOVE(dest, src, num) memmove(dest, src, num)
#endif
#endif
// NOTE: Math.h Dependnecies =======================================================================
#if !defined(DQN_SQRTF) || !defined(DQN_SINF) || !defined(DQN_COSF) || !defined(DQN_TANF)
#include <math.h>
#define DQN_SQRTF(val) sqrtf(val)
#if !defined(DQN_SINF)
#define DQN_SINF(val) sinf(val)
#endif
#if !defined(DQN_COSF)
#define DQN_COSF(val) cosf(val)
#endif
#if !defined(DQN_TANF)
#define DQN_TANF(val) tanf(val)
#endif
#endif
// NOTE: Math Macros ===============================================================================
#define DQN_PI 3.14159265359f
#define DQN_DEGREE_TO_RADIAN(degrees) ((degrees) * (DQN_PI / 180.0f))
#define DQN_RADIAN_TO_DEGREE(radians) ((radians) * (180.f * DQN_PI))
#define DQN_ABS(val) (((val) < 0) ? (-(val)) : (val))
#define DQN_MAX(a, b) ((a > b) ? (a) : (b))
#define DQN_MIN(a, b) ((a < b) ? (a) : (b))
#define DQN_CLAMP(val, lo, hi) DQN_MAX(DQN_MIN(val, hi), lo)
#define DQN_SQUARED(val) ((val) * (val))
// NOTE: Function/Variable Annotations =============================================================
#if defined(DQN_STATIC_API)
#define DQN_API static
#else
#define DQN_API
#endif
#define DQN_LOCAL_PERSIST static
#define DQN_FILE_SCOPE static
#define DQN_CAST(val) (val)
#if defined(DQN_COMPILER_W32_MSVC) || defined(DQN_COMPILER_W32_CLANG)
#define DQN_FORCE_INLINE __forceinline
#else
#define DQN_FORCE_INLINE inline __attribute__((always_inline))
#endif
// NOTE: Preprocessor Token Tricks =================================================================
#define DQN_TOKEN_COMBINE2(x, y) x ## y
#define DQN_TOKEN_COMBINE(x, y) DQN_TOKEN_COMBINE2(x, y)
#define DQN_UNIQUE_NAME(prefix) DQN_TOKEN_COMBINE(prefix, __LINE__)
#define DQN_SWAP(a, b) \
do \
{ \
auto temp = a; \
a = b; \
b = temp; \
} while (0)
// NOTE: Size Macros ===============================================================================
#define DQN_ISIZEOF(val) DQN_CAST(ptrdiff_t)sizeof(val)
#define DQN_ARRAY_UCOUNT(array) (sizeof(array)/(sizeof((array)[0])))
#define DQN_ARRAY_ICOUNT(array) (Dqn_isize)DQN_ARRAY_UCOUNT(array)
#define DQN_CHAR_COUNT(string) (sizeof(string) - 1)
// NOTE: SI Byte Macros ============================================================================
#define DQN_BYTES(val) (val)
#define DQN_KILOBYTES(val) (1024ULL * DQN_BYTES(val))
#define DQN_MEGABYTES(val) (1024ULL * DQN_KILOBYTES(val))
#define DQN_GIGABYTES(val) (1024ULL * DQN_MEGABYTES(val))
// NOTE: Time Macros ===============================================================================
#define DQN_SECONDS_TO_MS(val) ((val) * 1000.0f)
#define DQN_MINS_TO_S(val) ((val) * 60ULL)
#define DQN_HOURS_TO_S(val) (DQN_MINS_TO_S(val) * 60ULL)
#define DQN_DAYS_TO_S(val) (DQN_HOURS_TO_S(val) * 24ULL)
#define DQN_YEARS_TO_S(val) (DQN_DAYS_TO_S(val) * 365ULL)
// NOTE: Debug Macros ==============================================================================
#if !defined(DQN_DEBUG_BREAK)
#if defined(NDEBUG)
#define DQN_DEBUG_BREAK
#else
#if defined(DQN_COMPILER_W32_MSVC) || defined(DQN_COMPILER_W32_CLANG)
#define DQN_DEBUG_BREAK __debugbreak()
#elif defined(DQN_COMPILER_CLANG) || defined(DQN_COMPILER_GCC)
#include <signal.h>
#define DQN_DEBUG_BREAK raise(SIGTRAP)
#elif
#error "Unhandled compiler"
#endif
#endif
#endif
#if !defined(DQN_MEMSET_BYTE)
#define DQN_MEMSET_BYTE 0
#endif
// NOTE: Assert Macros =============================================================================
#define DQN_HARD_ASSERT(expr) DQN_HARD_ASSERTF(expr, "")
#define DQN_HARD_ASSERTF(expr, fmt, ...) \
if (!(expr)) { \
Dqn_Log_ErrorF("Hard assert triggered " #expr ". " fmt, ##__VA_ARGS__); \
DQN_DEBUG_BREAK; \
}
#if defined(DQN_NO_ASSERT)
#define DQN_ASSERTF(...)
#define DQN_ASSERT(...)
#else
#define DQN_ASSERT(expr) DQN_ASSERTF(expr, "")
#define DQN_ASSERTF(expr, fmt, ...) \
if (!(expr)) { \
Dqn_Log_ErrorF("Assert triggered " #expr ". " fmt, ##__VA_ARGS__); \
DQN_DEBUG_BREAK; \
}
#endif
#define DQN_CHECK(expr) DQN_CHECKF(expr, "")
#if defined(DQN_NO_CHECK_BREAK)
#define DQN_CHECKF(expr, fmt, ...) \
((expr) ? true : (Dqn_Log_TypeFCallSite(Dqn_LogType_Warning, DQN_CALL_SITE, fmt, ## __VA_ARGS__), false))
#else
#define DQN_CHECKF(expr, fmt, ...) \
((expr) ? true : (Dqn_Log_TypeFCallSite(Dqn_LogType_Error, DQN_CALL_SITE, fmt, ## __VA_ARGS__), DQN_DEBUG_BREAK, false))
#endif
#if 0
DQN_API bool DQN_CHECKF_(bool assertion_expr, Dqn_CallSite call_site, char const *fmt, ...)
{
bool result = assertion_expr;
if (!result) {
va_list args;
va_start(args, fmt);
Dqn_Log_TypeFVCallSite(Dqn_LogType_Error, call_site, fmt, args);
va_end(args);
DQN_DEBUG_BREAK;
}
return result;
}
#endif
#if defined(__cplusplus)
#define DQN_ZERO_INIT {}
#else
#define DQN_ZERO_INIT {0}
#endif
#define DQN_INVALID_CODE_PATHF(fmt, ...) DQN_ASSERTF(0, fmt, ##__VA_ARGS__)
#define DQN_INVALID_CODE_PATH DQN_INVALID_CODE_PATHF("Invalid code path triggered")
// NOTE: Defer Macro ===============================================================================
#if 0
#include <stdio.h>
int main()
{
DQN_DEFER { printf("Three ..\n"); };
printf("One ..\n");
printf("Two ..\n");
// One ..
// Two ..
// Three ..
return 0;
}
#endif
template <typename Procedure>
struct Dqn_Defer
{
Procedure proc;
Dqn_Defer(Procedure p) : proc(p) {}
~Dqn_Defer() { proc(); }
};
struct Dqn_DeferHelper
{
template <typename Lambda>
Dqn_Defer<Lambda> operator+(Lambda lambda) { return Dqn_Defer<Lambda>(lambda); };
};
#define DQN_DEFER const auto DQN_UNIQUE_NAME(defer_lambda_) = Dqn_DeferHelper() + [&]()
#define DQN_DEFER_LOOP(begin, end) \
for (bool DQN_UNIQUE_NAME(once) = (begin, true); \
DQN_UNIQUE_NAME(once); \
end, DQN_UNIQUE_NAME(once) = false)
// =================================================================================================
// [$TYPE] Typedefs | | Typedefs used in the library
// =================================================================================================
typedef intptr_t Dqn_isize;
typedef uintptr_t Dqn_usize;
typedef intptr_t Dqn_isize;
typedef float Dqn_f32;
typedef double Dqn_f64;
typedef unsigned int Dqn_uint;
typedef int32_t Dqn_b32;
#define DQN_USIZE_MAX UINTPTR_MAX
#define DQN_ISIZE_MAX INTPTR_MAX
#define DQN_ISIZE_MIN INTPTR_MIN
// =================================================================================================
// [$GSTR] Global Structs | | Forward declare useful structs
// =================================================================================================
struct Dqn_String8 ///< Pointer and length style UTF8 strings
{
char *data; ///< The UTF8 bytes of the string
Dqn_usize size; ///< The number of bytes in the string
#if defined(__cplusplus)
char const *begin() const { return data; } ///< Const begin iterator for range-for loops
char const *end () const { return data + size; } ///< Const end iterator for range-for loops
char *begin() { return data; } ///< Begin iterator for range-for loops
char *end () { return data + size; } ///< End iterator for range-for loops
#endif
};
// =================================================================================================
// [$INTR] Intrinsics | | Atomics, cpuid, ticket mutex
// =================================================================================================
typedef enum Dqn_ZeroMem {
Dqn_ZeroMem_No, ///< Memory can be handed out without zero-ing it out
Dqn_ZeroMem_Yes, ///< Memory should be zero-ed out before giving to the callee
} Dqn_ZeroMem;
// NOTE: Dqn_Atomic_Add/Exchange return the previous value store in the target
#if defined(DQN_COMPILER_W32_MSVC) || defined(DQN_COMPILER_W32_CLANG)
#include <intrin.h>
#define Dqn_Atomic_CompareExchange64(dest, desired_val, prev_val) _InterlockedCompareExchange64((__int64 volatile *)dest, desired_val, prev_val)
#define Dqn_Atomic_CompareExchange32(dest, desired_val, prev_val) _InterlockedCompareExchange((long volatile *)dest, desired_val, prev_val)
#define Dqn_Atomic_AddU32(target, value) _InterlockedExchangeAdd((long volatile *)target, value)
#define Dqn_Atomic_AddU64(target, value) _InterlockedExchangeAdd64((__int64 volatile *)target, value)
#define Dqn_Atomic_SubU32(target, value) Dqn_Atomic_AddU32(DQN_CAST(long volatile *)target, (long)-value)
#define Dqn_Atomic_SubU64(target, value) Dqn_Atomic_AddU64(target, (uint64_t)-value)
#define Dqn_CPUClockCycle() __rdtsc()
#define Dqn_CompilerReadBarrierAndCPUReadFence _ReadBarrier(); _mm_lfence()
#define Dqn_CompilerWriteBarrierAndCPUWriteFence _WriteBarrier(); _mm_sfence()
#elif defined(DQN_COMPILER_GCC) || defined(DQN_COMPILER_CLANG)
#if defined(__ANDROID__)
#else
#include <x86intrin.h>
#endif
#define Dqn_Atomic_AddU32(target, value) __atomic_fetch_add(target, value, __ATOMIC_ACQ_REL)
#define Dqn_Atomic_AddU64(target, value) __atomic_fetch_add(target, value, __ATOMIC_ACQ_REL)
#define Dqn_Atomic_SubU32(target, value) __atomic_fetch_sub(target, value, __ATOMIC_ACQ_REL)
#define Dqn_Atomic_SubU64(target, value) __atomic_fetch_sub(target, value, __ATOMIC_ACQ_REL)
#if defined(DQN_COMPILER_GCC)
#define Dqn_CPUClockCycle() __rdtsc()
#else
#define Dqn_CPUClockCycle() __builtin_readcyclecounter()
#endif
#define Dqn_CompilerReadBarrierAndCPUReadFence asm volatile("lfence" ::: "memory")
#define Dqn_CompilerWriteBarrierAndCPUWriteFence asm volatile("sfence" ::: "memory")
#else
#error "Compiler not supported"
#endif
/// Atomically set the value into the target using an atomic compare and swap.
/// @param[in,out] target The target pointer to set atomically
/// @param[in] value The value to set atomically into the target
/// @return The value that was last stored in the target
DQN_FORCE_INLINE uint64_t Dqn_Atomic_SetValue64(uint64_t volatile *target, uint64_t value)
{
#if defined(DQN_COMPILER_W32_MSVC) || defined(DQN_COMPILER_W32_CLANG)
__int64 result;
do {
result = *target;
} while (Dqn_Atomic_CompareExchange64(target, value, result) != result);
return DQN_CAST(uint64_t)result;
#elif defined(DQN_COMPILER_GCC) || defined(DQN_COMPILER_CLANG)
uint64_t result = __sync_lock_test_and_set(target, value);
return result;
#else
#error Unsupported compiler
#endif
}
/// Atomically set the value into the target using an atomic compare and swap.
/// @param[in,out] target The target pointer to set atomically
/// @param[in] value The value to set atomically into the target
/// @return The value that was last stored in the target
DQN_FORCE_INLINE long Dqn_Atomic_SetValue32(long volatile *target, long value)
{
#if defined(DQN_COMPILER_W32_MSVC) || defined(DQN_COMPILER_W32_CLANG)
long result;
do {
result = *target;
} while (Dqn_Atomic_CompareExchange32(target, value, result) != result);
return result;
#elif defined(DQN_COMPILER_GCC) || defined(DQN_COMPILER_CLANG)
long result = __sync_lock_test_and_set(target, value);
return result;
#else
#error Unsupported compiler
#endif
}
#if !defined(DQN_OS_ARM64)
struct Dqn_CPUIDRegisters
{
Dqn_uint array[4]; ///< Values from 'CPUID' instruction for each register (EAX, EBX, ECX, EDX)
};
/// Execute 'CPUID' instruction to query the capabilities of the current CPU.
Dqn_CPUIDRegisters Dqn_CPUID(int function_id);
#endif // DQN_OS_ARM64
// =================================================================================================
// [$TMUT] Dqn_TicketMutex | | Userland mutex via spinlocking atomics
// =================================================================================================
// A mutex implemented using an atomic compare and swap on tickets handed out
// for each critical section.
//
// This mutex serves ticket in order and will block all other threads until the
// tickets are returned in order. The thread with the oldest ticket that has
// not been returned has right of way to execute, all other threads will be
// blocked in an atomic compare and swap loop. block execution by going into an
// atomic
//
// When a thread is blocked by this mutex, a spinlock intrinsic `_mm_pause` is
// used to yield the CPU and reduce spinlock on the thread. This mutex is not
// ideal for long blocking operations. This mutex does not issue any syscalls
// and relies entirely on atomic instructions.
//
// NOTE: API
//
// @proc Dqn_TicketMutex_Begin, End
// @desc Lock and unlock the mutex respectively
//
// @proc Dqn_TicketMutex_MakeTicket
// @desc Allocate the next available ticket from the mutex for locking using
// Dqn_TicketMutex_BeginTicket().
// @param[in] mutex The mutex
// @code
// Dqn_TicketMutex mutex = {};
// unsigned int ticket = Dqn_TicketMutex_MakeTicket(&mutex);
// Dqn_TicketMutex_BeginTicket(&mutex, ticket); // Blocking call until we attain the lock
// Dqn_TicketMutex_End(&mutex);
// @endcode
//
// @proc Dqn_TicketMutex_BeginTicket
// @desc Lock the mutex using the given ticket if possible, otherwise block
// waiting until the mutex can be locked.
//
// @proc Dqn_TicketMutex_CanLock
// @desc Determine if the mutex can be locked using the given ticket number
struct Dqn_TicketMutex
{
unsigned int volatile ticket; ///< The next ticket to give out to the thread taking the mutex
unsigned int volatile serving; ///< The ticket ID to block the mutex on until it is returned
};
void Dqn_TicketMutex_Begin (Dqn_TicketMutex *mutex);
void Dqn_TicketMutex_End (Dqn_TicketMutex *mutex);
Dqn_uint Dqn_TicketMutex_MakeTicket (Dqn_TicketMutex *mutex);
void Dqn_TicketMutex_BeginTicket(Dqn_TicketMutex const *mutex, Dqn_uint ticket);
bool Dqn_TicketMutex_CanLock (Dqn_TicketMutex const *mutex, Dqn_uint ticket);
// =================================================================================================
// [$CALL] Dqn_CallSite | | Source code location/tracing
// =================================================================================================
typedef struct Dqn_CallSite {
Dqn_String8 file;
Dqn_String8 function;
unsigned int line;
} Dqn_CalSite;
#define DQN_CALL_SITE Dqn_CallSite{DQN_STRING8(__FILE__), DQN_STRING8(__func__), __LINE__}

View File

@ -1,149 +0,0 @@
#ifndef DQN_CURL_H
#define DQN_CURL_H
// -----------------------------------------------------------------------------
// NOTE: Dqn_Curl
// -----------------------------------------------------------------------------
// Define DQN_CURL_IMPLEMENTATION in one and only one file to enable the
// implementation in translation unit.
//
// curl_global_init(CURL_GLOBAL_ALL) must return CURLE_OK before anything
// in this file is used. You may cleanup curl on exit via curl_global_cleanup()
// if desired.
//
// An easy way to generate the curl commands required to query a url is to use
// CURL itself and the option to dump the command to a C-compatible file, i.e.
//
// curl --libcurl RequestToCCode.c -X POST -H "Content-Type: application/json" --data-binary "{\"jsonrpc\": \"2.0\", \"id\": \"0\", \"method\": \"get_block_count\", \"params\": []}" oxen.observer:22023/json_rpc
//
// -----------------------------------------------------------------------------
// NOTE: Example
// -----------------------------------------------------------------------------
#if 0
struct CurlWriteFunctionUserData
{
Dqn_ArenaAllocator *arena;
Dqn_StringList data;
};
size_t CurlWriteFunction(char *ptr, size_t size, size_t nmemb, void *userdata)
{
auto *user_data = DQN_CAST(CurlWriteFunctionUserData *)userdata;
Dqn_StringList_AppendStringCopy(&user_data->data, user_data->arena, Dqn_String_Init(ptr, nmemb));
DQN_ASSERT(size == 1);
return nmemb;
}
void main()
{
// NOTE: Setup Curl handle
CURL *handle = curl_easy_init();
struct curl_slist *header_list = nullptr;
header_list = curl_slist_append(header_list, "Content-Type: application/json");
curl_easy_setopt(handle, CURLOPT_HTTPHEADER, header_list);
Dqn_String post_data = DQN_STRING("{\"jsonrpc\": \"2.0\", \"id\": \"0\", \"method\": \"get_block_count\", \"params\": []}");
Dqn_Curl_SetHTTPPost(handle, "oxen.observer:22023/json_rpc", post_data.str, DQN_CAST(int)post_data.size);
// NOTE: Set write callback
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch();
CurlWriteFunctionUserData user_data = {};
user_data.arena = scratch.arena;
Dqn_Curl_SetWriteCallback(handle, CurlWriteFunction, &user_data);
// NOTE: Execute CURL query
curl_easy_perform(handle);
Dqn_String output = Dqn_StringList_Build(&user_data.string_list, scoped_arena.arena);
DQN_LOG_I("%.*s", DQN_STRING_FMT(output));
// NOTE: Cleanup
curl_slist_free_all(header_list);
curl_easy_cleanup(handle);
}
#endif
// NOTE: Warning this causes Windows.h to be included.
#define CURL_STATICLIB
#define NOMINMAX
#include <curl-7.72.0/include/curl/curl.h>
struct Dqn_CurlProcs
{
CURL *(*curl_easy_init)(void);
CURLcode (*curl_easy_getinfo)(CURL *curl, CURLINFO info, ...);
CURLcode (*curl_easy_setopt)(CURL *handle, CURLoption option, ...);
const char *(*curl_easy_strerror)(CURLcode);
void (*curl_easy_cleanup)(CURL *curl);
CURLM *(*curl_multi_init)(void);
CURLMcode (*curl_multi_perform)(CURLM *multi_handle, int *running_handles);
CURLMsg *(*curl_multi_info_read)(CURLM *multi_handle, int *msgs_in_queue);
CURLMcode (*curl_multi_remove_handle)(CURLM *multi_handle, CURL *curl_handle);
CURLMcode (*curl_multi_add_handle)(CURLM *multi_handle, CURL *curl_handle);
const char *(*curl_multi_strerror)(CURLMcode);
struct curl_slist *(*curl_slist_append)(struct curl_slist *, const char *);
void (*curl_slist_free_all)(struct curl_slist *);
};
Dqn_CurlProcs Dqn_CurlProcs_Init();
void Dqn_Curl_SetURL(CURL *handle, char const *url);
void Dqn_Curl_SetHTTPPost(CURL *handle, char const *url, char const *post_data, int post_data_size);
void Dqn_Curl_SetWriteCallback(CURL *handle, size_t (*curl_write_callback)(char *ptr, size_t size, size_t nmemb, void *userdata), void *user_data);
#endif // DQN_CURL_H
#if defined(DQN_CURL_IMPLEMENTATION)
#pragma comment (lib, "Advapi32")
#pragma comment (lib, "Crypt32")
#pragma comment (lib, "Ws2_32")
#pragma comment (lib, "Wldap32")
#pragma comment (lib, "Normaliz")
Dqn_CurlProcs Dqn_CurlProcs_Init()
{
Dqn_CurlProcs result = {};
result.curl_easy_init = curl_easy_init;
result.curl_easy_getinfo = curl_easy_getinfo;
result.curl_easy_setopt = curl_easy_setopt;
result.curl_easy_strerror = curl_easy_strerror;
result.curl_easy_cleanup = curl_easy_cleanup;
result.curl_multi_init = curl_multi_init;
result.curl_multi_perform = curl_multi_perform;
result.curl_multi_info_read = curl_multi_info_read;
result.curl_multi_remove_handle = curl_multi_remove_handle;
result.curl_multi_add_handle = curl_multi_add_handle;
result.curl_multi_strerror = curl_multi_strerror;
result.curl_slist_append = curl_slist_append;
result.curl_slist_free_all = curl_slist_free_all;
return result;
}
void Dqn_Curl_SetURL(CURL *handle, char const *url)
{
curl_easy_setopt(handle, CURLOPT_URL, url);
}
void Dqn_Curl_SetHTTPPost(CURL *handle, char const *url, char const *post_data, int post_data_size)
{
curl_easy_setopt(handle, CURLOPT_BUFFERSIZE, 102400L);
curl_easy_setopt(handle, CURLOPT_URL, url);
curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 1L);
curl_easy_setopt(handle, CURLOPT_POSTFIELDS, post_data);
curl_easy_setopt(handle, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)post_data_size);
curl_easy_setopt(handle, CURLOPT_USERAGENT, "curl/7.55.1");
curl_easy_setopt(handle, CURLOPT_MAXREDIRS, 50L);
curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "POST");
curl_easy_setopt(handle, CURLOPT_TCP_KEEPALIVE, 1L);
}
void Dqn_Curl_SetWriteCallback(CURL *handle,
size_t (*curl_write_callback)(char *ptr, size_t size, size_t nmemb, void *userdata),
void *user_data)
{
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, curl_write_callback);
curl_easy_setopt(handle, CURLOPT_WRITEDATA, user_data);
}
#endif // DQN_CURL_IMPLEMENTATION

251
dqn_hash.cpp Normal file
View File

@ -0,0 +1,251 @@
// =================================================================================================
// [$FNV1] Dqn_FNV1A | | Hash(x) -> 32/64bit via FNV1a
// =================================================================================================
// Default values recommended by: http://isthe.com/chongo/tech/comp/fnv/
DQN_API uint32_t Dqn_FNV1A32_Iterate(void const *bytes, Dqn_usize size, uint32_t hash)
{
auto buffer = DQN_CAST(uint8_t const *)bytes;
for (Dqn_usize i = 0; i < size; i++)
hash = (buffer[i] ^ hash) * 16777619 /*FNV Prime*/;
return hash;
}
DQN_API uint32_t Dqn_FNV1A32_Hash(void const *bytes, Dqn_usize size)
{
uint32_t result = Dqn_FNV1A32_Iterate(bytes, size, DQN_FNV1A32_SEED);
return result;
}
DQN_API uint64_t Dqn_FNV1A64_Iterate(void const *bytes, Dqn_usize size, uint64_t hash)
{
auto buffer = DQN_CAST(uint8_t const *)bytes;
for (Dqn_usize i = 0; i < size; i++)
hash = (buffer[i] ^ hash) * 1099511628211 /*FNV Prime*/;
return hash;
}
DQN_API uint64_t Dqn_FNV1A64_Hash(void const *bytes, Dqn_usize size)
{
uint64_t result = Dqn_FNV1A64_Iterate(bytes, size, DQN_FNV1A64_SEED);
return result;
}
// =================================================================================================
// [$MMUR] Dqn_MurmurHash3 | | Hash(x) -> 32/128bit via MurmurHash3
// =================================================================================================
#if defined(DQN_COMPILER_W32_MSVC) || defined(DQN_COMPILER_W32_CLANG)
#define DQN_MMH3_ROTL32(x, y) _rotl(x, y)
#define DQN_MMH3_ROTL64(x, y) _rotl64(x, y)
#else
#define DQN_MMH3_ROTL32(x, y) ((x) << (y)) | ((x) >> (32 - (y)))
#define DQN_MMH3_ROTL64(x, y) ((x) << (y)) | ((x) >> (64 - (y)))
#endif
//-----------------------------------------------------------------------------
// Block read - if your platform needs to do endian-swapping or can only
// handle aligned reads, do the conversion here
DQN_FORCE_INLINE uint32_t Dqn_MurmurHash3_GetBlock32(uint32_t const *p, int i)
{
return p[i];
}
DQN_FORCE_INLINE uint64_t Dqn_MurmurHash3_GetBlock64(uint64_t const *p, int i)
{
return p[i];
}
//-----------------------------------------------------------------------------
// Finalization mix - force all bits of a hash block to avalanche
DQN_FORCE_INLINE uint32_t Dqn_MurmurHash3_FMix32(uint32_t h)
{
h ^= h >> 16;
h *= 0x85ebca6b;
h ^= h >> 13;
h *= 0xc2b2ae35;
h ^= h >> 16;
return h;
}
DQN_FORCE_INLINE uint64_t Dqn_MurmurHash3_FMix64(uint64_t k)
{
k ^= k >> 33;
k *= 0xff51afd7ed558ccd;
k ^= k >> 33;
k *= 0xc4ceb9fe1a85ec53;
k ^= k >> 33;
return k;
}
DQN_API uint32_t Dqn_MurmurHash3_x86U32(void const *key, int len, uint32_t seed)
{
const uint8_t *data = (const uint8_t *)key;
const int nblocks = len / 4;
uint32_t h1 = seed;
const uint32_t c1 = 0xcc9e2d51;
const uint32_t c2 = 0x1b873593;
//----------
// body
const uint32_t *blocks = (const uint32_t *)(data + nblocks * 4);
for (int i = -nblocks; i; i++)
{
uint32_t k1 = Dqn_MurmurHash3_GetBlock32(blocks, i);
k1 *= c1;
k1 = DQN_MMH3_ROTL32(k1, 15);
k1 *= c2;
h1 ^= k1;
h1 = DQN_MMH3_ROTL32(h1, 13);
h1 = h1 * 5 + 0xe6546b64;
}
//----------
// tail
const uint8_t *tail = (const uint8_t *)(data + nblocks * 4);
uint32_t k1 = 0;
switch (len & 3)
{
case 3:
k1 ^= tail[2] << 16;
case 2:
k1 ^= tail[1] << 8;
case 1:
k1 ^= tail[0];
k1 *= c1;
k1 = DQN_MMH3_ROTL32(k1, 15);
k1 *= c2;
h1 ^= k1;
};
//----------
// finalization
h1 ^= len;
h1 = Dqn_MurmurHash3_FMix32(h1);
return h1;
}
DQN_API Dqn_MurmurHash3 Dqn_MurmurHash3_x64U128(void const *key, int len, uint32_t seed)
{
const uint8_t *data = (const uint8_t *)key;
const int nblocks = len / 16;
uint64_t h1 = seed;
uint64_t h2 = seed;
const uint64_t c1 = 0x87c37b91114253d5;
const uint64_t c2 = 0x4cf5ad432745937f;
//----------
// body
const uint64_t *blocks = (const uint64_t *)(data);
for (int i = 0; i < nblocks; i++)
{
uint64_t k1 = Dqn_MurmurHash3_GetBlock64(blocks, i * 2 + 0);
uint64_t k2 = Dqn_MurmurHash3_GetBlock64(blocks, i * 2 + 1);
k1 *= c1;
k1 = DQN_MMH3_ROTL64(k1, 31);
k1 *= c2;
h1 ^= k1;
h1 = DQN_MMH3_ROTL64(h1, 27);
h1 += h2;
h1 = h1 * 5 + 0x52dce729;
k2 *= c2;
k2 = DQN_MMH3_ROTL64(k2, 33);
k2 *= c1;
h2 ^= k2;
h2 = DQN_MMH3_ROTL64(h2, 31);
h2 += h1;
h2 = h2 * 5 + 0x38495ab5;
}
//----------
// tail
const uint8_t *tail = (const uint8_t *)(data + nblocks * 16);
uint64_t k1 = 0;
uint64_t k2 = 0;
switch (len & 15)
{
case 15:
k2 ^= ((uint64_t)tail[14]) << 48;
case 14:
k2 ^= ((uint64_t)tail[13]) << 40;
case 13:
k2 ^= ((uint64_t)tail[12]) << 32;
case 12:
k2 ^= ((uint64_t)tail[11]) << 24;
case 11:
k2 ^= ((uint64_t)tail[10]) << 16;
case 10:
k2 ^= ((uint64_t)tail[9]) << 8;
case 9:
k2 ^= ((uint64_t)tail[8]) << 0;
k2 *= c2;
k2 = DQN_MMH3_ROTL64(k2, 33);
k2 *= c1;
h2 ^= k2;
case 8:
k1 ^= ((uint64_t)tail[7]) << 56;
case 7:
k1 ^= ((uint64_t)tail[6]) << 48;
case 6:
k1 ^= ((uint64_t)tail[5]) << 40;
case 5:
k1 ^= ((uint64_t)tail[4]) << 32;
case 4:
k1 ^= ((uint64_t)tail[3]) << 24;
case 3:
k1 ^= ((uint64_t)tail[2]) << 16;
case 2:
k1 ^= ((uint64_t)tail[1]) << 8;
case 1:
k1 ^= ((uint64_t)tail[0]) << 0;
k1 *= c1;
k1 = DQN_MMH3_ROTL64(k1, 31);
k1 *= c2;
h1 ^= k1;
};
//----------
// finalization
h1 ^= len;
h2 ^= len;
h1 += h2;
h2 += h1;
h1 = Dqn_MurmurHash3_FMix64(h1);
h2 = Dqn_MurmurHash3_FMix64(h2);
h1 += h2;
h2 += h1;
Dqn_MurmurHash3 result = {};
result.e[0] = h1;
result.e[1] = h2;
return result;
}

40
dqn_hash.h Normal file
View File

@ -0,0 +1,40 @@
// =================================================================================================
// [$FNV1] Dqn_FNV1A | | Hash(x) -> 32/64bit via FNV1a
// =================================================================================================
#ifndef DQN_FNV1A32_SEED
#define DQN_FNV1A32_SEED 2166136261U
#endif
#ifndef DQN_FNV1A64_SEED
#define DQN_FNV1A64_SEED 14695981039346656037ULL
#endif
/// @begincode
/// char buffer1[128] = {random bytes};
/// char buffer2[128] = {random bytes};
/// uint64_t hash = Dqn_FNV1A64_Hash(buffer1, sizeof(buffer1));
/// hash = Dqn_FNV1A64_Iterate(buffer2, sizeof(buffer2), hash); // subsequent hashing
/// @endcode
DQN_API uint32_t Dqn_FNV1A32_Hash (void const *bytes, Dqn_usize size);
DQN_API uint64_t Dqn_FNV1A64_Hash (void const *bytes, Dqn_usize size);
DQN_API uint32_t Dqn_FNV1A32_Iterate(void const *bytes, Dqn_usize size, uint32_t hash);
DQN_API uint64_t Dqn_FNV1A64_Iterate(void const *bytes, Dqn_usize size, uint64_t hash);
// =================================================================================================
// [$MMUR] Dqn_MurmurHash3 | | Hash(x) -> 32/128bit via MurmurHash3
// =================================================================================================
// MurmurHash3 was written by Austin Appleby, and is placed in the public
// domain. The author (Austin Appleby) hereby disclaims copyright to this source
// code.
//
// Note - The x86 and x64 versions do _not_ produce the same results, as the
// algorithms are optimized for their respective platforms. You can still
// compile and run any of them on any platform, but your performance with the
// non-native version will be less than optimal.
struct Dqn_MurmurHash3 { uint64_t e[2]; };
DQN_API uint32_t Dqn_MurmurHash3_x86U32 (void const *key, int len, uint32_t seed);
DQN_API Dqn_MurmurHash3 Dqn_MurmurHash3_x64U128(void const *key, int len, uint32_t seed);
#define Dqn_MurmurHash3_x64U128AsU64(key, len, seed) (Dqn_MurmurHash3_x64U128(key, len, seed).e[0])
#define Dqn_MurmurHash3_x64U128AsU32(key, len, seed) (DQN_CAST(uint32_t)Dqn_MurmurHash3_x64U128(key, len, seed).e[0])

450
dqn_math.cpp Normal file
View File

@ -0,0 +1,450 @@
#if !defined(DQN_NO_MATH)
// =================================================================================================
// [$MATH] Math | DQN_NO_MATH | v2i, V2, V3, V4, Mat4, Rect, RectI32, Lerp
// =================================================================================================
DQN_API Dqn_V2I Dqn_V2ToV2I(Dqn_V2 a)
{
Dqn_V2I result = Dqn_V2I(DQN_CAST(int32_t)a.x, DQN_CAST(int32_t)a.y);
return result;
}
DQN_API Dqn_V2 Dqn_V2Min(Dqn_V2 a, Dqn_V2 b)
{
Dqn_V2 result = Dqn_V2(DQN_MIN(a.x, b.x), DQN_MIN(a.y, b.y));
return result;
}
DQN_API Dqn_V2 Dqn_V2Max(Dqn_V2 a, Dqn_V2 b)
{
Dqn_V2 result = Dqn_V2(DQN_MAX(a.x, b.x), DQN_MAX(a.y, b.y));
return result;
}
DQN_API Dqn_V2 Dqn_V2Abs(Dqn_V2 a)
{
Dqn_V2 result = Dqn_V2(DQN_ABS(a.x), DQN_ABS(a.y));
return result;
}
DQN_API Dqn_f32 Dqn_V2Dot(Dqn_V2 a, Dqn_V2 b)
{
Dqn_f32 result = (a.x * b.x) + (a.y * b.y);
return result;
}
DQN_API Dqn_f32 Dqn_V2LengthSq(Dqn_V2 a, Dqn_V2 b)
{
Dqn_f32 x_side = b.x - a.x;
Dqn_f32 y_side = b.y - a.y;
Dqn_f32 result = DQN_SQUARED(x_side) + DQN_SQUARED(y_side);
return result;
}
DQN_API Dqn_V2 Dqn_V2Normalise(Dqn_V2 a)
{
Dqn_f32 length_sq = DQN_SQUARED(a.x) + DQN_SQUARED(a.y);
Dqn_f32 length = DQN_SQRTF(length_sq);
Dqn_V2 result = a / length;
return result;
}
DQN_API Dqn_V2 Dqn_V2Perpendicular(Dqn_V2 a)
{
Dqn_V2 result = Dqn_V2(-a.y, a.x);
return result;
}
// NOTE: Dqn_V3 Implementation
// -------------------------------------------------------------------------------------------------
DQN_API Dqn_f32 Dqn_V3LengthSq(Dqn_V3 a)
{
Dqn_f32 result = DQN_SQUARED(a.x) + DQN_SQUARED(a.y) + DQN_SQUARED(a.z);
return result;
}
DQN_API Dqn_f32 Dqn_V3Length(Dqn_V3 a)
{
Dqn_f32 length_sq = DQN_SQUARED(a.x) + DQN_SQUARED(a.y) + DQN_SQUARED(a.z);
Dqn_f32 result = DQN_SQRTF(length_sq);
return result;
}
DQN_API Dqn_V3 Dqn_V3Normalise(Dqn_V3 a)
{
Dqn_f32 length = Dqn_V3Length(a);
Dqn_V3 result = a / length;
return result;
}
// NOTE: Dqn_V4 Implementation
// -------------------------------------------------------------------------------------------------
DQN_API Dqn_f32 Dqn_V4Dot(Dqn_V4 a, Dqn_V4 b)
{
Dqn_f32 result = (a.x * b.x) + (a.y * b.y) + (a.z * b.z) + (a.w * b.w);
return result;
}
// NOTE: Dqn_M4 Implementation
// -------------------------------------------------------------------------------------------------
DQN_API Dqn_M4 Dqn_M4_Identity()
{
Dqn_M4 result =
{{
{1, 0, 0, 0},
{0, 1, 0, 0},
{0, 0, 1, 0},
{0, 0, 0, 1},
}};
return result;
}
DQN_API Dqn_M4 Dqn_M4_ScaleF(Dqn_f32 x, Dqn_f32 y, Dqn_f32 z)
{
Dqn_M4 result =
{{
{x, 0, 0, 0},
{0, y, 0, 0},
{0, 0, z, 0},
{0, 0, 0, 1},
}};
return result;
}
DQN_API Dqn_M4 Dqn_M4_Scale(Dqn_V3 xyz)
{
Dqn_M4 result =
{{
{xyz.x, 0, 0, 0},
{0, xyz.y, 0, 0},
{0, 0, xyz.z, 0},
{0, 0, 0, 1},
}};
return result;
}
DQN_API Dqn_M4 Dqn_M4_TranslateF(Dqn_f32 x, Dqn_f32 y, Dqn_f32 z)
{
Dqn_M4 result =
{{
{1, 0, 0, 0},
{0, 1, 0, 0},
{0, 0, 1, 0},
{x, y, z, 1},
}};
return result;
}
DQN_API Dqn_M4 Dqn_M4_Translate(Dqn_V3 xyz)
{
Dqn_M4 result =
{{
{1, 0, 0, 0},
{0, 1, 0, 0},
{0, 0, 1, 0},
{xyz.x, xyz.y, xyz.z, 1},
}};
return result;
}
DQN_API Dqn_M4 Dqn_M4_Transpose(Dqn_M4 mat)
{
Dqn_M4 result;
for (int col = 0; col < 4; col++)
for (int row = 0; row < 4; row++)
result.columns[col][row] = mat.columns[row][col];
return result;
}
DQN_API Dqn_M4 Dqn_M4_Rotate(Dqn_V3 axis01, Dqn_f32 radians)
{
DQN_ASSERTF(DQN_ABS(Dqn_V3Length(axis01) - 1.f) <= 0.01f,
"Rotation axis must be normalised, length = %f",
Dqn_V3Length(axis01));
Dqn_f32 sin = DQN_SINF(radians);
Dqn_f32 cos = DQN_COSF(radians);
Dqn_f32 one_minus_cos = 1.f - cos;
Dqn_f32 x = axis01.x;
Dqn_f32 y = axis01.y;
Dqn_f32 z = axis01.z;
Dqn_f32 x2 = DQN_SQUARED(x);
Dqn_f32 y2 = DQN_SQUARED(y);
Dqn_f32 z2 = DQN_SQUARED(z);
Dqn_M4 result =
{{
{cos + x2*one_minus_cos, y*x*one_minus_cos + z*sin, z*x*one_minus_cos - y*sin, 0}, // Col 1
{x*y*one_minus_cos - z*sin, cos + y2*one_minus_cos, z*y*one_minus_cos + x*sin, 0}, // Col 2
{x*z*one_minus_cos + y*sin, y*z*one_minus_cos - x*sin, cos + z2*one_minus_cos, 0}, // Col 3
{0, 0, 0, 1}, // Col 4
}};
return result;
}
DQN_API Dqn_M4 Dqn_M4_Orthographic(Dqn_f32 left, Dqn_f32 right, Dqn_f32 bottom, Dqn_f32 top, Dqn_f32 z_near, Dqn_f32 z_far)
{
// NOTE: Here is the matrix in column major for readability. Below it's
// transposed due to how you have to declare column major matrices in C/C++.
//
// m = [2/r-l, 0, 0, -1*(r+l)/(r-l)]
// [0, 2/t-b, 0, 1*(t+b)/(t-b)]
// [0, 0, -2/f-n, -1*(f+n)/(f-n)]
// [0, 0, 0, 1 ]
Dqn_M4 result =
{{
{2.f / (right - left), 0.f, 0.f, 0.f},
{0.f, 2.f / (top - bottom), 0.f, 0.f},
{0.f, 0.f, -2.f / (z_far - z_near), 0.f},
{(-1.f * (right + left)) / (right - left), (-1.f * (top + bottom)) / (top - bottom), (-1.f * (z_far + z_near)) / (z_far - z_near), 1.f},
}};
return result;
}
DQN_API Dqn_M4 Dqn_M4_Perspective(Dqn_f32 fov /*radians*/, Dqn_f32 aspect, Dqn_f32 z_near, Dqn_f32 z_far)
{
Dqn_f32 tan_fov = DQN_TANF(fov / 2.f);
Dqn_M4 result =
{{
{1.f / (aspect * tan_fov), 0.f, 0.f, 0.f},
{0, 1.f / tan_fov, 0.f, 0.f},
{0.f, 0.f, (z_near + z_far) / (z_near - z_far), -1.f},
{0.f, 0.f, (2.f * z_near * z_far)/(z_near - z_far), 0.f},
}};
return result;
}
DQN_API Dqn_M4 Dqn_M4_Add(Dqn_M4 lhs, Dqn_M4 rhs)
{
Dqn_M4 result;
for (int col = 0; col < 4; col++)
{
for (int it = 0; it < 4; it++)
result.columns[col][it] = lhs.columns[col][it] + rhs.columns[col][it];
}
return result;
}
DQN_API Dqn_M4 Dqn_M4_Sub(Dqn_M4 lhs, Dqn_M4 rhs)
{
Dqn_M4 result;
for (int col = 0; col < 4; col++)
{
for (int it = 0; it < 4; it++)
result.columns[col][it] = lhs.columns[col][it] - rhs.columns[col][it];
}
return result;
}
DQN_API Dqn_M4 Dqn_M4_Mul(Dqn_M4 lhs, Dqn_M4 rhs)
{
Dqn_M4 result;
for (int col = 0; col < 4; col++)
{
for (int row = 0; row < 4; row++)
{
Dqn_f32 sum = 0;
for (int f32_it = 0; f32_it < 4; f32_it++)
sum += lhs.columns[f32_it][row] * rhs.columns[col][f32_it];
result.columns[col][row] = sum;
}
}
return result;
}
DQN_API Dqn_M4 Dqn_M4_Div(Dqn_M4 lhs, Dqn_M4 rhs)
{
Dqn_M4 result;
for (int col = 0; col < 4; col++)
{
for (int it = 0; it < 4; it++)
result.columns[col][it] = lhs.columns[col][it] / rhs.columns[col][it];
}
return result;
}
DQN_API Dqn_M4 Dqn_M4_AddF(Dqn_M4 lhs, Dqn_f32 rhs)
{
Dqn_M4 result;
for (int col = 0; col < 4; col++)
{
for (int it = 0; it < 4; it++)
result.columns[col][it] = lhs.columns[col][it] + rhs;
}
return result;
}
DQN_API Dqn_M4 Dqn_M4_SubF(Dqn_M4 lhs, Dqn_f32 rhs)
{
Dqn_M4 result;
for (int col = 0; col < 4; col++)
{
for (int it = 0; it < 4; it++)
result.columns[col][it] = lhs.columns[col][it] - rhs;
}
return result;
}
DQN_API Dqn_M4 Dqn_M4_MulF(Dqn_M4 lhs, Dqn_f32 rhs)
{
Dqn_M4 result;
for (int col = 0; col < 4; col++)
{
for (int it = 0; it < 4; it++)
result.columns[col][it] = lhs.columns[col][it] * rhs;
}
return result;
}
DQN_API Dqn_M4 Dqn_M4_DivF(Dqn_M4 lhs, Dqn_f32 rhs)
{
Dqn_M4 result;
for (int col = 0; col < 4; col++)
{
for (int it = 0; it < 4; it++)
result.columns[col][it] = lhs.columns[col][it] / rhs;
}
return result;
}
#if !defined(DQN_NO_FSTRING8)
DQN_API Dqn_FString8<256> Dqn_M4_ColumnMajorString(Dqn_M4 mat)
{
Dqn_FString8<256> result = {};
for (int row = 0; row < 4; row++) {
for (int it = 0; it < 4; it++) {
if (it == 0) Dqn_FString8_Append(&result, DQN_STRING8("|"));
Dqn_FString8_AppendF(&result, "%.5f", mat.columns[it][row]);
if (it != 3) Dqn_FString8_Append(&result, DQN_STRING8(", "));
else Dqn_FString8_Append(&result, DQN_STRING8("|\n"));
}
}
return result;
}
#endif
// NOTE: Dqn_Rect
// -------------------------------------------------------------------------------------------------
DQN_API Dqn_Rect Dqn_Rect_InitFromPosAndSize(Dqn_V2 pos, Dqn_V2 size)
{
Dqn_Rect result = {};
result.min = pos;
if (size.x < 0) result.min.x -= size.x;
if (size.y < 0) result.min.y -= size.y;
result.max = result.min + Dqn_V2Abs(size);
return result;
}
DQN_API Dqn_V2 Dqn_Rect_Center(Dqn_Rect rect)
{
Dqn_V2 size = rect.max - rect.min;
Dqn_V2 result = rect.min + (size * 0.5f);
return result;
}
DQN_API bool Dqn_Rect_ContainsPoint(Dqn_Rect rect, Dqn_V2 p)
{
bool result = (p.x >= rect.min.x && p.x <= rect.max.x && p.y >= rect.min.y && p.y <= rect.max.y);
return result;
}
DQN_API bool Dqn_Rect_ContainsRect(Dqn_Rect a, Dqn_Rect b)
{
bool result = (b.min >= a.min && b.max <= a.max);
return result;
}
DQN_API Dqn_V2 Dqn_Rect_Size(Dqn_Rect rect)
{
Dqn_V2 result = rect.max - rect.min;
return result;
}
DQN_API Dqn_Rect Dqn_Rect_Move(Dqn_Rect src, Dqn_V2 move_amount)
{
Dqn_Rect result = src;
result.min += move_amount;
result.max += move_amount;
return result;
}
DQN_API Dqn_Rect Dqn_Rect_MoveTo(Dqn_Rect src, Dqn_V2 dest)
{
Dqn_V2 move_amount = dest - src.min;
Dqn_Rect result = src;
result.min += move_amount;
result.max += move_amount;
return result;
}
DQN_API bool Dqn_Rect_Intersects(Dqn_Rect a, Dqn_Rect b)
{
bool result = (a.min.x <= b.max.x && a.max.x >= b.min.x) &&
(a.min.y <= b.max.y && a.max.y >= b.min.y);
return result;
}
DQN_API Dqn_Rect Dqn_Rect_Intersection(Dqn_Rect a, Dqn_Rect b)
{
Dqn_Rect result = {};
if (Dqn_Rect_Intersects(a, b))
{
result.min.x = DQN_MAX(a.min.x, b.min.x);
result.min.y = DQN_MAX(a.min.y, b.min.y);
result.max.x = DQN_MIN(a.max.x, b.max.x);
result.max.y = DQN_MIN(a.max.y, b.max.y);
}
return result;
}
DQN_API Dqn_Rect Dqn_Rect_Union(Dqn_Rect a, Dqn_Rect b)
{
Dqn_Rect result = {};
result.min.x = DQN_MIN(a.min.x, b.min.x);
result.min.y = DQN_MIN(a.min.y, b.min.y);
result.max.x = DQN_MAX(a.max.x, b.max.x);
result.max.y = DQN_MAX(a.max.y, b.max.y);
return result;
}
DQN_API Dqn_Rect Dqn_Rect_FromRectI32(Dqn_RectI32 a)
{
Dqn_Rect result = Dqn_Rect(a.min, a.max);
return result;
}
DQN_API Dqn_V2I Dqn_RectI32_Size(Dqn_RectI32 rect)
{
Dqn_V2I result = rect.max - rect.min;
return result;
}
// NOTE: Math Utils
// -------------------------------------------------------------------------------------------------
DQN_API Dqn_V2 Dqn_Lerp_V2(Dqn_V2 a, Dqn_f32 t, Dqn_V2 b)
{
Dqn_V2 result = {};
result.x = a.x + ((b.x - a.x) * t);
result.y = a.y + ((b.y - a.y) * t);
return result;
}
DQN_API Dqn_f32 Dqn_Lerp_F32(Dqn_f32 a, Dqn_f32 t, Dqn_f32 b)
{
Dqn_f32 result = a + ((b - a) * t);
return result;
}
#endif // !defined(DQN_NO_MATH)

215
dqn_math.h Normal file
View File

@ -0,0 +1,215 @@
// =================================================================================================
// [$MATH] Math | DQN_NO_MATH | v2i, V2, V3, V4, Mat4, Rect, RectI32, Lerp
// =================================================================================================
#if !defined(DQN_NO_MATH)
struct Dqn_V2I
{
int32_t x, y;
Dqn_V2I() = default;
Dqn_V2I(Dqn_f32 x_, Dqn_f32 y_): x((int32_t)x_), y((int32_t)y_) {}
Dqn_V2I(int32_t x_, int32_t y_): x(x_), y(y_) {}
Dqn_V2I(int32_t xy): x(xy), y(xy) {}
bool operator!=(Dqn_V2I other) const { return !(*this == other); }
bool operator==(Dqn_V2I other) const { return (x == other.x) && (y == other.y); }
bool operator>=(Dqn_V2I other) const { return (x >= other.x) && (y >= other.y); }
bool operator<=(Dqn_V2I other) const { return (x <= other.x) && (y <= other.y); }
bool operator< (Dqn_V2I other) const { return (x < other.x) && (y < other.y); }
bool operator> (Dqn_V2I other) const { return (x > other.x) && (y > other.y); }
Dqn_V2I operator- (Dqn_V2I other) const { Dqn_V2I result(x - other.x, y - other.y); return result; }
Dqn_V2I operator+ (Dqn_V2I other) const { Dqn_V2I result(x + other.x, y + other.y); return result; }
Dqn_V2I operator* (Dqn_V2I other) const { Dqn_V2I result(x * other.x, y * other.y); return result; }
Dqn_V2I operator* (Dqn_f32 other) const { Dqn_V2I result(x * other, y * other); return result; }
Dqn_V2I operator* (int32_t other) const { Dqn_V2I result(x * other, y * other); return result; }
Dqn_V2I operator/ (Dqn_V2I other) const { Dqn_V2I result(x / other.x, y / other.y); return result; }
Dqn_V2I operator/ (Dqn_f32 other) const { Dqn_V2I result(x / other, y / other); return result; }
Dqn_V2I operator/ (int32_t other) const { Dqn_V2I result(x / other, y / other); return result; }
Dqn_V2I &operator*=(Dqn_V2I other) { *this = *this * other; return *this; }
Dqn_V2I &operator*=(Dqn_f32 other) { *this = *this * other; return *this; }
Dqn_V2I &operator*=(int32_t other) { *this = *this * other; return *this; }
Dqn_V2I &operator-=(Dqn_V2I other) { *this = *this - other; return *this; }
Dqn_V2I &operator+=(Dqn_V2I other) { *this = *this + other; return *this; }
};
struct Dqn_V2
{
Dqn_f32 x, y;
Dqn_V2() = default;
Dqn_V2(Dqn_f32 a) : x(a), y(a) {}
Dqn_V2(int32_t a) : x((Dqn_f32)a), y((Dqn_f32)a) {}
Dqn_V2(Dqn_f32 x, Dqn_f32 y): x(x), y(y) {}
Dqn_V2(int32_t x, int32_t y): x((Dqn_f32)x), y((Dqn_f32)y) {}
Dqn_V2(Dqn_V2I a) : x((Dqn_f32)a.x),y((Dqn_f32)a.y){}
bool operator!=(Dqn_V2 other) const { return !(*this == other); }
bool operator==(Dqn_V2 other) const { return (x == other.x) && (y == other.y); }
bool operator>=(Dqn_V2 other) const { return (x >= other.x) && (y >= other.y); }
bool operator<=(Dqn_V2 other) const { return (x <= other.x) && (y <= other.y); }
bool operator< (Dqn_V2 other) const { return (x < other.x) && (y < other.y); }
bool operator> (Dqn_V2 other) const { return (x > other.x) && (y > other.y); }
Dqn_V2 operator- (Dqn_V2 other) const { Dqn_V2 result(x - other.x, y - other.y); return result; }
Dqn_V2 operator+ (Dqn_V2 other) const { Dqn_V2 result(x + other.x, y + other.y); return result; }
Dqn_V2 operator* (Dqn_V2 other) const { Dqn_V2 result(x * other.x, y * other.y); return result; }
Dqn_V2 operator* (Dqn_f32 other) const { Dqn_V2 result(x * other, y * other); return result; }
Dqn_V2 operator* (int32_t other) const { Dqn_V2 result(x * other, y * other); return result; }
Dqn_V2 operator/ (Dqn_V2 other) const { Dqn_V2 result(x / other.x, y / other.y); return result; }
Dqn_V2 operator/ (Dqn_f32 other) const { Dqn_V2 result(x / other, y / other); return result; }
Dqn_V2 operator/ (int32_t other) const { Dqn_V2 result(x / other, y / other); return result; }
Dqn_V2 &operator*=(Dqn_V2 other) { *this = *this * other; return *this; }
Dqn_V2 &operator*=(Dqn_f32 other) { *this = *this * other; return *this; }
Dqn_V2 &operator*=(int32_t other) { *this = *this * other; return *this; }
Dqn_V2 &operator/=(Dqn_V2 other) { *this = *this / other; return *this; }
Dqn_V2 &operator/=(Dqn_f32 other) { *this = *this / other; return *this; }
Dqn_V2 &operator/=(int32_t other) { *this = *this / other; return *this; }
Dqn_V2 &operator-=(Dqn_V2 other) { *this = *this - other; return *this; }
Dqn_V2 &operator+=(Dqn_V2 other) { *this = *this + other; return *this; }
};
struct Dqn_V3
{
Dqn_f32 x, y, z;
Dqn_V3() = default;
Dqn_V3(Dqn_f32 a) : x(a), y(a), z(a) {}
Dqn_V3(int32_t a) : x(DQN_CAST(Dqn_f32)a), y(DQN_CAST(Dqn_f32)a), z(DQN_CAST(Dqn_f32)a) {}
Dqn_V3(Dqn_f32 x, Dqn_f32 y, Dqn_f32 z): x(x), y(y), z(z) {}
Dqn_V3(int32_t x, int32_t y, Dqn_f32 z): x(DQN_CAST(Dqn_f32)x), y(DQN_CAST(Dqn_f32)y), z(DQN_CAST(Dqn_f32)z) {}
Dqn_V3(Dqn_V2 xy, Dqn_f32 z) : x(xy.x), y(xy.y), z(z) {}
bool operator!= (Dqn_V3 other) const { return !(*this == other); }
bool operator== (Dqn_V3 other) const { return (x == other.x) && (y == other.y) && (z == other.z); }
bool operator>= (Dqn_V3 other) const { return (x >= other.x) && (y >= other.y) && (z >= other.z); }
bool operator<= (Dqn_V3 other) const { return (x <= other.x) && (y <= other.y) && (z <= other.z); }
bool operator< (Dqn_V3 other) const { return (x < other.x) && (y < other.y) && (z < other.z); }
bool operator> (Dqn_V3 other) const { return (x > other.x) && (y > other.y) && (z > other.z); }
Dqn_V3 operator- (Dqn_V3 other) const { Dqn_V3 result(x - other.x, y - other.y, z - other.z); return result; }
Dqn_V3 operator+ (Dqn_V3 other) const { Dqn_V3 result(x + other.x, y + other.y, z + other.z); return result; }
Dqn_V3 operator* (Dqn_V3 other) const { Dqn_V3 result(x * other.x, y * other.y, z * other.z); return result; }
Dqn_V3 operator* (Dqn_f32 other) const { Dqn_V3 result(x * other, y * other, z * other); return result; }
Dqn_V3 operator* (int32_t other) const { Dqn_V3 result(x * other, y * other, z * other); return result; }
Dqn_V3 operator/ (Dqn_V3 other) const { Dqn_V3 result(x / other.x, y / other.y, z / other.z); return result; }
Dqn_V3 operator/ (Dqn_f32 other) const { Dqn_V3 result(x / other, y / other, z / other); return result; }
Dqn_V3 operator/ (int32_t other) const { Dqn_V3 result(x / other, y / other, z / other); return result; }
Dqn_V3 &operator*=(Dqn_V3 other) { *this = *this * other; return *this; }
Dqn_V3 &operator*=(Dqn_f32 other) { *this = *this * other; return *this; }
Dqn_V3 &operator*=(int32_t other) { *this = *this * other; return *this; }
Dqn_V3 &operator/=(Dqn_V3 other) { *this = *this / other; return *this; }
Dqn_V3 &operator/=(Dqn_f32 other) { *this = *this / other; return *this; }
Dqn_V3 &operator/=(int32_t other) { *this = *this / other; return *this; }
Dqn_V3 &operator-=(Dqn_V3 other) { *this = *this - other; return *this; }
Dqn_V3 &operator+=(Dqn_V3 other) { *this = *this + other; return *this; }
};
union Dqn_V4
{
struct { Dqn_f32 x, y, z, w; };
struct { Dqn_f32 r, g, b, a; };
struct { Dqn_V2 min; Dqn_V2 max; } v2;
Dqn_V3 rgb;
Dqn_f32 e[4];
Dqn_V4() = default;
Dqn_V4(Dqn_f32 xyzw) : x(xyzw), y(xyzw), z(xyzw), w(xyzw) {}
Dqn_V4(Dqn_f32 x, Dqn_f32 y, Dqn_f32 z, Dqn_f32 w): x(x), y(y), z(z), w(w) {}
Dqn_V4(int32_t x, int32_t y, int32_t z, int32_t w): x(DQN_CAST(Dqn_f32)x), y(DQN_CAST(Dqn_f32)y), z(DQN_CAST(Dqn_f32)z), w(DQN_CAST(Dqn_f32)w) {}
Dqn_V4(Dqn_V3 xyz, Dqn_f32 w) : x(xyz.x), y(xyz.y), z(xyz.z), w(w) {}
Dqn_V4(Dqn_V2 v2) : x(v2.x), y(v2.y), z(v2.x), w(v2.y) {}
bool operator!=(Dqn_V4 other) const { return !(*this == other); }
bool operator==(Dqn_V4 other) const { return (x == other.x) && (y == other.y) && (z == other.z) && (w == other.w); }
bool operator>=(Dqn_V4 other) const { return (x >= other.x) && (y >= other.y) && (z >= other.z) && (w >= other.w); }
bool operator<=(Dqn_V4 other) const { return (x <= other.x) && (y <= other.y) && (z <= other.z) && (w <= other.w); }
bool operator< (Dqn_V4 other) const { return (x < other.x) && (y < other.y) && (z < other.z) && (w < other.w); }
bool operator> (Dqn_V4 other) const { return (x > other.x) && (y > other.y) && (z > other.z) && (w > other.w); }
Dqn_V4 operator- (Dqn_V4 other) const { Dqn_V4 result(x - other.x, y - other.y, z - other.z, w - other.w); return result; }
Dqn_V4 operator+ (Dqn_V4 other) const { Dqn_V4 result(x + other.x, y + other.y, z + other.z, w + other.w); return result; }
Dqn_V4 operator* (Dqn_V4 other) const { Dqn_V4 result(x * other.x, y * other.y, z * other.z, w * other.w); return result; }
Dqn_V4 operator* (Dqn_f32 other) const { Dqn_V4 result(x * other, y * other, z * other, w * other); return result; }
Dqn_V4 operator* (int32_t other) const { Dqn_V4 result(x * other, y * other, z * other, w * other); return result; }
Dqn_V4 operator/ (Dqn_f32 other) const { Dqn_V4 result(x / other, y / other, z / other, w / other); return result; }
Dqn_V4 &operator*=(Dqn_V4 other) { *this = *this * other; return *this; }
Dqn_V4 &operator*=(Dqn_f32 other) { *this = *this * other; return *this; }
Dqn_V4 &operator*=(int32_t other) { *this = *this * other; return *this; }
Dqn_V4 &operator-=(Dqn_V4 other) { *this = *this - other; return *this; }
Dqn_V4 &operator+=(Dqn_V4 other) { *this = *this + other; return *this; }
};
// NOTE: Column major matrix
struct Dqn_M4
{
Dqn_f32 columns[4][4];
};
DQN_API Dqn_V2I Dqn_V2ToV2I(Dqn_V2 a);
DQN_API Dqn_V2 Dqn_V2Min(Dqn_V2 a, Dqn_V2 b);
DQN_API Dqn_V2 Dqn_V2Max(Dqn_V2 a, Dqn_V2 b);
DQN_API Dqn_V2 Dqn_V2Abs(Dqn_V2 a);
DQN_API Dqn_f32 Dqn_V2Dot(Dqn_V2 a, Dqn_V2 b);
DQN_API Dqn_f32 Dqn_V2LengthSq(Dqn_V2 a, Dqn_V2 b);
DQN_API Dqn_V2 Dqn_V2Normalise(Dqn_V2 a);
DQN_API Dqn_V2 Dqn_V2Perpendicular(Dqn_V2 a);
DQN_API Dqn_f32 Dqn_V3LengthSq(Dqn_V3 a);
DQN_API Dqn_f32 Dqn_V3Length(Dqn_V3 a);
DQN_API Dqn_V3 Dqn_V3Normalise(Dqn_V3 a);
DQN_API Dqn_f32 Dqn_V4Dot(Dqn_V4 a, Dqn_V4 b);
DQN_API Dqn_M4 Dqn_M4_Identity();
DQN_API Dqn_M4 Dqn_M4_ScaleF(Dqn_f32 x, Dqn_f32 y, Dqn_f32 z);
DQN_API Dqn_M4 Dqn_M4_Scale(Dqn_V3 xyz);
DQN_API Dqn_M4 Dqn_M4_TranslateF(Dqn_f32 x, Dqn_f32 y, Dqn_f32 z);
DQN_API Dqn_M4 Dqn_M4_Translate(Dqn_V3 xyz);
DQN_API Dqn_M4 Dqn_M4_Transpose(Dqn_M4 mat);
DQN_API Dqn_M4 Dqn_M4_Rotate(Dqn_V3 axis, Dqn_f32 radians);
DQN_API Dqn_M4 Dqn_M4_Orthographic(Dqn_f32 left, Dqn_f32 right, Dqn_f32 bottom, Dqn_f32 top, Dqn_f32 z_near, Dqn_f32 z_far);
DQN_API Dqn_M4 Dqn_M4_Perspective(Dqn_f32 fov /*radians*/, Dqn_f32 aspect, Dqn_f32 z_near, Dqn_f32 z_far);
DQN_API Dqn_M4 Dqn_M4_Add(Dqn_M4 lhs, Dqn_M4 rhs);
DQN_API Dqn_M4 Dqn_M4_Sub(Dqn_M4 lhs, Dqn_M4 rhs);
DQN_API Dqn_M4 Dqn_M4_Mul(Dqn_M4 lhs, Dqn_M4 rhs);
DQN_API Dqn_M4 Dqn_M4_Div(Dqn_M4 lhs, Dqn_M4 rhs);
DQN_API Dqn_M4 Dqn_M4_AddF(Dqn_M4 lhs, Dqn_f32 rhs);
DQN_API Dqn_M4 Dqn_M4_SubF(Dqn_M4 lhs, Dqn_f32 rhs);
DQN_API Dqn_M4 Dqn_M4_MulF(Dqn_M4 lhs, Dqn_f32 rhs);
DQN_API Dqn_M4 Dqn_M4_DivF(Dqn_M4 lhs, Dqn_f32 rhs);
#if !defined(DQN_NO_FSTRING8)
DQN_API Dqn_FString8<256> Dqn_M4_ColumnMajorString(Dqn_M4 mat);
#endif
struct Dqn_Rect
{
Dqn_V2 min, max;
Dqn_Rect() = default;
Dqn_Rect(Dqn_V2 min, Dqn_V2 max) : min(min), max(max) {}
Dqn_Rect(Dqn_V2I min, Dqn_V2I max) : min(min), max(max) {}
Dqn_Rect(Dqn_f32 x, Dqn_f32 y, Dqn_f32 max_x, Dqn_f32 max_y) : min(x, y), max(max_x, max_y) {}
bool operator==(Dqn_Rect other) const { return (min == other.min) && (max == other.max); }
};
struct Dqn_RectI32
{
Dqn_V2I min, max;
Dqn_RectI32() = default;
Dqn_RectI32(Dqn_V2I min, Dqn_V2I max) : min(min), max(max) {}
};
DQN_API Dqn_Rect Dqn_Rect_InitFromPosAndSize(Dqn_V2 pos, Dqn_V2 size);
DQN_API Dqn_V2 Dqn_Rect_Center(Dqn_Rect rect);
DQN_API bool Dqn_Rect_ContainsPoint(Dqn_Rect rect, Dqn_V2 p);
DQN_API bool Dqn_Rect_ContainsRect(Dqn_Rect a, Dqn_Rect b);
DQN_API Dqn_V2 Dqn_Rect_Size(Dqn_Rect rect);
DQN_API Dqn_Rect Dqn_Rect_Move(Dqn_Rect src, Dqn_V2 move_amount);
DQN_API Dqn_Rect Dqn_Rect_MoveTo(Dqn_Rect src, Dqn_V2 dest);
DQN_API bool Dqn_Rect_Intersects(Dqn_Rect a, Dqn_Rect b);
DQN_API Dqn_Rect Dqn_Rect_Intersection(Dqn_Rect a, Dqn_Rect b);
DQN_API Dqn_Rect Dqn_Rect_Union(Dqn_Rect a, Dqn_Rect b);
DQN_API Dqn_Rect Dqn_Rect_FromRectI32(Dqn_RectI32 a);
DQN_API Dqn_V2I Dqn_RectI32_Size(Dqn_RectI32 rect);
DQN_API Dqn_V2 Dqn_Lerp_V2(Dqn_V2 a, Dqn_f32 t, Dqn_V2 b);
DQN_API Dqn_f32 Dqn_Lerp_F32(Dqn_f32 a, Dqn_f32 t, Dqn_f32 b);
#endif // !defined(DQN_NO_MATH)

641
dqn_memory.cpp Normal file
View File

@ -0,0 +1,641 @@
// =================================================================================================
// [$ALLO] Dqn_Allocator | | Generic allocator interface
// =================================================================================================
DQN_API void *Dqn_Allocator_Alloc_(DQN_LEAK_TRACE_FUNCTION Dqn_Allocator allocator, size_t size, uint8_t align, Dqn_ZeroMem zero_mem)
{
void *result = NULL;
if (allocator.alloc) {
result = allocator.alloc(DQN_LEAK_TRACE_ARG size, align, zero_mem, allocator.user_context);
} else {
result = DQN_ALLOC(size);
Dqn_Library_LeakTraceAdd(DQN_LEAK_TRACE_ARG result, size);
}
return result;
}
DQN_API void Dqn_Allocator_Dealloc_(DQN_LEAK_TRACE_FUNCTION Dqn_Allocator allocator, void *ptr, size_t size)
{
if (allocator.dealloc) {
allocator.dealloc(DQN_LEAK_TRACE_ARG ptr, size, allocator.user_context);
} else {
DQN_DEALLOC(ptr, size);
Dqn_Library_LeakTraceMarkFree(DQN_LEAK_TRACE_ARG ptr);
}
}
// =================================================================================================
// [$VMEM] Dqn_VMem | | Virtual memory allocation
// =================================================================================================
DQN_FILE_SCOPE uint32_t Dqn_VMem_ConvertPageToOSFlags_(uint32_t protect)
{
DQN_ASSERT((protect & ~(Dqn_VMemPage_ReadWrite | Dqn_VMemPage_Guard)) == 0);
DQN_ASSERT(protect != 0);
uint32_t result = 0;
#if defined(DQN_OS_WIN32)
if (protect & Dqn_VMemPage_NoAccess) {
result = PAGE_NOACCESS;
} else {
if (protect & Dqn_VMemPage_ReadWrite) {
result = PAGE_READWRITE;
} else if (protect & Dqn_VMemPage_Read) {
result = PAGE_READONLY;
} else if (protect & Dqn_VMemPage_Write) {
Dqn_Log_WarningF("Windows does not support write-only pages, granting read+write access");
result = PAGE_READWRITE;
}
}
if (protect & Dqn_VMemPage_Guard)
result |= PAGE_GUARD;
DQN_ASSERTF(result != PAGE_GUARD, "Page guard is a modifier, you must also specify a page permission like read or/and write");
#else
if (protect & (Dqn_VMemPage_NoAccess | Dqn_VMemPage_Guard)) {
result = PROT_NONE;
} else {
if (protect & Dqn_VMemPage_Read)
result = PROT_READ;
if (protect & Dqn_VMemPage_Write)
result = PROT_WRITE;
}
#endif
return result;
}
DQN_API void *Dqn_VMem_Reserve(Dqn_usize size, Dqn_VMemCommit commit, uint32_t page_flags)
{
unsigned long os_page_flags = Dqn_VMem_ConvertPageToOSFlags_(page_flags);
#if defined(DQN_OS_WIN32)
unsigned long flags = MEM_RESERVE | (commit == Dqn_VMemCommit_Yes ? MEM_COMMIT : 0);
void *result = VirtualAlloc(nullptr, size, flags, os_page_flags);
#elif defined(DQN_OS_UNIX)
if (commit == Dqn_VMemCommit_Yes)
os_page_flags |= (PROT_READ | PROT_WRITE);
void *result = mmap(nullptr, size, os_page_flags, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
if (result == MAP_FAILED)
result = nullptr;
#else
#error "Missing implementation for Dqn_VMem_Reserve"
#endif
return result;
}
DQN_API bool Dqn_VMem_Commit(void *ptr, Dqn_usize size, uint32_t page_flags)
{
unsigned long os_page_flags = Dqn_VMem_ConvertPageToOSFlags_(page_flags);
#if defined(DQN_OS_WIN32)
bool result = VirtualAlloc(ptr, size, MEM_COMMIT, os_page_flags) != nullptr;
#elif defined(DQN_OS_UNIX)
bool result = mprotect(ptr, size, os_page_flags) == 0;
#else
#error "Missing implementation for Dqn_VMem_Commit"
#endif
return result;
}
DQN_API void Dqn_VMem_Decommit(void *ptr, Dqn_usize size)
{
#if defined(DQN_OS_WIN32)
VirtualFree(ptr, size, MEM_DECOMMIT);
#elif defined(DQN_OS_UNIX)
mprotect(ptr, size, PROT_NONE);
madvise(ptr, size, MADV_FREE);
#else
#error "Missing implementation for Dqn_VMem_Decommit"
#endif
}
DQN_API void Dqn_VMem_Release(void *ptr, Dqn_usize size)
{
#if defined(DQN_OS_WIN32)
(void)size;
VirtualFree(ptr, 0, MEM_RELEASE);
#elif defined(DQN_OS_UNIX)
munmap(ptr, size);
#else
#error "Missing implementation for Dqn_VMem_Release"
#endif
}
DQN_API int Dqn_VMem_Protect(void *ptr, Dqn_usize size, uint32_t page_flags)
{
if (!ptr || size == 0)
return 0;
static Dqn_String8 const ALIGNMENT_ERROR_MSG =
DQN_STRING8("Page protection requires pointers to be page aligned because we "
"can only guard memory at a multiple of the page boundary.");
DQN_ASSERTF(Dqn_IsPowerOfTwoAligned(DQN_CAST(uintptr_t)ptr, DQN_VMEM_PAGE_GRANULARITY), "%s", ALIGNMENT_ERROR_MSG.data);
DQN_ASSERTF(Dqn_IsPowerOfTwoAligned(size, DQN_VMEM_PAGE_GRANULARITY), "%s", ALIGNMENT_ERROR_MSG.data);
unsigned long os_page_flags = Dqn_VMem_ConvertPageToOSFlags_(page_flags);
#if defined(DQN_OS_WIN32)
unsigned long prev_flags = 0;
int result = VirtualProtect(ptr, size, os_page_flags, &prev_flags);
(void)prev_flags;
if (result == 0) {
Dqn_WinErrorMsg error = Dqn_Win_LastError();
DQN_ASSERTF(result, "VirtualProtect failed (%d): %.*s", error.code, error.size, error.data);
}
#else
int result = mprotect(result->memory, result->size, os_page_flags);
DQN_ASSERTF(result == 0, "mprotect failed (%d)", errno);
#endif
return result;
}
// =================================================================================================
// [$AREN] Dqn_Arena | | Growing bump allocator
// =================================================================================================
DQN_API void Dqn_Arena_CommitFromBlock(Dqn_ArenaBlock *block, Dqn_usize size, Dqn_ArenaCommit commit)
{
Dqn_usize commit_size = 0;
switch (commit) {
case Dqn_ArenaCommit_GetNewPages: {
commit_size = Dqn_PowerOfTwoAlign(size, DQN_VMEM_COMMIT_GRANULARITY);
} break;
case Dqn_ArenaCommit_EnsureSpace: {
DQN_ASSERT(block->commit >= block->used);
Dqn_usize const unused_commit_space = block->commit - block->used;
if (unused_commit_space < size)
commit_size = Dqn_PowerOfTwoAlign(size - unused_commit_space, DQN_VMEM_COMMIT_GRANULARITY);
} break;
}
if (commit_size) {
uint32_t page_flags = Dqn_VMemPage_ReadWrite;
char *commit_ptr = DQN_CAST(char *)block->memory + block->commit;
Dqn_VMem_Commit(commit_ptr, commit_size, page_flags);
block->commit += commit_size;
DQN_ASSERTF(block->commit < block->size,
"Block size should be PoT aligned and its alignment should be greater than the commit granularity [block_size=%_$$d, block_commit=%_$$d]",
block->size, block->commit);
// NOTE: Guard new pages being allocated from the block
if (block->arena->use_after_free_guard)
Dqn_VMem_Protect(commit_ptr, commit_size, page_flags | Dqn_VMemPage_Guard);
}
}
DQN_API void *Dqn_Arena_AllocateFromBlock(Dqn_ArenaBlock *block, Dqn_usize size, uint8_t align, Dqn_ZeroMem zero_mem)
{
if (!block)
return nullptr;
DQN_ASSERTF((align & (align - 1)) == 0, "Power of two alignment required");
align = DQN_MAX(align, 1);
DQN_ASSERT(block->hwm_used <= block->commit);
DQN_ASSERT(block->hwm_used >= block->used);
DQN_ASSERTF(block->commit >= block->used,
"Internal error: Committed size must always be greater than the used size [commit=%_$$zd, used=%_$$zd]",
block->commit, block->used);
// NOTE: Calculate how much we need to pad the next pointer to divvy out
// (only if it is unaligned)
uintptr_t next_ptr = DQN_CAST(uintptr_t)block->memory + block->used;
Dqn_usize align_offset = 0;
if (next_ptr & (align - 1))
align_offset = (align - (next_ptr & (align - 1)));
Dqn_usize allocation_size = size + align_offset;
if ((block->used + allocation_size) > block->size)
return nullptr;
void *result = DQN_CAST(char *)next_ptr + align_offset;
if (zero_mem == Dqn_ZeroMem_Yes) {
// NOTE: Newly commit pages are always 0-ed out, we only need to
// memset the memory that are being reused.
Dqn_usize const reused_bytes = DQN_MIN(block->hwm_used - block->used, allocation_size);
DQN_MEMSET(DQN_CAST(void *)next_ptr, DQN_MEMSET_BYTE, reused_bytes);
}
// NOTE: Ensure requested bytes are backed by physical pages from the OS
Dqn_Arena_CommitFromBlock(block, allocation_size, Dqn_ArenaCommit_EnsureSpace);
// NOTE: Unlock the pages requested
if (block->arena->use_after_free_guard)
Dqn_VMem_Protect(result, allocation_size, Dqn_VMemPage_ReadWrite);
// NOTE: Update arena
Dqn_Arena *arena = block->arena;
arena->stats.used += allocation_size;
arena->stats.used_hwm = DQN_MAX(arena->stats.used_hwm, arena->stats.used);
block->used += allocation_size;
block->hwm_used = DQN_MAX(block->hwm_used, block->used);
DQN_ASSERTF(block->used <= block->commit, "Internal error: Committed size must be greater than used size [used=%_$$zd, commit=%_$$zd]", block->used, block->commit);
DQN_ASSERTF(block->commit <= block->size, "Internal error: Allocation exceeded block capacity [commit=%_$$zd, size=%_$$zd]", block->commit, block->size);
DQN_ASSERTF(((DQN_CAST(uintptr_t)result) & (align - 1)) == 0, "Internal error: Pointer alignment failed [address=%p, align=%x]", result, align);
return result;
}
DQN_FILE_SCOPE void *Dqn_Arena_AllocatorAlloc_(DQN_LEAK_TRACE_FUNCTION size_t size, uint8_t align, Dqn_ZeroMem zero_mem, void *user_context)
{
void *result = NULL;
if (!user_context)
return result;
Dqn_Arena *arena = DQN_CAST(Dqn_Arena *)user_context;
result = Dqn_Arena_Allocate_(DQN_LEAK_TRACE_ARG arena, size, align, zero_mem);
return result;
}
DQN_FILE_SCOPE void Dqn_Arena_AllocatorDealloc_(DQN_LEAK_TRACE_FUNCTION void *ptr, size_t size, void *user_context)
{
// NOTE: No-op, arenas batch allocate and batch deallocate. Call free on the
// underlying arena, since we can't free individual pointers.
DQN_LEAK_TRACE_UNUSED;
(void)ptr;
(void)size;
(void)user_context;
}
DQN_API Dqn_Allocator Dqn_Arena_Allocator(Dqn_Arena *arena)
{
Dqn_Allocator result = {};
if (arena) {
result.user_context = arena;
result.alloc = Dqn_Arena_AllocatorAlloc_;
result.dealloc = Dqn_Arena_AllocatorDealloc_;
}
return result;
}
struct Dqn_ArenaBlockResetInfo_
{
bool free_memory;
Dqn_usize used_value;
};
// Calculate the size in bytes required to allocate the memory for the block
// (*not* including the memory required for the user in the block!)
#define Dqn_Arena_BlockMetadataSize_(arena) ((arena)->use_after_free_guard ? (Dqn_PowerOfTwoAlign(sizeof(Dqn_ArenaBlock), DQN_VMEM_PAGE_GRANULARITY)) : sizeof(Dqn_ArenaBlock))
DQN_API void Dqn_Arena_BlockReset_(DQN_LEAK_TRACE_FUNCTION Dqn_ArenaBlock *block, Dqn_ZeroMem zero_mem, Dqn_ArenaBlockResetInfo_ reset_info)
{
if (!block)
return;
if (zero_mem == Dqn_ZeroMem_Yes)
DQN_MEMSET(block->memory, DQN_MEMSET_BYTE, block->commit);
if (reset_info.free_memory) {
Dqn_usize block_metadata_size = Dqn_Arena_BlockMetadataSize_(block->arena);
Dqn_VMem_Release(block, block_metadata_size + block->size);
Dqn_Library_LeakTraceMarkFree(DQN_LEAK_TRACE_ARG block);
} else {
block->used = reset_info.used_value;
// NOTE: Guard all the committed pages again
if (block->arena->use_after_free_guard)
Dqn_VMem_Protect(block->memory, block->commit, Dqn_VMemPage_ReadWrite | Dqn_VMemPage_Guard);
}
}
DQN_API void Dqn_Arena_Reset(Dqn_Arena *arena, Dqn_ZeroMem zero_mem)
{
if (!arena)
return;
// NOTE: Zero all the blocks until we reach the first block in the list
for (Dqn_ArenaBlock *block = arena->head; block; block = block->next) {
Dqn_ArenaBlockResetInfo_ reset_info = {};
Dqn_Arena_BlockReset_(DQN_LEAK_TRACE block, zero_mem, reset_info);
}
arena->curr = arena->head;
arena->stats.used = 0;
arena->stats.wasted = 0;
}
DQN_API Dqn_ArenaTempMemory Dqn_Arena_BeginTempMemory(Dqn_Arena *arena)
{
Dqn_ArenaTempMemory result = {};
if (arena) {
arena->temp_memory_count++;
result.arena = arena;
result.head = arena->head;
result.curr = arena->curr;
result.tail = arena->tail;
result.curr_used = (arena->curr) ? arena->curr->used : 0;
result.stats = arena->stats;
}
return result;
}
DQN_API void Dqn_Arena_EndTempMemory_(DQN_LEAK_TRACE_FUNCTION Dqn_ArenaTempMemory scope)
{
if (!scope.arena)
return;
Dqn_Arena *arena = scope.arena;
if (!DQN_CHECKF(arena->temp_memory_count > 0, "End temp memory has been called without a matching begin pair on the arena"))
return;
// NOTE: Revert arena stats
arena->temp_memory_count--;
arena->stats.capacity = scope.stats.capacity;
arena->stats.used = scope.stats.used;
arena->stats.wasted = scope.stats.wasted;
arena->stats.blocks = scope.stats.blocks;
// NOTE: Revert the current block to the scope's current block
arena->head = scope.head;
arena->curr = scope.curr;
if (arena->curr) {
Dqn_ArenaBlock *curr = arena->curr;
Dqn_ArenaBlockResetInfo_ reset_info = {};
reset_info.used_value = scope.curr_used;
Dqn_Arena_BlockReset_(DQN_LEAK_TRACE_ARG curr, Dqn_ZeroMem_No, reset_info);
}
// NOTE: Free the tail blocks until we reach the scope's tail block
while (arena->tail != scope.tail) {
Dqn_ArenaBlock *tail = arena->tail;
arena->tail = tail->prev;
Dqn_ArenaBlockResetInfo_ reset_info = {};
reset_info.free_memory = true;
Dqn_Arena_BlockReset_(DQN_LEAK_TRACE_ARG tail, Dqn_ZeroMem_No, reset_info);
}
// NOTE: Reset the usage of all the blocks between the tail and current block's
if (arena->tail) {
arena->tail->next = nullptr;
for (Dqn_ArenaBlock *block = arena->tail; block && block != arena->curr; block = block->prev) {
Dqn_ArenaBlockResetInfo_ reset_info = {};
Dqn_Arena_BlockReset_(DQN_LEAK_TRACE_ARG block, Dqn_ZeroMem_No, reset_info);
}
}
}
Dqn_ArenaTempMemoryScope_::Dqn_ArenaTempMemoryScope_(DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena)
{
temp_memory = Dqn_Arena_BeginTempMemory(arena);
#if defined(DQN_LEAK_TRACING)
leak_site__ = DQN_LEAK_TRACE_ARG_NO_COMMA;
#endif
}
Dqn_ArenaTempMemoryScope_::~Dqn_ArenaTempMemoryScope_()
{
#if defined(DQN_LEAK_TRACING)
Dqn_Arena_EndTempMemory_(leak_site__, temp_memory);
#else
Dqn_Arena_EndTempMemory_(temp_memory);
#endif
}
DQN_API Dqn_ArenaStatString Dqn_Arena_StatString(Dqn_ArenaStat const *stat)
{
// NOTE: We use a non-standard format string that is only usable via
// stb sprintf that GCC warns about as an error. This pragma mutes that.
#if defined(DQN_COMPILER_GCC)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat="
#pragma GCC diagnostic ignored "-Wformat-extra-args"
#elif defined(DQN_COMPILER_W32_CLANG)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat"
#pragma GCC diagnostic ignored "-Wformat-invalid-specifier"
#pragma GCC diagnostic ignored "-Wformat-extra-args"
#endif
Dqn_ArenaStatString result = {};
int size16 = STB_SPRINTF_DECORATE(snprintf)(result.data, DQN_ARRAY_ICOUNT(result.data),
"ArenaStat{"
"used/hwm=%_$$zd/%_$$zd, "
"cap/hwm=%_$$zd/%_$$zd, "
"wasted/hwm=%_$$zd/%_$$zd, "
"blocks/hwm=%_$$u/%_$$u, "
"syscalls=%'zd"
"}",
stat->used, stat->used_hwm,
stat->capacity, stat->capacity_hwm,
stat->wasted, stat->wasted_hwm,
stat->blocks, stat->blocks_hwm,
stat->syscalls);
result.size = Dqn_Safe_SaturateCastIntToU16(size16);
#if defined(DQN_COMPILER_GCC)
#pragma GCC diagnostic pop
#elif defined(DQN_COMPILER_W32_CLANG)
#pragma GCC diagnostic pop
#endif
return result;
}
DQN_API void Dqn_Arena_LogStats(Dqn_Arena const *arena)
{
Dqn_ArenaStatString string = Dqn_Arena_StatString(&arena->stats);
Dqn_Log_InfoF("%.*s\n", DQN_STRING_FMT(string));
}
DQN_API Dqn_ArenaBlock *Dqn_Arena_Grow_(DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena, Dqn_usize size, Dqn_usize commit, uint8_t flags)
{
DQN_ASSERT(commit <= size);
if (!arena || size == 0)
return nullptr;
Dqn_usize block_metadata_size = Dqn_Arena_BlockMetadataSize_(arena);
commit = DQN_MIN(commit, size);
Dqn_usize reserve_aligned = Dqn_PowerOfTwoAlign(size + block_metadata_size, DQN_VMEM_RESERVE_GRANULARITY);
Dqn_usize commit_aligned = Dqn_PowerOfTwoAlign(commit + block_metadata_size, DQN_VMEM_COMMIT_GRANULARITY);
DQN_ASSERT(commit_aligned < reserve_aligned);
// NOTE: If the commit amount is the same as reserve size we can save one
// syscall by asking the OS to reserve+commit in the same call.
Dqn_VMemCommit commit_on_reserve = reserve_aligned == commit_aligned ? Dqn_VMemCommit_Yes : Dqn_VMemCommit_No;
uint32_t page_flags = Dqn_VMemPage_ReadWrite;
auto *result = DQN_CAST(Dqn_ArenaBlock *)Dqn_VMem_Reserve(reserve_aligned, commit_on_reserve, page_flags);
if (result) {
// NOTE: Commit the amount requested by the user if we did not commit
// on reserve the initial range.
if (commit_on_reserve == Dqn_VMemCommit_No) {
Dqn_VMem_Commit(result, commit_aligned, page_flags);
} else {
DQN_ASSERT(commit_aligned == reserve_aligned);
}
// NOTE: Sanity check memory is zero-ed out
DQN_ASSERT(result->used == 0);
DQN_ASSERT(result->next == nullptr);
DQN_ASSERT(result->prev == nullptr);
// NOTE: Setup the block
result->memory = DQN_CAST(uint8_t *)result + block_metadata_size;
result->size = reserve_aligned - block_metadata_size;
result->commit = commit_aligned - block_metadata_size;
result->flags = flags;
result->arena = arena;
// NOTE: Reset the block (this will guard the memory pages if required, otherwise no-op).
Dqn_ArenaBlockResetInfo_ reset_info = {};
Dqn_Arena_BlockReset_(DQN_LEAK_TRACE_ARG result, Dqn_ZeroMem_No, reset_info);
// NOTE: Attach the block to the arena
if (arena->tail) {
arena->tail->next = result;
result->prev = arena->tail;
} else {
DQN_ASSERT(!arena->curr);
arena->curr = result;
arena->head = result;
}
arena->tail = result;
// NOTE: Update stats
arena->stats.syscalls += (commit_on_reserve ? 1 : 2);
arena->stats.blocks += 1;
arena->stats.capacity += arena->curr->size;
arena->stats.blocks_hwm = DQN_MAX(arena->stats.blocks_hwm, arena->stats.blocks);
arena->stats.capacity_hwm = DQN_MAX(arena->stats.capacity_hwm, arena->stats.capacity);
Dqn_Library_LeakTraceAdd(DQN_LEAK_TRACE_ARG result, size);
}
return result;
}
DQN_API void *Dqn_Arena_Allocate_(DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena, Dqn_usize size, uint8_t align, Dqn_ZeroMem zero_mem)
{
DQN_ASSERTF((align & (align - 1)) == 0, "Power of two alignment required");
align = DQN_MAX(align, 1);
if (arena->use_after_free_guard) {
size = Dqn_PowerOfTwoAlign(size, DQN_VMEM_PAGE_GRANULARITY);
align = 1;
}
void *result = nullptr;
for (; !result;) {
while (arena->curr && (arena->curr->flags & Dqn_ArenaBlockFlags_Private))
arena->curr = arena->curr->next;
result = Dqn_Arena_AllocateFromBlock(arena->curr, size, align, zero_mem);
if (!result) {
if (!arena->curr || arena->curr == arena->tail) {
Dqn_usize allocation_size = size + (align - 1);
if (!Dqn_Arena_Grow(DQN_LEAK_TRACE_ARG arena, allocation_size, allocation_size /*commit*/, 0 /*flags*/)) {
break;
}
}
if (arena->curr != arena->tail)
arena->curr = arena->curr->next;
}
}
if (result)
DQN_ASSERT((arena->curr->flags & Dqn_ArenaBlockFlags_Private) == 0);
return result;
}
DQN_API void *Dqn_Arena_Copy_(DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena, void *src, Dqn_usize size, uint8_t align)
{
void *result = Dqn_Arena_Allocate_(DQN_LEAK_TRACE_ARG arena, size, align, Dqn_ZeroMem_No);
DQN_MEMCPY(result, src, size);
return result;
}
DQN_API void *Dqn_Arena_CopyZ_(DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena, void *src, Dqn_usize size, uint8_t align)
{
void *result = Dqn_Arena_Allocate_(DQN_LEAK_TRACE_ARG arena, size + 1, align, Dqn_ZeroMem_No);
DQN_MEMCPY(result, src, size);
(DQN_CAST(char *)result)[size] = 0;
return result;
}
DQN_API void Dqn_Arena_Free_(DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena, Dqn_ZeroMem zero_mem)
{
if (!arena)
return;
if (!DQN_CHECKF(arena->temp_memory_count == 0, "You cannot free an arena whilst in an temp memory region"))
return;
while (arena->tail) {
Dqn_ArenaBlock *block = arena->tail;
arena->tail = block->prev;
Dqn_ArenaBlockResetInfo_ reset_info = {};
reset_info.free_memory = true;
Dqn_Arena_BlockReset_(DQN_LEAK_TRACE_ARG block, zero_mem, reset_info);
}
arena->curr = arena->tail = nullptr;
arena->stats.capacity = 0;
arena->stats.used = 0;
arena->stats.wasted = 0;
arena->stats.blocks = 0;
}
// =================================================================================================
// [$ACAT] Dqn_ArenaCatalog | | Collate, create & manage arenas in a catalog
// =================================================================================================
DQN_API void Dqn_ArenaCatalog_Init(Dqn_ArenaCatalog *catalog, Dqn_Arena *arena)
{
catalog->arena = arena;
catalog->sentinel.next = &catalog->sentinel;
catalog->sentinel.prev = &catalog->sentinel;
}
DQN_API void Dqn_ArenaCatalog_Add(Dqn_ArenaCatalog *catalog, Dqn_Arena *arena)
{
// NOTE: We could use an atomic for appending to the sentinel but it is such
// a rare operation to append to the catalog that we don't bother.
Dqn_TicketMutex_Begin(&catalog->ticket_mutex);
// NOTE: Create item in the catalog
Dqn_ArenaCatalogItem *result = Dqn_Arena_New(catalog->arena, Dqn_ArenaCatalogItem, Dqn_ZeroMem_Yes);
result->arena = arena;
// NOTE: Add to the catalog (linked list)
Dqn_ArenaCatalogItem *sentinel = &catalog->sentinel;
result->next = sentinel;
result->prev = sentinel->prev;
result->next->prev = result;
result->prev->next = result;
Dqn_TicketMutex_End(&catalog->ticket_mutex);
Dqn_Atomic_AddU32(&catalog->arena_count, 1);
}
DQN_API Dqn_Arena *Dqn_ArenaCatalog_Alloc(Dqn_ArenaCatalog *catalog, Dqn_usize byte_size, Dqn_usize commit)
{
Dqn_TicketMutex_Begin(&catalog->ticket_mutex);
Dqn_Arena *result = Dqn_Arena_New(catalog->arena, Dqn_Arena, Dqn_ZeroMem_Yes);
Dqn_TicketMutex_End(&catalog->ticket_mutex);
Dqn_Arena_Grow(result, byte_size, commit, 0 /*flags*/);
Dqn_ArenaCatalog_Add(catalog, result);
return result;
}
DQN_API Dqn_Arena *Dqn_ArenaCatalog_AllocFV(Dqn_ArenaCatalog *catalog, Dqn_usize byte_size, Dqn_usize commit, char const *fmt, va_list args)
{
Dqn_Arena *result = Dqn_ArenaCatalog_Alloc(catalog, byte_size, commit);
result->label = Dqn_String8_InitFV(Dqn_Arena_Allocator(result), fmt, args);
return result;
}
DQN_API Dqn_Arena *Dqn_ArenaCatalog_AllocF(Dqn_ArenaCatalog *catalog, Dqn_usize byte_size, Dqn_usize commit, char const *fmt, ...)
{
va_list args;
va_start(args, fmt);
Dqn_Arena *result = Dqn_ArenaCatalog_AllocFV(catalog, byte_size, commit, fmt, args);
va_end(args);
return result;
}

339
dqn_memory.h Normal file
View File

@ -0,0 +1,339 @@
// =================================================================================================
// [$ALLO] Dqn_Allocator | | Generic allocator interface
// =================================================================================================
#if defined(DQN_LEAK_TRACING)
#if defined(DQN_NO_DSMAP)
#error "DSMap is required for allocation tracing"
#endif
#define DQN_LEAK_TRACE DQN_CALL_SITE,
#define DQN_LEAK_TRACE_NO_COMMA DQN_CALL_SITE
#define DQN_LEAK_TRACE_FUNCTION Dqn_CallSite leak_site_,
#define DQN_LEAK_TRACE_FUNCTION_NO_COMMA Dqn_CallSite leak_site_
#define DQN_LEAK_TRACE_ARG leak_site_,
#define DQN_LEAK_TRACE_ARG_NO_COMMA leak_site_
#define DQN_LEAK_TRACE_UNUSED (void)leak_site_;
#else
#define DQN_LEAK_TRACE
#define DQN_LEAK_TRACE_NO_COMMA
#define DQN_LEAK_TRACE_FUNCTION
#define DQN_LEAK_TRACE_FUNCTION_NO_COMMA
#define DQN_LEAK_TRACE_ARG
#define DQN_LEAK_TRACE_ARG_NO_COMMA
#define DQN_LEAK_TRACE_UNUSED
#endif
typedef struct Dqn_LeakTrace {
void *ptr; ///< The pointer we are tracking
Dqn_usize size; ///< Size of the allocation
Dqn_CallSite call_site; ///< Call site where the allocation was allocated
bool freed; ///< True if this pointer has been freed
Dqn_usize freed_size; ///< Size of the allocation that has been freed
Dqn_CallSite freed_call_site; ///< Call site where the allocation was freed
} Dqn_LeakTrace;
/// Allocate memory
/// @param size The number of bytes to allocate
/// @param zero_mem Flag to indicate if the allocated memory should be zero-ed out
/// @param user_context The user assigned pointer in the allocator
typedef void *Dqn_Allocator_AllocProc(DQN_LEAK_TRACE_FUNCTION size_t size, uint8_t align, Dqn_ZeroMem zero_mem, void *user_context);
/// Deallocate memory
/// @param ptr The pointer to deallocate memory for
/// @param size The number of bytes to deallocate
/// @param user_context The user assigned pointer in the allocator
typedef void Dqn_Allocator_DeallocProc(DQN_LEAK_TRACE_FUNCTION void *ptr, size_t size, void *user_context);
typedef struct Dqn_Allocator {
void *user_context; ///< User assigned pointer that is passed into the allocator functions
Dqn_Allocator_AllocProc *alloc; ///< Memory allocating routine
Dqn_Allocator_DeallocProc *dealloc; ///< Memory deallocating routine
} Dqn_Allocator;
/// Allocate bytes from the given allocator
/// @param[in] allocator The allocator to allocate bytes from
/// @param[in] size The amount of bytes to allocator
/// @param[in] zero_mem Flag to indicate if the allocated must be zero-ed out
#define Dqn_Allocator_Alloc(allocator, size, align, zero_mem) Dqn_Allocator_Alloc_(DQN_LEAK_TRACE allocator, size, align, zero_mem)
void *Dqn_Allocator_Alloc_(DQN_LEAK_TRACE_FUNCTION Dqn_Allocator allocator, size_t size, uint8_t align, Dqn_ZeroMem zero_mem);
/// Deallocate the memory from the pointer using the allocator
///
/// The pointer must originally have been allocated from the passed in
/// allocator. The size must also match the size that was originally allocated.
///
/// @param[in] allocator The allocator to allocate bytes from
/// @param[in] ptr The pointer to deallocate
/// @param[in] size The amount of bytes to deallocate.
#define Dqn_Allocator_Dealloc(allocator, ptr, size) Dqn_Allocator_Dealloc_(DQN_LEAK_TRACE allocator, ptr, size)
void Dqn_Allocator_Dealloc_(DQN_LEAK_TRACE_FUNCTION Dqn_Allocator allocator, void *ptr, size_t size);
#define Dqn_Allocator_NewArray(allocator, Type, count, zero_mem) (Type *)Dqn_Allocator_Alloc_(DQN_LEAK_TRACE allocator, sizeof(Type) * count, alignof(Type), zero_mem)
#define Dqn_Allocator_New(allocator, Type, zero_mem) (Type *)Dqn_Allocator_Alloc_(DQN_LEAK_TRACE allocator, sizeof(Type), alignof(Type), zero_mem)
// =================================================================================================
// [$VMEM] Dqn_VMem | | Virtual memory allocation
// =================================================================================================
enum Dqn_VMemCommit
{
Dqn_VMemCommit_No,
Dqn_VMemCommit_Yes,
};
enum Dqn_VMemPage
{
/// Exception on read/write with a page. This flag overrides the read/write access.
Dqn_VMemPage_NoAccess = 1 << 0,
/// Only read permitted on the page.
Dqn_VMemPage_Read = 1 << 1,
/// Only write permitted on the page. On Windows this is not supported and will be promoted to
/// read+write permissions.
Dqn_VMemPage_Write = 1 << 2,
Dqn_VMemPage_ReadWrite = Dqn_VMemPage_Read | Dqn_VMemPage_Write,
/// Modifier used in conjunction with previous flags. Raises exception on first access to the
/// page, then, the underlying protection flags are active. This is supported on Windows, on
/// other OS's using this flag will set the OS equivalent of Dqn_VMemPage_NoAccess.
Dqn_VMemPage_Guard = 1 << 3,
};
#if !defined(DQN_VMEM_PAGE_GRANULARITY)
#define DQN_VMEM_PAGE_GRANULARITY DQN_KILOBYTES(4)
#endif
#if !defined(DQN_VMEM_RESERVE_GRANULARITY)
#define DQN_VMEM_RESERVE_GRANULARITY DQN_KILOBYTES(64)
#endif
#if !defined(DQN_VMEM_COMMIT_GRANULARITY)
#define DQN_VMEM_COMMIT_GRANULARITY DQN_VMEM_PAGE_GRANULARITY
#endif
static_assert(Dqn_IsPowerOfTwo(DQN_VMEM_RESERVE_GRANULARITY),
"This library assumes that the memory allocation routines from the OS has PoT allocation granularity");
static_assert(Dqn_IsPowerOfTwo(DQN_VMEM_COMMIT_GRANULARITY),
"This library assumes that the memory allocation routines from the OS has PoT allocation granularity");
static_assert(DQN_VMEM_COMMIT_GRANULARITY < DQN_VMEM_RESERVE_GRANULARITY,
"Minimum commit size must be lower than the reserve size to avoid OOB math on pointers in this library");
DQN_API void *Dqn_VMem_Reserve (Dqn_usize size, Dqn_VMemCommit commit, uint32_t page_flags);
DQN_API bool Dqn_VMem_Commit (void *ptr, Dqn_usize size, uint32_t page_flags);
DQN_API void Dqn_VMem_Decommit(void *ptr, Dqn_usize size);
DQN_API void Dqn_VMem_Release (void *ptr, Dqn_usize size);
DQN_API int Dqn_VMem_Protect (void *ptr, Dqn_usize size, uint32_t page_flags);
// =================================================================================================
// [$AREN] Dqn_Arena | | Growing bump allocator
// =================================================================================================
//
// A bump-allocator that can grow dynamically by chaining blocks of memory
// together. The arena's memory is backed by virtual memory allowing the
// allocator to reserve and commit physical pages as memory is given from
// the block of memory.
//
// Arena's allow grouping of multiple allocations into one lifetime that is
// bound to the arena. Allocation involves a simple 'bump' of the pointer in the
// memory block. Freeing involves resetting the pointer to the start of the
// block and/or releasing the single pointer to the entire block of memory.
//
// This allocator reserves memory blocks at a 64k granularity as per the minimum
// granularity reserve size of VirtualAlloc on Windows. Memory is commit at
// a 4k granularity for similar reasons. On 64 bit platforms you have access
// to 48 bits of address space for applications, this is 256TB of address space
// you can reserve. The typical usage for this style of arena is to reserve
// as much space as you possibly need, ever, for the lifetime of the arena (e.g.
// 64GB) since the arena only commits as much as needed.
//
// NOTE: API
//
// @proc Dqn_Arena_Grow
// @desc Grow the arena's capacity by allocating a block of memory with the
// requested size. The requested size is rounded up to the nearest 64k
// boundary as that is the minimum reserve granularity (atleast on Windows)
// for virtual memory.
// @param size[in] The size in bytes to expand the capacity of the arena
// @param commit[in] The amount of bytes to request to be physically backed by
// pages from the OS.
// @param flags[in] Bit flags from 'Dqn_ArenaBlockFlags', set to 0 if none
// @return The block of memory that
//
// @proc Dqn_Arena_Allocate, Dqn_Arena_New, Dqn_Arena_NewArray,
// Dqn_Arena_NewArrayWithBlock,
// @desc Allocate byte/objects
// `Allocate` allocates bytes
// `New` allocates an object
// `NewArray` allocates an array of objects
// `NewArrayWithBlock` allocates an array of objects from the given memory 'block'
// @return A pointer to the allocated bytes/object. Null pointer on failure
//
// @proc Dqn_Arena_Copy, Dqn_Arena_CopyZ
// @desc Allocate a copy of an object's bytes. The 'Z' variant adds
// a null-terminating byte at the end of the stream.
// @return A pointer to the allocated object. Null pointer on failure.
//
// @proc Dqn_Arena_Reset
// @desc Set the arena's current block to the first block in the linked list
// of blocks and mark all blocks free.
// @param[in] zero_mem When yes, the memory is cleared using DQN_MEMSET with the
// value of DQN_MEMSET_BYTE
//
// @proc Dqn_Arena_Free
// @desc Free the arena returning all memory back to the OS
// @param[in] zero_mem: When true, the memory is cleared using DQN_MEMSET with
// the value of DQN_MEMSET_BYTE
//
// @proc Dqn_Arena_BeginTempMemory
// @desc Begin an allocation scope where all allocations between begin and end
// calls will be reverted. Useful for short-lived or highly defined lifetime
// allocations. An allocation scope is invalidated if the arena is freed
// between the begin and end call.
//
// @proc Dqn_Arena_EndTempMemory
// @desc End an allocation scope previously begun by calling begin scope.
//
// @proc Dqn_Arena_StatString
// @desc Dump the stats of the given arena to a string
// @param[in] arena The arena to dump stats for
// @return A stack allocated string containing the stats of the arena
//
// @proc Dqn_Arena_LogStats
// @desc Dump the stats of the given arena to the memory log-stream.
// @param[in] arena The arena to dump stats for
enum Dqn_ArenaBlockFlags
{
Dqn_ArenaBlockFlags_Private = 1 << 0, ///< Private blocks can only allocate its memory when used in the 'FromBlock' API variants
};
struct Dqn_ArenaStat
{
Dqn_usize capacity; ///< Total allocating capacity of the arena in bytes
Dqn_usize used; ///< Total amount of bytes used in the arena
Dqn_usize wasted; ///< Orphaned space in blocks due to allocations requiring more space than available in the active block
uint32_t blocks; ///< Number of memory blocks in the arena
Dqn_usize syscalls; ///< Number of memory allocation syscalls into the OS
Dqn_usize capacity_hwm; ///< High-water mark for 'capacity'
Dqn_usize used_hwm; ///< High-water mark for 'used'
Dqn_usize wasted_hwm; ///< High-water mark for 'wasted'
uint32_t blocks_hwm; ///< High-water mark for 'blocks'
};
struct Dqn_ArenaBlock
{
struct Dqn_Arena *arena; ///< Arena that owns this block
void *memory; ///< Backing memory of the block
Dqn_usize size; ///< Size of the block
Dqn_usize used; ///< Number of bytes used up in the block. Always less than the commit amount.
Dqn_usize hwm_used;///< High-water mark for 'used' bytes in this block
Dqn_usize commit; ///< Number of bytes in the block physically backed by pages
Dqn_ArenaBlock *prev; ///< Previous linked block
Dqn_ArenaBlock *next; ///< Next linked block
uint8_t flags; ///< Bit field for 'Dqn_ArenaBlockFlags'
};
struct Dqn_ArenaStatString
{
char data[256];
uint16_t size;
};
struct Dqn_Arena
{
bool use_after_free_guard;
uint32_t temp_memory_count;
Dqn_String8 label; ///< Optional label to describe the arena
Dqn_ArenaBlock *head; ///< Active block the arena is allocating from
Dqn_ArenaBlock *curr; ///< Active block the arena is allocating from
Dqn_ArenaBlock *tail; ///< Last block in the linked list of blocks
Dqn_ArenaStat stats; ///< Current arena stats, reset when reset usage is invoked.
};
struct Dqn_ArenaTempMemory
{
Dqn_Arena *arena; ///< Arena the scope is for
Dqn_ArenaBlock *head; ///< Head block of the arena at the beginning of the scope
Dqn_ArenaBlock *curr; ///< Current block of the arena at the beginning of the scope
Dqn_ArenaBlock *tail; ///< Tail block of the arena at the beginning of the scope
Dqn_usize curr_used; ///< Current used amount of the current block
Dqn_ArenaStat stats; ///< Stats of the arena at the beginning of the scope
};
// Automatically begin and end a temporary memory scope on object construction
// and destruction respectively.
#define DQN_ARENA_TEMP_MEMORY_SCOPE(arena) Dqn_ArenaTempMemoryScope_ DQN_UNIQUE_NAME(temp_memory_) = Dqn_ArenaTempMemoryScope_(DQN_LEAK_TRACE arena)
#define Dqn_ArenaTempMemoryScope(arena) Dqn_ArenaTempMemoryScope_(DQN_LEAK_TRACE arena)
struct Dqn_ArenaTempMemoryScope_
{
Dqn_ArenaTempMemoryScope_(DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena);
~Dqn_ArenaTempMemoryScope_();
Dqn_ArenaTempMemory temp_memory;
#if defined(DQN_LEAK_TRACING)
Dqn_CallSite leak_site__;
#endif
};
enum Dqn_ArenaCommit
{
/// Commit the pages to ensure the block has the requested commit amount.
/// No-op if the block has sufficient commit space already.
Dqn_ArenaCommit_EnsureSpace,
Dqn_ArenaCommit_GetNewPages, ///< Grow the block by the requested commit amount
};
// NOTE: Allocation ================================================================================
#define Dqn_Arena_Grow(arena, size, commit, flags) Dqn_Arena_Grow_(DQN_LEAK_TRACE arena, size, commit, flags)
#define Dqn_Arena_Allocate(arena, size, align, zero_mem) Dqn_Arena_Allocate_(DQN_LEAK_TRACE arena, size, align, zero_mem)
#define Dqn_Arena_New(arena, Type, zero_mem) (Type *)Dqn_Arena_Allocate_(DQN_LEAK_TRACE arena, sizeof(Type), alignof(Type), zero_mem)
#define Dqn_Arena_NewArray(arena, Type, count, zero_mem) (Type *)Dqn_Arena_Allocate_(DQN_LEAK_TRACE arena, sizeof(Type) * count, alignof(Type), zero_mem)
#define Dqn_Arena_NewArrayWithBlock(block, Type, count, zero_mem) (Type *)Dqn_Arena_AllocateFromBlock(block, sizeof(Type) * count, alignof(Type), zero_mem)
#define Dqn_Arena_Copy(arena, Type, src, count) (Type *)Dqn_Arena_Copy_(DQN_LEAK_TRACE arena, src, sizeof(*src) * count, alignof(Type))
#define Dqn_Arena_CopyZ(arena, Type, src, count) (Type *)Dqn_Arena_CopyZ_(DQN_LEAK_TRACE arena, src, sizeof(*src) * count, alignof(Type))
#define Dqn_Arena_Free(arena, zero_mem) Dqn_Arena_Free_(DQN_LEAK_TRACE arena, zero_mem)
DQN_API void Dqn_Arena_CommitFromBlock (Dqn_ArenaBlock *block, Dqn_usize size, Dqn_ArenaCommit commit);
DQN_API void * Dqn_Arena_AllocateFromBlock(Dqn_ArenaBlock *block, Dqn_usize size, uint8_t align, Dqn_ZeroMem zero_mem);
DQN_API Dqn_Allocator Dqn_Arena_Allocator (Dqn_Arena *arena);
DQN_API void Dqn_Arena_Reset (Dqn_Arena *arena, Dqn_ZeroMem zero_mem);
// NOTE: Temp Memory ===============================================================================
DQN_API Dqn_ArenaTempMemory Dqn_Arena_BeginTempMemory (Dqn_Arena *arena);
#define Dqn_Arena_EndTempMemory(arena_temp_memory) Dqn_Arena_EndTempMemory_(DQN_LEAK_TRACE arena_temp_memory)
// NOTE: Arena Stats ===============================================================================
DQN_API Dqn_ArenaStatString Dqn_Arena_StatString (Dqn_ArenaStat const *stat);
DQN_API void Dqn_Arena_LogStats (Dqn_Arena const *arena);
// NOTE: Internal ==================================================================================
DQN_API Dqn_ArenaBlock * Dqn_Arena_Grow_ (DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena, Dqn_usize size, Dqn_usize commit, uint8_t flags);
DQN_API void * Dqn_Arena_Allocate_ (DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena, Dqn_usize size, uint8_t align, Dqn_ZeroMem zero_mem);
DQN_API void * Dqn_Arena_Copy_ (DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena, void *src, Dqn_usize size, uint8_t alignment);
DQN_API void Dqn_Arena_Free_ (DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena, Dqn_ZeroMem zero_mem);
DQN_API void * Dqn_Arena_CopyZ_ (DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena, void *src, Dqn_usize size, uint8_t alignment);
DQN_API void Dqn_Arena_EndTempMemory_ (DQN_LEAK_TRACE_FUNCTION Dqn_ArenaTempMemory arena_temp_memory);
// =================================================================================================
// [$ACAT] Dqn_ArenaCatalog | | Collate, create & manage arenas in a catalog
// =================================================================================================
struct Dqn_ArenaCatalogItem
{
Dqn_Arena *arena;
Dqn_ArenaCatalogItem *next;
Dqn_ArenaCatalogItem *prev;
};
struct Dqn_ArenaCatalog
{
Dqn_TicketMutex ticket_mutex; ///< Mutex for adding to the linked list of arenas
Dqn_Arena *arena;
Dqn_ArenaCatalogItem sentinel;
uint16_t arena_count;
};
DQN_API void Dqn_ArenaCatalog_Init (Dqn_ArenaCatalog *catalog, Dqn_Arena *arena);
DQN_API void Dqn_ArenaCatalog_Add (Dqn_ArenaCatalog *catalog, Dqn_Arena *arena);
DQN_API Dqn_Arena *Dqn_ArenaCatalog_Alloc (Dqn_ArenaCatalog *catalog, Dqn_usize byte_size, Dqn_usize commit);
DQN_API Dqn_Arena *Dqn_ArenaCatalog_AllocFV(Dqn_ArenaCatalog *catalog, Dqn_usize byte_size, Dqn_usize commit, char const *fmt, va_list args);
DQN_API Dqn_Arena *Dqn_ArenaCatalog_AllocF (Dqn_ArenaCatalog *catalog, Dqn_usize byte_size, Dqn_usize commit, char const *fmt, ...);

596
dqn_misc.cpp Normal file
View File

@ -0,0 +1,596 @@
Dqn_Library dqn_library;
// =================================================================================================
// [$DLIB] Dqn_Library | | Library run-time behaviour configuration
// =================================================================================================
DQN_API Dqn_Library *Dqn_Library_Init(Dqn_Arena *arena)
{
Dqn_Library *result = &dqn_library;
Dqn_TicketMutex_Begin(&result->lib_mutex);
if (!result->lib_init) {
Dqn_ArenaCatalog_Init(&result->arena_catalog, arena ? arena : &result->arena_catalog_backup_arena);
result->lib_init = true;
}
Dqn_TicketMutex_End(&result->lib_mutex);
return result;
}
DQN_API void Dqn_Library_SetLogCallback(Dqn_LogProc *proc, void *user_data)
{
dqn_library.log_callback = proc;
dqn_library.log_user_data = user_data;
}
DQN_API void Dqn_Library_SetLogFile(FILE *file)
{
Dqn_TicketMutex_Begin(&dqn_library.log_file_mutex);
dqn_library.log_file = file;
dqn_library.log_to_file = file ? true : false;
Dqn_TicketMutex_End(&dqn_library.log_file_mutex);
}
DQN_API void Dqn_Library_DumpThreadContextArenaStat(Dqn_String8 file_path)
{
#if defined(DQN_DEBUG_THREAD_CONTEXT)
// NOTE: Open a file to write the arena stats to
FILE *file = nullptr;
fopen_s(&file, file_path.data, "a+b");
if (file) {
Dqn_Log_ErrorF("Failed to dump thread context arenas [file=%.*s]", DQN_STRING_FMT(file_path));
return;
}
// NOTE: Copy the stats from library book-keeping
// NOTE: Extremely short critical section, copy the stats then do our
// work on it.
Dqn_ArenaStat stats[Dqn_CArray_CountI(dqn_library.thread_context_arena_stats)];
int stats_size = 0;
Dqn_TicketMutex_Begin(&dqn_library.thread_context_mutex);
stats_size = dqn_library.thread_context_arena_stats_count;
DQN_MEMCPY(stats, dqn_library.thread_context_arena_stats, sizeof(stats[0]) * stats_size);
Dqn_TicketMutex_End(&dqn_library.thread_context_mutex);
// NOTE: Print the cumulative stat
Dqn_DateHMSTimeString now = Dqn_Date_HMSLocalTimeStringNow();
fprintf(file,
"Time=%.*s %.*s | Thread Context Arenas | Count=%d\n",
now.date_size, now.date,
now.hms_size, now.hms,
dqn_library.thread_context_arena_stats_count);
// NOTE: Write the cumulative thread arena data
{
Dqn_ArenaStat stat = {};
for (Dqn_usize index = 0; index < stats_size; index++) {
Dqn_ArenaStat const *current = stats + index;
stat.capacity += current->capacity;
stat.used += current->used;
stat.wasted += current->wasted;
stat.blocks += current->blocks;
stat.capacity_hwm = DQN_MAX(stat.capacity_hwm, current->capacity_hwm);
stat.used_hwm = DQN_MAX(stat.used_hwm, current->used_hwm);
stat.wasted_hwm = DQN_MAX(stat.wasted_hwm, current->wasted_hwm);
stat.blocks_hwm = DQN_MAX(stat.blocks_hwm, current->blocks_hwm);
}
Dqn_ArenaStatString stats_string = Dqn_Arena_StatString(&stat);
fprintf(file, " [ALL] CURR %.*s\n", stats_string.size, stats_string.data);
}
// NOTE: Print individual thread arena data
for (Dqn_usize index = 0; index < stats_size; index++) {
Dqn_ArenaStat const *current = stats + index;
Dqn_ArenaStatString current_string = Dqn_Arena_StatString(current);
fprintf(file, " [%03d] CURR %.*s\n", DQN_CAST(int)index, current_string.size, current_string.data);
}
fclose(file);
Dqn_Log_InfoF("Dumped thread context arenas [file=%.*s]", DQN_STRING_FMT(file_path));
#else
(void)file_path;
#endif // #if defined(DQN_DEBUG_THREAD_CONTEXT)
}
#if defined(DQN_LEAK_TRACING)
DQN_API void Dqn_Library_LeakTraceAdd(Dqn_CallSite call_site, void *ptr, Dqn_usize size)
{
if (!ptr)
return;
Dqn_TicketMutex_Begin(&dqn_library.alloc_table_mutex);
if (!Dqn_DSMap_IsValid(&dqn_library.alloc_table))
dqn_library.alloc_table = Dqn_DSMap_Init<Dqn_LeakTrace>(4096);
// NOTE: If the entry was not added, we are reusing a pointer that has been freed.
// TODO: Add API for always making the item but exposing a var to indicate if the item was newly created or it already existed.
Dqn_LeakTrace *trace = Dqn_DSMap_Find(&dqn_library.alloc_table, Dqn_DSMap_KeyU64(DQN_CAST(uintptr_t)ptr));
if (trace) {
DQN_HARD_ASSERTF(trace->freed, "This pointer is already in the leak tracker, however it"
" has not been freed yet. Somehow this pointer has been"
" given to the allocation table and has not being marked"
" freed with an equivalent call to LeakTraceMarkFree()"
" [ptr=%p, size=%_$$d, file=\"%.*s:%u\","
" function=\"%.*s\"]",
ptr,
size,
DQN_STRING_FMT(trace->call_site.file),
trace->call_site.line,
DQN_STRING_FMT(trace->call_site.function));
} else {
trace = Dqn_DSMap_Make(&dqn_library.alloc_table, Dqn_DSMap_KeyU64(DQN_CAST(uintptr_t)ptr));
}
trace->ptr = ptr;
trace->size = size;
trace->call_site = call_site;
Dqn_TicketMutex_End(&dqn_library.alloc_table_mutex);
}
DQN_API void Dqn_Library_LeakTraceMarkFree(Dqn_CallSite call_site, void *ptr)
{
if (!ptr)
return;
Dqn_TicketMutex_Begin(&dqn_library.alloc_table_mutex);
Dqn_LeakTrace *trace = Dqn_DSMap_Find(&dqn_library.alloc_table, Dqn_DSMap_KeyU64(DQN_CAST(uintptr_t)ptr));
DQN_HARD_ASSERTF(trace, "Allocated pointer can not be removed as it does not exist in the"
" allocation table. When this memory was allocated, the pointer was"
" not added to the allocation table [ptr=%p]",
ptr);
DQN_HARD_ASSERTF(!trace->freed,
"Double free detected, pointer was previously allocated at [ptr=%p, %_$$d, file=\"%.*s:%u\", function=\"%.*s\"]",
ptr,
trace->size,
DQN_STRING_FMT(trace->call_site.file),
trace->call_site.line,
DQN_STRING_FMT(trace->call_site.function));
trace->freed = true;
trace->freed_size = trace->size;
trace->freed_call_site = call_site;
Dqn_TicketMutex_End(&dqn_library.alloc_table_mutex);
}
#endif /// defined(DQN_LEAK_TRACING)
// =================================================================================================
// [$BITS] Dqn_Bit | | Bitset manipulation
// =================================================================================================
DQN_API void Dqn_Bit_UnsetInplace(uint64_t *flags, uint64_t bitfield)
{
*flags = (*flags & ~bitfield);
}
DQN_API void Dqn_Bit_SetInplace(uint64_t *flags, uint64_t bitfield)
{
*flags = (*flags | bitfield);
}
DQN_API bool Dqn_Bit_IsSet(uint64_t bits, uint64_t bits_to_set)
{
auto result = DQN_CAST(bool)((bits & bits_to_set) == bits_to_set);
return result;
}
DQN_API bool Dqn_Bit_IsNotSet(uint64_t bits, uint64_t bits_to_check)
{
auto result = !Dqn_Bit_IsSet(bits, bits_to_check);
return result;
}
// =================================================================================================
// [$SAFE] Dqn_Safe | | Safe arithmetic, casts, asserts
// =================================================================================================
DQN_API int64_t Dqn_Safe_AddI64(int64_t a, int64_t b)
{
int64_t result = DQN_CHECKF(a <= INT64_MAX - b, "a=%zd, b=%zd", a, b) ? (a + b) : INT64_MAX;
return result;
}
DQN_API int64_t Dqn_Safe_MulI64(int64_t a, int64_t b)
{
int64_t result = DQN_CHECKF(a <= INT64_MAX / b, "a=%zd, b=%zd", a, b) ? (a * b) : INT64_MAX;
return result;
}
DQN_API uint64_t Dqn_Safe_AddU64(uint64_t a, uint64_t b)
{
uint64_t result = DQN_CHECKF(a <= UINT64_MAX - b, "a=%zu, b=%zu", a, b) ? (a + b) : UINT64_MAX;
return result;
}
DQN_API uint64_t Dqn_Safe_SubU64(uint64_t a, uint64_t b)
{
uint64_t result = DQN_CHECKF(a >= b, "a=%zu, b=%zu", a, b) ? (a - b) : 0;
return result;
}
DQN_API uint64_t Dqn_Safe_MulU64(uint64_t a, uint64_t b)
{
uint64_t result = DQN_CHECKF(a <= UINT64_MAX / b, "a=%zu, b=%zu", a, b) ? (a * b) : UINT64_MAX;
return result;
}
DQN_API uint32_t Dqn_Safe_SubU32(uint32_t a, uint32_t b)
{
uint32_t result = DQN_CHECKF(a >= b, "a=%u, b=%u", a, b) ? (a - b) : 0;
return result;
}
// NOTE: Dqn_Safe_SaturateCastUSizeToI*
// -----------------------------------------------------------------------------
// INT*_MAX literals will be promoted to the type of uintmax_t as uintmax_t is
// the highest possible rank (unsigned > signed).
DQN_API int Dqn_Safe_SaturateCastUSizeToInt(Dqn_usize val)
{
int result = DQN_CHECK(DQN_CAST(uintmax_t)val <= INT_MAX) ? DQN_CAST(int)val : INT_MAX;
return result;
}
DQN_API int8_t Dqn_Safe_SaturateCastUSizeToI8(Dqn_usize val)
{
int8_t result = DQN_CHECK(DQN_CAST(uintmax_t)val <= INT8_MAX) ? DQN_CAST(int8_t)val : INT8_MAX;
return result;
}
DQN_API int16_t Dqn_Safe_SaturateCastUSizeToI16(Dqn_usize val)
{
int16_t result = DQN_CHECK(DQN_CAST(uintmax_t)val <= INT16_MAX) ? DQN_CAST(int16_t)val : INT16_MAX;
return result;
}
DQN_API int32_t Dqn_Safe_SaturateCastUSizeToI32(Dqn_usize val)
{
int32_t result = DQN_CHECK(DQN_CAST(uintmax_t)val <= INT32_MAX) ? DQN_CAST(int32_t)val : INT32_MAX;
return result;
}
DQN_API int64_t Dqn_Safe_SaturateCastUSizeToI64(Dqn_usize val)
{
int64_t result = DQN_CHECK(DQN_CAST(uintmax_t)val <= INT64_MAX) ? DQN_CAST(int64_t)val : INT64_MAX;
return result;
}
// NOTE: Dqn_Safe_SaturateCastUSizeToU*
// -----------------------------------------------------------------------------
// Both operands are unsigned and the lowest rank operand will be promoted to
// match the highest rank operand.
DQN_API uint8_t Dqn_Safe_SaturateCastUSizeToU8(Dqn_usize val)
{
uint8_t result = DQN_CHECK(val <= UINT8_MAX) ? DQN_CAST(uint8_t)val : UINT8_MAX;
return result;
}
DQN_API uint16_t Dqn_Safe_SaturateCastUSizeToU16(Dqn_usize val)
{
uint16_t result = DQN_CHECK(val <= UINT16_MAX) ? DQN_CAST(uint16_t)val : UINT16_MAX;
return result;
}
DQN_API uint32_t Dqn_Safe_SaturateCastUSizeToU32(Dqn_usize val)
{
uint32_t result = DQN_CHECK(val <= UINT32_MAX) ? DQN_CAST(uint32_t)val : UINT32_MAX;
return result;
}
DQN_API uint64_t Dqn_Safe_SaturateCastUSizeToU64(Dqn_usize val)
{
uint64_t result = DQN_CHECK(val <= UINT64_MAX) ? DQN_CAST(uint64_t)val : UINT64_MAX;
return result;
}
// NOTE: Dqn_Safe_SaturateCastU64ToU*
// -----------------------------------------------------------------------------
// Both operands are unsigned and the lowest rank operand will be promoted to
// match the highest rank operand.
DQN_API unsigned int Dqn_Safe_SaturateCastU64ToUInt(uint64_t val)
{
unsigned int result = DQN_CHECK(val <= UINT8_MAX) ? DQN_CAST(unsigned int)val : UINT_MAX;
return result;
}
DQN_API uint8_t Dqn_Safe_SaturateCastU64ToU8(uint64_t val)
{
uint8_t result = DQN_CHECK(val <= UINT8_MAX) ? DQN_CAST(uint8_t)val : UINT8_MAX;
return result;
}
DQN_API uint16_t Dqn_Safe_SaturateCastU64ToU16(uint64_t val)
{
uint16_t result = DQN_CHECK(val <= UINT16_MAX) ? DQN_CAST(uint16_t)val : UINT16_MAX;
return result;
}
DQN_API uint32_t Dqn_Safe_SaturateCastU64ToU32(uint64_t val)
{
uint32_t result = DQN_CHECK(val <= UINT32_MAX) ? DQN_CAST(uint32_t)val : UINT32_MAX;
return result;
}
// NOTE: Dqn_Safe_SaturateCastISizeToI*
// -----------------------------------------------------------------------------
// Both operands are signed so the lowest rank operand will be promoted to
// match the highest rank operand.
DQN_API int Dqn_Safe_SaturateCastISizeToInt(Dqn_isize val)
{
DQN_ASSERT(val >= INT_MIN && val <= INT_MAX);
int result = DQN_CAST(int)DQN_CLAMP(val, INT_MIN, INT_MAX);
return result;
}
DQN_API int8_t Dqn_Safe_SaturateCastISizeToI8(Dqn_isize val)
{
DQN_ASSERT(val >= INT8_MIN && val <= INT8_MAX);
int8_t result = DQN_CAST(int8_t)DQN_CLAMP(val, INT8_MIN, INT8_MAX);
return result;
}
DQN_API int16_t Dqn_Safe_SaturateCastISizeToI16(Dqn_isize val)
{
DQN_ASSERT(val >= INT16_MIN && val <= INT16_MAX);
int16_t result = DQN_CAST(int16_t)DQN_CLAMP(val, INT16_MIN, INT16_MAX);
return result;
}
DQN_API int32_t Dqn_Safe_SaturateCastISizeToI32(Dqn_isize val)
{
DQN_ASSERT(val >= INT32_MIN && val <= INT32_MAX);
int32_t result = DQN_CAST(int32_t)DQN_CLAMP(val, INT32_MIN, INT32_MAX);
return result;
}
DQN_API int64_t Dqn_Safe_SaturateCastISizeToI64(Dqn_isize val)
{
DQN_ASSERT(val >= INT64_MIN && val <= INT64_MAX);
int64_t result = DQN_CAST(int64_t)DQN_CLAMP(val, INT64_MIN, INT64_MAX);
return result;
}
// NOTE: Dqn_Safe_SaturateCastISizeToU*
// -----------------------------------------------------------------------------
// If the value is a negative integer, we clamp to 0. Otherwise, we know that
// the value is >=0, we can upcast safely to bounds check against the maximum
// allowed value.
DQN_API unsigned int Dqn_Safe_SaturateCastISizeToUInt(Dqn_isize val)
{
unsigned int result = 0;
if (DQN_CHECK(val >= DQN_CAST(Dqn_isize)0)) {
if (DQN_CHECK(DQN_CAST(uintmax_t)val <= UINT_MAX))
result = DQN_CAST(unsigned int)val;
else
result = UINT_MAX;
}
return result;
}
DQN_API uint8_t Dqn_Safe_SaturateCastISizeToU8(Dqn_isize val)
{
uint8_t result = 0;
if (DQN_CHECK(val >= DQN_CAST(Dqn_isize)0)) {
if (DQN_CHECK(DQN_CAST(uintmax_t)val <= UINT8_MAX))
result = DQN_CAST(uint8_t)val;
else
result = UINT8_MAX;
}
return result;
}
DQN_API uint16_t Dqn_Safe_SaturateCastISizeToU16(Dqn_isize val)
{
uint16_t result = 0;
if (DQN_CHECK(val >= DQN_CAST(Dqn_isize)0)) {
if (DQN_CHECK(DQN_CAST(uintmax_t)val <= UINT16_MAX))
result = DQN_CAST(uint16_t)val;
else
result = UINT16_MAX;
}
return result;
}
DQN_API uint32_t Dqn_Safe_SaturateCastISizeToU32(Dqn_isize val)
{
uint32_t result = 0;
if (DQN_CHECK(val >= DQN_CAST(Dqn_isize)0)) {
if (DQN_CHECK(DQN_CAST(uintmax_t)val <= UINT32_MAX))
result = DQN_CAST(uint32_t)val;
else
result = UINT32_MAX;
}
return result;
}
DQN_API uint64_t Dqn_Safe_SaturateCastISizeToU64(Dqn_isize val)
{
uint64_t result = 0;
if (DQN_CHECK(val >= DQN_CAST(Dqn_isize)0)) {
if (DQN_CHECK(DQN_CAST(uintmax_t)val <= UINT64_MAX))
result = DQN_CAST(uint64_t)val;
else
result = UINT64_MAX;
}
return result;
}
// NOTE: Dqn_Safe_SaturateCastI64To*
// -----------------------------------------------------------------------------
// Both operands are signed so the lowest rank operand will be promoted to
// match the highest rank operand.
DQN_API Dqn_isize Dqn_Safe_SaturateCastI64ToISize(int64_t val)
{
DQN_CHECK(val >= DQN_ISIZE_MIN && val <= DQN_ISIZE_MAX);
Dqn_isize result = DQN_CAST(int64_t)DQN_CLAMP(val, DQN_ISIZE_MIN, DQN_ISIZE_MAX);
return result;
}
DQN_API int8_t Dqn_Safe_SaturateCastI64ToI8(int64_t val)
{
DQN_CHECK(val >= INT8_MIN && val <= INT8_MAX);
int8_t result = DQN_CAST(int8_t)DQN_CLAMP(val, INT8_MIN, INT8_MAX);
return result;
}
DQN_API int16_t Dqn_Safe_SaturateCastI64ToI16(int64_t val)
{
DQN_CHECK(val >= INT16_MIN && val <= INT16_MAX);
int16_t result = DQN_CAST(int16_t)DQN_CLAMP(val, INT16_MIN, INT16_MAX);
return result;
}
DQN_API int32_t Dqn_Safe_SaturateCastI64ToI32(int64_t val)
{
DQN_CHECK(val >= INT32_MIN && val <= INT32_MAX);
int32_t result = DQN_CAST(int32_t)DQN_CLAMP(val, INT32_MIN, INT32_MAX);
return result;
}
// NOTE: Dqn_Safe_SaturateCastIntTo*
// -----------------------------------------------------------------------------
DQN_API int8_t Dqn_Safe_SaturateCastIntToI8(int val)
{
DQN_CHECK(val >= INT8_MIN && val <= INT8_MAX);
int8_t result = DQN_CAST(int8_t)DQN_CLAMP(val, INT8_MIN, INT8_MAX);
return result;
}
DQN_API int16_t Dqn_Safe_SaturateCastIntToI16(int val)
{
DQN_CHECK(val >= INT16_MIN && val <= INT16_MAX);
int16_t result = DQN_CAST(int16_t)DQN_CLAMP(val, INT16_MIN, INT16_MAX);
return result;
}
DQN_API uint8_t Dqn_Safe_SaturateCastIntToU8(int val)
{
uint8_t result = 0;
if (DQN_CHECK(val >= DQN_CAST(Dqn_isize)0)) {
if (DQN_CHECK(DQN_CAST(uintmax_t)val <= UINT8_MAX))
result = DQN_CAST(uint8_t)val;
else
result = UINT8_MAX;
}
return result;
}
DQN_API uint16_t Dqn_Safe_SaturateCastIntToU16(int val)
{
uint16_t result = 0;
if (DQN_CHECK(val >= DQN_CAST(Dqn_isize)0)) {
if (DQN_CHECK(DQN_CAST(uintmax_t)val <= UINT16_MAX))
result = DQN_CAST(uint16_t)val;
else
result = UINT16_MAX;
}
return result;
}
DQN_API uint32_t Dqn_Safe_SaturateCastIntToU32(int val)
{
static_assert(sizeof(val) <= sizeof(uint32_t), "Sanity check to allow simplifying of casting");
uint32_t result = 0;
if (DQN_CHECK(val >= 0))
result = DQN_CAST(uint32_t)val;
return result;
}
DQN_API uint64_t Dqn_Safe_SaturateCastIntToU64(int val)
{
static_assert(sizeof(val) <= sizeof(uint64_t), "Sanity check to allow simplifying of casting");
uint64_t result = 0;
if (DQN_CHECK(val >= 0))
result = DQN_CAST(uint64_t)val;
return result;
}
// =================================================================================================
// [$TCTX] Dqn_ThreadContext | | Per-thread data structure e.g. temp arenas
// =================================================================================================
Dqn_ThreadScratch::Dqn_ThreadScratch(DQN_LEAK_TRACE_FUNCTION Dqn_ThreadContext *context, uint8_t context_index)
{
index = context_index;
allocator = context->temp_allocators[index];
arena = context->temp_arenas[index];
temp_memory = Dqn_Arena_BeginTempMemory(arena);
#if defined(DQN_LEAK_TRACING)
leak_site__ = DQN_LEAK_TRACE_ARG_NO_COMMA;
#endif
}
Dqn_ThreadScratch::~Dqn_ThreadScratch()
{
#if defined(DQN_DEBUG_THREAD_CONTEXT)
temp_arenas_stat[index] = arena->stats;
#endif
DQN_ASSERT(destructed == false);
#if defined(DQN_LEAK_TRACING)
Dqn_Arena_EndTempMemory_(leak_site__, temp_memory);
#else
Dqn_Arena_EndTempMemory_(temp_memory);
#endif
destructed = true;
}
DQN_API uint32_t Dqn_Thread_GetID()
{
#if defined(DQN_OS_WIN32)
unsigned long result = GetCurrentThreadId();
#else
pid_t result = gettid();
assert(gettid() >= 0);
#endif
return (uint32_t)result;
}
DQN_API Dqn_ThreadContext *Dqn_Thread_GetContext_(DQN_LEAK_TRACE_FUNCTION_NO_COMMA)
{
thread_local Dqn_ThreadContext result = {};
if (!result.init) {
result.init = true;
DQN_ASSERTF(dqn_library.lib_init, "Library must be initialised by calling Dqn_Library_Init(nullptr)");
// NOTE: Setup permanent arena
Dqn_ArenaCatalog *catalog = &dqn_library.arena_catalog;
result.allocator = Dqn_Arena_Allocator(result.arena);
result.arena = Dqn_ArenaCatalog_AllocF(catalog,
DQN_GIGABYTES(1) /*size*/,
DQN_KILOBYTES(64) /*commit*/,
"Thread %u Arena",
Dqn_Thread_GetID());
// NOTE: Setup temporary arenas
for (uint8_t index = 0; index < DQN_THREAD_CONTEXT_ARENAS; index++) {
result.temp_arenas[index] = Dqn_ArenaCatalog_AllocF(catalog,
DQN_GIGABYTES(1) /*size*/,
DQN_KILOBYTES(64) /*commit*/,
"Thread %u Temp Arena %u",
Dqn_Thread_GetID(),
index);
result.temp_allocators[index] = Dqn_Arena_Allocator(result.temp_arenas[index]);
}
}
return &result;
}
// TODO: Is there a way to handle conflict arenas without the user needing to
// manually pass it in?
DQN_API Dqn_ThreadScratch Dqn_Thread_GetScratch_(DQN_LEAK_TRACE_FUNCTION void const *conflict_arena)
{
static_assert(DQN_THREAD_CONTEXT_ARENAS < (uint8_t)-1, "We use UINT8_MAX as a sentinel value");
Dqn_ThreadContext *context = Dqn_Thread_GetContext_(DQN_LEAK_TRACE_ARG_NO_COMMA);
uint8_t context_index = (uint8_t)-1;
for (uint8_t index = 0; index < DQN_THREAD_CONTEXT_ARENAS; index++) {
Dqn_Arena *arena = context->temp_arenas[index];
if (!conflict_arena || arena != conflict_arena) {
context_index = index;
break;
}
}
DQN_ASSERT(context_index != (uint8_t)-1);
return Dqn_ThreadScratch(DQN_LEAK_TRACE_ARG context, context_index);
}

496
dqn_misc.h Normal file
View File

@ -0,0 +1,496 @@
// =================================================================================================
// [$DLIB] Dqn_Library | | Library run-time behaviour configuration
// =================================================================================================
//
// Book-keeping data for the library and allow customisation of certain features
// provided.
//
// NOTE: API
//
// @proc Dqn_Library_SetLogCallback
// @desc Update the default logging function, all logging functions will run through
// this callback
// @param[in] proc The new logging function, set to nullptr to revert back to
// the default logger.
// @param[in] user_data A user defined parameter to pass to the callback
//
// @proc Dqn_Library_SetLogFile
// @param[in] file Pass in nullptr to turn off writing logs to disk, otherwise
// point it to the FILE that you wish to write to.
//
// @proc Dqn_Library_DumpThreadContextArenaStat
// @desc Dump the per-thread arena statistics to the specified file
struct Dqn_Library
{
bool lib_init;
Dqn_TicketMutex lib_mutex;
Dqn_LogProc *log_callback; ///< Set this pointer to override the logging routine
void * log_user_data;
bool log_to_file; ///< Output logs to file as well as standard out
void * log_file; ///< TODO(dqn): Hmmm, how should we do this... ?
Dqn_TicketMutex log_file_mutex; ///< Is locked when instantiating the log_file for the first time
bool log_no_colour; ///< Disable colours in the logging output
/// The backup arena to use if no arena is passed into Dqn_Library_Init
Dqn_Arena arena_catalog_backup_arena;
Dqn_ArenaCatalog arena_catalog;
#if defined(DQN_LEAK_TRACING)
Dqn_TicketMutex alloc_table_mutex;
Dqn_DSMap<Dqn_LeakTrace> alloc_table;
#endif
#if defined(DQN_OS_WIN32)
LARGE_INTEGER win32_qpc_frequency;
Dqn_TicketMutex win32_bcrypt_rng_mutex;
void *win32_bcrypt_rng_handle;
#endif
#if defined(DQN_DEBUG_THREAD_CONTEXT)
Dqn_TicketMutex thread_context_mutex;
Dqn_ArenaStat thread_context_arena_stats[256];
uint8_t thread_context_arena_stats_count;
#endif
} extern dqn_library;
// NOTE: Properties ================================================================================
DQN_API Dqn_Library *Dqn_Library_Init (Dqn_Arena *arena);
DQN_API void Dqn_Library_SetLogCallback (Dqn_LogProc *proc, void *user_data);
DQN_API void Dqn_Library_SetLogFile (void *file);
DQN_API void Dqn_Library_DumpThreadContextArenaStat(Dqn_String8 file_path);
// NOTE: Leak Trace ================================================================================
#if defined(DQN_LEAK_TRACING)
DQN_API void Dqn_Library_LeakTraceAdd (Dqn_CallSite call_site, void *ptr, Dqn_usize size);
DQN_API void Dqn_Library_LeakTraceMarkFree (Dqn_CallSite call_site, void *ptr);
#else
#define Dqn_Library_LeakTraceAdd(...)
#define Dqn_Library_LeakTraceMarkFree(...)
#endif
// =================================================================================================
// [$BITS] Dqn_Bit | | Bitset manipulation
// =================================================================================================
DQN_API void Dqn_Bit_UnsetInplace(uint32_t *flags, uint32_t bitfield);
DQN_API void Dqn_Bit_SetInplace(uint32_t *flags, uint32_t bitfield);
DQN_API bool Dqn_Bit_IsSet(uint32_t bits, uint32_t bits_to_set);
DQN_API bool Dqn_Bit_IsNotSet(uint32_t bits, uint32_t bits_to_check);
// =================================================================================================
// [$SAFE] Dqn_Safe | | Safe arithmetic, casts, asserts
// =================================================================================================
/// Assert the expression given in debug, whilst in release- assertion is
/// removed and the expression is evaluated and returned.
///
/// This function provides dual logic which allows handling of the condition
/// gracefully in release mode, but asserting in debug mode. This is an internal
/// function, prefer the @see DQN_CHECK macros.
///
/// @param assertion_expr[in] Expressin to assert on
/// @param fmt[in] Format string for providing a message on assertion
/// @return True if the expression evaluated to true, false otherwise.
DQN_API bool DQN_CHECKF_(bool assertion_expr, Dqn_CallSite call_site, char const *fmt, ...);
// NOTE: Dqn_Safe Arithmetic
// -----------------------------------------------------------------------------
/// Add 2 I64's together, safe asserting that the operation does not overflow.
/// @return The result of operation, INT64_MAX if it overflowed.
DQN_API int64_t Dqn_Safe_AddI64(int64_t a, int64_t b);
/// Multiply 2 I64's together, safe asserting that the operation does not
/// overflow.
/// @return The result of operation, IINT64_MAX if it overflowed.
DQN_API int64_t Dqn_Safe_MulI64(int64_t a, int64_t b);
/// Add 2 U64's together, safe asserting that the operation does not overflow.
/// @return The result of operation, UINT64_MAX if it overflowed.
DQN_API uint64_t Dqn_Safe_AddU64(uint64_t a, uint64_t b);
/// Subtract 2 U64's together, safe asserting that the operation does not
/// overflow.
/// @return The result of operation, 0 if it overflowed.
DQN_API uint64_t Dqn_Safe_SubU64(uint64_t a, uint64_t b);
/// Multiple 2 U64's together, safe asserting that the operation does not
/// overflow.
/// @return The result of operation, UINT64_MAX if it overflowed.
DQN_API uint64_t Dqn_Safe_MulU64(uint64_t a, uint64_t b);
/// Subtract 2 U32's together, safe asserting that the operation does not
/// overflow.
/// @return The result of operation, 0 if it overflowed.
DQN_API uint32_t Dqn_Safe_SubU32(uint32_t a, uint32_t b);
// NOTE: Dqn_Safe_SaturateCastUSizeToI*
// -----------------------------------------------------------------------------
/// Truncate a usize to int clamping the result to the max value of the desired
/// data type. Safe asserts if clamping occurs.
/// @return The truncated value or INT_MAX if the value would go out of the
/// valid range when casted.
DQN_API int Dqn_Safe_SaturateCastUSizeToInt(Dqn_usize val);
/// Truncate a usize to I8 clamping the result to the max value of the desired
/// data type. Safe asserts if clamping occurs.
/// @return The truncated value or INT8_MAX if the value would go out of the
/// valid range when casted.
DQN_API int8_t Dqn_Safe_SaturateCastUSizeToI8(Dqn_usize val);
/// Truncate a usize to I16 clamping the result to the max value of the desired
/// data type. Safe asserts if clamping occurs.
/// @return The truncated value or INT16_MAX if the value would go out of the
/// valid range when casted.
DQN_API int16_t Dqn_Safe_SaturateCastUSizeToI16(Dqn_usize val);
/// Truncate a usize to I32 clamping the result to the max value of the desired
/// data type. Safe asserts if clamping occurs.
/// @return The truncated value or INT32_MAX if the value would go out of the
/// valid range when casted.
DQN_API int32_t Dqn_Safe_SaturateCastUSizeToI32(Dqn_usize val);
/// Truncate a usize to I64 clamping the result to the max value of the desired
/// data type. Safe asserts if clamping occurs.
/// @return The truncated value or INT64_MAX if the value would go out of the
/// valid range when casted.
DQN_API int64_t Dqn_Safe_SaturateCastUSizeToI64(Dqn_usize val);
// NOTE: Dqn_Safe_SaturateCastU64ToU*
// -----------------------------------------------------------------------------
/// Truncate a u64 to uint clamping the result to the max value of the desired
/// data type. Safe asserts if clamping occurs.
/// @return The truncated value or UINT_MAX if the value would go out of the
/// valid range when casted.
DQN_API unsigned int Dqn_Safe_SaturateCastU64ToUInt(uint64_t val);
/// Truncate a u64 to u8 clamping the result to the max value of the desired
/// data type. Safe asserts if clamping occurs.
/// @return The truncated value or UINT8_MAX if the value would go out of the
/// valid range when casted.
DQN_API uint8_t Dqn_Safe_SaturateCastU64ToU8(uint64_t val);
/// Truncate a u64 to u16 clamping the result to the max value of the desired
/// data type. Safe asserts if clamping occurs.
/// @return The truncated value or UINT16_MAX if the value would go out of the
/// valid range when casted.
DQN_API uint16_t Dqn_Safe_SaturateCastU64ToU16(uint64_t val);
/// Truncate a u64 to u32 clamping the result to the max value of the desired
/// data type. Safe asserts if clamping occurs.
/// @return The truncated value or UINT32_MAX if the value would go out of the
/// valid range when casted.
DQN_API uint32_t Dqn_Safe_SaturateCastU64ToU32(uint64_t val);
// NOTE: Dqn_Safe_SaturateCastUSizeToU*
// -----------------------------------------------------------------------------
/// Truncate a usize to U8 clamping the result to the max value of the desired
/// data type. Safe asserts if clamping occurs.
/// @return The truncated value or UINT8_MAX if the value would go out of the
/// valid range when casted.
DQN_API uint8_t Dqn_Safe_SaturateCastUSizeToU8(Dqn_usize val);
/// Truncate a usize to U16 clamping the result to the max value of the desired
/// data type. Safe asserts if clamping occurs.
/// @return The truncated value or UINT16_MAX if the value would go out of the
/// valid range when casted.
DQN_API uint16_t Dqn_Safe_SaturateCastUSizeToU16(Dqn_usize val);
/// Truncate a usize to U32 clamping the result to the max value of the desired
/// data type. Safe asserts if clamping occurs.
/// @return The truncated value or UINT32_MAX if the value would go out of the
/// valid range when casted.
DQN_API uint32_t Dqn_Safe_SaturateCastUSizeToU32(Dqn_usize val);
/// Truncate a usize to U64 clamping the result to the max value of the desired
/// data type. Safe asserts if clamping occurs.
/// @return The truncated value or UINT64_MAX if the value would go out of the
/// valid range when casted.
DQN_API uint64_t Dqn_Safe_SaturateCastUSizeToU64(Dqn_usize val);
// NOTE: Dqn_Safe_SaturateCastISizeToI*
// -----------------------------------------------------------------------------
/// Truncate an isize to int clamping the result to the min and max value of the
/// desired data type. Safe asserts if clamping occurs.
/// @return The truncated value or INT_MIN or INT_MAX if the value would go
/// out of the valid range when casted.
DQN_API int Dqn_Safe_SaturateCastISizeToInt(Dqn_isize val);
/// Truncate an isize to I8 clamping the result to the min and max value of the
/// desired data type. Safe asserts if clamping occurs.
/// @return The truncated value or INT8_MIN or INT8_MAX if the value would go
/// out of the valid range when casted.
DQN_API int8_t Dqn_Safe_SaturateCastISizeToI8(Dqn_isize val);
/// Truncate an isize to I16 clamping the result to the min and max value of the
/// desired data type. Safe asserts if clamping occurs.
/// @return The truncated value or INT16_MIN or INT16_MAX if the value would go
/// out of the valid range when casted.
DQN_API int16_t Dqn_Safe_SaturateCastISizeToI16(Dqn_isize val);
/// Truncate an isize to I32 clamping the result to the min and max value of the
/// desired data type. Safe asserts if clamping occurs.
/// @return The truncated value or INT32_MIN or INT32_MAX if the value would go
/// out of the valid range when casted.
DQN_API int32_t Dqn_Safe_SaturateCastISizeToI32(Dqn_isize val);
/// Truncate an isize to I64 clamping the result to the min and max value of the
/// desired data type. Safe asserts if clamping occurs.
/// @return The truncated value or INT64_MIN or INT64_MAX if the value would go
/// out of the valid range when casted.
DQN_API int64_t Dqn_Safe_SaturateCastISizeToI64(Dqn_isize val);
// NOTE: Dqn_Safe_SaturateCastISizeToU*
// -----------------------------------------------------------------------------
/// Truncate an isize to uint clamping the result to 0 and the max value of the
/// desired data type. Safe asserts if clamping occurs.
/// @return The truncated value or 0 or UINT_MAX if the value would go
/// out of the valid range when casted.
DQN_API unsigned int Dqn_Safe_SaturateCastISizeToUInt(Dqn_isize val);
/// Truncate an isize to U8 clamping the result to 0 and the max value of the
/// desired data type. Safe asserts if clamping occurs.
/// @return The truncated value or 0 or UINT8_MAX if the value would go
/// out of the valid range when casted.
DQN_API uint8_t Dqn_Safe_SaturateCastISizeToU8(Dqn_isize val);
/// Truncate an isize to U16 clamping the result to 0 and the max value of the
/// desired data type. Safe asserts if clamping occurs.
/// @return The truncated value or 0 or UINT16_MAX if the value would go
/// out of the valid range when casted.
DQN_API uint16_t Dqn_Safe_SaturateCastISizeToU16(Dqn_isize val);
/// Truncate an isize to U32 clamping the result to 0 and the max value of the
/// desired data type. Safe asserts if clamping occurs.
/// @return The truncated value or 0 or UINT32_MAX if the value would go
/// out of the valid range when casted.
DQN_API uint32_t Dqn_Safe_SaturateCastISizeToU32(Dqn_isize val);
/// Truncate an isize to U64 clamping the result to 0 and the max value of the
/// desired data type. Safe asserts if clamping occurs.
/// @return The truncated value or 0 or UINT64_MAX if the value would go
/// out of the valid range when casted.
DQN_API uint64_t Dqn_Safe_SaturateCastISizeToU64(Dqn_isize val);
// NOTE: Dqn_Safe_SaturateCastI64To*
// -----------------------------------------------------------------------------
/// Truncate an I64 to isize clamping the result to the min and max value of the
/// desired data type. Safe asserts if clamping occurs.
/// @return The truncated value or DQN_ISIZE_MIN or DQN_ISIZE_MAX if the value
/// would go out of the valid range when casted.
DQN_API Dqn_isize Dqn_Safe_SaturateCastI64ToISize(Dqn_isize val);
/// Truncate an I64 to I8 clamping the result to the min and max value of the
/// desired data type. Safe asserts if clamping occurs.
/// @return The truncated value or INT8_MIN or INT8_MAX if the value would go
/// out of the valid range when casted.
DQN_API int8_t Dqn_Safe_SaturateCastI64ToI8(int64_t val);
/// Truncate an I64 to I16 clamping the result to the min and max value of the
/// desired data type. Safe asserts if clamping occurs.
/// @return The truncated value or INT16_MIN or INT16_MAX if the value would go
/// out of the valid range when casted.
DQN_API int16_t Dqn_Safe_SaturateCastI64ToI16(int64_t val);
/// Truncate an I64 to I32 clamping the result to the min and max value of the
/// desired data type. Safe asserts if clamping occurs.
/// @return The truncated value or INT32_MIN or INT32_MAX if the value would go
/// out of the valid range when casted.
DQN_API int32_t Dqn_Safe_SaturateCastI64ToI32(int64_t val);
// NOTE: Dqn_Safe_SaturateCastIntTo*
// -----------------------------------------------------------------------------
/// Truncate an int to I8 clamping the result to the min and max value of the
/// desired data type. Safe asserts if clamping occurs.
/// @return The truncated value or INT8_MIN or INT8_MAX if the value
/// would go out of the valid range when casted.
DQN_API int8_t Dqn_Safe_SaturateCastIntToI8(int val);
/// Truncate an int to I16 clamping the result to the min and max value of the
/// desired data type. Safe asserts if clamping occurs.
/// @return The truncated value or INT16_MIN or INT16_MAX if the value
/// would go out of the valid range when casted.
DQN_API int16_t Dqn_Safe_SaturateCastIntToI16(int val);
/// Truncate an int to U8 clamping the result to 0 and the max value of the
/// desired data type. Safe asserts if clamping occurs.
/// @return The truncated value or 0 or UINT8_MAX if the value would go
/// out of the valid range when casted.
DQN_API uint8_t Dqn_Safe_SaturateCastIntToU8(int val);
/// Truncate an int to U16 clamping the result to 0 and the max value of the
/// desired data type. Safe asserts if clamping occurs.
/// @return The truncated value or 0 or UINT16_MAX if the value would go
/// out of the valid range when casted.
DQN_API uint16_t Dqn_Safe_SaturateCastIntToU16(int val);
/// Truncate an int to U32 clamping the result to 0 and the max value of the
/// desired data type. Safe asserts if clamping occurs.
/// @return The truncated value or 0 or UINT32_MAX if the value would go
/// out of the valid range when casted.
DQN_API uint32_t Dqn_Safe_SaturateCastIntToU32(int val);
/// Truncate an int to U64 clamping the result to 0 and the max value of the
/// desired data type. Safe asserts if clamping occurs.
/// @return The truncated value or 0 or UINT64_MAX if the value would go
/// out of the valid range when casted.
DQN_API uint64_t Dqn_Safe_SaturateCastIntToU64(int val);
// =================================================================================================
// [$TCTX] Dqn_ThreadContext | | Per-thread data structure e.g. temp arenas
// =================================================================================================
//
// Each thread is assigned in their thread-local storage (TLS) scratch and
// permanent arena allocators. These can be used for allocations with a lifetime
// scoped to the lexical scope or for storing data permanently using the arena
// paradigm.
//
// TLS in this implementation is implemented using the `thread_local` C/C++
// keyword.
//
// NOTE: API
//
// @proc Dqn_Thread_GetContext
// @desc Get the current thread's context- this contains all the metadata for managing
// the thread scratch data. In general you probably want Dqn_Thread_GetScratch()
// which ensures you get a usable scratch arena for temporary allocations
// without having to worry about selecting the right arena from the state.
//
// @proc Dqn_Thread_GetScratch
// @desc Retrieve the per-thread temporary arena allocator that is reset on scope
// exit.
//
// The scratch arena must be deconflicted with any existing arenas in the
// function to avoid trampling over each other's memory. Consider the situation
// where the scratch arena is passed into the function. Inside the function, if
// the same arena is reused then, if both arenas allocate, when the inner arena
// is reset, this will undo the passed in arena's allocations in the function.
//
// @param[in] conflict_arena A pointer to the arena currently being used in the
// function
#if !defined(DQN_THREAD_CONTEXT_ARENAS)
#define DQN_THREAD_CONTEXT_ARENAS 2
#endif
struct Dqn_ThreadContext
{
Dqn_b32 init;
Dqn_Arena *arena; ///< Per thread arena
Dqn_Allocator allocator; ///< Allocator that uses the arena
/// Temp memory arena's for the calling thread
Dqn_Arena *temp_arenas[DQN_THREAD_CONTEXT_ARENAS];
/// Allocators that use the corresponding arena from the thread context.
/// Provided for convenience when interfacing with allocator interfaces.
Dqn_Allocator temp_allocators[DQN_THREAD_CONTEXT_ARENAS];
#if defined(DQN_DEBUG_THREAD_CONTEXT)
Dqn_ArenaStat temp_arenas_stat[DQN_THREAD_CONTEXT_ARENAS];
#endif
};
struct Dqn_ThreadScratch
{
Dqn_ThreadScratch(DQN_LEAK_TRACE_FUNCTION Dqn_ThreadContext *context, uint8_t context_index);
~Dqn_ThreadScratch();
/// Index into the arena/allocator/stat array in the thread context
/// specifying what arena was assigned.
uint8_t index;
Dqn_Allocator allocator;
Dqn_Arena *arena;
Dqn_b32 destructed = false; /// Detect copies of the scratch
Dqn_ArenaTempMemory temp_memory;
#if defined(DQN_LEAK_TRACING)
Dqn_CallSite leak_site__;
#endif
};
// NOTE: Context ===================================================================================
#define Dqn_Thread_GetContext() Dqn_Thread_GetContext_(DQN_LEAK_TRACE_NO_COMMA)
#define Dqn_Thread_GetScratch(conflict_arena) Dqn_Thread_GetScratch_(DQN_LEAK_TRACE conflict_arena)
DQN_API uint32_t Dqn_Thread_GetID();
// NOTE: Internal ==================================================================================
DQN_API Dqn_ThreadContext *Dqn_Thread_GetContext_(DQN_LEAK_TRACE_FUNCTION_NO_COMMA);
DQN_API Dqn_ThreadScratch Dqn_Thread_GetScratch_(DQN_LEAK_TRACE_FUNCTION void const *conflict_arena);
// =================================================================================================
// [$BSEA] Binary Search | |
// =================================================================================================
template <typename T>
using Dqn_BinarySearchLessThanProc = bool(T const &lhs, T const &rhs);
template <typename T>
DQN_FORCE_INLINE bool Dqn_BinarySearch_DefaultLessThan(T const &lhs, T const &rhs);
enum Dqn_BinarySearchType
{
/// Index of the match. If no match is found, found is set to false and the
/// index is set to 0
Dqn_BinarySearchType_Match,
/// Index after the match. If no match is found, found is set to false and
/// the index is set to one past the closest match.
Dqn_BinarySearchType_OnePastMatch,
};
struct Dqn_BinarySearchResult
{
bool found;
Dqn_usize index;
};
template <typename T>
Dqn_BinarySearchResult
Dqn_BinarySearch(T const *array,
Dqn_usize array_size,
T const &find,
Dqn_BinarySearchType type = Dqn_BinarySearchType_Match,
Dqn_BinarySearchLessThanProc<T> less_than = Dqn_BinarySearch_DefaultLessThan);
template <typename T> DQN_FORCE_INLINE bool Dqn_BinarySearch_DefaultLessThan(T const &lhs, T const &rhs)
{
bool result = lhs < rhs;
return result;
}
template <typename T>
Dqn_BinarySearchResult
Dqn_BinarySearch(T const *array,
Dqn_usize array_size,
T const &find,
Dqn_BinarySearchType type,
Dqn_BinarySearchLessThanProc<T> less_than)
{
Dqn_BinarySearchResult result = {};
Dqn_usize head = 0;
Dqn_usize tail = array_size - 1;
if (array && array_size > 0) {
while (!result.found && head <= tail) {
Dqn_usize mid = (head + tail) / 2;
T const &value = array[mid];
if (less_than(find, value)) {
tail = mid - 1;
if (mid == 0)
break;
} else if (less_than(value, find)) {
head = mid + 1;
} else {
result.found = true;
result.index = mid;
}
}
}
if (type == Dqn_BinarySearchType_OnePastMatch)
result.index = result.found ? result.index + 1 : tail + 1;
else
DQN_ASSERT(type == Dqn_BinarySearchType_Match);
return result;
}

2228
dqn_platform.cpp Normal file

File diff suppressed because it is too large Load Diff

475
dqn_platform.h Normal file
View File

@ -0,0 +1,475 @@
// =================================================================================================
// [$FSYS] Dqn_Fs | | Filesystem helpers
// =================================================================================================
enum Dqn_FsInfoType
{
Dqn_FsInfoType_Unknown,
Dqn_FsInfoType_Directory,
Dqn_FsInfoType_File,
};
struct Dqn_FsInfo
{
bool exists;
Dqn_FsInfoType type;
uint64_t create_time_in_s;
uint64_t last_write_time_in_s;
uint64_t last_access_time_in_s;
uint64_t size;
};
// NOTE: File System API
// =============================================================================
// TODO(dqn): We should have a Dqn_String8 interface and a CString interface
DQN_API bool Dqn_Fs_Exists(Dqn_String8 path);
DQN_API bool Dqn_Fs_DirExists(Dqn_String8 path);
DQN_API Dqn_FsInfo Dqn_Fs_GetInfo(Dqn_String8 path);
DQN_API bool Dqn_Fs_Copy(Dqn_String8 src, Dqn_String8 dest, bool overwrite);
DQN_API bool Dqn_Fs_MakeDir(Dqn_String8 path);
DQN_API bool Dqn_Fs_Move(Dqn_String8 src, Dqn_String8 dest, bool overwrite);
// TODO(dqn): This doesn't work on directories unless you delete the files
// in that directory first.
DQN_API bool Dqn_Fs_Delete(Dqn_String8 path);
// NOTE: Read/Write Entire File API
// =============================================================================
// file_size: (Optional) The size of the file in bytes, the allocated buffer is (file_size + 1 [null terminator]) in bytes.
DQN_API bool Dqn_Fs_WriteCString8(char const *file_path, Dqn_usize file_path_size, char const *buffer, Dqn_usize buffer_size);
DQN_API bool Dqn_Fs_WriteString8(Dqn_String8 file_path, Dqn_String8 buffer);
/// Read a file at the specified path into memory.
/// @param[in] path Path to the file to read
/// @param[in] path_size The string size of the file path
/// @param[out] file_size (Optional) Pass a pointer to receive the number of bytes read
/// @param[in] allocator Allocator used to read the file to memory with
/// @return A cstring with the read file, null pointer on failure.
#define Dqn_Fs_ReadCString8(path, path_size, file_size, allocator) Dqn_Fs_ReadCString8_(DQN_LEAK_TRACE path, path_size, file_size, allocator)
DQN_API char *Dqn_Fs_ReadCString8_(DQN_LEAK_TRACE_FUNCTION char const *path, Dqn_usize path_size, Dqn_usize *file_size, Dqn_Allocator allocator);
/// Read a file at the specified path into memory.
/// @param[in] file_path Path to the file to read
/// @param[in] allocator Allocator used to read the file to memory with
/// @return A string with the read file, invalid string on failure.
#define Dqn_Fs_ReadString8(path, allocator) Dqn_Fs_ReadString8_(DQN_LEAK_TRACE path, allocator)
DQN_API Dqn_String8 Dqn_Fs_ReadString8_(DQN_LEAK_TRACE_FUNCTION Dqn_String8 path, Dqn_Allocator allocator);
// NOTE: Read/Write File Stream API
// =============================================================================
struct Dqn_FsFile
{
void *handle;
char error[512];
uint16_t error_size;
};
enum Dqn_FsFileOpen
{
Dqn_FsFileOpen_CreateAlways, ///< Create file if it does not exist, otherwise, zero out the file and open
Dqn_FsFileOpen_OpenIfExist, ///< Open file at path only if it exists
Dqn_FsFileOpen_OpenAlways, ///< Open file at path, create file if it does not exist
};
enum Dqn_FsFileAccess
{
Dqn_FsFileAccess_Read = 1 << 0,
Dqn_FsFileAccess_Write = 1 << 1,
Dqn_FsFileAccess_Execute = 1 << 2,
Dqn_FsFileAccess_AppendOnly = 1 << 3, ///< This flag cannot be combined with any other acess mode
Dqn_FsFileAccess_ReadWrite = Dqn_FsFileAccess_Read | Dqn_FsFileAccess_Write,
Dqn_FsFileAccess_All = Dqn_FsFileAccess_ReadWrite | Dqn_FsFileAccess_Execute,
};
DQN_API Dqn_FsFile Dqn_Fs_OpenFile(Dqn_String8 path, Dqn_FsFileOpen open_mode, uint32_t access);
DQN_API bool Dqn_Fs_WriteFile(Dqn_FsFile *file, char const *buffer, Dqn_usize size);
DQN_API void Dqn_Fs_CloseFile(Dqn_FsFile *file);
// NOTE: Filesystem paths
// =============================================================================
#if !defined(Dqn_FsPathOSSeperator)
#if defined(DQN_OS_WIN32)
#define Dqn_FsPathOSSeperator "\\"
#else
#define Dqn_FsPathOSSeperator "/"
#endif
#define Dqn_FsPathOSSeperatorString DQN_STRING8(Dqn_FsPathOSSeperator)
#endif
struct Dqn_FsPathLink
{
Dqn_String8 string;
Dqn_FsPathLink *next;
Dqn_FsPathLink *prev;
};
struct Dqn_FsPath
{
Dqn_FsPathLink *head;
Dqn_FsPathLink *tail;
Dqn_usize string_size;
uint16_t links_size;
};
DQN_API bool Dqn_FsPath_AddRef (Dqn_Arena *arena, Dqn_FsPath *fs_path, Dqn_String8 path);
DQN_API bool Dqn_FsPath_Add (Dqn_Arena *arena, Dqn_FsPath *fs_path, Dqn_String8 path);
DQN_API bool Dqn_FsPath_Pop (Dqn_FsPath *fs_path);
DQN_API Dqn_String8 Dqn_FsPath_BuildWithSeparator(Dqn_Arena *arena, Dqn_FsPath const *fs_path, Dqn_String8 path_separator);
DQN_API Dqn_String8 Dqn_FsPath_ConvertString8 (Dqn_Arena *arena, Dqn_String8 path);
#define Dqn_FsPath_BuildFwdSlash(arena, fs_path) Dqn_FsPath_BuildWithSeparator(arena, fs_path, DQN_STRING8("/"))
#define Dqn_FsPath_BuildBackSlash(arena, fs_path) Dqn_FsPath_BuildWithSeparator(arena, fs_path, DQN_STRING8("\\"))
#if !defined(Dqn_FsPath_Build)
#if defined(DQN_OS_WIN32)
#define Dqn_FsPath_Build(arena, fs_path) Dqn_FsPath_BuildBackSlash(arena, fs_path)
#else
#define Dqn_FsPath_Build(arena, fs_path) Dqn_FsPath_BuildFwdSlash(arena, fs_path)
#endif
#endif
// =================================================================================================
// [$DATE] Dqn_Date | | Date-time helpers
// =================================================================================================
struct Dqn_DateHMSTimeString
{
char date[DQN_ARRAY_UCOUNT("YYYY-MM-SS")];
uint8_t date_size;
char hms[DQN_ARRAY_UCOUNT("HH:MM:SS")];
uint8_t hms_size;
};
struct Dqn_DateHMSTime
{
uint8_t day;
uint8_t month;
int16_t year;
uint8_t hour;
uint8_t minutes;
uint8_t seconds;
};
// @return The current time at the point of invocation
DQN_API Dqn_DateHMSTime Dqn_Date_HMSLocalTimeNow();
DQN_API Dqn_DateHMSTimeString Dqn_Date_HMSLocalTimeStringNow(char date_separator = '-', char hms_separator = ':');
DQN_API Dqn_DateHMSTimeString Dqn_Date_HMSLocalTimeString(Dqn_DateHMSTime time, char date_separator = '-', char hms_separator = ':');
// return: The time elapsed since Unix epoch (1970-01-01T00:00:00Z) in seconds
DQN_API uint64_t Dqn_Date_EpochTime();
// =================================================================================================
// [$W32H] Win32 minimal header | DQN_NO_WIN32_MINIMAL_HEADER | Minimal windows.h subset
// =================================================================================================
#if defined(DQN_OS_WIN32)
#if !defined(DQN_NO_WIN32_MINIMAL_HEADER) && !defined(_INC_WINDOWS)
// Taken from Windows.h
// typedef unsigned long DWORD;
// typedef unsigned short WORD;
// typedef int BOOL;
// typedef void * HWND;
// typedef void * HANDLE;
// typedef long NTSTATUS;
typedef void * HMODULE;
typedef union {
struct {
unsigned long LowPart;
long HighPart;
};
struct {
unsigned long LowPart;
long HighPart;
} u;
uint64_t QuadPart;
} LARGE_INTEGER;
#endif // !defined(DQN_NO_WIN32_MINIMAL_HEADER) && !defined(_INC_WINDOWS)
// =================================================================================================
// [$WIND] Dqn_Win | | Windows OS helpers
// =================================================================================================
struct Dqn_WinErrorMsg
{
unsigned long code;
char data[DQN_KILOBYTES(64) - 1]; // Maximum error size
unsigned long size;
};
DQN_API void Dqn_Win_LastErrorToBuffer(Dqn_WinErrorMsg *msg);
DQN_API Dqn_WinErrorMsg Dqn_Win_LastError();
/// Call once at application start-up to ensure that the application is DPI
/// aware on Windows and ensure that application UI is scaled up appropriately
/// for the monitor.
DQN_API void Dqn_Win_MakeProcessDPIAware();
// NOTE: Windows String8 To String16
// -----------------------------------------------------------------------------
/// Convert a UTF8 to UTF16 string.
///
/// The exact size buffer required for this function can be determined by
/// calling this function with the 'dest' set to null and 'dest_size' set to 0,
/// the return size is the size required for conversion not-including space for
/// the null-terminator. This function *always* null-terminates the input
/// buffer.
///
/// @return The number of u16's written/required for conversion. 0 if there was
/// a conversion error and can be queried using 'Dqn_Win_LastError'
DQN_API int Dqn_Win_CString8ToCString16(const char *src, int src_size, wchar_t *dest, int dest_size);
DQN_API int Dqn_Win_String8ToCString16(Dqn_String8 src, wchar_t *dest, int dest_size);
DQN_API Dqn_String16 Dqn_Win_String8ToString16Allocator(Dqn_String8 src, Dqn_Allocator allocator);
// NOTE: Windows String16 To String8
// -----------------------------------------------------------------------------
/// Convert a UTF16 to UTF8 string.
///
/// The exact size buffer required for this function can be determined by
/// calling this function with the 'dest' set to null and 'dest_size' set to 0,
/// the return size is the size required for conversion not-including space for
/// the null-terminator. This function *always* null-terminates the input
/// buffer.
///
/// @return The number of u8's written/required for conversion. 0 if there was
/// a conversion error and can be queried using 'Dqn_Win_LastError'
DQN_API int Dqn_Win_CString16ToCString8(const wchar_t *src, int src_size, char *dest, int dest_size);
DQN_API Dqn_String8 Dqn_Win_CString16ToString8Allocator(const wchar_t *src, int src_size, Dqn_Allocator allocator);
DQN_API int Dqn_Win_String16ToCString8(Dqn_String16 src, char *dest, int dest_size);
DQN_API Dqn_String8 Dqn_Win_String16ToString8Allocator(Dqn_String16 src, Dqn_Allocator allocator);
// NOTE: Windows Executable Directory
// -----------------------------------------------------------------------------
/// Evaluate the current executable's directory that is running when this
/// function is called.
/// @param[out] buffer The buffer to write the executable directory into. Set
/// this to null to calculate the required buffer size for the directory.
/// @param[in] size The size of the buffer given. Set this to 0 to calculate the
/// required buffer size for the directory.
/// @return The length of the executable directory string. If this return value
/// exceeds the capacity of the 'buffer', the 'buffer' is untouched.
DQN_API Dqn_usize Dqn_Win_EXEDirW(wchar_t *buffer, Dqn_usize size);
DQN_API Dqn_String16 Dqn_Win_EXEDirWArena(Dqn_Arena *arena);
// @param[in] size (Optional) The size of the current directory string returned
// @param[in] suffix (Optional) A suffix to append to the current working directory
// @param[in] suffix_size (Optional) The size of the suffix to append
DQN_API Dqn_String8 Dqn_Win_WorkingDir(Dqn_Allocator allocator, Dqn_String8 suffix);
DQN_API Dqn_String16 Dqn_Win_WorkingDirW(Dqn_Allocator allocator, Dqn_String16 suffix);
struct Dqn_Win_FolderIteratorW
{
void *handle;
Dqn_String16 file_name;
wchar_t file_name_buf[512];
};
struct Dqn_Win_FolderIterator
{
void *handle;
Dqn_String8 file_name;
char file_name_buf[512];
};
DQN_API bool Dqn_Win_FolderIterate(Dqn_String8 path, Dqn_Win_FolderIterator *it);
DQN_API bool Dqn_Win_FolderWIterate(Dqn_String16 path, Dqn_Win_FolderIteratorW *it);
#if !defined(DQN_NO_WINNET)
// =================================================================================================
// [$WINN] Dqn_WinNet | DQN_NO_WINNET | Windows internet download/query helpers
// =================================================================================================
enum Dqn_WinNetHandleState
{
Dqn_WinNetHandleState_Invalid,
Dqn_WinNetHandleState_Initialised,
Dqn_WinNetHandleState_HttpMethodReady,
Dqn_WinNetHandleState_RequestFailed,
Dqn_WinNetHandleState_RequestGood,
};
// The number of bytes each pump of the connection downloads at most. If this is
// zero we default to DQN_WIN_NET_HANDLE_DOWNLOAD_SIZE.
#if !defined(DQN_WIN_NET_HANDLE_DOWNLOAD_SIZE)
#define DQN_WIN_NET_HANDLE_DOWNLOAD_SIZE 4096
#endif
struct Dqn_WinNetHandle
{
// NOTE: We copy out the host name because it needs to be null-terminated.
// Luckily, we can assume a DNS domain won't exceed 256 characters so this
// will generally always work.
char host_name[256];
int host_name_size;
// NOTE: Everything after the domain/host name part of the string i.e. the
// '/test' part of the full url 'mywebsite.com/test'.
// TODO(dqn): I don't want to make our network API allocate here so we don't
// copy the string since we require that the string is null-terminated so
// then taking a pointer to the input string should work .. maybe this is
// ok?
char *url;
int url_size;
// NOTE: docs.microsoft.com/en-us/windows/win32/wininet/setting-and-retrieving-internet-options#scope-of-hinternet-handle
// These handles have three levels:
//
// The root HINTERNET handle (created by a call to InternetOpen) would contain all the Internet options that affect this instance of WinINet.
// HINTERNET handles that connect to a server (created by a call to InternetConnect)
// HINTERNET handles associated with a resource or enumeration of resources on a particular server.
//
// More detailed information about the HINTERNET dependency is listed here
// NOTE: https://docs.microsoft.com/en-us/windows/win32/wininet/appendix-a-hinternet-handles
void *internet_open_handle;
void *internet_connect_handle;
void *http_handle;
Dqn_WinNetHandleState state;
};
// TODO(dqn): Useful options to expose in the handle
// https://docs.microsoft.com/en-us/windows/win32/wininet/option-flags
// INTERNET_OPTION_CONNECT_RETRIES -- default is 5 retries
// INTERNET_OPTION_CONNECT_TIMEOUT -- milliseconds
// INTERNET_OPTION_RECEIVE_TIMEOUT
// INTERNET_OPTION_SEND_TIMEOUT
// Setup a handle to the URL with the given HTTP verb.
DQN_API Dqn_WinNetHandle Dqn_Win_NetHandleInitCString(char const *url, int url_size);
DQN_API Dqn_WinNetHandle Dqn_Win_NetHandleInit(Dqn_String8 url);
// Setup a handle to the URL with the given HTTP verb.
//
// This function is the same as calling Dqn_Win_NetHandleInit() followed by
// Dqn_Win_NetHandleSetHTTPMethod().
//
// @param http_method The HTTP request type, e.g. "GET" or "POST" e.t.c
DQN_API Dqn_WinNetHandle Dqn_Win_NetHandleInitHTTPMethodCString(char const *url, int url_size, char const *http_method);
DQN_API Dqn_WinNetHandle Dqn_Win_NetHandleInitHTTPMethod(Dqn_String8 url, Dqn_String8 http_method);
DQN_API void Dqn_Win_NetHandleClose(Dqn_WinNetHandle *handle);
DQN_API bool Dqn_Win_NetHandleIsValid(Dqn_WinNetHandle const *handle);
DQN_API void Dqn_Win_NetHandleSetUserAgentCString(Dqn_WinNetHandle *handle, char const *user_agent, int user_agent_size);
// Set the HTTP request method for the given handle. This function can be used
// on a pre-existing valid handle that has at the minimum been initialised.
DQN_API bool Dqn_Win_NetHandleSetHTTPMethod(Dqn_WinNetHandle *handle, char const *method);
enum Dqn_WinNetHandleRequestHeaderFlag
{
Dqn_WinNetHandleRequestHeaderFlag_Add,
Dqn_WinNetHandleRequestHeaderFlag_AddIfNew,
Dqn_WinNetHandleRequestHeaderFlag_Replace,
Dqn_WinNetHandleRequestHeaderFlag_Count,
};
DQN_API bool Dqn_Win_NetHandleSetRequestHeaderCString8(Dqn_WinNetHandle *handle, char const *header, int header_size, uint32_t mode);
DQN_API bool Dqn_Win_NetHandleSetRequestHeaderString8(Dqn_WinNetHandle *handle, Dqn_String8 header, uint32_t mode);
struct Dqn_WinNetHandleResponse
{
Dqn_String8 raw_headers;
Dqn_String8 *headers;
Dqn_usize headers_size;
// NOTE: Headers pulled from the 'raw_headers' for convenience
uint64_t content_length;
Dqn_String8 content_type;
};
DQN_API Dqn_WinNetHandleResponse Dqn_Win_NetHandleSendRequest(Dqn_WinNetHandle *handle, Dqn_Allocator allocator, char const *post_data, unsigned long post_data_size);
DQN_API bool Dqn_Win_NetHandlePump(Dqn_WinNetHandle *handle, char *dest, int dest_size, size_t *download_size);
DQN_API char * Dqn_Win_NetHandlePumpCString8(Dqn_WinNetHandle *handle, Dqn_Arena *arena, size_t *download_size);
DQN_API Dqn_String8 Dqn_Win_NetHandlePumpString8(Dqn_WinNetHandle *handle, Dqn_Arena *arena);
DQN_API void Dqn_Win_NetHandlePumpToCRTFile(Dqn_WinNetHandle *handle, FILE *file);
DQN_API char *Dqn_Win_NetHandlePumpToAllocCString(Dqn_WinNetHandle *handle, size_t *download_size);
DQN_API Dqn_String8 Dqn_Win_NetHandlePumpToAllocString(Dqn_WinNetHandle *handle);
#endif // !defined(DQN_NO_WINNET)
#endif // defined(DQN_OS_WIN32)
// =================================================================================================
// [$OSYS] Dqn_OS | DQN_NO_WIN | Operating-system APIs
// =================================================================================================
/// Generate cryptographically secure bytes
DQN_API bool Dqn_OS_SecureRNGBytes(void *buffer, uint32_t size);
// return: The directory without the trailing '/' or ('\' for windows). Empty
// string with a nullptr if it fails.
DQN_API Dqn_String8 Dqn_OS_EXEDir(Dqn_Allocator allocator);
DQN_API void Dqn_OS_SleepMs(Dqn_uint milliseconds);
DQN_API uint64_t Dqn_OS_PerfCounterNow ();
DQN_API Dqn_f64 Dqn_OS_PerfCounterS (uint64_t begin, uint64_t end);
DQN_API Dqn_f64 Dqn_OS_PerfCounterMs (uint64_t begin, uint64_t end);
DQN_API Dqn_f64 Dqn_OS_PerfCounterMicroS(uint64_t begin, uint64_t end);
DQN_API Dqn_f64 Dqn_OS_PerfCounterNs (uint64_t begin, uint64_t end);
/// Record time between two time-points using the OS's performance counter.
struct Dqn_OSTimer
{
uint64_t start;
uint64_t end;
};
DQN_API Dqn_OSTimer Dqn_OS_TimerBegin();
DQN_API void Dqn_OS_TimerEnd (Dqn_OSTimer *timer);
DQN_API Dqn_f64 Dqn_OS_TimerS (Dqn_OSTimer timer);
DQN_API Dqn_f64 Dqn_OS_TimerMs (Dqn_OSTimer timer);
DQN_API Dqn_f64 Dqn_OS_TimerMicroS (Dqn_OSTimer timer);
DQN_API Dqn_f64 Dqn_OS_TimerNs (Dqn_OSTimer timer);
// OS_TimedBlock provides a extremely primitive way of measuring the duration of
// code blocks, by sprinkling DQN_OS_TIMED_BLOCK_RECORD("record label"), you can
// measure the time between the macro and the next record call.
//
// Example: Record the duration of the for-loop below and print it at the end.
/*
int main()
{
DQN_OS_TIMED_BLOCK_INIT("Profiling Region", 32); // name, records to allocate
DQN_OS_TIMED_BLOCK_RECORD("a");
for (int unused1_ = 0; unused1_ < 1000000; unused1_++)
{
for (int unused2_ = 0; unused2_ < 1000000; unused2_++)
{
(void)unused1_;
(void)unused2_;
}
}
DQN_OS_TIMED_BLOCK_RECORD("b");
DQN_OS_TIMED_BLOCK_DUMP;
return 0;
}
*/
struct Dqn_OSTimedBlock
{
char const *label;
uint64_t tick;
};
// Initialise a timing block region,
#define DQN_OS_TIMED_BLOCK_INIT(label, size) \
Dqn_OSTimedBlock timings_[size]; \
Dqn_usize timings_size_ = 0; \
DQN_OS_TIMED_BLOCK_RECORD(label)
// Add a timing record at the current location this macro is invoked.
// DQN_OS_TIMED_BLOCK_INIT must have been called in a scope visible to the macro
// prior.
// label: The label to give to the timing record
#define DQN_OS_TIMED_BLOCK_RECORD(label) timings_[timings_size_++] = {label, Dqn_OS_PerfCounterNow()}
// Dump the timing block via Dqn_Log
#define DQN_OS_TIMED_BLOCK_DUMP \
DQN_ASSERTF(timings_size_ < sizeof(timings_) / sizeof(timings_[0]), \
"Timings array indexed out-of-bounds, use a bigger size"); \
for (int timings_index_ = 0; timings_index_ < (timings_size_ - 1); timings_index_++) { \
Dqn_OSTimedBlock t1 = timings_[timings_index_ + 0]; \
Dqn_OSTimedBlock t2 = timings_[timings_index_ + 1]; \
DQN_LOG_D("%s -> %s: %fms", t1.label, t2.label, Dqn_OS_PerfCounterMs(t1.tick, t2.tick)); \
} \
\
if (timings_size_ >= 1) { \
Dqn_OSTimedBlock t1 = timings_[0]; \
Dqn_OSTimedBlock t2 = timings_[timings_size_ - 1]; \
DQN_LOG_D("%s -> %s (total): %fms", t1.label, t2.label, Dqn_OS_PerfCounterMs(t1.tick, t2.tick));\
}

345
dqn_print.cpp Normal file
View File

@ -0,0 +1,345 @@
// =================================================================================================
// [$PRIN] Dqn_Print | | Printing
// =================================================================================================
DQN_API Dqn_PrintStyle Dqn_Print_StyleColour(uint8_t r, uint8_t g, uint8_t b, Dqn_PrintBold bold)
{
Dqn_PrintStyle result = {};
result.bold = bold;
result.colour = true;
result.r = r;
result.g = g;
result.b = b;
return result;
}
DQN_API Dqn_PrintStyle Dqn_Print_StyleColourU32(uint32_t rgb, Dqn_PrintBold bold)
{
uint8_t r = (rgb >> 24) & 0xFF;
uint8_t g = (rgb >> 16) & 0xFF;
uint8_t b = (rgb >> 8) & 0xFF;
Dqn_PrintStyle result = Dqn_Print_StyleColour(r, g, b, bold);
return result;
}
DQN_API Dqn_PrintStyle Dqn_Print_StyleBold()
{
Dqn_PrintStyle result = {};
result.bold = Dqn_PrintBold_Yes;
return result;
}
DQN_API void Dqn_Print_Std(Dqn_PrintStd std_handle, Dqn_String8 string)
{
DQN_ASSERT(std_handle == Dqn_PrintStd_Out || std_handle == Dqn_PrintStd_Err);
#if defined(DQN_OS_WIN32)
// NOTE: Get the output handles from kernel
// =========================================================================
thread_local void *std_out_print_handle = nullptr;
thread_local void *std_err_print_handle = nullptr;
thread_local bool std_out_print_to_console = false;
thread_local bool std_err_print_to_console = false;
if (!std_out_print_handle) {
unsigned long mode = 0; (void)mode;
std_out_print_handle = GetStdHandle(STD_OUTPUT_HANDLE);
std_out_print_to_console = GetConsoleMode(std_out_print_handle, &mode) != 0;
std_err_print_handle = GetStdHandle(STD_ERROR_HANDLE);
std_err_print_to_console = GetConsoleMode(std_err_print_handle, &mode) != 0;
}
// NOTE: Select the output handle
// =========================================================================
void *print_handle = std_out_print_handle;
bool print_to_console = std_out_print_to_console;
if (std_handle == Dqn_PrintStd_Err) {
print_handle = std_err_print_handle;
print_to_console = std_err_print_to_console;
}
// NOTE: Write the string
// =========================================================================
DQN_ASSERT(string.size < DQN_CAST(unsigned long)-1);
unsigned long bytes_written = 0; (void)bytes_written;
if (print_to_console) {
WriteConsoleA(print_handle, string.data, DQN_CAST(unsigned long)string.size, &bytes_written, nullptr);
} else {
WriteFile(print_handle, string.data, DQN_CAST(unsigned long)string.size, &bytes_written, nullptr);
}
#else
fprintf(std_handle == Dqn_PrintStd_Out ? stdout : stderr, "%.*s", DQN_STRING_FMT(string));
#endif
}
DQN_API void Dqn_Print_StdStyle(Dqn_PrintStd std_handle, Dqn_PrintStyle style, Dqn_String8 string)
{
if (string.data && string.size) {
if (style.colour)
Dqn_Print_Std(std_handle, Dqn_Print_ESCColourFgString(style.r, style.g, style.b));
if (style.bold == Dqn_PrintBold_Yes)
Dqn_Print_Std(std_handle, Dqn_Print_ESCBoldString);
Dqn_Print_Std(std_handle, string);
if (style.colour || style.bold == Dqn_PrintBold_Yes)
Dqn_Print_Std(std_handle, Dqn_Print_ESCResetString);
}
}
DQN_FILE_SCOPE char *Dqn_Print_VSPrintfChunker_(const char *buf, void *user, int len)
{
Dqn_String8 string = {};
string.data = DQN_CAST(char *)buf;
string.size = len;
Dqn_PrintStd std_handle = DQN_CAST(Dqn_PrintStd)DQN_CAST(uintptr_t)user;
Dqn_Print_Std(std_handle, string);
return (char *)buf;
}
DQN_API void Dqn_Print_StdF(Dqn_PrintStd std_handle, char const *fmt, ...)
{
va_list args;
va_start(args, fmt);
Dqn_Print_StdFV(std_handle, fmt, args);
va_end(args);
}
DQN_API void Dqn_Print_StdFStyle(Dqn_PrintStd std_handle, Dqn_PrintStyle style, char const *fmt, ...)
{
va_list args;
va_start(args, fmt);
Dqn_Print_StdFVStyle(std_handle, style, fmt, args);
va_end(args);
}
DQN_API void Dqn_Print_StdFV(Dqn_PrintStd std_handle, char const *fmt, va_list args)
{
char buffer[STB_SPRINTF_MIN];
STB_SPRINTF_DECORATE(vsprintfcb)(Dqn_Print_VSPrintfChunker_, DQN_CAST(void *)DQN_CAST(uintptr_t)std_handle, buffer, fmt, args);
}
DQN_API void Dqn_Print_StdFVStyle(Dqn_PrintStd std_handle, Dqn_PrintStyle style, char const *fmt, va_list args)
{
if (fmt) {
if (style.colour)
Dqn_Print_Std(std_handle, Dqn_Print_ESCColourFgString(style.r, style.g, style.b));
if (style.bold == Dqn_PrintBold_Yes)
Dqn_Print_Std(std_handle, Dqn_Print_ESCBoldString);
Dqn_Print_StdFV(std_handle, fmt, args);
if (style.colour || style.bold == Dqn_PrintBold_Yes)
Dqn_Print_Std(std_handle, Dqn_Print_ESCResetString);
}
}
DQN_API void Dqn_Print_StdLn(Dqn_PrintStd std_handle, Dqn_String8 string)
{
Dqn_Print_Std(std_handle, string);
Dqn_Print_Std(std_handle, DQN_STRING8("\n"));
}
DQN_API void Dqn_Print_StdLnF(Dqn_PrintStd std_handle, char const *fmt, ...)
{
va_list args;
va_start(args, fmt);
Dqn_Print_StdLnFV(std_handle, fmt, args);
va_end(args);
}
DQN_API void Dqn_Print_StdLnFV(Dqn_PrintStd std_handle, char const *fmt, va_list args)
{
Dqn_Print_StdFV(std_handle, fmt, args);
Dqn_Print_Std(std_handle, DQN_STRING8("\n"));
}
DQN_API void Dqn_Print_StdLnStyle(Dqn_PrintStd std_handle, Dqn_PrintStyle style, Dqn_String8 string)
{
Dqn_Print_StdStyle(std_handle, style, string);
Dqn_Print_Std(std_handle, DQN_STRING8("\n"));
}
DQN_API void Dqn_Print_StdLnFStyle(Dqn_PrintStd std_handle, Dqn_PrintStyle style, char const *fmt, ...)
{
va_list args;
va_start(args, fmt);
Dqn_Print_StdLnFVStyle(std_handle, style, fmt, args);
va_end(args);
}
DQN_API void Dqn_Print_StdLnFVStyle(Dqn_PrintStd std_handle, Dqn_PrintStyle style, char const *fmt, va_list args)
{
Dqn_Print_StdFVStyle(std_handle, style, fmt, args);
Dqn_Print_Std(std_handle, DQN_STRING8("\n"));
}
DQN_API Dqn_String8 Dqn_Print_ESCColourString(Dqn_PrintESCColour colour, uint8_t r, uint8_t g, uint8_t b)
{
thread_local char buffer[32];
buffer[0] = 0;
Dqn_String8 result = {};
result.size = STB_SPRINTF_DECORATE(snprintf)(buffer,
DQN_ARRAY_UCOUNT(buffer),
"\x1b[%d;2;%u;%u;%um",
colour == Dqn_PrintESCColour_Fg ? 38 : 48,
r, g, b);
result.data = buffer;
return result;
}
DQN_API Dqn_String8 Dqn_Print_ESCColourU32String(Dqn_PrintESCColour colour, uint32_t value)
{
uint8_t r = DQN_CAST(uint8_t)(value >> 24);
uint8_t g = DQN_CAST(uint8_t)(value >> 16);
uint8_t b = DQN_CAST(uint8_t)(value >> 8);
Dqn_String8 result = Dqn_Print_ESCColourString(colour, r, g, b);
return result;
}
// =================================================================================================
// [$LLOG] Dqn_Log | | Library logging
// =================================================================================================
DQN_API Dqn_String8 Dqn_Log_MakeString(Dqn_Allocator allocator,
bool colour,
Dqn_String8 type,
int log_type,
Dqn_CallSite call_site,
char const *fmt,
va_list args)
{
Dqn_usize header_size_no_ansi_codes = 0;
Dqn_String8 header = {};
{
DQN_LOCAL_PERSIST Dqn_usize max_type_length = 0;
max_type_length = DQN_MAX(max_type_length, type.size);
int type_padding = DQN_CAST(int)(max_type_length - type.size);
Dqn_String8 colour_esc = {};
Dqn_String8 bold_esc = {};
Dqn_String8 reset_esc = {};
if (colour) {
bold_esc = Dqn_Print_ESCBoldString;
reset_esc = Dqn_Print_ESCResetString;
switch (log_type) {
case Dqn_LogType_Debug: break;
case Dqn_LogType_Info: colour_esc = Dqn_Print_ESCColourFgU32String(Dqn_LogTypeColourU32_Info); break;
case Dqn_LogType_Warning: colour_esc = Dqn_Print_ESCColourFgU32String(Dqn_LogTypeColourU32_Warning); break;
case Dqn_LogType_Error: colour_esc = Dqn_Print_ESCColourFgU32String(Dqn_LogTypeColourU32_Error); break;
}
}
Dqn_String8 file_name = Dqn_String8_FileNameFromPath(call_site.file);
Dqn_DateHMSTimeString const time = Dqn_Date_HMSLocalTimeStringNow();
header = Dqn_String8_InitF(allocator,
"%.*s " // date
"%.*s " // hms
"%.*s" // colour
"%.*s" // bold
"%.*s" // type
"%*s" // type padding
"%.*s" // reset
" %.*s" // file name
":%05u ", // line number
time.date_size - 2, time.date + 2,
time.hms_size, time.hms,
colour_esc.size, colour_esc.data,
bold_esc.size, bold_esc.data,
type.size, type.data,
type_padding, "",
reset_esc.size, reset_esc.data,
file_name.size, file_name.data,
call_site.line);
header_size_no_ansi_codes = header.size - colour_esc.size - Dqn_Print_ESCResetString.size;
}
// NOTE: Header padding
// =========================================================================
Dqn_usize header_padding = 0;
{
DQN_LOCAL_PERSIST Dqn_usize max_header_length = 0;
max_header_length = DQN_MAX(max_header_length, header_size_no_ansi_codes);
header_padding = max_header_length - header_size_no_ansi_codes;
}
// NOTE: Construct final log
// =========================================================================
Dqn_String8 user_msg = Dqn_String8_InitFV(allocator, fmt, args);
Dqn_String8 result = Dqn_String8_Allocate(allocator, header.size + header_padding + user_msg.size, Dqn_ZeroMem_No);
DQN_MEMCPY(result.data, header.data, header.size);
DQN_MEMSET(result.data + header.size, ' ', header_padding);
DQN_MEMCPY(result.data + header.size + header_padding, user_msg.data, user_msg.size);
return result;
}
DQN_FILE_SCOPE void Dqn_Log_FVDefault_(Dqn_String8 type, int log_type, void *user_data, Dqn_CallSite call_site, char const *fmt, va_list args)
{
(void)log_type;
(void)user_data;
// NOTE: Open log file for appending if requested
// =========================================================================
Dqn_TicketMutex_Begin(&dqn_library.log_file_mutex);
if (dqn_library.log_to_file && !dqn_library.log_file) {
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr);
Dqn_String8 exe_dir = Dqn_OS_EXEDir(scratch.allocator);
Dqn_String8 log_file = Dqn_String8_InitF(scratch.allocator, "%.*s/dqn.log", DQN_STRING_FMT(exe_dir));
fopen_s(DQN_CAST(FILE **)&dqn_library.log_file, log_file.data, "a");
}
Dqn_TicketMutex_End(&dqn_library.log_file_mutex);
// NOTE: Generate the log header
// =========================================================================
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr);
Dqn_String8 log_line = Dqn_Log_MakeString(scratch.allocator,
!dqn_library.log_no_colour,
type,
log_type,
call_site,
fmt,
args);
// NOTE: Print log
// =========================================================================
Dqn_Print_StdLn(Dqn_PrintStd_Out, log_line);
Dqn_TicketMutex_Begin(&dqn_library.log_file_mutex);
if (dqn_library.log_to_file) {
fprintf(DQN_CAST(FILE *)dqn_library.log_file, "%.*s\n", DQN_STRING_FMT(log_line));
}
Dqn_TicketMutex_End(&dqn_library.log_file_mutex);
}
DQN_API void Dqn_Log_FVCallSite(Dqn_String8 type, Dqn_CallSite call_site, char const *fmt, va_list args)
{
Dqn_LogProc *logging_function = dqn_library.log_callback ? dqn_library.log_callback : Dqn_Log_FVDefault_;
logging_function(type, -1 /*log_type*/, dqn_library.log_user_data, call_site, fmt, args);
}
DQN_API void Dqn_Log_FCallSite(Dqn_String8 type, Dqn_CallSite call_site, char const *fmt, ...)
{
va_list args;
va_start(args, fmt);
Dqn_Log_FVCallSite(type, call_site, fmt, args);
va_end(args);
}
DQN_API void Dqn_Log_TypeFVCallSite(Dqn_LogType type, Dqn_CallSite call_site, char const *fmt, va_list args)
{
Dqn_String8 type_string = DQN_STRING8("DQN-BAD-LOG-TYPE");
switch (type) {
case Dqn_LogType_Error: type_string = DQN_STRING8("ERROR"); break;
case Dqn_LogType_Info: type_string = DQN_STRING8("INFO"); break;
case Dqn_LogType_Warning: type_string = DQN_STRING8("WARN"); break;
case Dqn_LogType_Debug: type_string = DQN_STRING8("DEBUG"); break;
case Dqn_LogType_Count: type_string = DQN_STRING8("BADXX"); break;
}
Dqn_LogProc *logging_function = dqn_library.log_callback ? dqn_library.log_callback : Dqn_Log_FVDefault_;
logging_function(type_string, type /*log_type*/, dqn_library.log_user_data, call_site, fmt, args);
}
DQN_API void Dqn_Log_TypeFCallSite(Dqn_LogType type, Dqn_CallSite call_site, char const *fmt, ...)
{
va_list args;
va_start(args, fmt);
Dqn_Log_TypeFVCallSite(type, call_site, fmt, args);
va_end(args);
}

135
dqn_print.h Normal file
View File

@ -0,0 +1,135 @@
// =================================================================================================
// [$PRIN] Dqn_Print | | Console printing
// =================================================================================================
enum Dqn_PrintStd
{
Dqn_PrintStd_Out,
Dqn_PrintStd_Err,
};
enum Dqn_PrintBold
{
Dqn_PrintBold_No,
Dqn_PrintBold_Yes,
};
struct Dqn_PrintStyle
{
Dqn_PrintBold bold;
bool colour;
uint8_t r, g, b;
};
enum Dqn_PrintESCColour
{
Dqn_PrintESCColour_Fg,
Dqn_PrintESCColour_Bg,
};
// NOTE: Print Style ===============================================================================
DQN_API Dqn_PrintStyle Dqn_Print_StyleColour (uint8_t r, uint8_t g, uint8_t b, Dqn_PrintBold bold);
DQN_API Dqn_PrintStyle Dqn_Print_StyleColourU32 (uint32_t rgb, Dqn_PrintBold bold);
DQN_API Dqn_PrintStyle Dqn_Print_StyleBold ();
// NOTE: Print Standard Out ========================================================================
#define Dqn_Print(string) Dqn_Print_Std(Dqn_PrintStd_Out, string)
#define Dqn_Print_F(fmt, ...) Dqn_Print_StdF(Dqn_PrintStd_Out, fmt, ## __VA_ARGS__)
#define Dqn_Print_FV(fmt, args) Dqn_Print_StdFV(Dqn_PrintStd_Out, fmt, args)
#define Dqn_Print_Style(style, string) Dqn_Print_StdStyle(Dqn_PrintStd_Out, style, string)
#define Dqn_Print_FStyle(style, fmt, ...) Dqn_Print_StdFStyle(Dqn_PrintStd_Out, style, fmt, ## __VA_ARGS__)
#define Dqn_Print_FVStyle(style, fmt, args, ...) Dqn_Print_StdFVStyle(Dqn_PrintStd_Out, style, fmt, args)
#define Dqn_Print_Ln(string) Dqn_Print_StdLn(Dqn_PrintStd_Out, string)
#define Dqn_Print_LnF(fmt, ...) Dqn_Print_StdLnF(Dqn_PrintStd_Out, fmt, ## __VA_ARGS__)
#define Dqn_Print_LnFV(fmt, args) Dqn_Print_StdLnFV(Dqn_PrintStd_Out, fmt, args)
#define Dqn_Print_LnStyle(style, string) Dqn_Print_StdLnStyle(Dqn_PrintStd_Out, style, string);
#define Dqn_Print_LnFStyle(style, fmt, ...) Dqn_Print_StdLnFStyle(Dqn_PrintStd_Out, style, fmt, ## __VA_ARGS__);
#define Dqn_Print_LnFVStyle(style, fmt, args) Dqn_Print_StdLnFVStyle(Dqn_PrintStd_Out, style, fmt, args);
// NOTE: Print =====================================================================================
DQN_API void Dqn_Print_Std (Dqn_PrintStd std_handle, Dqn_String8 string);
DQN_API void Dqn_Print_StdF (Dqn_PrintStd std_handle, char const *fmt, ...);
DQN_API void Dqn_Print_StdFV (Dqn_PrintStd std_handle, char const *fmt, va_list args);
DQN_API void Dqn_Print_StdStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, Dqn_String8 string);
DQN_API void Dqn_Print_StdFStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, char const *fmt, ...);
DQN_API void Dqn_Print_StdFVStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, char const *fmt, va_list args);
DQN_API void Dqn_Print_StdLn (Dqn_PrintStd std_handle, Dqn_String8 string);
DQN_API void Dqn_Print_StdLnF (Dqn_PrintStd std_handle, char const *fmt, ...);
DQN_API void Dqn_Print_StdLnFV (Dqn_PrintStd std_handle, char const *fmt, va_list args);
DQN_API void Dqn_Print_StdLnStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, Dqn_String8 string);
DQN_API void Dqn_Print_StdLnFStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, char const *fmt, ...);
DQN_API void Dqn_Print_StdLnFVStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, char const *fmt, va_list args);
// NOTE: ANSI Formatting Codes =====================================================================
Dqn_String8 Dqn_Print_ESCColourString (Dqn_PrintESCColour colour, uint8_t r, uint8_t g, uint8_t b);
Dqn_String8 Dqn_Print_ESCColourU32String(Dqn_PrintESCColour colour, uint32_t value);
#define Dqn_Print_ESCColourFgString(r, g, b) Dqn_Print_ESCColourString(Dqn_PrintESCColour_Fg, r, g, b)
#define Dqn_Print_ESCColourBgString(r, g, b) Dqn_Print_ESCColourString(Dqn_PrintESCColour_Bg, r, g, b)
#define Dqn_Print_ESCColourFg(r, g, b) Dqn_Print_ESCColourString(Dqn_PrintESCColour_Fg, r, g, b).data
#define Dqn_Print_ESCColourBg(r, g, b) Dqn_Print_ESCColourString(Dqn_PrintESCColour_Bg, r, g, b).data
#define Dqn_Print_ESCColourFgU32String(value) Dqn_Print_ESCColourU32String(Dqn_PrintESCColour_Fg, value)
#define Dqn_Print_ESCColourBgU32String(value) Dqn_Print_ESCColourU32String(Dqn_PrintESCColour_Bg, value)
#define Dqn_Print_ESCColourFgU32(value) Dqn_Print_ESCColourU32String(Dqn_PrintESCColour_Fg, value).data
#define Dqn_Print_ESCColourBgU32(value) Dqn_Print_ESCColourU32String(Dqn_PrintESCColour_Bg, value).data
#define Dqn_Print_ESCReset "\x1b[0m"
#define Dqn_Print_ESCBold "\x1b[1m"
#define Dqn_Print_ESCResetString DQN_STRING8(Dqn_Print_ESCReset)
#define Dqn_Print_ESCBoldString DQN_STRING8(Dqn_Print_ESCBold)
// =================================================================================================
// [$LLOG] Dqn_Log | | Library logging
// =================================================================================================
enum Dqn_LogType
{
Dqn_LogType_Debug,
Dqn_LogType_Info,
Dqn_LogType_Warning,
Dqn_LogType_Error,
Dqn_LogType_Count,
};
/// RGBA
#define Dqn_LogTypeColourU32_Info 0x00'87'ff'ff // Blue
#define Dqn_LogTypeColourU32_Warning 0xff'ff'00'ff // Yellow
#define Dqn_LogTypeColourU32_Error 0xff'00'00'ff // Red
/// The logging procedure of the library. Users can override the default logging
/// function by setting the logging function pointer in Dqn_Library. This
/// function will be invoked every time a log is recorded using the following
/// functions.
///
/// @param[in] log_type This value is one of the Dqn_LogType values if the log
/// was generated from one of the default categories. -1 if the log is not from
/// one of the default categories.
typedef void Dqn_LogProc(Dqn_String8 type, int log_type, void *user_data, Dqn_CallSite call_site, char const *fmt, va_list va);
#define Dqn_Log_DebugF(fmt, ...) Dqn_Log_TypeFCallSite(Dqn_LogType_Debug, DQN_CALL_SITE, fmt, ## __VA_ARGS__)
#define Dqn_Log_InfoF(fmt, ...) Dqn_Log_TypeFCallSite(Dqn_LogType_Info, DQN_CALL_SITE, fmt, ## __VA_ARGS__)
#define Dqn_Log_WarningF(fmt, ...) Dqn_Log_TypeFCallSite(Dqn_LogType_Warning, DQN_CALL_SITE, fmt, ## __VA_ARGS__)
#define Dqn_Log_ErrorF(fmt, ...) Dqn_Log_TypeFCallSite(Dqn_LogType_Error, DQN_CALL_SITE, fmt, ## __VA_ARGS__)
#define Dqn_Log_DebugFV(fmt, args) Dqn_Log_TypeFVCallSite(Dqn_LogType_Debug, DQN_CALL_SITE, fmt, args)
#define Dqn_Log_InfoFV(fmt, args) Dqn_Log_TypeFVCallSite(Dqn_LogType_Info, DQN_CALL_SITE, fmt, args)
#define Dqn_Log_WarningFV(fmt, args) Dqn_Log_TypeFVCallSite(Dqn_LogType_Warning, DQN_CALL_SITE, fmt, args)
#define Dqn_Log_ErrorFV(fmt, args) Dqn_Log_TypeFVCallSite(Dqn_LogType_Error, DQN_CALL_SITE, fmt, args)
#define Dqn_Log_TypeFV(type, fmt, args) Dqn_Log_TypeFVCallSite(type, DQN_CALL_SITE, fmt, args)
#define Dqn_Log_TypeF(type, fmt, ...) Dqn_Log_TypeFCallSite(type, DQN_CALL_SITE, fmt, ## __VA_ARGS__)
#define Dqn_Log_FV(type, fmt, args) Dqn_Log_FVCallSite(type, DQN_CALL_SITE, fmt, args)
#define Dqn_Log_F(type, fmt, ...) Dqn_Log_FCallSite(type, DQN_CALL_SITE, fmt, ## __VA_ARGS__)
DQN_API Dqn_String8 Dqn_Log_MakeString (Dqn_Allocator allocator, bool colour, Dqn_String8 type, int log_type, Dqn_CallSite call_site, char const *fmt, va_list args);
DQN_API void Dqn_Log_TypeFVCallSite(Dqn_LogType type, Dqn_CallSite call_site, char const *fmt, va_list va);
DQN_API void Dqn_Log_TypeFCallSite (Dqn_LogType type, Dqn_CallSite call_site, char const *fmt, ...);
DQN_API void Dqn_Log_FVCallSite (Dqn_String8 type, Dqn_CallSite call_site, char const *fmt, va_list va);
DQN_API void Dqn_Log_FCallSite (Dqn_String8 type, Dqn_CallSite call_site, char const *fmt, ...);

2948
dqn_strings.cpp Normal file

File diff suppressed because it is too large Load Diff

1151
dqn_strings.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,28 @@
# Dqn
Personal utility library that provides allocator aware data structures, custom
memory allocators and various miscellaneous helpers.
Personal standard library that provides allocator aware data structures, custom
memory allocators and various miscellaneous helpers for prototyping. The library
is a unity-build style library where data structures and functions are separated
by category into files for organisation. You only need to include `dqn.h` which
amalgamates all the files into one translation unit.
## Build
To build with this library, copy all the `*.[h|cpp]` files at the root of the
repository and in one header file,
```cpp
#include "dqn.h"
```
Which includes all other files and their declaration into your header. In one
`.cpp` file defined the implementation macro to enable the implementation of the
header in that translation unit,
```cpp
#define DQN_IMPLEMENTATION
#include "dqn.h"
```
Ensure that the folder containing the files is part of the include search path
for the compiler for the amalgamated `dqn.h` to successfully locate the files.