375 lines
12 KiB
C++
375 lines
12 KiB
C++
#define DN_HELPERS_CPP
|
|
|
|
/*
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// $$\ $$\ $$$$$$$$\ $$\ $$$$$$$\ $$$$$$$$\ $$$$$$$\ $$$$$$\
|
|
// $$ | $$ |$$ _____|$$ | $$ __$$\ $$ _____|$$ __$$\ $$ __$$\
|
|
// $$ | $$ |$$ | $$ | $$ | $$ |$$ | $$ | $$ |$$ / \__|
|
|
// $$$$$$$$ |$$$$$\ $$ | $$$$$$$ |$$$$$\ $$$$$$$ |\$$$$$$\
|
|
// $$ __$$ |$$ __| $$ | $$ ____/ $$ __| $$ __$$< \____$$\
|
|
// $$ | $$ |$$ | $$ | $$ | $$ | $$ | $$ |$$\ $$ |
|
|
// $$ | $$ |$$$$$$$$\ $$$$$$$$\ $$ | $$$$$$$$\ $$ | $$ |\$$$$$$ |
|
|
// \__| \__|\________|\________|\__| \________|\__| \__| \______/
|
|
//
|
|
// dn_helpers.cpp
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
*/
|
|
|
|
// NOTE: DN_PCG32 //////////////////////////////////////////////////////////////////////////////////
|
|
#define DN_PCG_DEFAULT_MULTIPLIER_64 6364136223846793005ULL
|
|
#define DN_PCG_DEFAULT_INCREMENT_64 1442695040888963407ULL
|
|
|
|
DN_API DN_PCG32 DN_PCG32_Init(uint64_t seed)
|
|
{
|
|
DN_PCG32 result = {};
|
|
DN_PCG32_Next(&result);
|
|
result.state += seed;
|
|
DN_PCG32_Next(&result);
|
|
return result;
|
|
}
|
|
|
|
DN_API uint32_t DN_PCG32_Next(DN_PCG32 *rng)
|
|
{
|
|
uint64_t state = rng->state;
|
|
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;
|
|
}
|
|
|
|
DN_API uint64_t DN_PCG32_Next64(DN_PCG32 *rng)
|
|
{
|
|
uint64_t value = DN_PCG32_Next(rng);
|
|
value <<= 32;
|
|
value |= DN_PCG32_Next(rng);
|
|
return value;
|
|
}
|
|
|
|
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 (;;) {
|
|
uint32_t r = DN_PCG32_Next(rng);
|
|
if (r >= threshold)
|
|
return low + (r % bound);
|
|
}
|
|
}
|
|
|
|
DN_API float DN_PCG32_NextF32(DN_PCG32 *rng)
|
|
{
|
|
uint32_t x = DN_PCG32_Next(rng);
|
|
return (float)(int32_t)(x >> 8) * 0x1.0p-24f;
|
|
}
|
|
|
|
DN_API double DN_PCG32_NextF64(DN_PCG32 *rng)
|
|
{
|
|
uint64_t x = DN_PCG32_Next64(rng);
|
|
return (double)(int64_t)(x >> 11) * 0x1.0p-53;
|
|
}
|
|
|
|
DN_API void DN_PCG32_Advance(DN_PCG32 *rng, uint64_t delta)
|
|
{
|
|
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;
|
|
}
|
|
|
|
#if !defined(DN_NO_JSON_BUILDER)
|
|
// NOTE: DN_JSONBuilder ////////////////////////////////////////////////////////////////////////////
|
|
DN_API DN_JSONBuilder DN_JSONBuilder_Init(DN_Arena *arena, int spaces_per_indent)
|
|
{
|
|
DN_JSONBuilder result = {};
|
|
result.spaces_per_indent = spaces_per_indent;
|
|
result.string_builder.arena = arena;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_JSONBuilder_Build(DN_JSONBuilder const *builder, DN_Arena *arena)
|
|
{
|
|
DN_Str8 result = DN_Str8Builder_Build(&builder->string_builder, arena);
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_JSONBuilder_KeyValue(DN_JSONBuilder *builder, DN_Str8 key, DN_Str8 value)
|
|
{
|
|
if (key.size == 0 && value.size == 0)
|
|
return;
|
|
|
|
DN_JSONBuilderItem item = DN_JSONBuilderItem_KeyValue;
|
|
if (value.size >= 1) {
|
|
if (value.data[0] == '{' || value.data[0] == '[')
|
|
item = DN_JSONBuilderItem_OpenContainer;
|
|
else if (value.data[0] == '}' || value.data[0] == ']')
|
|
item = DN_JSONBuilderItem_CloseContainer;
|
|
}
|
|
|
|
bool adding_to_container_with_items =
|
|
item != DN_JSONBuilderItem_CloseContainer && (builder->last_item == DN_JSONBuilderItem_KeyValue ||
|
|
builder->last_item == DN_JSONBuilderItem_CloseContainer);
|
|
|
|
uint8_t prefix_size = 0;
|
|
char prefix[2] = {0};
|
|
if (adding_to_container_with_items)
|
|
prefix[prefix_size++] = ',';
|
|
|
|
if (builder->last_item != DN_JSONBuilderItem_Empty)
|
|
prefix[prefix_size++] = '\n';
|
|
|
|
if (item == DN_JSONBuilderItem_CloseContainer)
|
|
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)
|
|
DN_Str8Builder_AppendF(&builder->string_builder,
|
|
"%.*s%*c\"%.*s\": %.*s",
|
|
prefix_size,
|
|
prefix,
|
|
spaces,
|
|
' ',
|
|
DN_STR_FMT(key),
|
|
DN_STR_FMT(value));
|
|
else 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));
|
|
|
|
if (item == DN_JSONBuilderItem_OpenContainer)
|
|
builder->indent_level++;
|
|
|
|
builder->last_item = item;
|
|
}
|
|
|
|
DN_API void DN_JSONBuilder_KeyValueFV(DN_JSONBuilder *builder, DN_Str8 key, char const *value_fmt, va_list args)
|
|
{
|
|
DN_OSTLSTMem tmem = DN_OS_TLSTMem(builder->string_builder.arena);
|
|
DN_Str8 value = DN_Str8_InitFV(tmem.arena, value_fmt, args);
|
|
DN_JSONBuilder_KeyValue(builder, key, value);
|
|
}
|
|
|
|
DN_API void DN_JSONBuilder_KeyValueF(DN_JSONBuilder *builder, DN_Str8 key, char const *value_fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, value_fmt);
|
|
DN_JSONBuilder_KeyValueFV(builder, key, value_fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
DN_API void DN_JSONBuilder_ObjectBeginNamed(DN_JSONBuilder *builder, DN_Str8 name)
|
|
{
|
|
DN_JSONBuilder_KeyValue(builder, name, DN_STR8("{"));
|
|
}
|
|
|
|
DN_API void DN_JSONBuilder_ObjectEnd(DN_JSONBuilder *builder)
|
|
{
|
|
DN_JSONBuilder_KeyValue(builder, DN_STR8(""), DN_STR8("}"));
|
|
}
|
|
|
|
DN_API void DN_JSONBuilder_ArrayBeginNamed(DN_JSONBuilder *builder, DN_Str8 name)
|
|
{
|
|
DN_JSONBuilder_KeyValue(builder, name, DN_STR8("["));
|
|
}
|
|
|
|
DN_API void DN_JSONBuilder_ArrayEnd(DN_JSONBuilder *builder)
|
|
{
|
|
DN_JSONBuilder_KeyValue(builder, DN_STR8(""), DN_STR8("]"));
|
|
}
|
|
|
|
DN_API void DN_JSONBuilder_Str8Named(DN_JSONBuilder *builder, DN_Str8 key, DN_Str8 value)
|
|
{
|
|
DN_JSONBuilder_KeyValueF(builder, key, "\"%.*s\"", value.size, value.data);
|
|
}
|
|
|
|
DN_API void DN_JSONBuilder_LiteralNamed(DN_JSONBuilder *builder, DN_Str8 key, DN_Str8 value)
|
|
{
|
|
DN_JSONBuilder_KeyValueF(builder, key, "%.*s", value.size, value.data);
|
|
}
|
|
|
|
DN_API void DN_JSONBuilder_U64Named(DN_JSONBuilder *builder, DN_Str8 key, uint64_t value)
|
|
{
|
|
DN_JSONBuilder_KeyValueF(builder, key, "%I64u", value);
|
|
}
|
|
|
|
DN_API void DN_JSONBuilder_I64Named(DN_JSONBuilder *builder, DN_Str8 key, int64_t value)
|
|
{
|
|
DN_JSONBuilder_KeyValueF(builder, key, "%I64d", value);
|
|
}
|
|
|
|
DN_API void DN_JSONBuilder_F64Named(DN_JSONBuilder *builder, DN_Str8 key, double value, int decimal_places)
|
|
{
|
|
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
|
|
DN_SNPrintF(float_fmt, sizeof(float_fmt), "%%.%df", decimal_places);
|
|
} else {
|
|
// NOTE: Emit the format string "%f"
|
|
DN_SNPrintF(float_fmt, sizeof(float_fmt), "%%f");
|
|
}
|
|
DN_JSONBuilder_KeyValueF(builder, key, float_fmt, value);
|
|
}
|
|
|
|
DN_API void DN_JSONBuilder_BoolNamed(DN_JSONBuilder *builder, DN_Str8 key, bool value)
|
|
{
|
|
DN_Str8 value_string = value ? DN_STR8("true") : DN_STR8("false");
|
|
DN_JSONBuilder_KeyValueF(builder, key, "%.*s", value_string.size, value_string.data);
|
|
}
|
|
#endif // !defined(DN_NO_JSON_BUILDER)
|
|
|
|
// NOTE: 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_ArrayCountU(result.complete_queue));
|
|
result.mutex = DN_OS_MutexInit();
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_JobQueueSPMCCanAdd(DN_JobQueueSPMC const *queue, uint32_t count)
|
|
{
|
|
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_ArrayCountU(queue->jobs);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_JobQueueSPMCAddArray(DN_JobQueueSPMC *queue, DN_Job *jobs, uint32_t count)
|
|
{
|
|
if (!queue)
|
|
return false;
|
|
|
|
uint32_t const pot_mask = DN_ArrayCountU(queue->jobs) - 1;
|
|
uint32_t read_index = queue->read_index;
|
|
uint32_t write_index = queue->write_index;
|
|
uint32_t size = write_index - read_index;
|
|
|
|
if ((size + count) > DN_ArrayCountU(queue->jobs))
|
|
return false;
|
|
|
|
for (size_t offset = 0; offset < count; offset++) {
|
|
uint32_t wrapped_write_index = (write_index + offset) & pot_mask;
|
|
queue->jobs[wrapped_write_index] = jobs[offset];
|
|
}
|
|
|
|
DN_OS_MutexLock(&queue->mutex);
|
|
queue->write_index += count;
|
|
DN_OS_SemaphoreIncrement(&queue->thread_wait_for_job_semaphore, count);
|
|
DN_OS_MutexUnlock(&queue->mutex);
|
|
return true;
|
|
}
|
|
|
|
DN_API bool DN_OS_JobQueueSPMCAdd(DN_JobQueueSPMC *queue, DN_Job job)
|
|
{
|
|
bool result = DN_OS_JobQueueSPMCAddArray(queue, &job, 1);
|
|
return result;
|
|
}
|
|
|
|
DN_API int32_t DN_OS_JobQueueSPMCThread(DN_OSThread *thread)
|
|
{
|
|
DN_JobQueueSPMC *queue = DN_CAST(DN_JobQueueSPMC *) thread->user_context;
|
|
uint32_t const pot_mask = DN_ArrayCountU(queue->jobs) - 1;
|
|
static_assert(DN_ArrayCountU(queue->jobs) == DN_ArrayCountU(queue->complete_queue), "PoT mask is used to mask access to both arrays");
|
|
|
|
for (;;) {
|
|
DN_OS_SemaphoreWait(&queue->thread_wait_for_job_semaphore, DN_OS_SEMAPHORE_INFINITE_TIMEOUT);
|
|
if (queue->quit)
|
|
break;
|
|
|
|
DN_Assert(queue->read_index != queue->write_index);
|
|
|
|
DN_OS_MutexLock(&queue->mutex);
|
|
uint32_t wrapped_read_index = queue->read_index & pot_mask;
|
|
DN_Job job = queue->jobs[wrapped_read_index];
|
|
queue->read_index += 1;
|
|
DN_OS_MutexUnlock(&queue->mutex);
|
|
|
|
job.elapsed_tsc -= DN_CPU_TSC();
|
|
job.func(thread, job.user_context);
|
|
job.elapsed_tsc += DN_CPU_TSC();
|
|
|
|
if (job.add_to_completion_queue) {
|
|
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;
|
|
DN_OS_MutexUnlock(&queue->mutex);
|
|
DN_OS_SemaphoreIncrement(&queue->complete_queue_write_semaphore, 1);
|
|
}
|
|
|
|
// NOTE: Update finish counter
|
|
DN_OS_MutexLock(&queue->mutex);
|
|
queue->finish_index += 1;
|
|
|
|
// 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) {
|
|
DN_OS_SemaphoreIncrement(&queue->wait_for_completion_semaphore,
|
|
queue->threads_waiting_for_completion);
|
|
queue->threads_waiting_for_completion = 0;
|
|
}
|
|
DN_OS_MutexUnlock(&queue->mutex);
|
|
}
|
|
|
|
return queue->quit_exit_code;
|
|
}
|
|
|
|
DN_API void DN_OS_JobQueueSPMCWaitForCompletion(DN_JobQueueSPMC *queue)
|
|
{
|
|
DN_OS_MutexLock(&queue->mutex);
|
|
if (queue->finish_index == queue->write_index) {
|
|
DN_OS_MutexUnlock(&queue->mutex);
|
|
return;
|
|
}
|
|
queue->threads_waiting_for_completion++;
|
|
DN_OS_MutexUnlock(&queue->mutex);
|
|
|
|
DN_OS_SemaphoreWait(&queue->wait_for_completion_semaphore, DN_OS_SEMAPHORE_INFINITE_TIMEOUT);
|
|
}
|
|
|
|
DN_API DN_USize DN_OS_JobQueueSPMCGetFinishedJobs(DN_JobQueueSPMC *queue, DN_Job *jobs, DN_USize jobs_size)
|
|
{
|
|
DN_USize result = 0;
|
|
if (!queue || !jobs || jobs_size <= 0)
|
|
return result;
|
|
|
|
uint32_t const pot_mask = DN_ArrayCountU(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)];
|
|
DN_OS_MutexUnlock(&queue->mutex);
|
|
|
|
return result;
|
|
}
|