Dqn/dqn_helpers.cpp

1531 lines
52 KiB
C++
Raw Normal View History

2024-04-18 22:59:11 +10:00
#pragma once
#include "dqn.h"
/*
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// $$\ $$\ $$$$$$$$\ $$\ $$$$$$$\ $$$$$$$$\ $$$$$$$\ $$$$$$\
// $$ | $$ |$$ _____|$$ | $$ __$$\ $$ _____|$$ __$$\ $$ __$$\
// $$ | $$ |$$ | $$ | $$ | $$ |$$ | $$ | $$ |$$ / \__|
// $$$$$$$$ |$$$$$\ $$ | $$$$$$$ |$$$$$\ $$$$$$$ |\$$$$$$\
// $$ __$$ |$$ __| $$ | $$ ____/ $$ __| $$ __$$< \____$$\
// $$ | $$ |$$ | $$ | $$ | $$ | $$ | $$ |$$\ $$ |
// $$ | $$ |$$$$$$$$\ $$$$$$$$\ $$ | $$$$$$$$\ $$ | $$ |\$$$$$$ |
// \__| \__|\________|\________|\__| \________|\__| \__| \______/
//
// dqn_helpers.cpp
//
////////////////////////////////////////////////////////////////////////////////////////////////////
*/
2025-02-14 00:27:42 +11:00
// NOTE: [$PCGX] DN_PCG32 /////////////////////////////////////////////////////////////////////////
#define DN_PCG_DEFAULT_MULTIPLIER_64 6364136223846793005ULL
#define DN_PCG_DEFAULT_INCREMENT_64 1442695040888963407ULL
2025-02-14 00:27:42 +11:00
DN_API DN_PCG32 DN_PCG32_Init(uint64_t seed)
{
2025-02-14 00:27:42 +11:00
DN_PCG32 result = {};
DN_PCG32_Next(&result);
result.state += seed;
2025-02-14 00:27:42 +11:00
DN_PCG32_Next(&result);
return result;
}
2025-02-14 00:27:42 +11:00
DN_API uint32_t DN_PCG32_Next(DN_PCG32 *rng)
{
uint64_t state = rng->state;
2025-02-14 00:27:42 +11:00
rng->state = state * DN_PCG_DEFAULT_MULTIPLIER_64 + DN_PCG_DEFAULT_INCREMENT_64;
// XSH-RR
uint32_t value = (uint32_t)((state ^ (state >> 18)) >> 27);
int rot = state >> 59;
return rot ? (value >> rot) | (value << (32 - rot)) : value;
}
2025-02-14 00:27:42 +11:00
DN_API uint64_t DN_PCG32_Next64(DN_PCG32 *rng)
{
2025-02-14 00:27:42 +11:00
uint64_t value = DN_PCG32_Next(rng);
value <<= 32;
2025-02-14 00:27:42 +11:00
value |= DN_PCG32_Next(rng);
return value;
}
2025-02-14 00:27:42 +11:00
DN_API uint32_t DN_PCG32_Range(DN_PCG32 *rng, uint32_t low, uint32_t high)
{
uint32_t bound = high - low;
uint32_t threshold = -(int32_t)bound % bound;
for (;;) {
2025-02-14 00:27:42 +11:00
uint32_t r = DN_PCG32_Next(rng);
if (r >= threshold)
return low + (r % bound);
}
}
2025-02-14 00:27:42 +11:00
DN_API float DN_PCG32_NextF32(DN_PCG32 *rng)
{
2025-02-14 00:27:42 +11:00
uint32_t x = DN_PCG32_Next(rng);
return (float)(int32_t)(x >> 8) * 0x1.0p-24f;
}
2025-02-14 00:27:42 +11:00
DN_API double DN_PCG32_NextF64(DN_PCG32 *rng)
{
2025-02-14 00:27:42 +11:00
uint64_t x = DN_PCG32_Next64(rng);
return (double)(int64_t)(x >> 11) * 0x1.0p-53;
}
2025-02-14 00:27:42 +11:00
DN_API void DN_PCG32_Advance(DN_PCG32 *rng, uint64_t delta)
{
2025-02-14 00:27:42 +11:00
uint64_t cur_mult = DN_PCG_DEFAULT_MULTIPLIER_64;
uint64_t cur_plus = DN_PCG_DEFAULT_INCREMENT_64;
uint64_t acc_mult = 1;
uint64_t acc_plus = 0;
while (delta != 0) {
if (delta & 1) {
acc_mult *= cur_mult;
acc_plus = acc_plus * cur_mult + cur_plus;
}
cur_plus = (cur_mult + 1) * cur_plus;
cur_mult *= cur_mult;
delta >>= 1;
}
rng->state = acc_mult * rng->state + acc_plus;
}
2025-02-14 00:27:42 +11:00
#if !defined(DN_NO_JSON_BUILDER)
// NOTE: [$JSON] DN_JSONBuilder ///////////////////////////////////////////////////////////////////
DN_API DN_JSONBuilder DN_JSONBuilder_Init(DN_Arena *arena, int spaces_per_indent)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
DN_JSONBuilder result = {};
result.spaces_per_indent = spaces_per_indent;
result.string_builder.arena = arena;
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API DN_Str8 DN_JSONBuilder_Build(DN_JSONBuilder const *builder, DN_Arena *arena)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
DN_Str8 result = DN_Str8Builder_Build(&builder->string_builder, arena);
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API void DN_JSONBuilder_KeyValue(DN_JSONBuilder *builder, DN_Str8 key, DN_Str8 value)
2023-08-16 21:59:38 +10:00
{
if (key.size == 0 && value.size == 0)
return;
2025-02-14 00:27:42 +11:00
DN_JSONBuilderItem item = DN_JSONBuilderItem_KeyValue;
if (value.size >= 1) {
if (value.data[0] == '{' || value.data[0] == '[')
2025-02-14 00:27:42 +11:00
item = DN_JSONBuilderItem_OpenContainer;
else if (value.data[0] == '}' || value.data[0] == ']')
2025-02-14 00:27:42 +11:00
item = DN_JSONBuilderItem_CloseContainer;
2023-08-16 21:59:38 +10:00
}
bool adding_to_container_with_items =
2025-02-14 00:27:42 +11:00
item != DN_JSONBuilderItem_CloseContainer && (builder->last_item == DN_JSONBuilderItem_KeyValue ||
builder->last_item == DN_JSONBuilderItem_CloseContainer);
2023-08-16 21:59:38 +10:00
uint8_t prefix_size = 0;
char prefix[2] = {0};
2023-08-16 21:59:38 +10:00
if (adding_to_container_with_items)
prefix[prefix_size++] = ',';
2025-02-14 00:27:42 +11:00
if (builder->last_item != DN_JSONBuilderItem_Empty)
2023-08-16 21:59:38 +10:00
prefix[prefix_size++] = '\n';
2025-02-14 00:27:42 +11:00
if (item == DN_JSONBuilderItem_CloseContainer)
2023-08-16 21:59:38 +10:00
builder->indent_level--;
int spaces_per_indent = builder->spaces_per_indent ? builder->spaces_per_indent : 2;
int spaces = builder->indent_level * spaces_per_indent;
if (key.size) {
2025-02-14 00:27:42 +11:00
DN_Str8Builder_AppendF(&builder->string_builder,
2024-08-01 13:34:36 +10:00
"%.*s%*c\"%.*s\": %.*s",
prefix_size,
prefix,
spaces,
' ',
2025-02-14 00:27:42 +11:00
DN_STR_FMT(key),
DN_STR_FMT(value));
2023-08-16 21:59:38 +10:00
} else {
2025-02-14 00:27:42 +11:00
if (spaces == 0)
DN_Str8Builder_AppendF(&builder->string_builder, "%.*s%.*s", prefix_size, prefix, DN_STR_FMT(value));
else
DN_Str8Builder_AppendF(&builder->string_builder, "%.*s%*c%.*s", prefix_size, prefix, spaces, ' ', DN_STR_FMT(value));
2023-08-16 21:59:38 +10:00
}
2025-02-14 00:27:42 +11:00
if (item == DN_JSONBuilderItem_OpenContainer)
2023-08-16 21:59:38 +10:00
builder->indent_level++;
builder->last_item = item;
}
2025-02-14 00:27:42 +11:00
DN_API void DN_JSONBuilder_KeyValueFV(DN_JSONBuilder *builder, DN_Str8 key, char const *value_fmt, va_list args)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
DN_TLSTMem tmem = DN_TLS_TMem(builder->string_builder.arena);
DN_Str8 value = DN_Str8_InitFV(tmem.arena, value_fmt, args);
DN_JSONBuilder_KeyValue(builder, key, value);
2023-08-16 21:59:38 +10:00
}
2025-02-14 00:27:42 +11:00
DN_API void DN_JSONBuilder_KeyValueF(DN_JSONBuilder *builder, DN_Str8 key, char const *value_fmt, ...)
2023-08-16 21:59:38 +10:00
{
va_list args;
va_start(args, value_fmt);
2025-02-14 00:27:42 +11:00
DN_JSONBuilder_KeyValueFV(builder, key, value_fmt, args);
2023-08-16 21:59:38 +10:00
va_end(args);
}
2025-02-14 00:27:42 +11:00
DN_API void DN_JSONBuilder_ObjectBeginNamed(DN_JSONBuilder *builder, DN_Str8 name)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
DN_JSONBuilder_KeyValue(builder, name, DN_STR8("{"));
2023-08-16 21:59:38 +10:00
}
2025-02-14 00:27:42 +11:00
DN_API void DN_JSONBuilder_ObjectEnd(DN_JSONBuilder *builder)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
DN_JSONBuilder_KeyValue(builder, DN_STR8(""), DN_STR8("}"));
2023-08-16 21:59:38 +10:00
}
2025-02-14 00:27:42 +11:00
DN_API void DN_JSONBuilder_ArrayBeginNamed(DN_JSONBuilder *builder, DN_Str8 name)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
DN_JSONBuilder_KeyValue(builder, name, DN_STR8("["));
2023-08-16 21:59:38 +10:00
}
2025-02-14 00:27:42 +11:00
DN_API void DN_JSONBuilder_ArrayEnd(DN_JSONBuilder *builder)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
DN_JSONBuilder_KeyValue(builder, DN_STR8(""), DN_STR8("]"));
2023-08-16 21:59:38 +10:00
}
2025-02-14 00:27:42 +11:00
DN_API void DN_JSONBuilder_Str8Named(DN_JSONBuilder *builder, DN_Str8 key, DN_Str8 value)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
DN_JSONBuilder_KeyValueF(builder, key, "\"%.*s\"", value.size, value.data);
2023-08-16 21:59:38 +10:00
}
2025-02-14 00:27:42 +11:00
DN_API void DN_JSONBuilder_LiteralNamed(DN_JSONBuilder *builder, DN_Str8 key, DN_Str8 value)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
DN_JSONBuilder_KeyValueF(builder, key, "%.*s", value.size, value.data);
2023-08-16 21:59:38 +10:00
}
2025-02-14 00:27:42 +11:00
DN_API void DN_JSONBuilder_U64Named(DN_JSONBuilder *builder, DN_Str8 key, uint64_t value)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
DN_JSONBuilder_KeyValueF(builder, key, "%I64u", value);
2023-08-16 21:59:38 +10:00
}
2025-02-14 00:27:42 +11:00
DN_API void DN_JSONBuilder_I64Named(DN_JSONBuilder *builder, DN_Str8 key, int64_t value)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
DN_JSONBuilder_KeyValueF(builder, key, "%I64d", value);
2023-08-16 21:59:38 +10:00
}
2025-02-14 00:27:42 +11:00
DN_API void DN_JSONBuilder_F64Named(DN_JSONBuilder *builder, DN_Str8 key, double value, int decimal_places)
2023-08-16 21:59:38 +10:00
{
if (!builder)
return;
if (decimal_places >= 16)
decimal_places = 16;
// NOTE: Generate the format string for the float, depending on how many
// decimals places it wants.
char float_fmt[16];
if (decimal_places > 0) {
// NOTE: Emit the format string "%.<decimal_places>f" i.e. %.1f
2025-02-14 00:27:42 +11:00
DN_SNPRINTF(float_fmt, sizeof(float_fmt), "%%.%df", decimal_places);
2023-08-16 21:59:38 +10:00
} else {
// NOTE: Emit the format string "%f"
2025-02-14 00:27:42 +11:00
DN_SNPRINTF(float_fmt, sizeof(float_fmt), "%%f");
2023-08-16 21:59:38 +10:00
}
2025-02-14 00:27:42 +11:00
DN_JSONBuilder_KeyValueF(builder, key, float_fmt, value);
2023-08-16 21:59:38 +10:00
}
2025-02-14 00:27:42 +11:00
DN_API void DN_JSONBuilder_BoolNamed(DN_JSONBuilder *builder, DN_Str8 key, bool value)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
DN_Str8 value_string = value ? DN_STR8("true") : DN_STR8("false");
DN_JSONBuilder_KeyValueF(builder, key, "%.*s", value_string.size, value_string.data);
2023-08-16 21:59:38 +10:00
}
2025-02-14 00:27:42 +11:00
#endif // !defined(DN_NO_JSON_BUILDER)
2023-08-16 21:59:38 +10:00
2025-02-14 00:27:42 +11:00
// NOTE: [$BITS] DN_Bit ///////////////////////////////////////////////////////////////////////////
DN_API void DN_Bit_UnsetInplace(DN_USize *flags, DN_USize bitfield)
2023-08-16 21:59:38 +10:00
{
*flags = (*flags & ~bitfield);
}
2025-02-14 00:27:42 +11:00
DN_API void DN_Bit_SetInplace(DN_USize *flags, DN_USize bitfield)
2023-08-16 21:59:38 +10:00
{
*flags = (*flags | bitfield);
}
2025-02-14 00:27:42 +11:00
DN_API bool DN_Bit_IsSet(DN_USize bits, DN_USize bits_to_set)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
auto result = DN_CAST(bool)((bits & bits_to_set) == bits_to_set);
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API bool DN_Bit_IsNotSet(DN_USize bits, DN_USize bits_to_check)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
auto result = !DN_Bit_IsSet(bits, bits_to_check);
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
// NOTE: [$SAFE] DN_Safe //////////////////////////////////////////////////////////////////////////
DN_API int64_t DN_Safe_AddI64(int64_t a, int64_t b)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
int64_t result = DN_CHECKF(a <= INT64_MAX - b, "a=%zd, b=%zd", a, b) ? (a + b) : INT64_MAX;
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API int64_t DN_Safe_MulI64(int64_t a, int64_t b)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
int64_t result = DN_CHECKF(a <= INT64_MAX / b, "a=%zd, b=%zd", a, b) ? (a * b) : INT64_MAX;
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API uint64_t DN_Safe_AddU64(uint64_t a, uint64_t b)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
uint64_t result = DN_CHECKF(a <= UINT64_MAX - b, "a=%zu, b=%zu", a, b) ? (a + b) : UINT64_MAX;
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API uint64_t DN_Safe_SubU64(uint64_t a, uint64_t b)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
uint64_t result = DN_CHECKF(a >= b, "a=%zu, b=%zu", a, b) ? (a - b) : 0;
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API uint64_t DN_Safe_MulU64(uint64_t a, uint64_t b)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
uint64_t result = DN_CHECKF(a <= UINT64_MAX / b, "a=%zu, b=%zu", a, b) ? (a * b) : UINT64_MAX;
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API uint32_t DN_Safe_SubU32(uint32_t a, uint32_t b)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
uint32_t result = DN_CHECKF(a >= b, "a=%u, b=%u", a, b) ? (a - b) : 0;
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
// NOTE: DN_Safe_SaturateCastUSizeToI* ////////////////////////////////////////////////////////////
2023-08-16 21:59:38 +10:00
// INT*_MAX literals will be promoted to the type of uintmax_t as uintmax_t is
// the highest possible rank (unsigned > signed).
2025-02-14 00:27:42 +11:00
DN_API int DN_Safe_SaturateCastUSizeToInt(DN_USize val)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
int result = DN_CHECK(DN_CAST(uintmax_t) val <= INT_MAX) ? DN_CAST(int) val : INT_MAX;
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API int8_t DN_Safe_SaturateCastUSizeToI8(DN_USize val)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
int8_t result = DN_CHECK(DN_CAST(uintmax_t) val <= INT8_MAX) ? DN_CAST(int8_t) val : INT8_MAX;
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API int16_t DN_Safe_SaturateCastUSizeToI16(DN_USize val)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
int16_t result = DN_CHECK(DN_CAST(uintmax_t) val <= INT16_MAX) ? DN_CAST(int16_t) val : INT16_MAX;
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API int32_t DN_Safe_SaturateCastUSizeToI32(DN_USize val)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
int32_t result = DN_CHECK(DN_CAST(uintmax_t) val <= INT32_MAX) ? DN_CAST(int32_t) val : INT32_MAX;
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API int64_t DN_Safe_SaturateCastUSizeToI64(DN_USize val)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
int64_t result = DN_CHECK(DN_CAST(uintmax_t) val <= INT64_MAX) ? DN_CAST(int64_t) val : INT64_MAX;
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
// NOTE: DN_Safe_SaturateCastUSizeToU* ////////////////////////////////////////////////////////////
2023-08-16 21:59:38 +10:00
// Both operands are unsigned and the lowest rank operand will be promoted to
// match the highest rank operand.
2025-02-14 00:27:42 +11:00
DN_API uint8_t DN_Safe_SaturateCastUSizeToU8(DN_USize val)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
uint8_t result = DN_CHECK(val <= UINT8_MAX) ? DN_CAST(uint8_t) val : UINT8_MAX;
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API uint16_t DN_Safe_SaturateCastUSizeToU16(DN_USize val)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
uint16_t result = DN_CHECK(val <= UINT16_MAX) ? DN_CAST(uint16_t) val : UINT16_MAX;
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API uint32_t DN_Safe_SaturateCastUSizeToU32(DN_USize val)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
uint32_t result = DN_CHECK(val <= UINT32_MAX) ? DN_CAST(uint32_t) val : UINT32_MAX;
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API uint64_t DN_Safe_SaturateCastUSizeToU64(DN_USize val)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
uint64_t result = DN_CHECK(DN_CAST(uint64_t) val <= UINT64_MAX) ? DN_CAST(uint64_t) val : UINT64_MAX;
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
// NOTE: DN_Safe_SaturateCastU64To* ///////////////////////////////////////////////////////////////
DN_API int DN_Safe_SaturateCastU64ToInt(uint64_t val)
{
2025-02-14 00:27:42 +11:00
int result = DN_CHECK(val <= INT_MAX) ? DN_CAST(int)val : INT_MAX;
return result;
}
2025-02-14 00:27:42 +11:00
DN_API int8_t DN_Safe_SaturateCastU64ToI8(uint64_t val)
{
2025-02-14 00:27:42 +11:00
int8_t result = DN_CHECK(val <= INT8_MAX) ? DN_CAST(int8_t)val : INT8_MAX;
return result;
}
DN_API int16_t DN_Safe_SaturateCastU64ToI16(uint64_t val)
{
int16_t result = DN_CHECK(val <= INT16_MAX) ? DN_CAST(int16_t)val : INT16_MAX;
return result;
}
DN_API int32_t DN_Safe_SaturateCastU64ToI32(uint64_t val)
{
int32_t result = DN_CHECK(val <= INT32_MAX) ? DN_CAST(int32_t)val : INT32_MAX;
return result;
}
DN_API int64_t DN_Safe_SaturateCastU64ToI64(uint64_t val)
{
int64_t result = DN_CHECK(val <= INT64_MAX) ? DN_CAST(int64_t)val : INT64_MAX;
return result;
}
2023-08-16 21:59:38 +10:00
// Both operands are unsigned and the lowest rank operand will be promoted to
// match the highest rank operand.
2025-02-14 00:27:42 +11:00
DN_API unsigned int DN_Safe_SaturateCastU64ToUInt(uint64_t val)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
unsigned int result = DN_CHECK(val <= UINT8_MAX) ? DN_CAST(unsigned int) val : UINT_MAX;
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API uint8_t DN_Safe_SaturateCastU64ToU8(uint64_t val)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
uint8_t result = DN_CHECK(val <= UINT8_MAX) ? DN_CAST(uint8_t) val : UINT8_MAX;
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API uint16_t DN_Safe_SaturateCastU64ToU16(uint64_t val)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
uint16_t result = DN_CHECK(val <= UINT16_MAX) ? DN_CAST(uint16_t) val : UINT16_MAX;
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API uint32_t DN_Safe_SaturateCastU64ToU32(uint64_t val)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
uint32_t result = DN_CHECK(val <= UINT32_MAX) ? DN_CAST(uint32_t) val : UINT32_MAX;
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
// NOTE: DN_Safe_SaturateCastISizeToI* ////////////////////////////////////////////////////////////
2023-08-16 21:59:38 +10:00
// Both operands are signed so the lowest rank operand will be promoted to
// match the highest rank operand.
2025-02-14 00:27:42 +11:00
DN_API int DN_Safe_SaturateCastISizeToInt(DN_ISize val)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
DN_ASSERT(val >= INT_MIN && val <= INT_MAX);
int result = DN_CAST(int) DN_CLAMP(val, INT_MIN, INT_MAX);
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API int8_t DN_Safe_SaturateCastISizeToI8(DN_ISize val)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
DN_ASSERT(val >= INT8_MIN && val <= INT8_MAX);
int8_t result = DN_CAST(int8_t) DN_CLAMP(val, INT8_MIN, INT8_MAX);
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API int16_t DN_Safe_SaturateCastISizeToI16(DN_ISize val)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
DN_ASSERT(val >= INT16_MIN && val <= INT16_MAX);
int16_t result = DN_CAST(int16_t) DN_CLAMP(val, INT16_MIN, INT16_MAX);
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API int32_t DN_Safe_SaturateCastISizeToI32(DN_ISize val)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
DN_ASSERT(val >= INT32_MIN && val <= INT32_MAX);
int32_t result = DN_CAST(int32_t) DN_CLAMP(val, INT32_MIN, INT32_MAX);
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API int64_t DN_Safe_SaturateCastISizeToI64(DN_ISize val)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
DN_ASSERT(DN_CAST(int64_t) val >= INT64_MIN && DN_CAST(int64_t) val <= INT64_MAX);
int64_t result = DN_CAST(int64_t) DN_CLAMP(DN_CAST(int64_t) val, INT64_MIN, INT64_MAX);
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
// NOTE: DN_Safe_SaturateCastISizeToU* ////////////////////////////////////////////////////////////
2023-08-16 21:59:38 +10:00
// 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.
2025-02-14 00:27:42 +11:00
DN_API unsigned int DN_Safe_SaturateCastISizeToUInt(DN_ISize val)
2023-08-16 21:59:38 +10:00
{
unsigned int result = 0;
2025-02-14 00:27:42 +11:00
if (DN_CHECK(val >= DN_CAST(DN_ISize) 0)) {
if (DN_CHECK(DN_CAST(uintmax_t) val <= UINT_MAX))
result = DN_CAST(unsigned int) val;
2023-08-16 21:59:38 +10:00
else
result = UINT_MAX;
}
return result;
}
2025-02-14 00:27:42 +11:00
DN_API uint8_t DN_Safe_SaturateCastISizeToU8(DN_ISize val)
2023-08-16 21:59:38 +10:00
{
uint8_t result = 0;
2025-02-14 00:27:42 +11:00
if (DN_CHECK(val >= DN_CAST(DN_ISize) 0)) {
if (DN_CHECK(DN_CAST(uintmax_t) val <= UINT8_MAX))
result = DN_CAST(uint8_t) val;
2023-08-16 21:59:38 +10:00
else
result = UINT8_MAX;
}
return result;
}
2025-02-14 00:27:42 +11:00
DN_API uint16_t DN_Safe_SaturateCastISizeToU16(DN_ISize val)
2023-08-16 21:59:38 +10:00
{
uint16_t result = 0;
2025-02-14 00:27:42 +11:00
if (DN_CHECK(val >= DN_CAST(DN_ISize) 0)) {
if (DN_CHECK(DN_CAST(uintmax_t) val <= UINT16_MAX))
result = DN_CAST(uint16_t) val;
2023-08-16 21:59:38 +10:00
else
result = UINT16_MAX;
}
return result;
}
2025-02-14 00:27:42 +11:00
DN_API uint32_t DN_Safe_SaturateCastISizeToU32(DN_ISize val)
2023-08-16 21:59:38 +10:00
{
uint32_t result = 0;
2025-02-14 00:27:42 +11:00
if (DN_CHECK(val >= DN_CAST(DN_ISize) 0)) {
if (DN_CHECK(DN_CAST(uintmax_t) val <= UINT32_MAX))
result = DN_CAST(uint32_t) val;
2023-08-16 21:59:38 +10:00
else
result = UINT32_MAX;
}
return result;
}
2025-02-14 00:27:42 +11:00
DN_API uint64_t DN_Safe_SaturateCastISizeToU64(DN_ISize val)
2023-08-16 21:59:38 +10:00
{
uint64_t result = 0;
2025-02-14 00:27:42 +11:00
if (DN_CHECK(val >= DN_CAST(DN_ISize) 0)) {
if (DN_CHECK(DN_CAST(uintmax_t) val <= UINT64_MAX))
result = DN_CAST(uint64_t) val;
2023-08-16 21:59:38 +10:00
else
result = UINT64_MAX;
}
return result;
}
2025-02-14 00:27:42 +11:00
// NOTE: DN_Safe_SaturateCastI64To* ///////////////////////////////////////////////////////////////
2023-08-16 21:59:38 +10:00
// Both operands are signed so the lowest rank operand will be promoted to
// match the highest rank operand.
2025-02-14 00:27:42 +11:00
DN_API DN_ISize DN_Safe_SaturateCastI64ToISize(int64_t val)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
DN_CHECK(val >= DN_ISIZE_MIN && val <= DN_ISIZE_MAX);
DN_ISize result = DN_CAST(int64_t) DN_CLAMP(val, DN_ISIZE_MIN, DN_ISIZE_MAX);
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API int8_t DN_Safe_SaturateCastI64ToI8(int64_t val)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
DN_CHECK(val >= INT8_MIN && val <= INT8_MAX);
int8_t result = DN_CAST(int8_t) DN_CLAMP(val, INT8_MIN, INT8_MAX);
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API int16_t DN_Safe_SaturateCastI64ToI16(int64_t val)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
DN_CHECK(val >= INT16_MIN && val <= INT16_MAX);
int16_t result = DN_CAST(int16_t) DN_CLAMP(val, INT16_MIN, INT16_MAX);
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API int32_t DN_Safe_SaturateCastI64ToI32(int64_t val)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
DN_CHECK(val >= INT32_MIN && val <= INT32_MAX);
int32_t result = DN_CAST(int32_t) DN_CLAMP(val, INT32_MIN, INT32_MAX);
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API unsigned int DN_Safe_SaturateCastI64ToUInt(int64_t val)
{
unsigned int result = 0;
2025-02-14 00:27:42 +11:00
if (DN_CHECK(val >= DN_CAST(int64_t) 0)) {
if (DN_CHECK(DN_CAST(uintmax_t) val <= UINT_MAX))
result = DN_CAST(unsigned int) val;
else
result = UINT_MAX;
}
return result;
}
2025-02-14 00:27:42 +11:00
DN_API DN_ISize DN_Safe_SaturateCastI64ToUSize(int64_t val)
{
2025-02-14 00:27:42 +11:00
DN_USize result = 0;
if (DN_CHECK(val >= DN_CAST(int64_t) 0)) {
if (DN_CHECK(DN_CAST(uintmax_t) val <= DN_USIZE_MAX))
result = DN_CAST(DN_USize) val;
else
2025-02-14 00:27:42 +11:00
result = DN_USIZE_MAX;
}
return result;
}
2025-02-14 00:27:42 +11:00
DN_API uint8_t DN_Safe_SaturateCastI64ToU8(int64_t val)
{
uint8_t result = 0;
2025-02-14 00:27:42 +11:00
if (DN_CHECK(val >= DN_CAST(int64_t) 0)) {
if (DN_CHECK(DN_CAST(uintmax_t) val <= UINT8_MAX))
result = DN_CAST(uint8_t) val;
else
result = UINT8_MAX;
}
return result;
}
2025-02-14 00:27:42 +11:00
DN_API uint16_t DN_Safe_SaturateCastI64ToU16(int64_t val)
{
uint16_t result = 0;
2025-02-14 00:27:42 +11:00
if (DN_CHECK(val >= DN_CAST(int64_t) 0)) {
if (DN_CHECK(DN_CAST(uintmax_t) val <= UINT16_MAX))
result = DN_CAST(uint16_t) val;
else
result = UINT16_MAX;
}
return result;
}
2025-02-14 00:27:42 +11:00
DN_API uint32_t DN_Safe_SaturateCastI64ToU32(int64_t val)
{
uint32_t result = 0;
2025-02-14 00:27:42 +11:00
if (DN_CHECK(val >= DN_CAST(int64_t) 0)) {
if (DN_CHECK(DN_CAST(uintmax_t) val <= UINT32_MAX))
result = DN_CAST(uint32_t) val;
else
result = UINT32_MAX;
}
return result;
}
2025-02-14 00:27:42 +11:00
DN_API uint64_t DN_Safe_SaturateCastI64ToU64(int64_t val)
{
uint64_t result = 0;
2025-02-14 00:27:42 +11:00
if (DN_CHECK(val >= DN_CAST(int64_t) 0)) {
if (DN_CHECK(DN_CAST(uintmax_t) val <= UINT64_MAX))
result = DN_CAST(uint64_t) val;
else
result = UINT64_MAX;
}
return result;
}
2025-02-14 00:27:42 +11:00
// NOTE: DN_Safe_SaturateCastIntTo* ///////////////////////////////////////////////////////////////
DN_API int8_t DN_Safe_SaturateCastIntToI8(int val)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
DN_CHECK(val >= INT8_MIN && val <= INT8_MAX);
int8_t result = DN_CAST(int8_t) DN_CLAMP(val, INT8_MIN, INT8_MAX);
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API int16_t DN_Safe_SaturateCastIntToI16(int val)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
DN_CHECK(val >= INT16_MIN && val <= INT16_MAX);
int16_t result = DN_CAST(int16_t) DN_CLAMP(val, INT16_MIN, INT16_MAX);
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API uint8_t DN_Safe_SaturateCastIntToU8(int val)
2023-08-16 21:59:38 +10:00
{
uint8_t result = 0;
2025-02-14 00:27:42 +11:00
if (DN_CHECK(val >= DN_CAST(DN_ISize) 0)) {
if (DN_CHECK(DN_CAST(uintmax_t) val <= UINT8_MAX))
result = DN_CAST(uint8_t) val;
2023-08-16 21:59:38 +10:00
else
result = UINT8_MAX;
}
return result;
}
2025-02-14 00:27:42 +11:00
DN_API uint16_t DN_Safe_SaturateCastIntToU16(int val)
2023-08-16 21:59:38 +10:00
{
uint16_t result = 0;
2025-02-14 00:27:42 +11:00
if (DN_CHECK(val >= DN_CAST(DN_ISize) 0)) {
if (DN_CHECK(DN_CAST(uintmax_t) val <= UINT16_MAX))
result = DN_CAST(uint16_t) val;
2023-08-16 21:59:38 +10:00
else
result = UINT16_MAX;
}
return result;
}
2025-02-14 00:27:42 +11:00
DN_API uint32_t DN_Safe_SaturateCastIntToU32(int val)
2023-08-16 21:59:38 +10:00
{
static_assert(sizeof(val) <= sizeof(uint32_t), "Sanity check to allow simplifying of casting");
uint32_t result = 0;
2025-02-14 00:27:42 +11:00
if (DN_CHECK(val >= 0))
result = DN_CAST(uint32_t) val;
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API uint64_t DN_Safe_SaturateCastIntToU64(int val)
2023-08-16 21:59:38 +10:00
{
static_assert(sizeof(val) <= sizeof(uint64_t), "Sanity check to allow simplifying of casting");
uint64_t result = 0;
2025-02-14 00:27:42 +11:00
if (DN_CHECK(val >= 0))
result = DN_CAST(uint64_t) val;
2023-08-16 21:59:38 +10:00
return result;
}
// NOTE: [$MISC] Misc //////////////////////////////////////////////////////////////////////////////
2025-02-14 00:27:42 +11:00
DN_API int DN_FmtBuffer3DotTruncate(char *buffer, int size, DN_FMT_ATTRIB char const *fmt, ...)
2023-08-16 21:59:38 +10:00
{
va_list args;
va_start(args, fmt);
2025-02-14 00:27:42 +11:00
int size_required = DN_VSNPRINTF(buffer, size, fmt, args);
int result = DN_MAX(DN_MIN(size_required, size - 1), 0);
2023-08-16 21:59:38 +10:00
if (result == size - 1) {
buffer[size - 2] = '.';
buffer[size - 3] = '.';
}
va_end(args);
return result;
}
2025-02-14 00:27:42 +11:00
DN_API DN_U64Str8 DN_U64ToStr8(uint64_t val, char separator)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
DN_U64Str8 result = {};
2023-08-16 21:59:38 +10:00
if (val == 0) {
result.data[result.size++] = '0';
} else {
// NOTE: The number is written in reverse because we form the string by
// dividing by 10, so we write it in, then reverse it out after all is
// done.
2025-02-14 00:27:42 +11:00
DN_U64Str8 temp = {};
for (DN_USize digit_count = 0; val > 0; digit_count++) {
2023-08-25 23:42:09 +10:00
if (separator && (digit_count != 0) && (digit_count % 3 == 0))
2023-08-16 21:59:38 +10:00
temp.data[temp.size++] = separator;
2025-02-14 00:27:42 +11:00
auto digit = DN_CAST(char)(val % 10);
2023-08-16 21:59:38 +10:00
temp.data[temp.size++] = '0' + digit;
val /= 10;
}
// NOTE: Reverse the string
2025-02-14 00:27:42 +11:00
DN_MSVC_WARNING_PUSH
DN_MSVC_WARNING_DISABLE(6293) // Ill-defined for-loop
DN_MSVC_WARNING_DISABLE(
6385) // Reading invalid data from 'temp.data' NOTE(doyle): Unsigned overflow is valid for loop termination
2025-02-14 00:27:42 +11:00
for (DN_USize temp_index = temp.size - 1; temp_index < temp.size; temp_index--) {
char ch = temp.data[temp_index];
2023-08-16 21:59:38 +10:00
result.data[result.size++] = ch;
}
2025-02-14 00:27:42 +11:00
DN_MSVC_WARNING_POP
2023-08-16 21:59:38 +10:00
}
return result;
}
2025-02-14 00:27:42 +11:00
DN_API DN_U64ByteSize DN_U64ToByteSize(uint64_t bytes, DN_U64ByteSizeType desired_type)
{
2025-02-14 00:27:42 +11:00
DN_U64ByteSize result = {};
result.bytes = DN_CAST(DN_F64)bytes;
if (!DN_CHECK(desired_type != DN_U64ByteSizeType_Count)) {
result.suffix = DN_U64ByteSizeTypeString(result.type);
return result;
}
2025-02-14 00:27:42 +11:00
if (desired_type == DN_U64ByteSizeType_Auto) {
for (; result.type < DN_U64ByteSizeType_Count && result.bytes >= 1024.0; result.type = DN_CAST(DN_U64ByteSizeType)(DN_CAST(DN_USize)result.type + 1))
result.bytes /= 1024.0;
} else {
2025-02-14 00:27:42 +11:00
for (; result.type < desired_type; result.type = DN_CAST(DN_U64ByteSizeType)(DN_CAST(DN_USize)result.type + 1))
result.bytes /= 1024.0;
}
2025-02-14 00:27:42 +11:00
result.suffix = DN_U64ByteSizeTypeString(result.type);
return result;
}
2025-02-14 00:27:42 +11:00
DN_API DN_Str8 DN_U64ToByteSizeStr8(DN_Arena *arena, uint64_t bytes, DN_U64ByteSizeType desired_type)
{
2025-02-14 00:27:42 +11:00
DN_U64ByteSize byte_size = DN_U64ToByteSize(bytes, desired_type);
DN_Str8 result = DN_Str8_InitF(arena, "%.2f%.*s", byte_size.bytes, DN_STR_FMT(byte_size.suffix));
return result;
}
2025-02-14 00:27:42 +11:00
DN_API DN_Str8 DN_U64ByteSizeTypeString(DN_U64ByteSizeType type)
{
2025-02-14 00:27:42 +11:00
DN_Str8 result = DN_STR8("");
switch (type) {
2025-02-14 00:27:42 +11:00
case DN_U64ByteSizeType_B: result = DN_STR8("B"); break;
case DN_U64ByteSizeType_KiB: result = DN_STR8("KiB"); break;
case DN_U64ByteSizeType_MiB: result = DN_STR8("MiB"); break;
case DN_U64ByteSizeType_GiB: result = DN_STR8("GiB"); break;
case DN_U64ByteSizeType_TiB: result = DN_STR8("TiB"); break;
case DN_U64ByteSizeType_Count: result = DN_STR8(""); break;
case DN_U64ByteSizeType_Auto: result = DN_STR8(""); break;
}
return result;
}
2025-02-14 00:27:42 +11:00
DN_API DN_Str8 DN_U64ToAge(DN_Arena *arena, uint64_t age_s, DN_U64AgeUnit unit)
{
2025-02-14 00:27:42 +11:00
DN_Str8 result = {};
if (!arena)
return result;
2025-02-14 00:27:42 +11:00
DN_TLSTMem tmem = DN_TLS_TMem(arena);
DN_Str8Builder builder = DN_Str8Builder_Init(tmem.arena);
uint64_t remainder = age_s;
2025-02-14 00:27:42 +11:00
if (unit & DN_U64AgeUnit_Year) {
DN_USize value = remainder / DN_YEARS_TO_S(1);
remainder -= DN_YEARS_TO_S(value);
2024-08-01 13:34:36 +10:00
if (value)
2025-02-14 00:27:42 +11:00
DN_Str8Builder_AppendF(&builder, "%s%I64uyr", builder.string_size ? " " : "", value);
}
2025-02-14 00:27:42 +11:00
if (unit & DN_U64AgeUnit_Week) {
DN_USize value = remainder / DN_WEEKS_TO_S(1);
remainder -= DN_WEEKS_TO_S(value);
2024-08-01 13:34:36 +10:00
if (value)
2025-02-14 00:27:42 +11:00
DN_Str8Builder_AppendF(&builder, "%s%I64uw", builder.string_size ? " " : "", value);
}
2025-02-14 00:27:42 +11:00
if (unit & DN_U64AgeUnit_Day) {
DN_USize value = remainder / DN_DAYS_TO_S(1);
remainder -= DN_DAYS_TO_S(value);
2024-08-01 13:34:36 +10:00
if (value)
2025-02-14 00:27:42 +11:00
DN_Str8Builder_AppendF(&builder, "%s%I64ud", builder.string_size ? " " : "", value);
}
2025-02-14 00:27:42 +11:00
if (unit & DN_U64AgeUnit_Hr) {
DN_USize value = remainder / DN_HOURS_TO_S(1);
remainder -= DN_HOURS_TO_S(value);
2024-08-01 13:34:36 +10:00
if (value)
2025-02-14 00:27:42 +11:00
DN_Str8Builder_AppendF(&builder, "%s%I64uh", builder.string_size ? " " : "", value);
}
2025-02-14 00:27:42 +11:00
if (unit & DN_U64AgeUnit_Min) {
DN_USize value = remainder / DN_MINS_TO_S(1);
remainder -= DN_MINS_TO_S(value);
2024-08-01 13:34:36 +10:00
if (value)
2025-02-14 00:27:42 +11:00
DN_Str8Builder_AppendF(&builder, "%s%I64um", builder.string_size ? " " : "", value);
}
if (unit & DN_U64AgeUnit_Sec) {
DN_USize value = remainder;
DN_Str8Builder_AppendF(&builder, "%s%I64us", builder.string_size ? " " : "", value);
}
result = DN_Str8Builder_Build(&builder, arena);
return result;
}
DN_API DN_Str8 DN_F64ToAge(DN_Arena *arena, DN_F64 age_s, DN_U64AgeUnit unit)
{
DN_Str8 result = {};
if (!arena)
return result;
DN_TLSTMem tmem = DN_TLS_TMem(arena);
DN_Str8Builder builder = DN_Str8Builder_Init(tmem.arena);
DN_F64 remainder = age_s;
if (unit & DN_U64AgeUnit_Year) {
DN_F64 value = remainder / DN_CAST(DN_F64)DN_YEARS_TO_S(1);
if (value >= 1.0) {
remainder -= DN_YEARS_TO_S(value);
DN_Str8Builder_AppendF(&builder, "%s%.1fyr", builder.string_size ? " " : "", value);
}
}
if (unit & DN_U64AgeUnit_Week) {
DN_F64 value = remainder / DN_CAST(DN_F64)DN_WEEKS_TO_S(1);
if (value >= 1.0) {
remainder -= DN_WEEKS_TO_S(value);
DN_Str8Builder_AppendF(&builder, "%s%.1fw", builder.string_size ? " " : "", value);
}
}
if (unit & DN_U64AgeUnit_Day) {
DN_F64 value = remainder / DN_CAST(DN_F64)DN_DAYS_TO_S(1);
if (value >= 1.0) {
remainder -= DN_WEEKS_TO_S(value);
DN_Str8Builder_AppendF(&builder, "%s%.1fd", builder.string_size ? " " : "", value);
}
}
2025-02-14 00:27:42 +11:00
if (unit & DN_U64AgeUnit_Hr) {
DN_F64 value = remainder / DN_CAST(DN_F64)DN_HOURS_TO_S(1);
if (value >= 1.0) {
remainder -= DN_HOURS_TO_S(value);
DN_Str8Builder_AppendF(&builder, "%s%.1fh", builder.string_size ? " " : "", value);
}
}
if (unit & DN_U64AgeUnit_Min) {
DN_F64 value = remainder / DN_CAST(DN_F64)DN_MINS_TO_S(1);
if (value >= 1.0) {
remainder -= DN_MINS_TO_S(value);
DN_Str8Builder_AppendF(&builder, "%s%.1fm", builder.string_size ? " " : "", value);
}
}
2025-02-14 00:27:42 +11:00
if (unit & DN_U64AgeUnit_Sec) {
DN_F64 value = remainder;
DN_Str8Builder_AppendF(&builder, "%s%.1fs", builder.string_size ? " " : "", value);
}
result = DN_Str8Builder_Build(&builder, arena);
return result;
}
2025-02-14 00:27:42 +11:00
DN_API uint64_t DN_HexToU64(DN_Str8 hex)
2024-04-18 22:59:11 +10:00
{
2025-02-14 00:27:42 +11:00
DN_Str8 real_hex = DN_Str8_TrimPrefix(DN_Str8_TrimPrefix(hex, DN_STR8("0x")), DN_STR8("0X"));
DN_USize max_hex_size = sizeof(uint64_t) * 2 /*hex chars per byte*/;
DN_ASSERT(real_hex.size <= max_hex_size);
2024-04-18 22:59:11 +10:00
2025-02-14 00:27:42 +11:00
DN_USize size = DN_MIN(max_hex_size, real_hex.size);
2024-04-18 22:59:11 +10:00
uint64_t result = 0;
2025-02-14 00:27:42 +11:00
for (DN_USize index = 0; index < size; index++) {
2024-04-18 22:59:11 +10:00
char ch = real_hex.data[index];
2025-02-14 00:27:42 +11:00
DN_CharHexToU8 val = DN_Char_HexToU8(ch);
2024-04-18 22:59:11 +10:00
if (!val.success)
break;
result = (result << 4) | val.value;
}
return result;
}
2025-02-14 00:27:42 +11:00
DN_API DN_Str8 DN_U64ToHex(DN_Arena *arena, uint64_t number, uint32_t flags)
2024-04-18 22:59:11 +10:00
{
2025-02-14 00:27:42 +11:00
DN_Str8 prefix = {};
if ((flags & DN_HexU64Str8Flags_0xPrefix))
prefix = DN_STR8("0x");
2024-04-18 22:59:11 +10:00
2025-02-14 00:27:42 +11:00
char const *fmt = (flags & DN_HexU64Str8Flags_UppercaseHex) ? "%I64X" : "%I64x";
DN_USize required_size = DN_CStr8_FSize(fmt, number) + prefix.size;
DN_Str8 result = DN_Str8_Alloc(arena, required_size, DN_ZeroMem_No);
2024-04-18 22:59:11 +10:00
2025-02-14 00:27:42 +11:00
if (DN_Str8_HasData(result)) {
DN_MEMCPY(result.data, prefix.data, prefix.size);
int space = DN_CAST(int) DN_MAX((result.size - prefix.size) + 1, 0); /*null-terminator*/
DN_SNPRINTF(result.data + prefix.size, space, fmt, number);
2024-04-18 22:59:11 +10:00
}
return result;
}
2025-02-14 00:27:42 +11:00
DN_API DN_U64HexStr8 DN_U64ToHexStr8(uint64_t number, DN_U64HexStr8Flags flags)
2024-04-18 22:59:11 +10:00
{
2025-02-14 00:27:42 +11:00
DN_Str8 prefix = {};
if (flags & DN_HexU64Str8Flags_0xPrefix)
prefix = DN_STR8("0x");
2024-04-18 22:59:11 +10:00
2025-02-14 00:27:42 +11:00
DN_U64HexStr8 result = {};
DN_MEMCPY(result.data, prefix.data, prefix.size);
result.size += DN_CAST(int8_t) prefix.size;
2024-04-18 22:59:11 +10:00
2025-02-14 00:27:42 +11:00
char const *fmt = (flags & DN_HexU64Str8Flags_UppercaseHex) ? "%I64X" : "%I64x";
int size = DN_SNPRINTF(result.data + result.size, DN_ARRAY_UCOUNT(result.data) - result.size, fmt, number);
result.size += DN_CAST(uint8_t) size;
DN_ASSERT(result.size < DN_ARRAY_UCOUNT(result.data));
2024-04-18 22:59:11 +10:00
// NOTE: snprintf returns the required size of the format string
// irrespective of if there's space or not, but, always null terminates so
// the last byte is wasted.
2025-02-14 00:27:42 +11:00
result.size = DN_MIN(result.size, DN_ARRAY_UCOUNT(result.data) - 1);
2024-04-18 22:59:11 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API bool DN_BytesToHexPtr(void const *src, DN_USize src_size, char *dest, DN_USize dest_size)
2024-04-18 22:59:11 +10:00
{
if (!src || !dest)
return false;
2025-02-14 00:27:42 +11:00
if (!DN_CHECK(dest_size >= src_size * 2))
2024-04-18 22:59:11 +10:00
return false;
char const *HEX = "0123456789abcdef";
2025-02-14 00:27:42 +11:00
unsigned char const *src_u8 = DN_CAST(unsigned char const *) src;
for (DN_USize src_index = 0, dest_index = 0; src_index < src_size; src_index++) {
2024-04-18 22:59:11 +10:00
char byte = src_u8[src_index];
char hex01 = (byte >> 4) & 0b1111;
char hex02 = (byte >> 0) & 0b1111;
dest[dest_index++] = HEX[(int)hex01];
dest[dest_index++] = HEX[(int)hex02];
}
return true;
}
2025-02-14 00:27:42 +11:00
DN_API DN_Str8 DN_BytesToHex(DN_Arena *arena, void const *src, DN_USize size)
2024-04-18 22:59:11 +10:00
{
2025-02-14 00:27:42 +11:00
DN_Str8 result = {};
2024-04-18 22:59:11 +10:00
if (!src || size <= 0)
return result;
2025-02-14 00:27:42 +11:00
result = DN_Str8_Alloc(arena, size * 2, DN_ZeroMem_No);
2024-08-01 13:34:36 +10:00
result.data[result.size] = 0;
2025-02-14 00:27:42 +11:00
bool converted = DN_BytesToHexPtr(src, size, result.data, result.size);
DN_ASSERT(converted);
2024-04-18 22:59:11 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API DN_USize DN_HexToBytesPtrUnchecked(DN_Str8 hex, void *dest, DN_USize dest_size)
2024-04-18 22:59:11 +10:00
{
2025-02-14 00:27:42 +11:00
DN_USize result = 0;
unsigned char *dest_u8 = DN_CAST(unsigned char *) dest;
2024-04-18 22:59:11 +10:00
2025-02-14 00:27:42 +11:00
for (DN_USize hex_index = 0; hex_index < hex.size; hex_index += 2, result += 1) {
2024-04-18 22:59:11 +10:00
char hex01 = hex.data[hex_index];
char hex02 = (hex_index + 1 < hex.size) ? hex.data[hex_index + 1] : 0;
2025-02-14 00:27:42 +11:00
char bit4_01 = DN_Char_HexToU8(hex01).value;
char bit4_02 = DN_Char_HexToU8(hex02).value;
2024-04-18 22:59:11 +10:00
char byte = (bit4_01 << 4) | (bit4_02 << 0);
dest_u8[result] = byte;
}
2025-02-14 00:27:42 +11:00
DN_ASSERT(result <= dest_size);
2024-04-18 22:59:11 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API DN_USize DN_HexToBytesPtr(DN_Str8 hex, void *dest, DN_USize dest_size)
2024-04-18 22:59:11 +10:00
{
2025-02-14 00:27:42 +11:00
hex = DN_Str8_TrimPrefix(hex, DN_STR8("0x"));
hex = DN_Str8_TrimPrefix(hex, DN_STR8("0X"));
2024-04-18 22:59:11 +10:00
2025-02-14 00:27:42 +11:00
DN_USize result = 0;
if (!DN_Str8_HasData(hex))
2024-04-18 22:59:11 +10:00
return result;
// NOTE: Trimmed hex can be "0xf" -> "f" or "0xAB" -> "AB"
// Either way, the size can be odd or even, hence we round up to the nearest
// multiple of two to ensure that we calculate the min buffer size orrectly.
2025-02-14 00:27:42 +11:00
DN_USize hex_size_rounded_up = hex.size + (hex.size % 2);
DN_USize min_buffer_size = hex_size_rounded_up / 2;
if (hex.size <= 0 || !DN_CHECK(dest_size >= min_buffer_size)) {
2024-04-18 22:59:11 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
result = DN_HexToBytesPtrUnchecked(hex, dest, dest_size);
2024-04-18 22:59:11 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API DN_Str8 DN_HexToBytesUnchecked(DN_Arena *arena, DN_Str8 hex)
2024-04-18 22:59:11 +10:00
{
2025-02-14 00:27:42 +11:00
DN_USize hex_size_rounded_up = hex.size + (hex.size % 2);
DN_Str8 result = DN_Str8_Alloc(arena, (hex_size_rounded_up / 2), DN_ZeroMem_No);
2024-04-18 22:59:11 +10:00
if (result.data) {
2025-02-14 00:27:42 +11:00
DN_USize bytes_written = DN_HexToBytesPtr(hex, result.data, result.size);
DN_ASSERT(bytes_written == result.size);
2024-04-18 22:59:11 +10:00
}
return result;
}
2025-02-14 00:27:42 +11:00
DN_API DN_Str8 DN_HexToBytes(DN_Arena *arena, DN_Str8 hex)
2024-04-18 22:59:11 +10:00
{
2025-02-14 00:27:42 +11:00
hex = DN_Str8_TrimPrefix(hex, DN_STR8("0x"));
hex = DN_Str8_TrimPrefix(hex, DN_STR8("0X"));
DN_Str8 result = {};
if (!DN_Str8_HasData(hex))
return result;
2024-04-18 22:59:11 +10:00
2025-02-14 00:27:42 +11:00
if (!DN_CHECK(DN_Str8_IsAll(hex, DN_Str8IsAll_Hex)))
2024-04-18 22:59:11 +10:00
return result;
2025-02-14 00:27:42 +11:00
result = DN_HexToBytesUnchecked(arena, hex);
2024-04-18 22:59:11 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
// NOTE: [$CORE] DN_Core //////////////////////////////////////////////////////////////////////////
DN_Core *g_dn_core;
2023-08-16 21:59:38 +10:00
2025-02-14 00:27:42 +11:00
DN_API void DN_Core_Init(DN_Core *core, DN_CoreOnInit on_init)
2023-08-16 21:59:38 +10:00
{
// NOTE: Init check ///////////////////////////////////////////////////////////////////////////
2025-02-14 00:27:42 +11:00
if (core->init)
return;
2024-04-18 22:59:11 +10:00
2025-02-14 00:27:42 +11:00
#define DN_CPU_FEAT_XENTRY(label) g_dn_cpu_feature_decl[DN_CPUFeature_##label] = {DN_CPUFeature_##label, DN_STR8(#label)};
DN_CPU_FEAT_XMACRO
#undef DN_CPU_FEAT_XENTRY
2023-08-16 21:59:38 +10:00
2025-02-14 00:27:42 +11:00
// NOTE: Setup OS //////////////////////////////////////////////////////////////////////////////
{
2025-02-14 00:27:42 +11:00
#if defined(DN_OS_WIN32)
SYSTEM_INFO system_info = {};
2023-08-16 21:59:38 +10:00
GetSystemInfo(&system_info);
2025-02-14 00:27:42 +11:00
core->os_page_size = system_info.dwPageSize;
core->os_alloc_granularity = system_info.dwAllocationGranularity;
QueryPerformanceFrequency(&core->win32_qpc_frequency);
2025-02-14 00:27:42 +11:00
HMODULE module = LoadLibraryA("kernel32.dll");
g_dn_win_set_thread_description = DN_CAST(DN_WinSetThreadDescriptionFunc *) GetProcAddress(module, "SetThreadDescription");
FreeLibrary(module);
#else
2023-08-16 21:59:38 +10:00
// TODO(doyle): Get the proper page size from the OS.
2025-02-14 00:27:42 +11:00
core->os_page_size = DN_KILOBYTES(4);
core->os_alloc_granularity = DN_KILOBYTES(64);
#endif
2023-08-16 21:59:38 +10:00
}
2025-02-14 00:27:42 +11:00
core->init_mutex = DN_OS_MutexInit();
DN_OS_MutexLock(&core->init_mutex);
DN_DEFER {
DN_OS_MutexUnlock(&core->init_mutex);
};
DN_Core_SetPointer(core);
core->init = true;
core->cpu_report = DN_CPU_Report();
// NOTE Initialise fields //////////////////////////////////////////////////////////////////////
2025-02-14 00:27:42 +11:00
#if !defined(DN_NO_PROFILER)
core->profiler = &core->profiler_default_instance;
#endif
// NOTE: BEGIN IMPORTANT ORDER OF STATEMENTS ///////////////////////////////////////////////////
2025-02-14 00:27:42 +11:00
#if defined(DN_LEAK_TRACKING)
// NOTE: Setup the allocation table with allocation tracking turned off on
// the arena we're using to initialise the table.
2025-02-14 00:27:42 +11:00
core->alloc_table_arena = DN_Arena_InitSize(DN_MEGABYTES(1), DN_KILOBYTES(512), DN_ArenaFlags_NoAllocTrack | DN_ArenaFlags_AllocCanLeak);
core->alloc_table = DN_DSMap_Init<DN_DebugAlloc>(&core->alloc_table_arena, 4096, DN_DSMapFlags_Nil);
#endif
2025-02-14 00:27:42 +11:00
core->arena = DN_Arena_InitSize(DN_KILOBYTES(64), DN_KILOBYTES(4), DN_ArenaFlags_AllocCanLeak);
core->pool = DN_Pool_Init(&core->arena, /*align*/ 0);
DN_ArenaCatalog_Init(&core->arena_catalog, &core->pool);
DN_ArenaCatalog_AddF(&core->arena_catalog, &core->arena, "DN Core");
2023-08-16 21:59:38 +10:00
2025-02-14 00:27:42 +11:00
#if defined(DN_LEAK_TRACKING)
DN_ArenaCatalog_AddF(&core->arena_catalog, &core->alloc_table_arena, "DN Allocation Table");
#endif
2023-08-16 21:59:38 +10:00
2024-08-01 13:34:36 +10:00
// NOTE: Initialise tmem arenas which allocate memory and will be
// recorded to the now initialised allocation table. The initialisation
2024-08-01 13:34:36 +10:00
// of tmem memory may request tmem memory itself in leak tracing mode.
// This is supported as the tmem arenas defer allocation tracking until
// initialisation is done.
2025-02-14 00:27:42 +11:00
DN_TLS_Init(&core->tls);
DN_OS_ThreadSetTLS(&core->tls);
DN_TLSTMem tmem = DN_TLS_TMem(nullptr);
// NOTE: END IMPORTANT ORDER OF STATEMENTS /////////////////////////////////////////////////////
2023-08-16 21:59:38 +10:00
2025-02-14 00:27:42 +11:00
core->exe_dir = DN_OS_EXEDir(&core->arena);
2023-08-16 21:59:38 +10:00
// NOTE: Print out init features ///////////////////////////////////////////////////////////////
2025-02-14 00:27:42 +11:00
DN_Str8Builder builder = DN_Str8Builder_Init(tmem.arena);
if (on_init & DN_CoreOnInit_LogLibFeatures) {
DN_Str8Builder_AppendRef(&builder, DN_STR8("DN Library initialised:\n"));
DN_F64 page_size_kib = core->os_page_size / 1024.0;
DN_F64 alloc_granularity_kib = core->os_alloc_granularity / 1024.0;
DN_Str8Builder_AppendF(
&builder, " OS Page Size/Alloc Granularity: %.1f/%.1fKiB\n", page_size_kib, alloc_granularity_kib);
2025-02-14 00:27:42 +11:00
#if DN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__)
if (DN_ASAN_POISON) {
DN_Str8Builder_AppendF(
&builder, " ASAN manual poisoning%s\n", DN_ASAN_VET_POISON ? " (+vet sanity checks)" : "");
DN_Str8Builder_AppendF(&builder, " ASAN poison guard size: %u\n", DN_ASAN_POISON_GUARD_SIZE);
}
#endif
2025-02-14 00:27:42 +11:00
#if defined(DN_LEAK_TRACKING)
DN_Str8Builder_AppendRef(&builder, DN_STR8(" Allocation leak tracing\n"));
#endif
2025-02-14 00:27:42 +11:00
#if !defined(DN_NO_PROFILER)
DN_Str8Builder_AppendRef(&builder, DN_STR8(" TSC profiler available\n"));
#endif
2025-02-14 00:27:42 +11:00
#if defined(DN_USE_STD_PRINTF)
DN_Str8Builder_AppendRef(&builder, DN_STR8(" Using stdio's printf functions\n"));
#else
2025-02-14 00:27:42 +11:00
DN_Str8Builder_AppendRef(&builder, DN_STR8(" Using stb_sprintf functions\n"));
#endif
// TODO(doyle): Add stacktrace feature log
2024-04-18 22:59:11 +10:00
}
2023-08-31 01:03:48 +10:00
2025-02-14 00:27:42 +11:00
if (on_init & DN_CoreOnInit_LogCPUFeatures) {
DN_CPUReport const *report = &core->cpu_report;
DN_Str8 brand = DN_Str8_TrimWhitespaceAround(DN_Str8_Init(report->brand, sizeof(report->brand) - 1));
DN_Str8Builder_AppendF(&builder,
" CPU '%.*s' from '%s' detected:\n",
DN_STR_FMT(brand),
report->vendor);
DN_USize longest_feature_name = 0;
DN_FOR_UINDEX(feature_index, DN_CPUFeature_Count) {
DN_CPUFeatureDecl feature_decl = g_dn_cpu_feature_decl[feature_index];
longest_feature_name = DN_MAX(longest_feature_name, feature_decl.label.size);
2024-04-18 22:59:11 +10:00
}
2025-02-14 00:27:42 +11:00
DN_FOR_UINDEX(feature_index, DN_CPUFeature_Count) {
DN_CPUFeatureDecl feature_decl = g_dn_cpu_feature_decl[feature_index];
bool has_feature = DN_CPU_HasFeature(report, feature_decl.value);
DN_Str8Builder_AppendF(&builder,
" %.*s:%*s%s\n",
DN_STR_FMT(feature_decl.label),
DN_CAST(int)(longest_feature_name - feature_decl.label.size),
"",
has_feature ? "available" : "not available");
2024-04-18 22:59:11 +10:00
}
}
2024-04-18 22:59:11 +10:00
2025-02-14 00:27:42 +11:00
DN_Str8 info_log = DN_Str8Builder_Build(&builder, tmem.arena);
if (DN_Str8_HasData(info_log))
DN_Log_DebugF("%.*s", DN_STR_FMT(info_log));
2023-08-16 21:59:38 +10:00
}
2025-02-14 00:27:42 +11:00
DN_API void DN_Core_BeginFrame()
{
DN_Atomic_SetValue64(&g_dn_core->mem_allocs_frame, 0);
}
DN_API void DN_Core_SetPointer(DN_Core *library)
2023-08-16 21:59:38 +10:00
{
2024-08-01 13:34:36 +10:00
if (library) {
2025-02-14 00:27:42 +11:00
g_dn_core = library;
DN_OS_ThreadSetTLS(&library->tls);
2024-08-01 13:34:36 +10:00
}
2023-08-16 21:59:38 +10:00
}
2025-02-14 00:27:42 +11:00
#if !defined(DN_NO_PROFILER)
DN_API void DN_Core_SetProfiler(DN_Profiler *profiler)
2023-08-16 21:59:38 +10:00
{
if (profiler)
2025-02-14 00:27:42 +11:00
g_dn_core->profiler = profiler;
2023-08-16 21:59:38 +10:00
}
#endif
2025-02-14 00:27:42 +11:00
DN_API void DN_Core_SetLogCallback(DN_LogProc *proc, void *user_data)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
g_dn_core->log_callback = proc;
g_dn_core->log_user_data = user_data;
2023-08-16 21:59:38 +10:00
}
2025-02-14 00:27:42 +11:00
DN_API void DN_Core_DumpThreadContextArenaStat(DN_Str8 file_path)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
#if defined(DN_DEBUG_THREAD_CONTEXT)
2023-08-16 21:59:38 +10:00
// NOTE: Open a file to write the arena stats to
FILE *file = nullptr;
fopen_s(&file, file_path.data, "a+b");
if (file) {
2025-02-14 00:27:42 +11:00
DN_Log_ErrorF("Failed to dump thread context arenas [file=%.*s]", DN_STR_FMT(file_path));
2023-08-16 21:59:38 +10:00
return;
}
// NOTE: Copy the stats from library book-keeping
// NOTE: Extremely short critical section, copy the stats then do our
// work on it.
2025-02-14 00:27:42 +11:00
DN_ArenaStat stats[DN_CArray_CountI(g_dn_core->thread_context_arena_stats)];
int stats_size = 0;
2023-08-16 21:59:38 +10:00
2025-02-14 00:27:42 +11:00
DN_TicketMutex_Begin(&g_dn_core->thread_context_mutex);
stats_size = g_dn_core->thread_context_arena_stats_count;
DN_MEMCPY(stats, g_dn_core->thread_context_arena_stats, sizeof(stats[0]) * stats_size);
DN_TicketMutex_End(&g_dn_core->thread_context_mutex);
2023-08-16 21:59:38 +10:00
// NOTE: Print the cumulative stat
2025-02-14 00:27:42 +11:00
DN_DateHMSTimeStr now = DN_Date_HMSLocalTimeStrNow();
2023-08-16 21:59:38 +10:00
fprintf(file,
"Time=%.*s %.*s | Thread Context Arenas | Count=%d\n",
now.date_size,
now.date,
now.hms_size,
now.hms,
2025-02-14 00:27:42 +11:00
g_dn_core->thread_context_arena_stats_count);
2023-08-16 21:59:38 +10:00
// NOTE: Write the cumulative thread arena data
{
2025-02-14 00:27:42 +11:00
DN_ArenaStat stat = {};
for (DN_USize index = 0; index < stats_size; index++) {
DN_ArenaStat const *current = stats + index;
2023-08-16 21:59:38 +10:00
stat.capacity += current->capacity;
stat.used += current->used;
stat.wasted += current->wasted;
stat.blocks += current->blocks;
2023-08-16 21:59:38 +10:00
2025-02-14 00:27:42 +11:00
stat.capacity_hwm = DN_MAX(stat.capacity_hwm, current->capacity_hwm);
stat.used_hwm = DN_MAX(stat.used_hwm, current->used_hwm);
stat.wasted_hwm = DN_MAX(stat.wasted_hwm, current->wasted_hwm);
stat.blocks_hwm = DN_MAX(stat.blocks_hwm, current->blocks_hwm);
2023-08-16 21:59:38 +10:00
}
2025-02-14 00:27:42 +11:00
DN_ArenaStatStr stats_string = DN_Arena_StatStr(&stat);
2023-08-16 21:59:38 +10:00
fprintf(file, " [ALL] CURR %.*s\n", stats_string.size, stats_string.data);
}
// NOTE: Print individual thread arena data
2025-02-14 00:27:42 +11:00
for (DN_USize index = 0; index < stats_size; index++) {
DN_ArenaStat const *current = stats + index;
DN_ArenaStatStr current_string = DN_Arena_StatStr(current);
fprintf(file, " [%03d] CURR %.*s\n", DN_CAST(int) index, current_string.size, current_string.data);
2023-08-16 21:59:38 +10:00
}
fclose(file);
2025-02-14 00:27:42 +11:00
DN_Log_InfoF("Dumped thread context arenas [file=%.*s]", DN_STR_FMT(file_path));
#else
2023-08-16 21:59:38 +10:00
(void)file_path;
2025-02-14 00:27:42 +11:00
#endif // #if defined(DN_DEBUG_THREAD_CONTEXT)
2023-08-16 21:59:38 +10:00
}
2025-02-14 00:27:42 +11:00
DN_API DN_Arena *DN_Core_AllocArenaF(DN_USize reserve, DN_USize commit, uint8_t arena_flags, char const *fmt, ...)
{
2025-02-14 00:27:42 +11:00
DN_ASSERT(g_dn_core->init);
va_list args;
va_start(args, fmt);
2025-02-14 00:27:42 +11:00
DN_ArenaCatalog *catalog = &g_dn_core->arena_catalog;
DN_Arena *result = DN_ArenaCatalog_AllocFV(catalog, reserve, commit, arena_flags, fmt, args);
va_end(args);
return result;
}
2023-08-16 21:59:38 +10:00
2025-02-14 00:27:42 +11:00
DN_API bool DN_Core_EraseArena(DN_Arena *arena, DN_ArenaCatalogFreeArena free_arena)
2024-08-01 13:34:36 +10:00
{
2025-02-14 00:27:42 +11:00
DN_ArenaCatalog *catalog = &g_dn_core->arena_catalog;
bool result = DN_ArenaCatalog_Erase(catalog, arena, free_arena);
2024-08-01 13:34:36 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
#if !defined(DN_NO_PROFILER)
// NOTE: [$PROF] DN_Profiler //////////////////////////////////////////////////////////////////////
DN_API DN_ProfilerZoneScope::DN_ProfilerZoneScope(DN_Str8 name, uint16_t anchor_index)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
zone = DN_Profiler_BeginZoneAtIndex(name, anchor_index);
2023-08-16 21:59:38 +10:00
}
2025-02-14 00:27:42 +11:00
DN_API DN_ProfilerZoneScope::~DN_ProfilerZoneScope()
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
DN_Profiler_EndZone(zone);
2023-08-16 21:59:38 +10:00
}
2025-02-14 00:27:42 +11:00
DN_API DN_ProfilerAnchor *DN_Profiler_ReadBuffer()
2024-08-01 13:34:36 +10:00
{
2025-02-14 00:27:42 +11:00
uint8_t mask = DN_ARRAY_UCOUNT(g_dn_core->profiler->anchors) - 1;
DN_ProfilerAnchor *result = g_dn_core->profiler->anchors[(g_dn_core->profiler->active_anchor_buffer - 1) & mask];
2024-08-01 13:34:36 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API DN_ProfilerAnchor *DN_Profiler_WriteBuffer()
2024-08-01 13:34:36 +10:00
{
2025-02-14 00:27:42 +11:00
uint8_t mask = DN_ARRAY_UCOUNT(g_dn_core->profiler->anchors) - 1;
DN_ProfilerAnchor *result = g_dn_core->profiler->anchors[(g_dn_core->profiler->active_anchor_buffer + 0) & mask];
2024-08-01 13:34:36 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API DN_ProfilerZone DN_Profiler_BeginZoneAtIndex(DN_Str8 name, uint16_t anchor_index)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
DN_ProfilerAnchor *anchor = DN_Profiler_WriteBuffer() + anchor_index;
// TODO: We need per-thread-local-storage profiler so that we can use these apis
// across threads. For now, we let them overwrite each other but this is not tenable.
#if 0
if (DN_Str8_HasData(anchor->name) && anchor->name != name)
DN_ASSERTF(name == anchor->name, "Potentially overwriting a zone by accident? Anchor is '%.*s', name is '%.*s'", DN_STR_FMT(anchor->name), DN_STR_FMT(name));
#endif
anchor->name = name;
DN_ProfilerZone result = {};
result.begin_tsc = DN_CPU_TSC();
result.anchor_index = anchor_index;
result.parent_zone = g_dn_core->profiler->parent_zone;
result.elapsed_tsc_at_zone_start = anchor->tsc_inclusive;
g_dn_core->profiler->parent_zone = anchor_index;
2023-08-16 21:59:38 +10:00
return result;
}
2025-02-14 00:27:42 +11:00
DN_API void DN_Profiler_EndZone(DN_ProfilerZone zone)
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
uint64_t elapsed_tsc = DN_CPU_TSC() - zone.begin_tsc;
DN_ProfilerAnchor *anchor_buffer = DN_Profiler_WriteBuffer();
DN_ProfilerAnchor *anchor = anchor_buffer + zone.anchor_index;
2023-08-16 21:59:38 +10:00
anchor->hit_count++;
anchor->tsc_inclusive = zone.elapsed_tsc_at_zone_start + elapsed_tsc;
2023-08-16 21:59:38 +10:00
anchor->tsc_exclusive += elapsed_tsc;
2025-02-14 00:27:42 +11:00
DN_ProfilerAnchor *parent_anchor = anchor_buffer + zone.parent_zone;
parent_anchor->tsc_exclusive -= elapsed_tsc;
2025-02-14 00:27:42 +11:00
g_dn_core->profiler->parent_zone = zone.parent_zone;
2023-08-16 21:59:38 +10:00
}
2025-02-14 00:27:42 +11:00
DN_API void DN_Profiler_SwapAnchorBuffer()
2023-08-16 21:59:38 +10:00
{
2025-02-14 00:27:42 +11:00
g_dn_core->profiler->active_anchor_buffer++;
g_dn_core->profiler->parent_zone = 0;
DN_ProfilerAnchor *anchors = DN_Profiler_WriteBuffer();
DN_MEMSET(anchors,
0,
2025-02-14 00:27:42 +11:00
DN_ARRAY_UCOUNT(g_dn_core->profiler->anchors[0]) * sizeof(g_dn_core->profiler->anchors[0][0]));
2023-08-16 21:59:38 +10:00
}
2025-02-14 00:27:42 +11:00
DN_API void DN_Profiler_Dump(uint64_t tsc_per_second)
{
2025-02-14 00:27:42 +11:00
DN_ProfilerAnchor *anchors = DN_Profiler_ReadBuffer();
for (size_t anchor_index = 1; anchor_index < DN_PROFILER_ANCHOR_BUFFER_SIZE; anchor_index++) {
DN_ProfilerAnchor const *anchor = anchors + anchor_index;
if (!anchor->hit_count)
continue;
uint64_t tsc_exclusive = anchor->tsc_exclusive;
uint64_t tsc_inclusive = anchor->tsc_inclusive;
2025-02-14 00:27:42 +11:00
DN_F64 tsc_exclusive_milliseconds = tsc_exclusive * 1000 / DN_CAST(DN_F64) tsc_per_second;
if (tsc_exclusive == tsc_inclusive) {
2025-02-14 00:27:42 +11:00
DN_Print_LnF("%.*s[%u]: %.1fms", DN_STR_FMT(anchor->name), anchor->hit_count, tsc_exclusive_milliseconds);
} else {
2025-02-14 00:27:42 +11:00
DN_F64 tsc_inclusive_milliseconds = tsc_inclusive * 1000 / DN_CAST(DN_F64) tsc_per_second;
DN_Print_LnF("%.*s[%u]: %.1f/%.1fms",
DN_STR_FMT(anchor->name),
anchor->hit_count,
tsc_exclusive_milliseconds,
tsc_inclusive_milliseconds);
}
}
}
2025-02-14 00:27:42 +11:00
#endif // !defined(DN_NO_PROFILER)
// NOTE: [$JOBQ] DN_JobQueue ///////////////////////////////////////////////////////////////////////
DN_API DN_JobQueueSPMC DN_OS_JobQueueSPMCInit()
{
DN_JobQueueSPMC result = {};
result.thread_wait_for_job_semaphore = DN_OS_SemaphoreInit(0 /*initial_count*/);
result.wait_for_completion_semaphore = DN_OS_SemaphoreInit(0 /*initial_count*/);
result.complete_queue_write_semaphore = DN_OS_SemaphoreInit(DN_ARRAY_UCOUNT(result.complete_queue));
result.mutex = DN_OS_MutexInit();
return result;
}
2025-02-14 00:27:42 +11:00
DN_API bool DN_OS_JobQueueSPMCCanAdd(DN_JobQueueSPMC const *queue, uint32_t count)
{
2025-02-14 00:27:42 +11:00
uint32_t read_index = queue->read_index;
uint32_t write_index = queue->write_index;
uint32_t size = write_index - read_index;
bool result = (size + count) <= DN_ARRAY_UCOUNT(queue->jobs);
return result;
}
2025-02-14 00:27:42 +11:00
DN_API bool DN_OS_JobQueueSPMCAddArray(DN_JobQueueSPMC *queue, DN_Job *jobs, uint32_t count)
{
if (!queue)
return false;
2025-02-14 00:27:42 +11:00
uint32_t const pot_mask = DN_ARRAY_UCOUNT(queue->jobs) - 1;
uint32_t read_index = queue->read_index;
uint32_t write_index = queue->write_index;
uint32_t size = write_index - read_index;
2025-02-14 00:27:42 +11:00
if ((size + count) > DN_ARRAY_UCOUNT(queue->jobs))
return false;
for (size_t offset = 0; offset < count; offset++) {
2024-04-18 22:59:11 +10:00
uint32_t wrapped_write_index = (write_index + offset) & pot_mask;
queue->jobs[wrapped_write_index] = jobs[offset];
}
2025-02-14 00:27:42 +11:00
DN_OS_MutexLock(&queue->mutex);
queue->write_index += count;
2025-02-14 00:27:42 +11:00
DN_OS_SemaphoreIncrement(&queue->thread_wait_for_job_semaphore, count);
DN_OS_MutexUnlock(&queue->mutex);
return true;
}
2025-02-14 00:27:42 +11:00
DN_API bool DN_OS_JobQueueSPMCAdd(DN_JobQueueSPMC *queue, DN_Job job)
{
2025-02-14 00:27:42 +11:00
bool result = DN_OS_JobQueueSPMCAddArray(queue, &job, 1);
return result;
}
2025-02-14 00:27:42 +11:00
DN_API int32_t DN_OS_JobQueueSPMCThread(DN_OSThread *thread)
{
2025-02-14 00:27:42 +11:00
DN_JobQueueSPMC *queue = DN_CAST(DN_JobQueueSPMC *) thread->user_context;
uint32_t const pot_mask = DN_ARRAY_UCOUNT(queue->jobs) - 1;
static_assert(DN_ARRAY_UCOUNT(queue->jobs) == DN_ARRAY_UCOUNT(queue->complete_queue), "PoT mask is used to mask access to both arrays");
for (;;) {
2025-02-14 00:27:42 +11:00
DN_OS_SemaphoreWait(&queue->thread_wait_for_job_semaphore, DN_OS_SEMAPHORE_INFINITE_TIMEOUT);
if (queue->quit)
break;
2025-02-14 00:27:42 +11:00
DN_ASSERT(queue->read_index != queue->write_index);
2025-02-14 00:27:42 +11:00
DN_OS_MutexLock(&queue->mutex);
uint32_t wrapped_read_index = queue->read_index & pot_mask;
2025-02-14 00:27:42 +11:00
DN_Job job = queue->jobs[wrapped_read_index];
queue->read_index += 1;
2025-02-14 00:27:42 +11:00
DN_OS_MutexUnlock(&queue->mutex);
2025-02-14 00:27:42 +11:00
job.elapsed_tsc -= DN_CPU_TSC();
2024-08-01 13:34:36 +10:00
job.func(thread, job.user_context);
2025-02-14 00:27:42 +11:00
job.elapsed_tsc += DN_CPU_TSC();
if (job.add_to_completion_queue) {
2025-02-14 00:27:42 +11:00
DN_OS_SemaphoreWait(&queue->complete_queue_write_semaphore, DN_OS_SEMAPHORE_INFINITE_TIMEOUT);
DN_OS_MutexLock(&queue->mutex);
queue->complete_queue[(queue->complete_write_index++ & pot_mask)] = job;
2025-02-14 00:27:42 +11:00
DN_OS_MutexUnlock(&queue->mutex);
DN_OS_SemaphoreIncrement(&queue->complete_queue_write_semaphore, 1);
}
2024-08-01 13:34:36 +10:00
// NOTE: Update finish counter
2025-02-14 00:27:42 +11:00
DN_OS_MutexLock(&queue->mutex);
queue->finish_index += 1;
2024-08-01 13:34:36 +10:00
// NOTE: If all jobs are finished and we have another thread who is
// blocked via `WaitForCompletion` for this job queue, we will go
// release the semaphore to wake them all up.
bool all_jobs_finished = queue->finish_index == queue->write_index;
if (all_jobs_finished && queue->threads_waiting_for_completion) {
2025-02-14 00:27:42 +11:00
DN_OS_SemaphoreIncrement(&queue->wait_for_completion_semaphore,
queue->threads_waiting_for_completion);
queue->threads_waiting_for_completion = 0;
}
2025-02-14 00:27:42 +11:00
DN_OS_MutexUnlock(&queue->mutex);
}
return queue->quit_exit_code;
}
2025-02-14 00:27:42 +11:00
DN_API void DN_OS_JobQueueSPMCWaitForCompletion(DN_JobQueueSPMC *queue)
{
2025-02-14 00:27:42 +11:00
DN_OS_MutexLock(&queue->mutex);
2024-04-18 22:59:11 +10:00
if (queue->finish_index == queue->write_index) {
2025-02-14 00:27:42 +11:00
DN_OS_MutexUnlock(&queue->mutex);
return;
}
queue->threads_waiting_for_completion++;
2025-02-14 00:27:42 +11:00
DN_OS_MutexUnlock(&queue->mutex);
2025-02-14 00:27:42 +11:00
DN_OS_SemaphoreWait(&queue->wait_for_completion_semaphore, DN_OS_SEMAPHORE_INFINITE_TIMEOUT);
}
2025-02-14 00:27:42 +11:00
DN_API DN_USize DN_OS_JobQueueSPMCGetFinishedJobs(DN_JobQueueSPMC *queue, DN_Job *jobs, DN_USize jobs_size)
{
2025-02-14 00:27:42 +11:00
DN_USize result = 0;
if (!queue || !jobs || jobs_size <= 0)
return result;
2025-02-14 00:27:42 +11:00
uint32_t const pot_mask = DN_ARRAY_UCOUNT(queue->jobs) - 1;
DN_OS_MutexLock(&queue->mutex);
while (queue->complete_read_index < queue->complete_write_index && result < jobs_size) {
jobs[result++] = queue->complete_queue[(queue->complete_read_index++ & pot_mask)];
}
2025-02-14 00:27:42 +11:00
DN_OS_MutexUnlock(&queue->mutex);
return result;
}